From 2ee0e07bb6346d29bc5e57ec8aac348143e2d50f Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 20 Apr 2020 21:35:27 +0900 Subject: [PATCH] refactor(client): :sparkles: --- src/client/components/page/page.block.vue | 4 +- src/client/components/page/page.button.vue | 20 +- src/client/components/page/page.canvas.vue | 4 +- src/client/components/page/page.counter.vue | 8 +- src/client/components/page/page.if.vue | 4 +- .../components/page/page.number-input.vue | 8 +- src/client/components/page/page.post.vue | 10 +- .../components/page/page.radio-button.vue | 8 +- src/client/components/page/page.section.vue | 4 +- src/client/components/page/page.switch.vue | 8 +- .../components/page/page.text-input.vue | 8 +- src/client/components/page/page.text.vue | 8 +- .../components/page/page.textarea-input.vue | 8 +- src/client/components/page/page.textarea.vue | 8 +- src/client/components/page/page.vue | 76 ++----- .../page-editor/els/page-editor.el.button.vue | 8 +- .../page-editor/els/page-editor.el.if.vue | 10 +- .../els/page-editor.el.section.vue | 4 +- .../pages/page-editor/page-editor.blocks.vue | 4 +- .../page-editor/page-editor.script-block.vue | 24 +-- src/client/pages/page-editor/page-editor.vue | 18 +- .../scripts/{aoiscript => hpml}/evaluator.ts | 192 +++++------------- .../scripts/{aoiscript => hpml}/index.ts | 2 +- src/client/scripts/hpml/lib.ts | 124 +++++++++++ .../{aoiscript => hpml}/type-checker.ts | 6 +- 25 files changed, 285 insertions(+), 293 deletions(-) rename src/client/scripts/{aoiscript => hpml}/evaluator.ts (58%) rename src/client/scripts/{aoiscript => hpml}/index.ts (99%) create mode 100644 src/client/scripts/hpml/lib.ts rename src/client/scripts/{aoiscript => hpml}/type-checker.ts (96%) diff --git a/src/client/components/page/page.block.vue b/src/client/components/page/page.block.vue index 04bbb0b85..0a4b068b6 100644 --- a/src/client/components/page/page.block.vue +++ b/src/client/components/page/page.block.vue @@ -1,5 +1,5 @@ diff --git a/src/client/components/page/page.counter.vue b/src/client/components/page/page.counter.vue index f7557c003..a3674b87a 100644 --- a/src/client/components/page/page.counter.vue +++ b/src/client/components/page/page.counter.vue @@ -1,6 +1,6 @@ @@ -16,7 +16,7 @@ export default Vue.extend({ value: { required: true }, - script: { + hpml: { required: true } }, @@ -27,8 +27,8 @@ export default Vue.extend({ }, watch: { v() { - this.script.aoiScript.updatePageVar(this.value.name, this.v); - this.script.eval(); + this.hpml.updatePageVar(this.value.name, this.v); + this.hpml.eval(); } }, methods: { diff --git a/src/client/components/page/page.if.vue b/src/client/components/page/page.if.vue index a714a522e..d73153bcd 100644 --- a/src/client/components/page/page.if.vue +++ b/src/client/components/page/page.if.vue @@ -1,6 +1,6 @@ @@ -12,7 +12,7 @@ export default Vue.extend({ value: { required: true }, - script: { + hpml: { required: true }, page: { diff --git a/src/client/components/page/page.number-input.vue b/src/client/components/page/page.number-input.vue index 9ea1ebb64..56899b1b2 100644 --- a/src/client/components/page/page.number-input.vue +++ b/src/client/components/page/page.number-input.vue @@ -1,6 +1,6 @@ @@ -16,7 +16,7 @@ export default Vue.extend({ value: { required: true }, - script: { + hpml: { required: true } }, @@ -27,8 +27,8 @@ export default Vue.extend({ }, watch: { v() { - this.script.aoiScript.updatePageVar(this.value.name, this.v); - this.script.eval(); + this.hpml.updatePageVar(this.value.name, this.v); + this.hpml.eval(); } } }); diff --git a/src/client/components/page/page.post.vue b/src/client/components/page/page.post.vue index 80e0f70cb..9942a68d5 100644 --- a/src/client/components/page/page.post.vue +++ b/src/client/components/page/page.post.vue @@ -23,22 +23,22 @@ export default Vue.extend({ value: { required: true }, - script: { + hpml: { required: true } }, data() { return { - text: this.script.interpolate(this.value.text), + text: this.hpml.interpolate(this.value.text), posted: false, posting: false, faCheck, faPaperPlane }; }, watch: { - 'script.vars': { + 'hpml.vars': { handler() { - this.text = this.script.interpolate(this.value.text); + this.text = this.hpml.interpolate(this.value.text); }, deep: true } @@ -53,7 +53,7 @@ export default Vue.extend({ showCancelButton: false, cancelableByBgClick: false }); - const canvas = this.script.aoiScript.canvases[this.value.canvasId]; + const canvas = this.hpml.canvases[this.value.canvasId]; canvas.toBlob(blob => { const data = new FormData(); data.append('file', blob); diff --git a/src/client/components/page/page.radio-button.vue b/src/client/components/page/page.radio-button.vue index dd5cbcbde..99d9ead38 100644 --- a/src/client/components/page/page.radio-button.vue +++ b/src/client/components/page/page.radio-button.vue @@ -1,6 +1,6 @@ @@ -17,7 +17,7 @@ export default Vue.extend({ value: { required: true }, - script: { + hpml: { required: true } }, @@ -28,8 +28,8 @@ export default Vue.extend({ }, watch: { v() { - this.script.aoiScript.updatePageVar(this.value.name, this.v); - this.script.eval(); + this.hpml.updatePageVar(this.value.name, this.v); + this.hpml.eval(); } } }); diff --git a/src/client/components/page/page.section.vue b/src/client/components/page/page.section.vue index b83c773f7..c9758a0db 100644 --- a/src/client/components/page/page.section.vue +++ b/src/client/components/page/page.section.vue @@ -3,7 +3,7 @@ {{ value.title }}
- +
@@ -16,7 +16,7 @@ export default Vue.extend({ value: { required: true }, - script: { + hpml: { required: true }, page: { diff --git a/src/client/components/page/page.switch.vue b/src/client/components/page/page.switch.vue index 79d871df8..9f04ad19c 100644 --- a/src/client/components/page/page.switch.vue +++ b/src/client/components/page/page.switch.vue @@ -1,6 +1,6 @@ @@ -16,7 +16,7 @@ export default Vue.extend({ value: { required: true }, - script: { + hpml: { required: true } }, @@ -27,8 +27,8 @@ export default Vue.extend({ }, watch: { v() { - this.script.aoiScript.updatePageVar(this.value.name, this.v); - this.script.eval(); + this.hpml.updatePageVar(this.value.name, this.v); + this.hpml.eval(); } } }); diff --git a/src/client/components/page/page.text-input.vue b/src/client/components/page/page.text-input.vue index 843d541de..0d09f9fb5 100644 --- a/src/client/components/page/page.text-input.vue +++ b/src/client/components/page/page.text-input.vue @@ -1,6 +1,6 @@ @@ -16,7 +16,7 @@ export default Vue.extend({ value: { required: true }, - script: { + hpml: { required: true } }, @@ -27,8 +27,8 @@ export default Vue.extend({ }, watch: { v() { - this.script.aoiScript.updatePageVar(this.value.name, this.v); - this.script.eval(); + this.hpml.updatePageVar(this.value.name, this.v); + this.hpml.eval(); } } }); diff --git a/src/client/components/page/page.text.vue b/src/client/components/page/page.text.vue index aeab31225..66e2acb90 100644 --- a/src/client/components/page/page.text.vue +++ b/src/client/components/page/page.text.vue @@ -15,13 +15,13 @@ export default Vue.extend({ value: { required: true }, - script: { + hpml: { required: true } }, data() { return { - text: this.script.interpolate(this.value.text), + text: this.hpml.interpolate(this.value.text), }; }, computed: { @@ -38,9 +38,9 @@ export default Vue.extend({ } }, watch: { - 'script.vars': { + 'hpml.vars': { handler() { - this.text = this.script.interpolate(this.value.text); + this.text = this.hpml.interpolate(this.value.text); }, deep: true } diff --git a/src/client/components/page/page.textarea-input.vue b/src/client/components/page/page.textarea-input.vue index 5ba22e7c5..5e0cc4377 100644 --- a/src/client/components/page/page.textarea-input.vue +++ b/src/client/components/page/page.textarea-input.vue @@ -1,6 +1,6 @@ @@ -16,7 +16,7 @@ export default Vue.extend({ value: { required: true }, - script: { + hpml: { required: true } }, @@ -27,8 +27,8 @@ export default Vue.extend({ }, watch: { v() { - this.script.aoiScript.updatePageVar(this.value.name, this.v); - this.script.eval(); + this.hpml.updatePageVar(this.value.name, this.v); + this.hpml.eval(); } } }); diff --git a/src/client/components/page/page.textarea.vue b/src/client/components/page/page.textarea.vue index 78b74dd64..abb30d78e 100644 --- a/src/client/components/page/page.textarea.vue +++ b/src/client/components/page/page.textarea.vue @@ -14,19 +14,19 @@ export default Vue.extend({ value: { required: true }, - script: { + hpml: { required: true } }, data() { return { - text: this.script.interpolate(this.value.text), + text: this.hpml.interpolate(this.value.text), }; }, watch: { - 'script.vars': { + 'hpml.vars': { handler() { - this.text = this.script.interpolate(this.value.text); + this.text = this.hpml.interpolate(this.value.text); }, deep: true } diff --git a/src/client/components/page/page.vue b/src/client/components/page/page.vue index 99cc6e67e..e3b04d7fd 100644 --- a/src/client/components/page/page.vue +++ b/src/client/components/page/page.vue @@ -1,56 +1,19 @@ diff --git a/src/client/pages/page-editor/els/page-editor.el.button.vue b/src/client/pages/page-editor/els/page-editor.el.button.vue index 508d77a7a..9ca9fe06f 100644 --- a/src/client/pages/page-editor/els/page-editor.el.button.vue +++ b/src/client/pages/page-editor/els/page-editor.el.button.vue @@ -21,12 +21,12 @@ - + - + - + @@ -57,7 +57,7 @@ export default Vue.extend({ value: { required: true }, - aoiScript: { + hpml: { required: true, }, }, diff --git a/src/client/pages/page-editor/els/page-editor.el.if.vue b/src/client/pages/page-editor/els/page-editor.el.if.vue index 1c6a33e1b..0449b9cf2 100644 --- a/src/client/pages/page-editor/els/page-editor.el.if.vue +++ b/src/client/pages/page-editor/els/page-editor.el.if.vue @@ -10,16 +10,16 @@
- + - + - + - +
@@ -45,7 +45,7 @@ export default Vue.extend({ value: { required: true }, - aoiScript: { + hpml: { required: true, }, }, diff --git a/src/client/pages/page-editor/els/page-editor.el.section.vue b/src/client/pages/page-editor/els/page-editor.el.section.vue index c1d5497f4..a32cf9c75 100644 --- a/src/client/pages/page-editor/els/page-editor.el.section.vue +++ b/src/client/pages/page-editor/els/page-editor.el.section.vue @@ -11,7 +11,7 @@
- +
@@ -37,7 +37,7 @@ export default Vue.extend({ value: { required: true }, - aoiScript: { + hpml: { required: true, }, }, diff --git a/src/client/pages/page-editor/page-editor.blocks.vue b/src/client/pages/page-editor/page-editor.blocks.vue index c6ec42b8d..6e9408e0b 100644 --- a/src/client/pages/page-editor/page-editor.blocks.vue +++ b/src/client/pages/page-editor/page-editor.blocks.vue @@ -1,6 +1,6 @@ @@ -32,7 +32,7 @@ export default Vue.extend({ type: Array, required: true }, - aoiScript: { + hpml: { required: true, }, }, diff --git a/src/client/pages/page-editor/page-editor.script-block.vue b/src/client/pages/page-editor/page-editor.script-block.vue index 7e3bbf0c8..9eafd5daa 100644 --- a/src/client/pages/page-editor/page-editor.script-block.vue +++ b/src/client/pages/page-editor/page-editor.script-block.vue @@ -24,15 +24,15 @@
@@ -44,13 +44,13 @@ {{ $t('_pages.script.blocks._fn.slots') }} - +
- +
- +
@@ -62,7 +62,7 @@ import { v4 as uuid } from 'uuid'; import i18n from '../../i18n'; import XContainer from './page-editor.container.vue'; import MkTextarea from '../../components/ui/textarea.vue'; -import { isLiteralBlock, funcDefs, blockDefs } from '../../scripts/aoiscript/index'; +import { isLiteralBlock, funcDefs, blockDefs } from '../../scripts/hpml/index'; export default Vue.extend({ i18n, @@ -88,7 +88,7 @@ export default Vue.extend({ required: false, default: false }, - aoiScript: { + hpml: { required: true, }, name: { @@ -156,7 +156,7 @@ export default Vue.extend({ if (this.value.type && this.value.type.startsWith('fn:')) { const fnName = this.value.type.split(':')[1]; - const fn = this.aoiScript.getVarByName(fnName); + const fn = this.hpml.getVarByName(fnName); const empties = []; for (let i = 0; i < fn.value.slots.length; i++) { @@ -202,9 +202,9 @@ export default Vue.extend({ deep: true }); - this.$watch('aoiScript.variables', () => { + this.$watch('hpml.variables', () => { if (this.type != null && this.value) { - this.error = this.aoiScript.typeCheck(this.value); + this.error = this.hpml.typeCheck(this.value); } }, { deep: true @@ -226,7 +226,7 @@ export default Vue.extend({ }, _getExpectedType(slot: number) { - return this.aoiScript.getExpectedType(this.value, slot); + return this.hpml.getExpectedType(this.value, slot); } } }); diff --git a/src/client/pages/page-editor/page-editor.vue b/src/client/pages/page-editor/page-editor.vue index 21d7af9a3..4437c7716 100644 --- a/src/client/pages/page-editor/page-editor.vue +++ b/src/client/pages/page-editor/page-editor.vue @@ -46,7 +46,7 @@ - + @@ -62,7 +62,7 @@ @input="v => updateVariable(v)" @remove="() => removeVariable(variable)" :key="variable.name" - :aoi-script="aoiScript" + :hpml="hpml" :name="variable.name" :title="variable.name" :draggable="true" @@ -100,8 +100,8 @@ import MkButton from '../../components/ui/button.vue'; import MkSelect from '../../components/ui/select.vue'; import MkSwitch from '../../components/ui/switch.vue'; import MkInput from '../../components/ui/input.vue'; -import { blockDefs } from '../../scripts/aoiscript/index'; -import { ASTypeChecker } from '../../scripts/aoiscript/type-checker'; +import { blockDefs } from '../../scripts/hpml/index'; +import { HpmlTypeChecker } from '../../scripts/hpml/type-checker'; import { url } from '../../config'; import { collectPageVars } from '../../scripts/collect-page-vars'; import { selectDriveFile } from '../../scripts/select-drive-file'; @@ -145,7 +145,7 @@ export default Vue.extend({ alignCenter: false, hideTitleWhenPinned: false, variables: [], - aoiScript: null, + hpml: null, script: '', showOptions: false, url, @@ -166,14 +166,14 @@ export default Vue.extend({ }, async created() { - this.aoiScript = new ASTypeChecker(); + this.hpml = new HpmlTypeChecker(); this.$watch('variables', () => { - this.aoiScript.variables = this.variables; + this.hpml.variables = this.variables; }, { deep: true }); this.$watch('content', () => { - this.aoiScript.pageVars = collectPageVars(this.content); + this.hpml.pageVars = collectPageVars(this.content); }, { deep: true }); if (this.initPageId) { @@ -322,7 +322,7 @@ export default Vue.extend({ name = name.trim(); - if (this.aoiScript.isUsedName(name)) { + if (this.hpml.isUsedName(name)) { this.$root.dialog({ type: 'error', text: this.$t('_pages.variableNameIsAlreadyUsed') diff --git a/src/client/scripts/aoiscript/evaluator.ts b/src/client/scripts/hpml/evaluator.ts similarity index 58% rename from src/client/scripts/aoiscript/evaluator.ts rename to src/client/scripts/hpml/evaluator.ts index dbd4735fd..f1fcdde0e 100644 --- a/src/client/scripts/aoiscript/evaluator.ts +++ b/src/client/scripts/hpml/evaluator.ts @@ -1,157 +1,45 @@ import autobind from 'autobind-decorator'; import * as seedrandom from 'seedrandom'; -import Chart from 'chart.js'; -import * as tinycolor from 'tinycolor2'; import { Variable, PageVar, envVarsDef, funcDefs, Block, isFnBlock } from '.'; import { version } from '../../config'; -import { AiScript, utils, parse, values } from '@syuilo/aiscript'; +import { AiScript, utils, values } from '@syuilo/aiscript'; import { createAiScriptEnv } from '../create-aiscript-env'; - -// https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs -Chart.pluginService.register({ - beforeDraw: function (chart, easing) { - if (chart.config.options.chartArea && chart.config.options.chartArea.backgroundColor) { - var ctx = chart.chart.ctx; - ctx.save(); - ctx.fillStyle = chart.config.options.chartArea.backgroundColor; - ctx.fillRect(0, 0, chart.chart.width, chart.chart.height); - ctx.restore(); - } - } -}); +import { collectPageVars } from '../collect-page-vars'; +import { initLib } from './lib'; type Fn = { slots: string[]; - exec: (args: Record) => ReturnType; + exec: (args: Record) => ReturnType; }; /** - * AoiScript evaluator + * Hpml evaluator */ -export class ASEvaluator { +export class Hpml { private variables: Variable[]; private pageVars: PageVar[]; private envVars: Record; public aiscript?: AiScript; private pageVarUpdatedCallback; public canvases: Record = {}; + public vars: Record; + public page: Record; private opts: { - randomSeed: string; visitor?: any; page?: any; url?: string; + randomSeed: string; visitor?: any; url?: string; enableAiScript: boolean; }; - constructor(vm: any, variables: Variable[], pageVars: PageVar[], opts: ASEvaluator['opts']) { - this.variables = variables; - this.pageVars = pageVars; + constructor(vm: any, page: Hpml['page'], opts: Hpml['opts']) { + this.page = page; + this.variables = this.page.variables; + this.pageVars = collectPageVars(this.page.content); this.opts = opts; if (this.opts.enableAiScript) { this.aiscript = new AiScript({ ...createAiScriptEnv(vm, { - storageKey: 'pages:' + opts.page.id - }), ...{ - '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) })], - ['arc', values.FN_NATIVE(([x, y, radius, startAngle, endAngle]) => { ctx.arc(x.value, y.value, radius.value, startAngle.value, endAngle.value) })], - ['rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.rect(x.value, y.value, width.value, height.value) })], - ['fill', values.FN_NATIVE(() => { ctx.fill() })], - ['stroke', values.FN_NATIVE(() => { ctx.stroke() })], - ])); - }), - 'MkPages:chart': values.FN_NATIVE(([id, opts]) => { - utils.assertString(id); - utils.assertObject(opts); - const canvas = this.canvases[id.value]; - const color = getComputedStyle(document.documentElement).getPropertyValue('--accent'); - const chart = new Chart(canvas, { - type: opts.value.get('type').value, - data: { - labels: opts.value.get('labels').value.map(x => x.value), - datasets: opts.value.get('datasets').value.map(x => ({ - label: x.value.has('label') ? x.value.get('label').value : '', - data: x.value.get('data').value.map(x => x.value), - pointRadius: 0, - lineTension: 0, - borderWidth: 2, - borderColor: x.value.has('color') ? x.value.get('color') : color, - backgroundColor: tinycolor(x.value.has('color') ? x.value.get('color') : color).setAlpha(0.1).toRgbString(), - })) - }, - options: { - responsive: false, - devicePixelRatio: 1.5, - title: { - display: opts.value.has('title'), - text: opts.value.has('title') ? opts.value.get('title').value : '', - fontSize: 14, - }, - layout: { - padding: { - left: 32, - right: 32, - top: opts.value.has('title') ? 16 : 32, - bottom: 16 - } - }, - legend: { - display: opts.value.get('datasets').value.filter(x => x.value.has('label') && x.value.get('label').value).length === 0 ? false : true, - position: 'bottom', - labels: { - boxWidth: 16, - } - }, - tooltips: { - enabled: false, - }, - chartArea: { - backgroundColor: '#fff' - }, - ...(opts.value.get('type').value === 'radar' ? { - scale: { - ticks: { - display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : false, - min: opts.value.has('min') ? opts.value.get('min').value : undefined, - max: opts.value.has('max') ? opts.value.get('max').value : undefined, - maxTicksLimit: 8, - }, - pointLabels: { - fontSize: 12 - } - } - } : { - scales: { - yAxes: [{ - ticks: { - display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : true, - min: opts.value.has('min') ? opts.value.get('min').value : undefined, - max: opts.value.has('max') ? opts.value.get('max').value : undefined, - } - }] - } - }) - } - }); - }), - }}, { + storageKey: 'pages:' + this.page.id + }), ...initLib(this)}, { in: (q) => { return new Promise(ok => { vm.$root.dialog({ @@ -168,6 +56,10 @@ export class ASEvaluator { log: (type, params) => { }, }); + + this.aiscript.scope.opts.onUpdated = (name, value) => { + this.eval(); + }; } const date = new Date(); @@ -175,7 +67,7 @@ export class ASEvaluator { this.envVars = { AI: 'kawaii', VERSION: version, - URL: opts.page ? `${opts.url}/@${opts.page.user.username}/pages/${opts.page.name}` : '', + URL: this.page ? `${opts.url}/@${this.page.user.username}/pages/${this.page.name}` : '', LOGIN: opts.visitor != null, NAME: opts.visitor ? opts.visitor.name || opts.visitor.username : '', USERNAME: opts.visitor ? opts.visitor.username : '', @@ -189,8 +81,36 @@ export class ASEvaluator { AISCRIPT_DISABLED: !this.opts.enableAiScript, NULL: null }; + + this.eval(); } + @autobind + public eval() { + try { + this.vars = this.evaluateVars(); + } catch (e) { + //this.onError(e); + } + } + + @autobind + public interpolate(str: string) { + if (str == null) return null; + return str.replace(/{(.+?)}/g, match => { + const v = this.vars ? this.vars[match.slice(1, -1).trim()] : null; + return v == null ? 'NULL' : v.toString(); + }); + } + + @autobind + public callAiScript(fn: string) { + try { + if (this.aiscript) this.aiscript.execFn(this.aiscript.scope.get(fn), []); + } catch (e) {} + } + + @autobind public registerCanvas(id: string, canvas: any) { this.canvases[id] = canvas; } @@ -204,7 +124,7 @@ export class ASEvaluator { if (this.aiscript) this.aiscript.execFn(this.pageVarUpdatedCallback, [values.STR(name), utils.jsToVal(value)]); } } else { - throw new AoiScriptError(`No such page var '${name}'`); + throw new HpmlError(`No such page var '${name}'`); } } @@ -215,7 +135,7 @@ export class ASEvaluator { } @autobind - private interpolate(str: string, scope: Scope) { + private _interpolate(str: string, scope: Scope) { return str.replace(/{(.+?)}/g, match => { const v = scope.getState(match.slice(1, -1).trim()); return v == null ? 'NULL' : v.toString(); @@ -252,11 +172,11 @@ export class ASEvaluator { } if (block.type === 'text' || block.type === 'multiLineText') { - return this.interpolate(block.value || '', scope); + return this._interpolate(block.value || '', scope); } if (block.type === 'textList') { - return this.interpolate(block.value || '', scope).trim().split('\n'); + return this._interpolate(block.value || '', scope).trim().split('\n'); } if (block.type === 'ref') { @@ -371,14 +291,14 @@ export class ASEvaluator { const fnName = block.type; const fn = (funcs as any)[fnName]; if (fn == null) { - throw new AoiScriptError(`No such function '${fnName}'`); + throw new HpmlError(`No such function '${fnName}'`); } else { return fn(...block.args.map(x => this.evaluate(x, scope))); } } } -class AoiScriptError extends Error { +class HpmlError extends Error { public info?: any; constructor(message: string, info?: any) { @@ -388,7 +308,7 @@ class AoiScriptError extends Error { // Maintains proper stack trace for where our error was thrown (only available on V8) if (Error.captureStackTrace) { - Error.captureStackTrace(this, AoiScriptError); + Error.captureStackTrace(this, HpmlError); } } } @@ -421,7 +341,7 @@ class Scope { } } - throw new AoiScriptError( + throw new HpmlError( `No such variable '${name}' in scope '${this.name}'`, { scope: this.layerdStates }); diff --git a/src/client/scripts/aoiscript/index.ts b/src/client/scripts/hpml/index.ts similarity index 99% rename from src/client/scripts/aoiscript/index.ts rename to src/client/scripts/hpml/index.ts index 7f3496406..c87d5b998 100644 --- a/src/client/scripts/aoiscript/index.ts +++ b/src/client/scripts/hpml/index.ts @@ -1,5 +1,5 @@ /** - * AoiScript + * Hpml */ import { diff --git a/src/client/scripts/hpml/lib.ts b/src/client/scripts/hpml/lib.ts new file mode 100644 index 000000000..9c71cfaba --- /dev/null +++ b/src/client/scripts/hpml/lib.ts @@ -0,0 +1,124 @@ +import * as tinycolor from 'tinycolor2'; +import Chart from 'chart.js'; +import { Hpml } from './evaluator'; +import { values, utils } from '@syuilo/aiscript'; + +// https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs +Chart.pluginService.register({ + beforeDraw: function (chart, easing) { + if (chart.config.options.chartArea && chart.config.options.chartArea.backgroundColor) { + var ctx = chart.chart.ctx; + ctx.save(); + ctx.fillStyle = chart.config.options.chartArea.backgroundColor; + ctx.fillRect(0, 0, chart.chart.width, chart.chart.height); + ctx.restore(); + } + } +}); + +export function initLib(hpml: Hpml) { + return { + 'MkPages:updated': values.FN_NATIVE(([callback]) => { + hpml.pageVarUpdatedCallback = callback; + }), + 'MkPages:get_canvas': values.FN_NATIVE(([id]) => { + utils.assertString(id); + const canvas = hpml.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) })], + ['arc', values.FN_NATIVE(([x, y, radius, startAngle, endAngle]) => { ctx.arc(x.value, y.value, radius.value, startAngle.value, endAngle.value) })], + ['rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.rect(x.value, y.value, width.value, height.value) })], + ['fill', values.FN_NATIVE(() => { ctx.fill() })], + ['stroke', values.FN_NATIVE(() => { ctx.stroke() })], + ])); + }), + 'MkPages:chart': values.FN_NATIVE(([id, opts]) => { + utils.assertString(id); + utils.assertObject(opts); + const canvas = hpml.canvases[id.value]; + const color = getComputedStyle(document.documentElement).getPropertyValue('--accent'); + Chart.defaults.global.defaultFontColor = '#555'; + const chart = new Chart(canvas, { + type: opts.value.get('type').value, + data: { + labels: opts.value.get('labels').value.map(x => x.value), + datasets: opts.value.get('datasets').value.map(x => ({ + label: x.value.has('label') ? x.value.get('label').value : '', + data: x.value.get('data').value.map(x => x.value), + pointRadius: 0, + lineTension: 0, + borderWidth: 2, + borderColor: x.value.has('color') ? x.value.get('color') : color, + backgroundColor: tinycolor(x.value.has('color') ? x.value.get('color') : color).setAlpha(0.1).toRgbString(), + })) + }, + options: { + responsive: false, + devicePixelRatio: 1.5, + title: { + display: opts.value.has('title'), + text: opts.value.has('title') ? opts.value.get('title').value : '', + fontSize: 14, + }, + layout: { + padding: { + left: 32, + right: 32, + top: opts.value.has('title') ? 16 : 32, + bottom: 16 + } + }, + legend: { + display: opts.value.get('datasets').value.filter(x => x.value.has('label') && x.value.get('label').value).length === 0 ? false : true, + position: 'bottom', + labels: { + boxWidth: 16, + } + }, + tooltips: { + enabled: false, + }, + chartArea: { + backgroundColor: '#fff' + }, + ...(opts.value.get('type').value === 'radar' ? { + scale: { + ticks: { + display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : false, + min: opts.value.has('min') ? opts.value.get('min').value : undefined, + max: opts.value.has('max') ? opts.value.get('max').value : undefined, + maxTicksLimit: 8, + }, + pointLabels: { + fontSize: 12 + } + } + } : { + scales: { + yAxes: [{ + ticks: { + display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : true, + min: opts.value.has('min') ? opts.value.get('min').value : undefined, + max: opts.value.has('max') ? opts.value.get('max').value : undefined, + } + }] + } + }) + } + }); + }) + }; +} diff --git a/src/client/scripts/aoiscript/type-checker.ts b/src/client/scripts/hpml/type-checker.ts similarity index 96% rename from src/client/scripts/aoiscript/type-checker.ts rename to src/client/scripts/hpml/type-checker.ts index c10198e11..14950e019 100644 --- a/src/client/scripts/aoiscript/type-checker.ts +++ b/src/client/scripts/hpml/type-checker.ts @@ -8,13 +8,13 @@ type TypeError = { }; /** - * AoiScript type checker + * Hpml type checker */ -export class ASTypeChecker { +export class HpmlTypeChecker { public variables: Variable[]; public pageVars: PageVar[]; - constructor(variables: ASTypeChecker['variables'] = [], pageVars: ASTypeChecker['pageVars'] = []) { + constructor(variables: HpmlTypeChecker['variables'] = [], pageVars: HpmlTypeChecker['pageVars'] = []) { this.variables = variables; this.pageVars = pageVars; }