This commit is contained in:
syuilo 2020-04-16 00:39:21 +09:00
parent 66377d3f27
commit 90e8527556
11 changed files with 163 additions and 49 deletions

View file

@ -797,6 +797,12 @@ _pages:
text: "タイトル" text: "タイトル"
default: "デフォルト値" default: "デフォルト値"
canvas: "キャンバス"
_canvas:
id: "キャンバスID"
width: "幅"
height: "高さ"
switch: "スイッチ" switch: "スイッチ"
_switch: _switch:
name: "変数名" name: "変数名"

View file

@ -42,7 +42,7 @@
"@koa/cors": "3.0.0", "@koa/cors": "3.0.0",
"@koa/multer": "2.0.2", "@koa/multer": "2.0.2",
"@koa/router": "8.0.8", "@koa/router": "8.0.8",
"@syuilo/aiscript": "0.2.0", "@syuilo/aiscript": "0.3.0",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.12.1", "@types/bull": "3.12.1",
"@types/cbor": "5.0.0", "@types/cbor": "5.0.0",

View file

@ -17,10 +17,11 @@ import XTextarea from './page.textarea.vue';
import XPost from './page.post.vue'; import XPost from './page.post.vue';
import XCounter from './page.counter.vue'; import XCounter from './page.counter.vue';
import XRadioButton from './page.radio-button.vue'; import XRadioButton from './page.radio-button.vue';
import XCanvas from './page.canvas.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter, XRadioButton XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter, XRadioButton, XCanvas
}, },
props: { props: {
value: { value: {

View file

@ -0,0 +1,29 @@
<template>
<div>
<canvas ref="canvas" class="ysrxegms" :width="value.width" :height="value.height"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
value: {
required: true
},
script: {
required: true
}
},
mounted() {
this.script.aoiScript.registerCanvas(this.value.name, this.$refs.canvas);
}
});
</script>
<style lang="scss" scoped>
.ysrxegms {
display: block;
}
</style>

View file

@ -21,39 +21,11 @@ class Script {
public vars: Record<string, any>; public vars: Record<string, any>;
public page: Record<string, any>; public page: Record<string, any>;
constructor(page, aoiScript, onError, cb) { constructor(page, aoiScript, onError) {
this.page = page; this.page = page;
this.aoiScript = aoiScript; this.aoiScript = aoiScript;
this.onError = onError; this.onError = onError;
if (this.page.script && this.aoiScript.aiscript) {
let ast;
try {
ast = parse(this.page.script);
} catch (e) {
console.error(e);
/*this.$root.dialog({
type: 'error',
text: 'Syntax error :('
});*/
return;
}
this.aoiScript.aiscript.exec(ast).then(() => {
this.eval(); this.eval();
cb();
}).catch(e => {
console.error(e);
/*this.$root.dialog({
type: 'error',
text: e
});*/
});
} else {
setTimeout(() => {
this.eval();
cb();
}, 1);
}
} }
public eval() { public eval() {
@ -67,13 +39,15 @@ class Script {
public interpolate(str: string) { public interpolate(str: string) {
if (str == null) return null; if (str == null) return null;
return str.replace(/{(.+?)}/g, match => { return str.replace(/{(.+?)}/g, match => {
const v = this.vars[match.slice(1, -1).trim()]; const v = this.vars ? this.vars[match.slice(1, -1).trim()] : null;
return v == null ? 'NULL' : v.toString(); return v == null ? 'NULL' : v.toString();
}); });
} }
public callAiScript(fn: string) { public callAiScript(fn: string) {
try {
if (this.aoiScript.aiscript) this.aoiScript.aiscript.execFn(this.aoiScript.aiscript.scope.get(fn), []); if (this.aoiScript.aiscript) this.aoiScript.aiscript.execFn(this.aoiScript.aiscript.scope.get(fn), []);
} catch (e) {}
} }
} }
@ -101,7 +75,7 @@ export default Vue.extend({
created() { created() {
const pageVars = this.getPageVars(); const pageVars = this.getPageVars();
const s = new Script(this.page, new ASEvaluator(this, this.page.variables, pageVars, { this.script = new Script(this.page, new ASEvaluator(this, this.page.variables, pageVars, {
randomSeed: Math.random(), randomSeed: Math.random(),
visitor: this.$store.state.i, visitor: this.$store.state.i,
page: this.page, page: this.page,
@ -109,15 +83,42 @@ export default Vue.extend({
enableAiScript: !this.$store.state.device.disablePagesScript enableAiScript: !this.$store.state.device.disablePagesScript
}), e => { }), e => {
console.dir(e); console.dir(e);
}, () => {
this.script = s;
}); });
if (s.aoiScript.aiscript) s.aoiScript.aiscript.scope.opts.onUpdated = (name, value) => { if (this.script.aoiScript.aiscript) this.script.aoiScript.aiscript.scope.opts.onUpdated = (name, value) => {
s.eval(); this.script.eval();
}; };
}, },
mounted() {
this.$nextTick(() => {
if (this.script.page.script && this.script.aoiScript.aiscript) {
let ast;
try {
ast = parse(this.script.page.script);
} catch (e) {
console.error(e);
/*this.$root.dialog({
type: 'error',
text: 'Syntax error :('
});*/
return;
}
this.script.aoiScript.aiscript.exec(ast).then(() => {
this.script.eval();
}).catch(e => {
console.error(e);
/*this.$root.dialog({
type: 'error',
text: e
});*/
});
} else {
this.script.eval();
}
});
},
beforeDestroy() { beforeDestroy() {
if (this.script.aoiScript.aiscript) this.script.aoiScript.aiscript.abort(); if (this.script.aoiScript.aiscript) this.script.aoiScript.aiscript.abort();
}, },

View file

@ -0,0 +1,45 @@
<template>
<x-container @remove="() => $emit('remove')" :draggable="true">
<template #header><fa :icon="faPaintBrush"/> {{ $t('_pages.blocks.canvas') }}</template>
<section style="padding: 0 16px 0 16px;">
<mk-input v-model="value.name"><template #prefix><fa :icon="faMagic"/></template><span>{{ $t('_pages.blocks._canvas.id') }}</span></mk-input>
<mk-input v-model="value.width" type="number"><span>{{ $t('_pages.blocks._canvas.width') }}</span><template #suffix>px</template></mk-input>
<mk-input v-model="value.height" type="number"><span>{{ $t('_pages.blocks._canvas.height') }}</span><template #suffix>px</template></mk-input>
</section>
</x-container>
</template>
<script lang="ts">
import Vue from 'vue';
import { faPaintBrush, faMagic } from '@fortawesome/free-solid-svg-icons';
import i18n from '../../../i18n';
import XContainer from '../page-editor.container.vue';
import MkInput from '../../../components/ui/input.vue';
export default Vue.extend({
i18n,
components: {
XContainer, MkInput
},
props: {
value: {
required: true
},
},
data() {
return {
faPaintBrush, faMagic
};
},
created() {
if (this.value.name == null) Vue.set(this.value, 'name', '');
if (this.value.width == null) Vue.set(this.value, 'width', 300);
if (this.value.height == null) Vue.set(this.value, 'height', 200);
},
});
</script>

View file

@ -20,10 +20,11 @@ import XIf from './els/page-editor.el.if.vue';
import XPost from './els/page-editor.el.post.vue'; import XPost from './els/page-editor.el.post.vue';
import XCounter from './els/page-editor.el.counter.vue'; import XCounter from './els/page-editor.el.counter.vue';
import XRadioButton from './els/page-editor.el.radio-button.vue'; import XRadioButton from './els/page-editor.el.radio-button.vue';
import XCanvas from './els/page-editor.el.canvas.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
XDraggable, XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter, XRadioButton XDraggable, XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter, XRadioButton, XCanvas
}, },
props: { props: {

View file

@ -351,6 +351,7 @@ export default Vue.extend({
{ value: 'text', text: this.$t('_pages.blocks.text') }, { value: 'text', text: this.$t('_pages.blocks.text') },
{ value: 'image', text: this.$t('_pages.blocks.image') }, { value: 'image', text: this.$t('_pages.blocks.image') },
{ value: 'textarea', text: this.$t('_pages.blocks.textarea') }, { value: 'textarea', text: this.$t('_pages.blocks.textarea') },
{ value: 'canvas', text: this.$t('_pages.blocks.canvas') },
] ]
}, { }, {
label: this.$t('_pages.inputBlocks'), label: this.$t('_pages.inputBlocks'),
@ -428,8 +429,6 @@ export default Vue.extend({
margin-bottom: var(--margin); margin-bottom: var(--margin);
> header { > header {
background: var(--faceHeader);
> .title { > .title {
z-index: 1; z-index: 1;
margin: 0; margin: 0;
@ -437,8 +436,7 @@ export default Vue.extend({
line-height: 42px; line-height: 42px;
font-size: 0.9em; font-size: 0.9em;
font-weight: bold; font-weight: bold;
color: var(--faceHeaderText); box-shadow: 0 1px rgba(#000, 0.07);
box-shadow: 0 var(--lineWidth) rgba(#000, 0.07);
> [data-icon] { > [data-icon] {
margin-right: 6px; margin-right: 6px;

View file

@ -19,6 +19,7 @@ export class ASEvaluator {
private envVars: Record<keyof typeof envVarsDef, any>; private envVars: Record<keyof typeof envVarsDef, any>;
public aiscript?: AiScript; public aiscript?: AiScript;
private pageVarUpdatedCallback; private pageVarUpdatedCallback;
private canvases: Record<string, HTMLCanvasElement> = {};
private opts: { private opts: {
randomSeed: string; visitor?: any; page?: any; url?: string; randomSeed: string; visitor?: any; page?: any; url?: string;
@ -36,6 +37,28 @@ export class ASEvaluator {
}), ...{ }), ...{
'MkPages:updated': values.FN_NATIVE(([callback]) => { 'MkPages:updated': values.FN_NATIVE(([callback]) => {
this.pageVarUpdatedCallback = callback; this.pageVarUpdatedCallback = callback;
}),
'MkPages:get_canvas': values.FN_NATIVE(([id]) => {
utils.assertString(id);
const canvas = this.canvases[id.value];
const ctx = canvas.getContext('2d');
return values.OBJ(new Map([
['clear_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.clearRect(x.value, y.value, width.value, height.value) })],
['fill_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.fillRect(x.value, y.value, width.value, height.value) })],
['stroke_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.strokeRect(x.value, y.value, width.value, height.value) })],
['fill_text', values.FN_NATIVE(([text, x, y, width]) => { ctx.fillText(text.value, x.value, y.value, width ? width.value : undefined) })],
['stroke_text', values.FN_NATIVE(([text, x, y, width]) => { ctx.strokeText(text.value, x.value, y.value, width ? width.value : undefined) })],
['set_line_width', values.FN_NATIVE(([width]) => { ctx.lineWidth = width.value })],
['set_font', values.FN_NATIVE(([font]) => { ctx.font = font.value })],
['set_fill_style', values.FN_NATIVE(([style]) => { ctx.fillStyle = style.value })],
['set_stroke_style', values.FN_NATIVE(([style]) => { ctx.strokeStyle = style.value })],
['begin_path', values.FN_NATIVE(() => { ctx.beginPath() })],
['close_path', values.FN_NATIVE(() => { ctx.closePath() })],
['move_to', values.FN_NATIVE(([x, y]) => { ctx.moveTo(x.value, y.value) })],
['line_to', values.FN_NATIVE(([x, y]) => { ctx.lineTo(x.value, y.value) })],
['fill', values.FN_NATIVE(() => { ctx.fill() })],
['stroke', values.FN_NATIVE(() => { ctx.stroke() })],
]));
}) })
}}, { }}, {
in: (q) => { in: (q) => {
@ -73,10 +96,15 @@ export class ASEvaluator {
IS_CAT: opts.visitor ? opts.visitor.isCat : false, IS_CAT: opts.visitor ? opts.visitor.isCat : false,
SEED: opts.randomSeed ? opts.randomSeed : '', SEED: opts.randomSeed ? opts.randomSeed : '',
YMD: `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`, YMD: `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`,
AISCRIPT_DISABLED: !this.opts.enableAiScript,
NULL: null NULL: null
}; };
} }
public registerCanvas(id: string, canvas: any) {
this.canvases[id] = canvas;
}
@autobind @autobind
public updatePageVar(name: string, value: any) { public updatePageVar(name: string, value: any) {
const pageVar = this.pageVars.find(v => v.name === name); const pageVar = this.pageVars.find(v => v.name === name);
@ -147,7 +175,11 @@ export class ASEvaluator {
if (block.type === 'aiScriptVar') { if (block.type === 'aiScriptVar') {
if (this.aiscript) { if (this.aiscript) {
try {
return utils.valToJs(this.aiscript.scope.get(block.value)); return utils.valToJs(this.aiscript.scope.get(block.value));
} catch (e) {
return null;
}
} else { } else {
return null; return null;
} }

View file

@ -128,6 +128,7 @@ export const envVarsDef: Record<string, Type> = {
IS_CAT: 'boolean', IS_CAT: 'boolean',
SEED: null, SEED: null,
YMD: 'string', YMD: 'string',
AISCRIPT_DISABLED: 'boolean',
NULL: null, NULL: null,
}; };

View file

@ -144,10 +144,10 @@
dependencies: dependencies:
type-detect "4.0.8" type-detect "4.0.8"
"@syuilo/aiscript@0.2.0": "@syuilo/aiscript@0.3.0":
version "0.2.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.2.0.tgz#dcb489bca13f6d965ac86034a45fd46514b1487a" resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.3.0.tgz#cb0645df40ae97a54eb7e318abef2ccb8045aa14"
integrity sha512-N9fYchn3zjtniG9fNmZ81PwYZFdulk+RSBcjDZWBgHsFJQc1wxOCr9hZux/vSXrZ/ZWEzK0loNz1dorl2jJLeA== integrity sha512-jjtcFqnp5ryzAU3mxP25YJEJH/FmIrMycnFwSer/q1BVsAIqHOIhnRTWjxjVI3n1YHIO5DSD4yG/Em6I3bxJow==
dependencies: dependencies:
"@types/seedrandom" "2.4.28" "@types/seedrandom" "2.4.28"
autobind-decorator "2.4.0" autobind-decorator "2.4.0"