pages refactoring, fix bug (#7066)
* pages refactoring * pages: fix if block * fix code format * remove passing of the page parameter * remove comment * fix indent * replace with unref * fix conditions of isVarBlock() * Update src/client/scripts/hpml/block.ts use includes() instead of find() Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
		
							parent
							
								
									7fc3e7dd8b
								
							
						
					
					
						commit
						100a131913
					
				
					 24 changed files with 600 additions and 348 deletions
				
			
		|  | @ -1,9 +1,9 @@ | |||
| <template> | ||||
| <component :is="'x-' + value.type" :value="value" :page="page" :hpml="hpml" :key="value.id" :h="h"/> | ||||
| <component :is="'x-' + block.type" :block="block" :hpml="hpml" :key="block.id" :h="h"/> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineComponent, PropType } from 'vue'; | ||||
| import XText from './page.text.vue'; | ||||
| import XSection from './page.section.vue'; | ||||
| import XImage from './page.image.vue'; | ||||
|  | @ -19,22 +19,24 @@ import XCounter from './page.counter.vue'; | |||
| import XRadioButton from './page.radio-button.vue'; | ||||
| import XCanvas from './page.canvas.vue'; | ||||
| import XNote from './page.note.vue'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| import { Block } from '@/scripts/hpml/block'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter, XRadioButton, XCanvas, XNote | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<Block>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		page: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		h: { | ||||
| 			type: Number, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
|  |  | |||
|  | @ -1,51 +1,55 @@ | |||
| <template> | ||||
| <div> | ||||
| 	<MkButton class="kudkigyw" @click="click()" :primary="value.primary">{{ hpml.interpolate(value.text) }}</MkButton> | ||||
| 	<MkButton class="kudkigyw" @click="click()" :primary="block.primary">{{ hpml.interpolate(block.text) }}</MkButton> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineComponent, PropType, unref } from 'vue'; | ||||
| import MkButton from '../ui/button.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { ButtonBlock } from '@/scripts/hpml/block'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkButton | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<ButtonBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		click() { | ||||
| 			if (this.value.action === 'dialog') { | ||||
| 			if (this.block.action === 'dialog') { | ||||
| 				this.hpml.eval(); | ||||
| 				os.dialog({ | ||||
| 					text: this.hpml.interpolate(this.value.content) | ||||
| 					text: this.hpml.interpolate(this.block.content) | ||||
| 				}); | ||||
| 			} else if (this.value.action === 'resetRandom') { | ||||
| 			} else if (this.block.action === 'resetRandom') { | ||||
| 				this.hpml.updateRandomSeed(Math.random()); | ||||
| 				this.hpml.eval(); | ||||
| 			} else if (this.value.action === 'pushEvent') { | ||||
| 			} else if (this.block.action === 'pushEvent') { | ||||
| 				os.api('page-push', { | ||||
| 					pageId: this.hpml.page.id, | ||||
| 					event: this.value.event, | ||||
| 					...(this.value.var ? { | ||||
| 						var: this.hpml.vars[this.value.var] | ||||
| 					event: this.block.event, | ||||
| 					...(this.block.var ? { | ||||
| 						var: unref(this.hpml.vars)[this.block.var] | ||||
| 					} : {}) | ||||
| 				}); | ||||
| 
 | ||||
| 				os.dialog({ | ||||
| 					type: 'success', | ||||
| 					text: this.hpml.interpolate(this.value.message) | ||||
| 					text: this.hpml.interpolate(this.block.message) | ||||
| 				}); | ||||
| 			} else if (this.value.action === 'callAiScript') { | ||||
| 				this.hpml.callAiScript(this.value.fn); | ||||
| 			} else if (this.block.action === 'callAiScript') { | ||||
| 				this.hpml.callAiScript(this.block.fn); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -1,24 +1,36 @@ | |||
| <template> | ||||
| <div class="ysrxegms"> | ||||
| 	<canvas ref="canvas" :width="value.width" :height="value.height"/> | ||||
| 	<canvas ref="canvas" :width="block.width" :height="block.height"/> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineComponent, onMounted, PropType, Ref, ref } from 'vue'; | ||||
| import * as os from '@/os'; | ||||
| import { CanvasBlock } from '@/scripts/hpml/block'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<CanvasBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.hpml.registerCanvas(this.value.name, this.$refs.canvas); | ||||
| 	setup(props, ctx) { | ||||
| 		const canvas: Ref<any> = ref(null); | ||||
| 
 | ||||
| 		onMounted(() => { | ||||
| 			props.hpml.registerCanvas(props.block.name, canvas.value); | ||||
| 		}); | ||||
| 
 | ||||
| 		return { | ||||
| 			canvas | ||||
| 		}; | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -1,41 +1,43 @@ | |||
| <template> | ||||
| <div> | ||||
| 	<MkButton class="llumlmnx" @click="click()">{{ hpml.interpolate(value.text) }}</MkButton> | ||||
| 	<MkButton class="llumlmnx" @click="click()">{{ hpml.interpolate(block.text) }}</MkButton> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { computed, defineComponent, PropType } from 'vue'; | ||||
| import MkButton from '../ui/button.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { CounterVarBlock } from '@/scripts/hpml/block'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkButton | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<CounterVarBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 	setup(props, ctx) { | ||||
| 		const value = computed(() => { | ||||
| 			return props.hpml.vars.value[props.block.name]; | ||||
| 		}); | ||||
| 
 | ||||
| 		function click() { | ||||
| 			props.hpml.updatePageVar(props.block.name, value.value + (props.block.inc || 1)); | ||||
| 			props.hpml.eval(); | ||||
| 		} | ||||
| 
 | ||||
| 		return { | ||||
| 			v: 0, | ||||
| 			click | ||||
| 		}; | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		v() { | ||||
| 			this.hpml.updatePageVar(this.value.name, this.v); | ||||
| 			this.hpml.eval(); | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		click() { | ||||
| 			this.v = this.v + (this.value.inc || 1); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -1,27 +1,29 @@ | |||
| <template> | ||||
| <div v-show="hpml.vars[value.var]"> | ||||
| 	<XBlock v-for="child in value.children" :value="child" :page="page" :hpml="hpml" :key="child.id" :h="h"/> | ||||
| <div v-show="hpml.vars.value[block.var]"> | ||||
| 	<XBlock v-for="child in block.children" :block="child" :hpml="hpml" :key="child.id" :h="h"/> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | ||||
| import { IfBlock } from '@/scripts/hpml/block'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| import { defineComponent, defineAsyncComponent, PropType } from 'vue'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XBlock: defineAsyncComponent(() => import('./page.block.vue')) | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<IfBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		page: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		h: { | ||||
| 			type: Number, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
|  |  | |||
|  | @ -5,25 +5,28 @@ | |||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineComponent, PropType } from 'vue'; | ||||
| import * as os from '@/os'; | ||||
| import { ImageBlock } from '@/scripts/hpml/block'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<ImageBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		page: { | ||||
| 		hpml: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 	setup(props, ctx) { | ||||
| 		const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId); | ||||
| 
 | ||||
| 		return { | ||||
| 			image: null, | ||||
| 			image | ||||
| 		}; | ||||
| 	}, | ||||
| 	created() { | ||||
| 		this.image = this.page.attachedFiles.find(x => x.id === this.value.fileId); | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -1,15 +1,16 @@ | |||
| <template> | ||||
| <div class="voxdxuby"> | ||||
| 	<XNote v-if="note && !value.detailed" v-model:note="note" :key="note.id + ':normal'"/> | ||||
| 	<XNoteDetailed v-if="note && value.detailed" v-model:note="note" :key="note.id + ':detail'"/> | ||||
| 	<XNote v-if="note && !block.detailed" v-model:note="note" :key="note.id + ':normal'"/> | ||||
| 	<XNoteDetailed v-if="note && block.detailed" v-model:note="note" :key="note.id + ':detail'"/> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineComponent, onMounted, PropType, Ref, ref } from 'vue'; | ||||
| import XNote from '@/components/note.vue'; | ||||
| import XNoteDetailed from '@/components/note-detailed.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { NoteBlock } from '@/scripts/hpml/block'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
|  | @ -17,20 +18,24 @@ export default defineComponent({ | |||
| 		XNoteDetailed, | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<NoteBlock>, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 	setup(props, ctx) { | ||||
| 		const note: Ref<Record<string, any> | null> = ref(null); | ||||
| 
 | ||||
| 		onMounted(() => { | ||||
| 			os.api('notes/show', { noteId: props.block.note }) | ||||
| 			.then(result => { | ||||
| 				note.value = result; | ||||
| 			}); | ||||
| 		}); | ||||
| 
 | ||||
| 		return { | ||||
| 			note: null, | ||||
| 			note | ||||
| 		}; | ||||
| 	}, | ||||
| 	async mounted() { | ||||
| 		this.note = await os.api('notes/show', { noteId: this.value.note }); | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -1,36 +1,44 @@ | |||
| <template> | ||||
| <div> | ||||
| 	<MkInput class="kudkigyw" v-model:value="v" type="number">{{ hpml.interpolate(value.text) }}</MkInput> | ||||
| 	<MkInput class="kudkigyw" :value="value" @update:value="updateValue($event)" type="number">{{ hpml.interpolate(block.text) }}</MkInput> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { computed, defineComponent, PropType } from 'vue'; | ||||
| import MkInput from '../ui/input.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| import { NumberInputVarBlock } from '@/scripts/hpml/block'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkInput | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<NumberInputVarBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			v: this.value.default, | ||||
| 		}; | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		v() { | ||||
| 			this.hpml.updatePageVar(this.value.name, this.v); | ||||
| 			this.hpml.eval(); | ||||
| 	setup(props, ctx) { | ||||
| 		const value = computed(() => { | ||||
| 			return props.hpml.vars.value[props.block.name]; | ||||
| 		}); | ||||
| 
 | ||||
| 		function updateValue(newValue) { | ||||
| 			props.hpml.updatePageVar(props.block.name, newValue); | ||||
| 			props.hpml.eval(); | ||||
| 		} | ||||
| 
 | ||||
| 		return { | ||||
| 			value, | ||||
| 			updateValue | ||||
| 		}; | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -6,12 +6,14 @@ | |||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineComponent, PropType } from 'vue'; | ||||
| import { faCheck, faPaperPlane } from '@fortawesome/free-solid-svg-icons'; | ||||
| import MkTextarea from '../ui/textarea.vue'; | ||||
| import MkButton from '../ui/button.vue'; | ||||
| import { apiUrl } from '@/config'; | ||||
| import * as os from '@/os'; | ||||
| import { PostBlock } from '@/scripts/hpml/block'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
|  | @ -19,16 +21,18 @@ export default defineComponent({ | |||
| 		MkButton, | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<PostBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			text: this.hpml.interpolate(this.value.text), | ||||
| 			text: this.hpml.interpolate(this.block.text), | ||||
| 			posted: false, | ||||
| 			posting: false, | ||||
| 			faCheck, faPaperPlane | ||||
|  | @ -37,7 +41,7 @@ export default defineComponent({ | |||
| 	watch: { | ||||
| 		'hpml.vars': { | ||||
| 			handler() { | ||||
| 				this.text = this.hpml.interpolate(this.value.text); | ||||
| 				this.text = this.hpml.interpolate(this.block.text); | ||||
| 			}, | ||||
| 			deep: true | ||||
| 		} | ||||
|  | @ -45,7 +49,7 @@ export default defineComponent({ | |||
| 	methods: { | ||||
| 		upload() { | ||||
| 			const promise = new Promise((ok) => { | ||||
| 				const canvas = this.hpml.canvases[this.value.canvasId]; | ||||
| 				const canvas = this.hpml.canvases[this.block.canvasId]; | ||||
| 				canvas.toBlob(blob => { | ||||
| 					const data = new FormData(); | ||||
| 					data.append('file', blob); | ||||
|  | @ -69,7 +73,7 @@ export default defineComponent({ | |||
| 		}, | ||||
| 		async post() { | ||||
| 			this.posting = true; | ||||
| 			const file = this.value.attachCanvasImage ? await this.upload() : null; | ||||
| 			const file = this.block.attachCanvasImage ? await this.upload() : null; | ||||
| 			os.apiWithDialog('notes/create', { | ||||
| 				text: this.text === '' ? null : this.text, | ||||
| 				fileIds: file ? [file.id] : undefined, | ||||
|  |  | |||
|  | @ -1,37 +1,45 @@ | |||
| <template> | ||||
| <div> | ||||
| 	<div>{{ hpml.interpolate(value.title) }}</div> | ||||
| 	<MkRadio v-for="x in value.values" v-model="v" :value="x" :key="x">{{ x }}</MkRadio> | ||||
| 	<div>{{ hpml.interpolate(block.title) }}</div> | ||||
| 	<MkRadio v-for="item in block.values" :modelValue="value" @update:modelValue="updateValue($event)" :value="item" :key="item">{{ item }}</MkRadio> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { computed, defineComponent, PropType } from 'vue'; | ||||
| import MkRadio from '../ui/radio.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| import { RadioButtonVarBlock } from '@/scripts/hpml/block'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkRadio | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<RadioButtonVarBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			v: this.value.default, | ||||
| 		}; | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		v() { | ||||
| 			this.hpml.updatePageVar(this.value.name, this.v); | ||||
| 			this.hpml.eval(); | ||||
| 	setup(props, ctx) { | ||||
| 		const value = computed(() => { | ||||
| 			return props.hpml.vars.value[props.block.name]; | ||||
| 		}); | ||||
| 
 | ||||
| 		function updateValue(newValue: string) { | ||||
| 			props.hpml.updatePageVar(props.block.name, newValue); | ||||
| 			props.hpml.eval(); | ||||
| 		} | ||||
| 
 | ||||
| 		return { | ||||
| 			value, | ||||
| 			updateValue | ||||
| 		}; | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -1,29 +1,30 @@ | |||
| <template> | ||||
| <section class="sdgxphyu"> | ||||
| 	<component :is="'h' + h">{{ value.title }}</component> | ||||
| 	<component :is="'h' + h">{{ block.title }}</component> | ||||
| 
 | ||||
| 	<div class="children"> | ||||
| 		<XBlock v-for="child in value.children" :value="child" :page="page" :hpml="hpml" :key="child.id" :h="h + 1"/> | ||||
| 		<XBlock v-for="child in block.children" :block="child" :hpml="hpml" :key="child.id" :h="h + 1"/> | ||||
| 	</div> | ||||
| </section> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | ||||
| import { defineComponent, defineAsyncComponent, PropType } from 'vue'; | ||||
| import * as os from '@/os'; | ||||
| import { SectionBlock } from '@/scripts/hpml/block'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XBlock: defineAsyncComponent(() => import('./page.block.vue')) | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<SectionBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		page: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		h: { | ||||
|  |  | |||
|  | @ -1,36 +1,44 @@ | |||
| <template> | ||||
| <div class="hkcxmtwj"> | ||||
| 	<MkSwitch v-model:value="v">{{ hpml.interpolate(value.text) }}</MkSwitch> | ||||
| 	<MkSwitch :value="value" @update:value="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkSwitch> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { computed, defineComponent, PropType } from 'vue'; | ||||
| import MkSwitch from '../ui/switch.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| import { SwitchVarBlock } from '@/scripts/hpml/block'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkSwitch | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<SwitchVarBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			v: this.value.default, | ||||
| 		}; | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		v() { | ||||
| 			this.hpml.updatePageVar(this.value.name, this.v); | ||||
| 			this.hpml.eval(); | ||||
| 	setup(props, ctx) { | ||||
| 		const value = computed(() => { | ||||
| 			return props.hpml.vars.value[props.block.name]; | ||||
| 		}); | ||||
| 
 | ||||
| 		function updateValue(newValue: boolean) { | ||||
| 			props.hpml.updatePageVar(props.block.name, newValue); | ||||
| 			props.hpml.eval(); | ||||
| 		} | ||||
| 
 | ||||
| 		return { | ||||
| 			value, | ||||
| 			updateValue | ||||
| 		}; | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -1,36 +1,44 @@ | |||
| <template> | ||||
| <div> | ||||
| 	<MkInput class="kudkigyw" v-model:value="v" type="text">{{ hpml.interpolate(value.text) }}</MkInput> | ||||
| 	<MkInput class="kudkigyw" :value="value" @update:value="updateValue($event)" type="text">{{ hpml.interpolate(block.text) }}</MkInput> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { computed, defineComponent, PropType } from 'vue'; | ||||
| import MkInput from '../ui/input.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| import { TextInputVarBlock } from '@/scripts/hpml/block'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkInput | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<TextInputVarBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			v: this.value.default, | ||||
| 		}; | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		v() { | ||||
| 			this.hpml.updatePageVar(this.value.name, this.v); | ||||
| 			this.hpml.eval(); | ||||
| 	setup(props, ctx) { | ||||
| 		const value = computed(() => { | ||||
| 			return props.hpml.vars.value[props.block.name]; | ||||
| 		}); | ||||
| 
 | ||||
| 		function updateValue(newValue) { | ||||
| 			props.hpml.updatePageVar(props.block.name, newValue); | ||||
| 			props.hpml.eval(); | ||||
| 		} | ||||
| 
 | ||||
| 		return { | ||||
| 			value, | ||||
| 			updateValue | ||||
| 		}; | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -6,7 +6,9 @@ | |||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineAsyncComponent, defineComponent } from 'vue'; | ||||
| import { TextBlock } from '@/scripts/hpml/block'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| import { defineAsyncComponent, defineComponent, PropType } from 'vue'; | ||||
| import { parse } from '../../../mfm/parse'; | ||||
| import { unique } from '../../../prelude/array'; | ||||
| 
 | ||||
|  | @ -15,16 +17,18 @@ export default defineComponent({ | |||
| 		MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')), | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<TextBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			text: this.hpml.interpolate(this.value.text), | ||||
| 			text: this.hpml.interpolate(this.block.text), | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
|  | @ -43,7 +47,7 @@ export default defineComponent({ | |||
| 	watch: { | ||||
| 		'hpml.vars': { | ||||
| 			handler() { | ||||
| 				this.text = this.hpml.interpolate(this.value.text); | ||||
| 				this.text = this.hpml.interpolate(this.block.text); | ||||
| 			}, | ||||
| 			deep: true | ||||
| 		} | ||||
|  |  | |||
|  | @ -1,36 +1,45 @@ | |||
| <template> | ||||
| <div> | ||||
| 	<MkTextarea v-model:value="v">{{ hpml.interpolate(value.text) }}</MkTextarea> | ||||
| 	<MkTextarea :value="value" @update:value="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkTextarea> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { computed, defineComponent, PropType } from 'vue'; | ||||
| import MkTextarea from '../ui/textarea.vue'; | ||||
| import * as os from '@/os'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| import { HpmlTextInput } from '@/scripts/hpml'; | ||||
| import { TextInputVarBlock } from '@/scripts/hpml/block'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkTextarea | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<TextInputVarBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			v: this.value.default, | ||||
| 		}; | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		v() { | ||||
| 			this.hpml.updatePageVar(this.value.name, this.v); | ||||
| 			this.hpml.eval(); | ||||
| 	setup(props, ctx) { | ||||
| 		const value = computed(() => { | ||||
| 			return props.hpml.vars.value[props.block.name]; | ||||
| 		}); | ||||
| 
 | ||||
| 		function updateValue(newValue) { | ||||
| 			props.hpml.updatePageVar(props.block.name, newValue); | ||||
| 			props.hpml.eval(); | ||||
| 		} | ||||
| 
 | ||||
| 		return { | ||||
| 			value, | ||||
| 			updateValue | ||||
| 		}; | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -3,7 +3,9 @@ | |||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { TextBlock } from '@/scripts/hpml/block'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| import { defineComponent, PropType } from 'vue'; | ||||
| import MkTextarea from '../ui/textarea.vue'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|  | @ -11,22 +13,24 @@ export default defineComponent({ | |||
| 		MkTextarea | ||||
| 	}, | ||||
| 	props: { | ||||
| 		value: { | ||||
| 		block: { | ||||
| 			type: Object as PropType<TextBlock>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		hpml: { | ||||
| 			type: Object as PropType<Hpml>, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			text: this.hpml.interpolate(this.value.text), | ||||
| 			text: this.hpml.interpolate(this.block.text), | ||||
| 		}; | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		'hpml.vars': { | ||||
| 			handler() { | ||||
| 				this.text = this.hpml.interpolate(this.value.text); | ||||
| 				this.text = this.hpml.interpolate(this.block.text); | ||||
| 			}, | ||||
| 			deep: true | ||||
| 		} | ||||
|  |  | |||
|  | @ -1,77 +1,72 @@ | |||
| <template> | ||||
| <div class="iroscrza" :class="{ center: page.alignCenter, serif: page.font === 'serif' }" v-if="hpml"> | ||||
| 	<XBlock v-for="child in page.content" :value="child" :page="page" :hpml="hpml" :key="child.id" :h="2"/> | ||||
| 	<XBlock v-for="child in page.content" :block="child" :hpml="hpml" :key="child.id" :h="2"/> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { defineComponent, onMounted, nextTick, onUnmounted, PropType } from 'vue'; | ||||
| import { parse } from '@syuilo/aiscript'; | ||||
| import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faHeart } from '@fortawesome/free-regular-svg-icons'; | ||||
| import XBlock from './page.block.vue'; | ||||
| import { Hpml } from '@/scripts/hpml/evaluator'; | ||||
| import { url } from '@/config'; | ||||
| import { $i } from '@/account'; | ||||
| import { defaultStore } from '@/store'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XBlock | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		page: { | ||||
| 			type: Object, | ||||
| 			type: Object as PropType<Record<string, any>>, | ||||
| 			required: true | ||||
| 		}, | ||||
| 	}, | ||||
| 	setup(props, ctx) { | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			hpml: null, | ||||
| 			faHeartS, faHeart | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	created() { | ||||
| 		this.hpml = new Hpml(this.page, { | ||||
| 		const hpml = new Hpml(props.page, { | ||||
| 			randomSeed: Math.random(), | ||||
| 			visitor: this.$i, | ||||
| 			visitor: $i, | ||||
| 			url: url, | ||||
| 			enableAiScript: !this.$store.state.disablePagesScript | ||||
| 			enableAiScript: !defaultStore.state.disablePagesScript | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		this.$nextTick(() => { | ||||
| 			if (this.page.script && this.hpml.aiscript) { | ||||
| 				let ast; | ||||
| 				try { | ||||
| 					ast = parse(this.page.script); | ||||
| 				} catch (e) { | ||||
| 					console.error(e); | ||||
| 					/*os.dialog({ | ||||
| 						type: 'error', | ||||
| 						text: 'Syntax error :(' | ||||
| 					});*/ | ||||
| 					return; | ||||
| 		onMounted(() => { | ||||
| 			nextTick(() => { | ||||
| 				if (props.page.script && hpml.aiscript) { | ||||
| 					let ast; | ||||
| 					try { | ||||
| 						ast = parse(props.page.script); | ||||
| 					} catch (e) { | ||||
| 						console.error(e); | ||||
| 						/*os.dialog({ | ||||
| 							type: 'error', | ||||
| 							text: 'Syntax error :(' | ||||
| 						});*/ | ||||
| 						return; | ||||
| 					} | ||||
| 					hpml.aiscript.exec(ast).then(() => { | ||||
| 						hpml.eval(); | ||||
| 					}).catch(e => { | ||||
| 						console.error(e); | ||||
| 						/*os.dialog({ | ||||
| 							type: 'error', | ||||
| 							text: e | ||||
| 						});*/ | ||||
| 					}); | ||||
| 				} else { | ||||
| 					hpml.eval(); | ||||
| 				} | ||||
| 				this.hpml.aiscript.exec(ast).then(() => { | ||||
| 					this.hpml.eval(); | ||||
| 				}).catch(e => { | ||||
| 					console.error(e); | ||||
| 					/*os.dialog({ | ||||
| 						type: 'error', | ||||
| 						text: e | ||||
| 					});*/ | ||||
| 				}); | ||||
| 			} else { | ||||
| 				this.hpml.eval(); | ||||
| 			} | ||||
| 			}); | ||||
| 			onUnmounted(() => { | ||||
| 				if (hpml.aiscript) hpml.aiscript.abort(); | ||||
| 			}); | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	beforeUnmount() { | ||||
| 		if (this.hpml.aiscript) this.hpml.aiscript.abort(); | ||||
| 		return { | ||||
| 			hpml, | ||||
| 		}; | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -61,8 +61,10 @@ import { faPencilAlt, faPlug } from '@fortawesome/free-solid-svg-icons'; | |||
| import { v4 as uuid } from 'uuid'; | ||||
| import XContainer from './page-editor.container.vue'; | ||||
| import MkTextarea from '@/components/ui/textarea.vue'; | ||||
| import { isLiteralBlock, funcDefs, blockDefs } from '@/scripts/hpml/index'; | ||||
| import { blockDefs } from '@/scripts/hpml/index'; | ||||
| import * as os from '@/os'; | ||||
| import { isLiteralValue } from '@/scripts/hpml/expr'; | ||||
| import { funcDefs } from '@/scripts/hpml/lib'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
|  | @ -166,7 +168,7 @@ export default defineComponent({ | |||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			if (isLiteralBlock(this.value)) return; | ||||
| 			if (isLiteralValue(this.value)) return; | ||||
| 
 | ||||
| 			const empties = []; | ||||
| 			for (let i = 0; i < funcDefs[this.value.type].in.length; i++) { | ||||
|  |  | |||
							
								
								
									
										109
									
								
								src/client/scripts/hpml/block.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/client/scripts/hpml/block.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,109 @@ | |||
| // blocks
 | ||||
| 
 | ||||
| export type BlockBase = { | ||||
| 	id: string; | ||||
| 	type: string; | ||||
| }; | ||||
| 
 | ||||
| export type TextBlock = BlockBase & { | ||||
| 	type: 'text'; | ||||
| 	text: string; | ||||
| }; | ||||
| 
 | ||||
| export type SectionBlock = BlockBase & { | ||||
| 	type: 'section'; | ||||
| 	title: string; | ||||
| 	children: (Block | VarBlock)[]; | ||||
| }; | ||||
| 
 | ||||
| export type ImageBlock = BlockBase & { | ||||
| 	type: 'image'; | ||||
| 	fileId: string | null; | ||||
| }; | ||||
| 
 | ||||
| export type ButtonBlock = BlockBase & { | ||||
| 	type: 'button'; | ||||
| 	text: any; | ||||
| 	primary: boolean; | ||||
| 	action: string; | ||||
| 	content: string; | ||||
| 	event: string; | ||||
| 	message: string; | ||||
| 	var: string; | ||||
| 	fn: string; | ||||
| }; | ||||
| 
 | ||||
| export type IfBlock = BlockBase & { | ||||
| 	type: 'if'; | ||||
| 	var: string; | ||||
| 	children: Block[]; | ||||
| }; | ||||
| 
 | ||||
| export type TextareaBlock = BlockBase & { | ||||
| 	type: 'textarea'; | ||||
| 	text: string; | ||||
| }; | ||||
| 
 | ||||
| export type PostBlock = BlockBase & { | ||||
| 	type: 'post'; | ||||
| 	text: string; | ||||
| 	attachCanvasImage: boolean; | ||||
| 	canvasId: string; | ||||
| }; | ||||
| 
 | ||||
| export type CanvasBlock = BlockBase & { | ||||
| 	type: 'canvas'; | ||||
| 	name: string; // canvas id
 | ||||
| 	width: number; | ||||
| 	height: number; | ||||
| }; | ||||
| 
 | ||||
| export type NoteBlock = BlockBase & { | ||||
| 	type: 'note'; | ||||
| 	detailed: boolean; | ||||
| 	note: string | null; | ||||
| }; | ||||
| 
 | ||||
| export type Block = | ||||
| 	TextBlock | SectionBlock | ImageBlock | ButtonBlock | IfBlock | TextareaBlock | PostBlock | CanvasBlock | NoteBlock | VarBlock; | ||||
| 
 | ||||
| // variable blocks
 | ||||
| 
 | ||||
| export type VarBlockBase = BlockBase & { | ||||
| 	name: string; | ||||
| }; | ||||
| 
 | ||||
| export type NumberInputVarBlock = VarBlockBase & { | ||||
| 	type: 'numberInput'; | ||||
| 	text: string; | ||||
| }; | ||||
| 
 | ||||
| export type TextInputVarBlock = VarBlockBase & { | ||||
| 	type: 'textInput'; | ||||
| 	text: string; | ||||
| }; | ||||
| 
 | ||||
| export type SwitchVarBlock = VarBlockBase & { | ||||
| 	type: 'switch'; | ||||
| 	text: string; | ||||
| }; | ||||
| 
 | ||||
| export type RadioButtonVarBlock = VarBlockBase & { | ||||
| 	type: 'radioButton'; | ||||
| 	title: string; | ||||
| 	values: string[]; | ||||
| }; | ||||
| 
 | ||||
| export type CounterVarBlock = VarBlockBase & { | ||||
| 	type: 'counter'; | ||||
| 	text: string; | ||||
| 	inc: number; | ||||
| }; | ||||
| 
 | ||||
| export type VarBlock = | ||||
| 	NumberInputVarBlock | TextInputVarBlock | SwitchVarBlock | RadioButtonVarBlock | CounterVarBlock; | ||||
| 
 | ||||
| const varBlock = ['numberInput', 'textInput', 'switch', 'radioButton', 'counter']; | ||||
| export function isVarBlock(block: Block): block is VarBlock { | ||||
| 	return varBlock.includes(block.type); | ||||
| } | ||||
|  | @ -1,12 +1,13 @@ | |||
| import autobind from 'autobind-decorator'; | ||||
| import { Variable, PageVar, envVarsDef, Block, isFnBlock, Fn, HpmlScope, HpmlError } from '.'; | ||||
| import { PageVar, envVarsDef, Fn, HpmlScope, HpmlError } from '.'; | ||||
| import { version } from '@/config'; | ||||
| import { AiScript, utils, values } from '@syuilo/aiscript'; | ||||
| import { createAiScriptEnv } from '../aiscript/api'; | ||||
| import { collectPageVars } from '../collect-page-vars'; | ||||
| import { initHpmlLib, initAiLib } from './lib'; | ||||
| import * as os from '@/os'; | ||||
| import { markRaw, ref, Ref } from 'vue'; | ||||
| import { markRaw, ref, Ref, unref } from 'vue'; | ||||
| import { Expr, isLiteralValue, Variable } from './expr'; | ||||
| 
 | ||||
| /** | ||||
|  * Hpml evaluator | ||||
|  | @ -94,7 +95,7 @@ export class Hpml { | |||
| 	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 = unref(this.vars)[match.slice(1, -1).trim()]; | ||||
| 			return v == null ? 'NULL' : v.toString(); | ||||
| 		}); | ||||
| 	} | ||||
|  | @ -158,72 +159,76 @@ export class Hpml { | |||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	private evaluate(block: Block, scope: HpmlScope): any { | ||||
| 		if (block.type === null) { | ||||
| 			return null; | ||||
| 		} | ||||
| 	private evaluate(expr: Expr, scope: HpmlScope): any { | ||||
| 
 | ||||
| 		if (block.type === 'number') { | ||||
| 			return parseInt(block.value, 10); | ||||
| 		} | ||||
| 
 | ||||
| 		if (block.type === 'text' || block.type === 'multiLineText') { | ||||
| 			return this._interpolateScope(block.value || '', scope); | ||||
| 		} | ||||
| 
 | ||||
| 		if (block.type === 'textList') { | ||||
| 			return this._interpolateScope(block.value || '', scope).trim().split('\n'); | ||||
| 		} | ||||
| 
 | ||||
| 		if (block.type === 'ref') { | ||||
| 			return scope.getState(block.value); | ||||
| 		} | ||||
| 
 | ||||
| 		if (block.type === 'aiScriptVar') { | ||||
| 			if (this.aiscript) { | ||||
| 				try { | ||||
| 					return utils.valToJs(this.aiscript.scope.get(block.value)); | ||||
| 				} catch (e) { | ||||
| 					return null; | ||||
| 				} | ||||
| 			} else { | ||||
| 		if (isLiteralValue(expr)) { | ||||
| 			if (expr.type === null) { | ||||
| 				return null; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// Define user function
 | ||||
| 		if (isFnBlock(block)) { | ||||
| 			return { | ||||
| 				slots: block.value.slots.map(x => x.name), | ||||
| 				exec: (slotArg: Record<string, any>) => { | ||||
| 					return this.evaluate(block.value.expression, scope.createChildScope(slotArg, block.id)); | ||||
| 			if (expr.type === 'number') { | ||||
| 				return parseInt((expr.value as any), 10); | ||||
| 			} | ||||
| 
 | ||||
| 			if (expr.type === 'text' || expr.type === 'multiLineText') { | ||||
| 				return this._interpolateScope(expr.value || '', scope); | ||||
| 			} | ||||
| 
 | ||||
| 			if (expr.type === 'textList') { | ||||
| 				return this._interpolateScope(expr.value || '', scope).trim().split('\n'); | ||||
| 			} | ||||
| 
 | ||||
| 			if (expr.type === 'ref') { | ||||
| 				return scope.getState(expr.value); | ||||
| 			} | ||||
| 
 | ||||
| 			if (expr.type === 'aiScriptVar') { | ||||
| 				if (this.aiscript) { | ||||
| 					try { | ||||
| 						return utils.valToJs(this.aiscript.scope.get(expr.value)); | ||||
| 					} catch (e) { | ||||
| 						return null; | ||||
| 					} | ||||
| 				} else { | ||||
| 					return null; | ||||
| 				} | ||||
| 			} as Fn; | ||||
| 			} | ||||
| 
 | ||||
| 			// Define user function
 | ||||
| 			if (expr.type == 'fn') { | ||||
| 				return { | ||||
| 					slots: expr.value.slots.map(x => x.name), | ||||
| 					exec: (slotArg: Record<string, any>) => { | ||||
| 						return this.evaluate(expr.value.expression, scope.createChildScope(slotArg, expr.id)); | ||||
| 					} | ||||
| 				} as Fn; | ||||
| 			} | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		// Call user function
 | ||||
| 		if (block.type.startsWith('fn:')) { | ||||
| 			const fnName = block.type.split(':')[1]; | ||||
| 		if (expr.type.startsWith('fn:')) { | ||||
| 			const fnName = expr.type.split(':')[1]; | ||||
| 			const fn = scope.getState(fnName); | ||||
| 			const args = {} as Record<string, any>; | ||||
| 			for (let i = 0; i < fn.slots.length; i++) { | ||||
| 				const name = fn.slots[i]; | ||||
| 				args[name] = this.evaluate(block.args[i], scope); | ||||
| 				args[name] = this.evaluate(expr.args[i], scope); | ||||
| 			} | ||||
| 			return fn.exec(args); | ||||
| 		} | ||||
| 
 | ||||
| 		if (block.args === undefined) return null; | ||||
| 		if (expr.args === undefined) return null; | ||||
| 
 | ||||
| 		const funcs = initHpmlLib(block, scope, this.opts.randomSeed, this.opts.visitor); | ||||
| 		const funcs = initHpmlLib(expr, scope, this.opts.randomSeed, this.opts.visitor); | ||||
| 
 | ||||
| 		// Call function
 | ||||
| 		const fnName = block.type; | ||||
| 		const fnName = expr.type; | ||||
| 		const fn = (funcs as any)[fnName]; | ||||
| 		if (fn == null) { | ||||
| 			throw new HpmlError(`No such function '${fnName}'`); | ||||
| 		} else { | ||||
| 			return fn(...block.args.map(x => this.evaluate(x, scope))); | ||||
| 			return fn(...expr.args.map(x => this.evaluate(x, scope))); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
							
								
								
									
										79
									
								
								src/client/scripts/hpml/expr.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/client/scripts/hpml/expr.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,79 @@ | |||
| import { literalDefs, Type } from '.'; | ||||
| 
 | ||||
| export type ExprBase = { | ||||
| 	id: string; | ||||
| }; | ||||
| 
 | ||||
| // value
 | ||||
| 
 | ||||
| export type EmptyValue = ExprBase & { | ||||
| 	type: null; | ||||
| 	value: null; | ||||
| }; | ||||
| 
 | ||||
| export type TextValue = ExprBase & { | ||||
| 	type: 'text'; | ||||
| 	value: string; | ||||
| }; | ||||
| 
 | ||||
| export type MultiLineTextValue = ExprBase  & { | ||||
| 	type: 'multiLineText'; | ||||
| 	value: string; | ||||
| }; | ||||
| 
 | ||||
| export type TextListValue = ExprBase & { | ||||
| 	type: 'textList'; | ||||
| 	value: string; | ||||
| }; | ||||
| 
 | ||||
| export type NumberValue = ExprBase & { | ||||
| 	type: 'number'; | ||||
| 	value: number; | ||||
| }; | ||||
| 
 | ||||
| export type RefValue = ExprBase & { | ||||
| 	type: 'ref'; | ||||
| 	value: string; // value is variable name
 | ||||
| }; | ||||
| 
 | ||||
| export type AiScriptRefValue = ExprBase & { | ||||
| 	type: 'aiScriptVar'; | ||||
| 	value: string; // value is variable name
 | ||||
| }; | ||||
| 
 | ||||
| export type UserFnValue = ExprBase & { | ||||
| 	type: 'fn'; | ||||
| 	value: UserFnInnerValue; | ||||
| }; | ||||
| type UserFnInnerValue = { | ||||
| 	slots: { | ||||
| 		name: string; | ||||
| 		type: Type; | ||||
| 	}[]; | ||||
| 	expression: Expr; | ||||
| }; | ||||
| 
 | ||||
| export type Value = | ||||
| 	EmptyValue | TextValue | MultiLineTextValue | TextListValue | NumberValue | RefValue | AiScriptRefValue | UserFnValue; | ||||
| 
 | ||||
| export function isLiteralValue(expr: Expr): expr is Value { | ||||
| 	if (expr.type == null) return true; | ||||
| 	if (literalDefs[expr.type]) return true; | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| // call function
 | ||||
| 
 | ||||
| export type CallFn = ExprBase & { // "fn:hoge" or string
 | ||||
| 	type: string; | ||||
| 	args: Expr[]; | ||||
| 	value: null; | ||||
| }; | ||||
| 
 | ||||
| // variable
 | ||||
| export type Variable = (Value | CallFn) & { | ||||
| 	name: string; | ||||
| }; | ||||
| 
 | ||||
| // expression
 | ||||
| export type Expr = Variable | Value | CallFn; | ||||
|  | @ -3,52 +3,16 @@ | |||
|  */ | ||||
| 
 | ||||
| import autobind from 'autobind-decorator'; | ||||
| 
 | ||||
| import { | ||||
| 	faMagic, | ||||
| 	faSquareRootAlt, | ||||
| 	faAlignLeft, | ||||
| 	faShareAlt, | ||||
| 	faPlus, | ||||
| 	faMinus, | ||||
| 	faTimes, | ||||
| 	faDivide, | ||||
| 	faList, | ||||
| 	faQuoteRight, | ||||
| 	faEquals, | ||||
| 	faGreaterThan, | ||||
| 	faLessThan, | ||||
| 	faGreaterThanEqual, | ||||
| 	faLessThanEqual, | ||||
| 	faNotEqual, | ||||
| 	faDice, | ||||
| 	faSortNumericUp, | ||||
| 	faExchangeAlt, | ||||
| 	faRecycle, | ||||
| 	faIndent, | ||||
| 	faCalculator, | ||||
| } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faFlag } from '@fortawesome/free-regular-svg-icons'; | ||||
| import { Hpml } from './evaluator'; | ||||
| 
 | ||||
| export type Block<V = any> = { | ||||
| 	id: string; | ||||
| 	type: string; | ||||
| 	args: Block[]; | ||||
| 	value: V; | ||||
| }; | ||||
| 
 | ||||
| export type FnBlock = Block<{ | ||||
| 	slots: { | ||||
| 		name: string; | ||||
| 		type: Type; | ||||
| 	}[]; | ||||
| 	expression: Block; | ||||
| }>; | ||||
| 
 | ||||
| export type Variable = Block & { | ||||
| 	name: string; | ||||
| }; | ||||
| import { funcDefs } from './lib'; | ||||
| 
 | ||||
| export type Fn = { | ||||
| 	slots: string[]; | ||||
|  | @ -57,46 +21,6 @@ export type Fn = { | |||
| 
 | ||||
| export type Type = 'string' | 'number' | 'boolean' | 'stringArray' | null; | ||||
| 
 | ||||
| export const funcDefs: Record<string, { in: any[]; out: any; category: string; icon: any; }> = { | ||||
| 	if:              { in: ['boolean', 0, 0],              out: 0,             category: 'flow',       icon: faShareAlt, }, | ||||
| 	for:             { in: ['number', 'function'],         out: null,          category: 'flow',       icon: faRecycle, }, | ||||
| 	not:             { in: ['boolean'],                    out: 'boolean',     category: 'logical',    icon: faFlag, }, | ||||
| 	or:              { in: ['boolean', 'boolean'],         out: 'boolean',     category: 'logical',    icon: faFlag, }, | ||||
| 	and:             { in: ['boolean', 'boolean'],         out: 'boolean',     category: 'logical',    icon: faFlag, }, | ||||
| 	add:             { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faPlus, }, | ||||
| 	subtract:        { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faMinus, }, | ||||
| 	multiply:        { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faTimes, }, | ||||
| 	divide:          { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faDivide, }, | ||||
| 	mod:             { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faDivide, }, | ||||
| 	round:           { in: ['number'],                     out: 'number',      category: 'operation',  icon: faCalculator, }, | ||||
| 	eq:              { in: [0, 0],                         out: 'boolean',     category: 'comparison', icon: faEquals, }, | ||||
| 	notEq:           { in: [0, 0],                         out: 'boolean',     category: 'comparison', icon: faNotEqual, }, | ||||
| 	gt:              { in: ['number', 'number'],           out: 'boolean',     category: 'comparison', icon: faGreaterThan, }, | ||||
| 	lt:              { in: ['number', 'number'],           out: 'boolean',     category: 'comparison', icon: faLessThan, }, | ||||
| 	gtEq:            { in: ['number', 'number'],           out: 'boolean',     category: 'comparison', icon: faGreaterThanEqual, }, | ||||
| 	ltEq:            { in: ['number', 'number'],           out: 'boolean',     category: 'comparison', icon: faLessThanEqual, }, | ||||
| 	strLen:          { in: ['string'],                     out: 'number',      category: 'text',       icon: faQuoteRight, }, | ||||
| 	strPick:         { in: ['string', 'number'],           out: 'string',      category: 'text',       icon: faQuoteRight, }, | ||||
| 	strReplace:      { in: ['string', 'string', 'string'], out: 'string',      category: 'text',       icon: faQuoteRight, }, | ||||
| 	strReverse:      { in: ['string'],                     out: 'string',      category: 'text',       icon: faQuoteRight, }, | ||||
| 	join:            { in: ['stringArray', 'string'],      out: 'string',      category: 'text',       icon: faQuoteRight, }, | ||||
| 	stringToNumber:  { in: ['string'],                     out: 'number',      category: 'convert',    icon: faExchangeAlt, }, | ||||
| 	numberToString:  { in: ['number'],                     out: 'string',      category: 'convert',    icon: faExchangeAlt, }, | ||||
| 	splitStrByLine:  { in: ['string'],                     out: 'stringArray', category: 'convert',    icon: faExchangeAlt, }, | ||||
| 	pick:            { in: [null, 'number'],               out: null,          category: 'list',       icon: faIndent, }, | ||||
| 	listLen:         { in: [null],                         out: 'number',      category: 'list',       icon: faIndent, }, | ||||
| 	rannum:          { in: ['number', 'number'],           out: 'number',      category: 'random',     icon: faDice, }, | ||||
| 	dailyRannum:     { in: ['number', 'number'],           out: 'number',      category: 'random',     icon: faDice, }, | ||||
| 	seedRannum:      { in: [null, 'number', 'number'],     out: 'number',      category: 'random',     icon: faDice, }, | ||||
| 	random:          { in: ['number'],                     out: 'boolean',     category: 'random',     icon: faDice, }, | ||||
| 	dailyRandom:     { in: ['number'],                     out: 'boolean',     category: 'random',     icon: faDice, }, | ||||
| 	seedRandom:      { in: [null, 'number'],               out: 'boolean',     category: 'random',     icon: faDice, }, | ||||
| 	randomPick:      { in: [0],                            out: 0,             category: 'random',     icon: faDice, }, | ||||
| 	dailyRandomPick: { in: [0],                            out: 0,             category: 'random',     icon: faDice, }, | ||||
| 	seedRandomPick:  { in: [null, 0],                      out: 0,             category: 'random',     icon: faDice, }, | ||||
| 	DRPWPM:      { in: ['stringArray'],                out: 'string',      category: 'random',     icon: faDice, }, // dailyRandomPickWithProbabilityMapping
 | ||||
| }; | ||||
| 
 | ||||
| export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = { | ||||
| 	text:          { out: 'string',      category: 'value', icon: faQuoteRight, }, | ||||
| 	multiLineText: { out: 'string',      category: 'value', icon: faAlignLeft, }, | ||||
|  | @ -116,10 +40,6 @@ export const blockDefs = [ | |||
| 	})) | ||||
| ]; | ||||
| 
 | ||||
| export function isFnBlock(block: Block): block is FnBlock { | ||||
| 	return block.type === 'fn'; | ||||
| } | ||||
| 
 | ||||
| export type PageVar = { name: string; value: any; type: Type; }; | ||||
| 
 | ||||
| export const envVarsDef: Record<string, Type> = { | ||||
|  | @ -140,12 +60,6 @@ export const envVarsDef: Record<string, Type> = { | |||
| 	NULL: null, | ||||
| }; | ||||
| 
 | ||||
| export function isLiteralBlock(v: Block) { | ||||
| 	if (v.type === null) return true; | ||||
| 	if (literalDefs[v.type]) return true; | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| export class HpmlScope { | ||||
| 	private layerdStates: Record<string, any>[]; | ||||
| 	public name: string; | ||||
|  |  | |||
|  | @ -2,9 +2,31 @@ import * as tinycolor from 'tinycolor2'; | |||
| import Chart from 'chart.js'; | ||||
| import { Hpml } from './evaluator'; | ||||
| import { values, utils } from '@syuilo/aiscript'; | ||||
| import { Block, Fn, HpmlScope } from '.'; | ||||
| import { Fn, HpmlScope } from '.'; | ||||
| import { Expr } from './expr'; | ||||
| import * as seedrandom from 'seedrandom'; | ||||
| 
 | ||||
| import { | ||||
| 	faShareAlt, | ||||
| 	faPlus, | ||||
| 	faMinus, | ||||
| 	faTimes, | ||||
| 	faDivide, | ||||
| 	faQuoteRight, | ||||
| 	faEquals, | ||||
| 	faGreaterThan, | ||||
| 	faLessThan, | ||||
| 	faGreaterThanEqual, | ||||
| 	faLessThanEqual, | ||||
| 	faNotEqual, | ||||
| 	faDice, | ||||
| 	faExchangeAlt, | ||||
| 	faRecycle, | ||||
| 	faIndent, | ||||
| 	faCalculator, | ||||
| } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faFlag } from '@fortawesome/free-regular-svg-icons'; | ||||
| 
 | ||||
| // https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs
 | ||||
| Chart.pluginService.register({ | ||||
| 	beforeDraw: (chart, easing) => { | ||||
|  | @ -125,7 +147,47 @@ export function initAiLib(hpml: Hpml) { | |||
| 	}; | ||||
| } | ||||
| 
 | ||||
| export function initHpmlLib(block: Block, scope: HpmlScope, randomSeed: string, visitor?: any) { | ||||
| export const funcDefs: Record<string, { in: any[]; out: any; category: string; icon: any; }> = { | ||||
| 	if:              { in: ['boolean', 0, 0],              out: 0,             category: 'flow',       icon: faShareAlt, }, | ||||
| 	for:             { in: ['number', 'function'],         out: null,          category: 'flow',       icon: faRecycle, }, | ||||
| 	not:             { in: ['boolean'],                    out: 'boolean',     category: 'logical',    icon: faFlag, }, | ||||
| 	or:              { in: ['boolean', 'boolean'],         out: 'boolean',     category: 'logical',    icon: faFlag, }, | ||||
| 	and:             { in: ['boolean', 'boolean'],         out: 'boolean',     category: 'logical',    icon: faFlag, }, | ||||
| 	add:             { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faPlus, }, | ||||
| 	subtract:        { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faMinus, }, | ||||
| 	multiply:        { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faTimes, }, | ||||
| 	divide:          { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faDivide, }, | ||||
| 	mod:             { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faDivide, }, | ||||
| 	round:           { in: ['number'],                     out: 'number',      category: 'operation',  icon: faCalculator, }, | ||||
| 	eq:              { in: [0, 0],                         out: 'boolean',     category: 'comparison', icon: faEquals, }, | ||||
| 	notEq:           { in: [0, 0],                         out: 'boolean',     category: 'comparison', icon: faNotEqual, }, | ||||
| 	gt:              { in: ['number', 'number'],           out: 'boolean',     category: 'comparison', icon: faGreaterThan, }, | ||||
| 	lt:              { in: ['number', 'number'],           out: 'boolean',     category: 'comparison', icon: faLessThan, }, | ||||
| 	gtEq:            { in: ['number', 'number'],           out: 'boolean',     category: 'comparison', icon: faGreaterThanEqual, }, | ||||
| 	ltEq:            { in: ['number', 'number'],           out: 'boolean',     category: 'comparison', icon: faLessThanEqual, }, | ||||
| 	strLen:          { in: ['string'],                     out: 'number',      category: 'text',       icon: faQuoteRight, }, | ||||
| 	strPick:         { in: ['string', 'number'],           out: 'string',      category: 'text',       icon: faQuoteRight, }, | ||||
| 	strReplace:      { in: ['string', 'string', 'string'], out: 'string',      category: 'text',       icon: faQuoteRight, }, | ||||
| 	strReverse:      { in: ['string'],                     out: 'string',      category: 'text',       icon: faQuoteRight, }, | ||||
| 	join:            { in: ['stringArray', 'string'],      out: 'string',      category: 'text',       icon: faQuoteRight, }, | ||||
| 	stringToNumber:  { in: ['string'],                     out: 'number',      category: 'convert',    icon: faExchangeAlt, }, | ||||
| 	numberToString:  { in: ['number'],                     out: 'string',      category: 'convert',    icon: faExchangeAlt, }, | ||||
| 	splitStrByLine:  { in: ['string'],                     out: 'stringArray', category: 'convert',    icon: faExchangeAlt, }, | ||||
| 	pick:            { in: [null, 'number'],               out: null,          category: 'list',       icon: faIndent, }, | ||||
| 	listLen:         { in: [null],                         out: 'number',      category: 'list',       icon: faIndent, }, | ||||
| 	rannum:          { in: ['number', 'number'],           out: 'number',      category: 'random',     icon: faDice, }, | ||||
| 	dailyRannum:     { in: ['number', 'number'],           out: 'number',      category: 'random',     icon: faDice, }, | ||||
| 	seedRannum:      { in: [null, 'number', 'number'],     out: 'number',      category: 'random',     icon: faDice, }, | ||||
| 	random:          { in: ['number'],                     out: 'boolean',     category: 'random',     icon: faDice, }, | ||||
| 	dailyRandom:     { in: ['number'],                     out: 'boolean',     category: 'random',     icon: faDice, }, | ||||
| 	seedRandom:      { in: [null, 'number'],               out: 'boolean',     category: 'random',     icon: faDice, }, | ||||
| 	randomPick:      { in: [0],                            out: 0,             category: 'random',     icon: faDice, }, | ||||
| 	dailyRandomPick: { in: [0],                            out: 0,             category: 'random',     icon: faDice, }, | ||||
| 	seedRandomPick:  { in: [null, 0],                      out: 0,             category: 'random',     icon: faDice, }, | ||||
| 	DRPWPM:      { in: ['stringArray'],                out: 'string',      category: 'random',     icon: faDice, }, // dailyRandomPickWithProbabilityMapping
 | ||||
| }; | ||||
| 
 | ||||
| export function initHpmlLib(expr: Expr, scope: HpmlScope, randomSeed: string, visitor?: any) { | ||||
| 
 | ||||
| 	const date = new Date(); | ||||
| 	const day = `${visitor ? visitor.id : ''} ${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`; | ||||
|  | @ -166,12 +228,12 @@ export function initHpmlLib(block: Block, scope: HpmlScope, randomSeed: string, | |||
| 		splitStrByLine: (a: string) => a.split('\n'), | ||||
| 		pick: (list: any[], i: number) => list[i - 1], | ||||
| 		listLen: (list: any[]) => list.length, | ||||
| 		random: (probability: number) => Math.floor(seedrandom(`${randomSeed}:${block.id}`)() * 100) < probability, | ||||
| 		rannum: (min: number, max: number) => min + Math.floor(seedrandom(`${randomSeed}:${block.id}`)() * (max - min + 1)), | ||||
| 		randomPick: (list: any[]) => list[Math.floor(seedrandom(`${randomSeed}:${block.id}`)() * list.length)], | ||||
| 		dailyRandom: (probability: number) => Math.floor(seedrandom(`${day}:${block.id}`)() * 100) < probability, | ||||
| 		dailyRannum: (min: number, max: number) => min + Math.floor(seedrandom(`${day}:${block.id}`)() * (max - min + 1)), | ||||
| 		dailyRandomPick: (list: any[]) => list[Math.floor(seedrandom(`${day}:${block.id}`)() * list.length)], | ||||
| 		random: (probability: number) => Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * 100) < probability, | ||||
| 		rannum: (min: number, max: number) => min + Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * (max - min + 1)), | ||||
| 		randomPick: (list: any[]) => list[Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * list.length)], | ||||
| 		dailyRandom: (probability: number) => Math.floor(seedrandom(`${day}:${expr.id}`)() * 100) < probability, | ||||
| 		dailyRannum: (min: number, max: number) => min + Math.floor(seedrandom(`${day}:${expr.id}`)() * (max - min + 1)), | ||||
| 		dailyRandomPick: (list: any[]) => list[Math.floor(seedrandom(`${day}:${expr.id}`)() * list.length)], | ||||
| 		seedRandom: (seed: any, probability: number) => Math.floor(seedrandom(seed)() * 100) < probability, | ||||
| 		seedRannum: (seed: any, min: number, max: number) => min + Math.floor(seedrandom(seed)() * (max - min + 1)), | ||||
| 		seedRandomPick: (seed: any, list: any[]) => list[Math.floor(seedrandom(seed)() * list.length)], | ||||
|  | @ -185,7 +247,7 @@ export function initHpmlLib(block: Block, scope: HpmlScope, randomSeed: string, | |||
| 				totalFactor += factor; | ||||
| 				xs.push({ factor, text }); | ||||
| 			} | ||||
| 			const r = seedrandom(`${day}:${block.id}`)() * totalFactor; | ||||
| 			const r = seedrandom(`${day}:${expr.id}`)() * totalFactor; | ||||
| 			let stackedFactor = 0; | ||||
| 			for (const x of xs) { | ||||
| 				if (r >= stackedFactor && r <= stackedFactor + x.factor) { | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| import autobind from 'autobind-decorator'; | ||||
| import { Type, Block, funcDefs, envVarsDef, Variable, PageVar, isLiteralBlock } from '.'; | ||||
| import { Type, envVarsDef, PageVar } from '.'; | ||||
| import { Expr, isLiteralValue, Variable } from './expr'; | ||||
| import { funcDefs } from './lib'; | ||||
| 
 | ||||
| type TypeError = { | ||||
| 	arg: number; | ||||
|  | @ -20,10 +22,10 @@ export class HpmlTypeChecker { | |||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public typeCheck(v: Block): TypeError | null { | ||||
| 		if (isLiteralBlock(v)) return null; | ||||
| 	public typeCheck(v: Expr): TypeError | null { | ||||
| 		if (isLiteralValue(v)) return null; | ||||
| 
 | ||||
| 		const def = funcDefs[v.type]; | ||||
| 		const def = funcDefs[v.type || '']; | ||||
| 		if (def == null) { | ||||
| 			throw new Error('Unknown type: ' + v.type); | ||||
| 		} | ||||
|  | @ -58,8 +60,8 @@ export class HpmlTypeChecker { | |||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public getExpectedType(v: Block, slot: number): Type { | ||||
| 		const def = funcDefs[v.type]; | ||||
| 	public getExpectedType(v: Expr, slot: number): Type { | ||||
| 		const def = funcDefs[v.type || '']; | ||||
| 		if (def == null) { | ||||
| 			throw new Error('Unknown type: ' + v.type); | ||||
| 		} | ||||
|  | @ -86,7 +88,7 @@ export class HpmlTypeChecker { | |||
| 	} | ||||
| 
 | ||||
| 	@autobind | ||||
| 	public infer(v: Block): Type { | ||||
| 	public infer(v: Expr): Type { | ||||
| 		if (v.type === null) return null; | ||||
| 		if (v.type === 'text') return 'string'; | ||||
| 		if (v.type === 'multiLineText') return 'string'; | ||||
|  | @ -103,7 +105,7 @@ export class HpmlTypeChecker { | |||
| 				return pageVar.type; | ||||
| 			} | ||||
| 
 | ||||
| 			const envVar = envVarsDef[v.value]; | ||||
| 			const envVar = envVarsDef[v.value || '']; | ||||
| 			if (envVar !== undefined) { | ||||
| 				return envVar; | ||||
| 			} | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue