diff options
Diffstat (limited to 'src/client/scripts')
| -rw-r--r-- | src/client/scripts/hpml/evaluator.ts (renamed from src/client/scripts/aoiscript/evaluator.ts) | 192 | ||||
| -rw-r--r-- | src/client/scripts/hpml/index.ts (renamed from src/client/scripts/aoiscript/index.ts) | 2 | ||||
| -rw-r--r-- | src/client/scripts/hpml/lib.ts | 124 | ||||
| -rw-r--r-- | src/client/scripts/hpml/type-checker.ts (renamed from src/client/scripts/aoiscript/type-checker.ts) | 6 |
4 files changed, 184 insertions, 140 deletions
diff --git a/src/client/scripts/aoiscript/evaluator.ts b/src/client/scripts/hpml/evaluator.ts index dbd4735fde..f1fcdde0e5 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<string, any>) => ReturnType<ASEvaluator['evaluate']>; + exec: (args: Record<string, any>) => ReturnType<Hpml['evaluate']>; }; /** - * AoiScript evaluator + * Hpml evaluator */ -export class ASEvaluator { +export class Hpml { private variables: Variable[]; private pageVars: PageVar[]; private envVars: Record<keyof typeof envVarsDef, any>; public aiscript?: AiScript; private pageVarUpdatedCallback; public canvases: Record<string, HTMLCanvasElement> = {}; + public vars: Record<string, any>; + public page: Record<string, any>; 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 index 7f34964064..c87d5b9985 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 0000000000..9c71cfaba5 --- /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 index c10198e119..14950e0195 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; } |