diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1812a2660..90b5a0d8c 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -797,6 +797,12 @@ _pages: text: "タイトル" default: "デフォルト値" + canvas: "キャンバス" + _canvas: + id: "キャンバスID" + width: "幅" + height: "高さ" + switch: "スイッチ" _switch: name: "変数名" diff --git a/package.json b/package.json index f34a0d3aa..491f937fc 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@koa/cors": "3.0.0", "@koa/multer": "2.0.2", "@koa/router": "8.0.8", - "@syuilo/aiscript": "0.2.0", + "@syuilo/aiscript": "0.3.0", "@types/bcryptjs": "2.4.2", "@types/bull": "3.12.1", "@types/cbor": "5.0.0", diff --git a/src/client/components/page/page.block.vue b/src/client/components/page/page.block.vue index c1d046fa2..04bbb0b85 100644 --- a/src/client/components/page/page.block.vue +++ b/src/client/components/page/page.block.vue @@ -17,10 +17,11 @@ import XTextarea from './page.textarea.vue'; import XPost from './page.post.vue'; import XCounter from './page.counter.vue'; import XRadioButton from './page.radio-button.vue'; +import XCanvas from './page.canvas.vue'; export default Vue.extend({ 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: { value: { diff --git a/src/client/components/page/page.canvas.vue b/src/client/components/page/page.canvas.vue new file mode 100644 index 000000000..edcb9cba3 --- /dev/null +++ b/src/client/components/page/page.canvas.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/client/components/page/page.vue b/src/client/components/page/page.vue index 3723fcd3c..99cc6e67e 100644 --- a/src/client/components/page/page.vue +++ b/src/client/components/page/page.vue @@ -21,39 +21,11 @@ class Script { public vars: Record; public page: Record; - constructor(page, aoiScript, onError, cb) { + constructor(page, aoiScript, onError) { this.page = page; this.aoiScript = aoiScript; 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(); - cb(); - }).catch(e => { - console.error(e); - /*this.$root.dialog({ - type: 'error', - text: e - });*/ - }); - } else { - setTimeout(() => { - this.eval(); - cb(); - }, 1); - } + this.eval(); } public eval() { @@ -67,13 +39,15 @@ class Script { public interpolate(str: string) { if (str == null) return null; 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(); }); } public callAiScript(fn: string) { - if (this.aoiScript.aiscript) this.aoiScript.aiscript.execFn(this.aoiScript.aiscript.scope.get(fn), []); + try { + 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() { 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(), visitor: this.$store.state.i, page: this.page, @@ -109,15 +83,42 @@ export default Vue.extend({ enableAiScript: !this.$store.state.device.disablePagesScript }), e => { console.dir(e); - }, () => { - this.script = s; }); - if (s.aoiScript.aiscript) s.aoiScript.aiscript.scope.opts.onUpdated = (name, value) => { - s.eval(); + if (this.script.aoiScript.aiscript) this.script.aoiScript.aiscript.scope.opts.onUpdated = (name, value) => { + 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() { if (this.script.aoiScript.aiscript) this.script.aoiScript.aiscript.abort(); }, diff --git a/src/client/pages/page-editor/els/page-editor.el.canvas.vue b/src/client/pages/page-editor/els/page-editor.el.canvas.vue new file mode 100644 index 000000000..497731891 --- /dev/null +++ b/src/client/pages/page-editor/els/page-editor.el.canvas.vue @@ -0,0 +1,45 @@ + + + diff --git a/src/client/pages/page-editor/page-editor.blocks.vue b/src/client/pages/page-editor/page-editor.blocks.vue index bfc75cada..c6ec42b8d 100644 --- a/src/client/pages/page-editor/page-editor.blocks.vue +++ b/src/client/pages/page-editor/page-editor.blocks.vue @@ -20,10 +20,11 @@ import XIf from './els/page-editor.el.if.vue'; import XPost from './els/page-editor.el.post.vue'; import XCounter from './els/page-editor.el.counter.vue'; import XRadioButton from './els/page-editor.el.radio-button.vue'; +import XCanvas from './els/page-editor.el.canvas.vue'; export default Vue.extend({ 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: { diff --git a/src/client/pages/page-editor/page-editor.vue b/src/client/pages/page-editor/page-editor.vue index 6177663b7..1af8689de 100644 --- a/src/client/pages/page-editor/page-editor.vue +++ b/src/client/pages/page-editor/page-editor.vue @@ -351,6 +351,7 @@ export default Vue.extend({ { value: 'text', text: this.$t('_pages.blocks.text') }, { value: 'image', text: this.$t('_pages.blocks.image') }, { value: 'textarea', text: this.$t('_pages.blocks.textarea') }, + { value: 'canvas', text: this.$t('_pages.blocks.canvas') }, ] }, { label: this.$t('_pages.inputBlocks'), @@ -428,8 +429,6 @@ export default Vue.extend({ margin-bottom: var(--margin); > header { - background: var(--faceHeader); - > .title { z-index: 1; margin: 0; @@ -437,8 +436,7 @@ export default Vue.extend({ line-height: 42px; font-size: 0.9em; font-weight: bold; - color: var(--faceHeaderText); - box-shadow: 0 var(--lineWidth) rgba(#000, 0.07); + box-shadow: 0 1px rgba(#000, 0.07); > [data-icon] { margin-right: 6px; diff --git a/src/client/scripts/aoiscript/evaluator.ts b/src/client/scripts/aoiscript/evaluator.ts index cd488aeda..e911be2ca 100644 --- a/src/client/scripts/aoiscript/evaluator.ts +++ b/src/client/scripts/aoiscript/evaluator.ts @@ -19,6 +19,7 @@ export class ASEvaluator { private envVars: Record; public aiscript?: AiScript; private pageVarUpdatedCallback; + private canvases: Record = {}; private opts: { randomSeed: string; visitor?: any; page?: any; url?: string; @@ -36,6 +37,28 @@ export class ASEvaluator { }), ...{ 'MkPages:updated': values.FN_NATIVE(([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) => { @@ -73,10 +96,15 @@ export class ASEvaluator { IS_CAT: opts.visitor ? opts.visitor.isCat : false, SEED: opts.randomSeed ? opts.randomSeed : '', YMD: `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`, + AISCRIPT_DISABLED: !this.opts.enableAiScript, NULL: null }; } + public registerCanvas(id: string, canvas: any) { + this.canvases[id] = canvas; + } + @autobind public updatePageVar(name: string, value: any) { const pageVar = this.pageVars.find(v => v.name === name); @@ -147,7 +175,11 @@ export class ASEvaluator { if (block.type === 'aiScriptVar') { if (this.aiscript) { - return utils.valToJs(this.aiscript.scope.get(block.value)); + try { + return utils.valToJs(this.aiscript.scope.get(block.value)); + } catch (e) { + return null; + } } else { return null; } diff --git a/src/client/scripts/aoiscript/index.ts b/src/client/scripts/aoiscript/index.ts index e6de5faaa..7f3496406 100644 --- a/src/client/scripts/aoiscript/index.ts +++ b/src/client/scripts/aoiscript/index.ts @@ -128,6 +128,7 @@ export const envVarsDef: Record = { IS_CAT: 'boolean', SEED: null, YMD: 'string', + AISCRIPT_DISABLED: 'boolean', NULL: null, }; diff --git a/yarn.lock b/yarn.lock index 724488a69..027894030 100644 --- a/yarn.lock +++ b/yarn.lock @@ -144,10 +144,10 @@ dependencies: type-detect "4.0.8" -"@syuilo/aiscript@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.2.0.tgz#dcb489bca13f6d965ac86034a45fd46514b1487a" - integrity sha512-N9fYchn3zjtniG9fNmZ81PwYZFdulk+RSBcjDZWBgHsFJQc1wxOCr9hZux/vSXrZ/ZWEzK0loNz1dorl2jJLeA== +"@syuilo/aiscript@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.3.0.tgz#cb0645df40ae97a54eb7e318abef2ccb8045aa14" + integrity sha512-jjtcFqnp5ryzAU3mxP25YJEJH/FmIrMycnFwSer/q1BVsAIqHOIhnRTWjxjVI3n1YHIO5DSD4yG/Em6I3bxJow== dependencies: "@types/seedrandom" "2.4.28" autobind-decorator "2.4.0"