mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-12-25 04:03:08 +02:00
Resolve #6256
This commit is contained in:
parent
66377d3f27
commit
90e8527556
11 changed files with 163 additions and 49 deletions
|
@ -797,6 +797,12 @@ _pages:
|
||||||
text: "タイトル"
|
text: "タイトル"
|
||||||
default: "デフォルト値"
|
default: "デフォルト値"
|
||||||
|
|
||||||
|
canvas: "キャンバス"
|
||||||
|
_canvas:
|
||||||
|
id: "キャンバスID"
|
||||||
|
width: "幅"
|
||||||
|
height: "高さ"
|
||||||
|
|
||||||
switch: "スイッチ"
|
switch: "スイッチ"
|
||||||
_switch:
|
_switch:
|
||||||
name: "変数名"
|
name: "変数名"
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
29
src/client/components/page/page.canvas.vue
Normal file
29
src/client/components/page/page.canvas.vue
Normal 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>
|
|
@ -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();
|
||||||
},
|
},
|
||||||
|
|
45
src/client/pages/page-editor/els/page-editor.el.canvas.vue
Normal file
45
src/client/pages/page-editor/els/page-editor.el.canvas.vue
Normal 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>
|
|
@ -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: {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue