Resolve #6256
This commit is contained in:
		
							parent
							
								
									66377d3f27
								
							
						
					
					
						commit
						90e8527556
					
				|  | @ -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: { | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
| 
 | 		this.eval(); | ||||||
| 		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); |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	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) { | ||||||
| 		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() { | 	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(); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
|  | @ -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) { | ||||||
| 				return utils.valToJs(this.aiscript.scope.get(block.value)); | 				try { | ||||||
|  | 					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 New Issue