1
1
mirror of https://github.com/kokonect-link/cherrypick synced 2024-11-01 07:35:57 +09:00

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:
marihachi 2021-01-30 10:59:05 +09:00 committed by GitHub
parent 7fc3e7dd8b
commit 100a131913
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 600 additions and 348 deletions

View File

@ -1,9 +1,9 @@
<template> <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> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, PropType } from 'vue';
import XText from './page.text.vue'; import XText from './page.text.vue';
import XSection from './page.section.vue'; import XSection from './page.section.vue';
import XImage from './page.image.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 XRadioButton from './page.radio-button.vue';
import XCanvas from './page.canvas.vue'; import XCanvas from './page.canvas.vue';
import XNote from './page.note.vue'; import XNote from './page.note.vue';
import { Hpml } from '@/scripts/hpml/evaluator';
import { Block } from '@/scripts/hpml/block';
export default defineComponent({ export default defineComponent({
components: { components: {
XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter, XRadioButton, XCanvas, XNote XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter, XRadioButton, XCanvas, XNote
}, },
props: { props: {
value: { block: {
type: Object as PropType<Block>,
required: true required: true
}, },
hpml: { hpml: {
required: true type: Object as PropType<Hpml>,
},
page: {
required: true required: true
}, },
h: { h: {
type: Number,
required: true required: true
} }
}, },

View File

@ -1,51 +1,55 @@
<template> <template>
<div> <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> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, PropType, unref } from 'vue';
import MkButton from '../ui/button.vue'; import MkButton from '../ui/button.vue';
import * as os from '@/os'; import * as os from '@/os';
import { ButtonBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
export default defineComponent({ export default defineComponent({
components: { components: {
MkButton MkButton
}, },
props: { props: {
value: { block: {
type: Object as PropType<ButtonBlock>,
required: true required: true
}, },
hpml: { hpml: {
type: Object as PropType<Hpml>,
required: true required: true
} }
}, },
methods: { methods: {
click() { click() {
if (this.value.action === 'dialog') { if (this.block.action === 'dialog') {
this.hpml.eval(); this.hpml.eval();
os.dialog({ 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.updateRandomSeed(Math.random());
this.hpml.eval(); this.hpml.eval();
} else if (this.value.action === 'pushEvent') { } else if (this.block.action === 'pushEvent') {
os.api('page-push', { os.api('page-push', {
pageId: this.hpml.page.id, pageId: this.hpml.page.id,
event: this.value.event, event: this.block.event,
...(this.value.var ? { ...(this.block.var ? {
var: this.hpml.vars[this.value.var] var: unref(this.hpml.vars)[this.block.var]
} : {}) } : {})
}); });
os.dialog({ os.dialog({
type: 'success', type: 'success',
text: this.hpml.interpolate(this.value.message) text: this.hpml.interpolate(this.block.message)
}); });
} else if (this.value.action === 'callAiScript') { } else if (this.block.action === 'callAiScript') {
this.hpml.callAiScript(this.value.fn); this.hpml.callAiScript(this.block.fn);
} }
} }
} }

View File

@ -1,24 +1,36 @@
<template> <template>
<div class="ysrxegms"> <div class="ysrxegms">
<canvas ref="canvas" :width="value.width" :height="value.height"/> <canvas ref="canvas" :width="block.width" :height="block.height"/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, onMounted, PropType, Ref, ref } from 'vue';
import * as os from '@/os'; import * as os from '@/os';
import { CanvasBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
export default defineComponent({ export default defineComponent({
props: { props: {
value: { block: {
type: Object as PropType<CanvasBlock>,
required: true required: true
}, },
hpml: { hpml: {
type: Object as PropType<Hpml>,
required: true required: true
} }
}, },
mounted() { setup(props, ctx) {
this.hpml.registerCanvas(this.value.name, this.$refs.canvas); const canvas: Ref<any> = ref(null);
onMounted(() => {
props.hpml.registerCanvas(props.block.name, canvas.value);
});
return {
canvas
};
} }
}); });
</script> </script>

View File

@ -1,41 +1,43 @@
<template> <template>
<div> <div>
<MkButton class="llumlmnx" @click="click()">{{ hpml.interpolate(value.text) }}</MkButton> <MkButton class="llumlmnx" @click="click()">{{ hpml.interpolate(block.text) }}</MkButton>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent, PropType } from 'vue';
import MkButton from '../ui/button.vue'; import MkButton from '../ui/button.vue';
import * as os from '@/os'; import * as os from '@/os';
import { CounterVarBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
export default defineComponent({ export default defineComponent({
components: { components: {
MkButton MkButton
}, },
props: { props: {
value: { block: {
type: Object as PropType<CounterVarBlock>,
required: true required: true
}, },
hpml: { hpml: {
type: Object as PropType<Hpml>,
required: true 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 { 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> </script>

View File

@ -1,27 +1,29 @@
<template> <template>
<div v-show="hpml.vars[value.var]"> <div v-show="hpml.vars.value[block.var]">
<XBlock v-for="child in value.children" :value="child" :page="page" :hpml="hpml" :key="child.id" :h="h"/> <XBlock v-for="child in block.children" :block="child" :hpml="hpml" :key="child.id" :h="h"/>
</div> </div>
</template> </template>
<script lang="ts"> <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({ export default defineComponent({
components: { components: {
XBlock: defineAsyncComponent(() => import('./page.block.vue')) XBlock: defineAsyncComponent(() => import('./page.block.vue'))
}, },
props: { props: {
value: { block: {
type: Object as PropType<IfBlock>,
required: true required: true
}, },
hpml: { hpml: {
required: true type: Object as PropType<Hpml>,
},
page: {
required: true required: true
}, },
h: { h: {
type: Number,
required: true required: true
} }
}, },

View File

@ -5,25 +5,28 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, PropType } from 'vue';
import * as os from '@/os'; import * as os from '@/os';
import { ImageBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
export default defineComponent({ export default defineComponent({
props: { props: {
value: { block: {
type: Object as PropType<ImageBlock>,
required: true required: true
}, },
page: { hpml: {
type: Object as PropType<Hpml>,
required: true required: true
}
}, },
}, setup(props, ctx) {
data() { const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId);
return { return {
image: null, image
}; };
},
created() {
this.image = this.page.attachedFiles.find(x => x.id === this.value.fileId);
} }
}); });
</script> </script>

View File

@ -1,15 +1,16 @@
<template> <template>
<div class="voxdxuby"> <div class="voxdxuby">
<XNote v-if="note && !value.detailed" v-model:note="note" :key="note.id + ':normal'"/> <XNote v-if="note && !block.detailed" v-model:note="note" :key="note.id + ':normal'"/>
<XNoteDetailed v-if="note && value.detailed" v-model:note="note" :key="note.id + ':detail'"/> <XNoteDetailed v-if="note && block.detailed" v-model:note="note" :key="note.id + ':detail'"/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, onMounted, PropType, Ref, ref } from 'vue';
import XNote from '@/components/note.vue'; import XNote from '@/components/note.vue';
import XNoteDetailed from '@/components/note-detailed.vue'; import XNoteDetailed from '@/components/note-detailed.vue';
import * as os from '@/os'; import * as os from '@/os';
import { NoteBlock } from '@/scripts/hpml/block';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -17,20 +18,24 @@ export default defineComponent({
XNoteDetailed, XNoteDetailed,
}, },
props: { props: {
value: { block: {
required: true type: Object as PropType<NoteBlock>,
},
hpml: {
required: true 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 { return {
note: null, note
}; };
},
async mounted() {
this.note = await os.api('notes/show', { noteId: this.value.note });
} }
}); });
</script> </script>

View File

@ -1,36 +1,44 @@
<template> <template>
<div> <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> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent, PropType } from 'vue';
import MkInput from '../ui/input.vue'; import MkInput from '../ui/input.vue';
import * as os from '@/os'; import * as os from '@/os';
import { Hpml } from '@/scripts/hpml/evaluator';
import { NumberInputVarBlock } from '@/scripts/hpml/block';
export default defineComponent({ export default defineComponent({
components: { components: {
MkInput MkInput
}, },
props: { props: {
value: { block: {
type: Object as PropType<NumberInputVarBlock>,
required: true required: true
}, },
hpml: { hpml: {
type: Object as PropType<Hpml>,
required: true required: true
} }
}, },
data() { setup(props, ctx) {
return { const value = computed(() => {
v: this.value.default, return props.hpml.vars.value[props.block.name];
}; });
},
watch: { function updateValue(newValue) {
v() { props.hpml.updatePageVar(props.block.name, newValue);
this.hpml.updatePageVar(this.value.name, this.v); props.hpml.eval();
this.hpml.eval();
} }
return {
value,
updateValue
};
} }
}); });
</script> </script>

View File

@ -6,12 +6,14 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, PropType } from 'vue';
import { faCheck, faPaperPlane } from '@fortawesome/free-solid-svg-icons'; import { faCheck, faPaperPlane } from '@fortawesome/free-solid-svg-icons';
import MkTextarea from '../ui/textarea.vue'; import MkTextarea from '../ui/textarea.vue';
import MkButton from '../ui/button.vue'; import MkButton from '../ui/button.vue';
import { apiUrl } from '@/config'; import { apiUrl } from '@/config';
import * as os from '@/os'; import * as os from '@/os';
import { PostBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -19,16 +21,18 @@ export default defineComponent({
MkButton, MkButton,
}, },
props: { props: {
value: { block: {
type: Object as PropType<PostBlock>,
required: true required: true
}, },
hpml: { hpml: {
type: Object as PropType<Hpml>,
required: true required: true
} }
}, },
data() { data() {
return { return {
text: this.hpml.interpolate(this.value.text), text: this.hpml.interpolate(this.block.text),
posted: false, posted: false,
posting: false, posting: false,
faCheck, faPaperPlane faCheck, faPaperPlane
@ -37,7 +41,7 @@ export default defineComponent({
watch: { watch: {
'hpml.vars': { 'hpml.vars': {
handler() { handler() {
this.text = this.hpml.interpolate(this.value.text); this.text = this.hpml.interpolate(this.block.text);
}, },
deep: true deep: true
} }
@ -45,7 +49,7 @@ export default defineComponent({
methods: { methods: {
upload() { upload() {
const promise = new Promise((ok) => { const promise = new Promise((ok) => {
const canvas = this.hpml.canvases[this.value.canvasId]; const canvas = this.hpml.canvases[this.block.canvasId];
canvas.toBlob(blob => { canvas.toBlob(blob => {
const data = new FormData(); const data = new FormData();
data.append('file', blob); data.append('file', blob);
@ -69,7 +73,7 @@ export default defineComponent({
}, },
async post() { async post() {
this.posting = true; 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', { os.apiWithDialog('notes/create', {
text: this.text === '' ? null : this.text, text: this.text === '' ? null : this.text,
fileIds: file ? [file.id] : undefined, fileIds: file ? [file.id] : undefined,

View File

@ -1,37 +1,45 @@
<template> <template>
<div> <div>
<div>{{ hpml.interpolate(value.title) }}</div> <div>{{ hpml.interpolate(block.title) }}</div>
<MkRadio v-for="x in value.values" v-model="v" :value="x" :key="x">{{ x }}</MkRadio> <MkRadio v-for="item in block.values" :modelValue="value" @update:modelValue="updateValue($event)" :value="item" :key="item">{{ item }}</MkRadio>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent, PropType } from 'vue';
import MkRadio from '../ui/radio.vue'; import MkRadio from '../ui/radio.vue';
import * as os from '@/os'; import * as os from '@/os';
import { Hpml } from '@/scripts/hpml/evaluator';
import { RadioButtonVarBlock } from '@/scripts/hpml/block';
export default defineComponent({ export default defineComponent({
components: { components: {
MkRadio MkRadio
}, },
props: { props: {
value: { block: {
type: Object as PropType<RadioButtonVarBlock>,
required: true required: true
}, },
hpml: { hpml: {
type: Object as PropType<Hpml>,
required: true required: true
} }
}, },
data() { setup(props, ctx) {
return { const value = computed(() => {
v: this.value.default, return props.hpml.vars.value[props.block.name];
}; });
},
watch: { function updateValue(newValue: string) {
v() { props.hpml.updatePageVar(props.block.name, newValue);
this.hpml.updatePageVar(this.value.name, this.v); props.hpml.eval();
this.hpml.eval();
} }
return {
value,
updateValue
};
} }
}); });
</script> </script>

View File

@ -1,29 +1,30 @@
<template> <template>
<section class="sdgxphyu"> <section class="sdgxphyu">
<component :is="'h' + h">{{ value.title }}</component> <component :is="'h' + h">{{ block.title }}</component>
<div class="children"> <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> </div>
</section> </section>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, defineAsyncComponent } from 'vue'; import { defineComponent, defineAsyncComponent, PropType } from 'vue';
import * as os from '@/os'; import * as os from '@/os';
import { SectionBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
export default defineComponent({ export default defineComponent({
components: { components: {
XBlock: defineAsyncComponent(() => import('./page.block.vue')) XBlock: defineAsyncComponent(() => import('./page.block.vue'))
}, },
props: { props: {
value: { block: {
type: Object as PropType<SectionBlock>,
required: true required: true
}, },
hpml: { hpml: {
required: true type: Object as PropType<Hpml>,
},
page: {
required: true required: true
}, },
h: { h: {

View File

@ -1,36 +1,44 @@
<template> <template>
<div class="hkcxmtwj"> <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> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent, PropType } from 'vue';
import MkSwitch from '../ui/switch.vue'; import MkSwitch from '../ui/switch.vue';
import * as os from '@/os'; import * as os from '@/os';
import { Hpml } from '@/scripts/hpml/evaluator';
import { SwitchVarBlock } from '@/scripts/hpml/block';
export default defineComponent({ export default defineComponent({
components: { components: {
MkSwitch MkSwitch
}, },
props: { props: {
value: { block: {
type: Object as PropType<SwitchVarBlock>,
required: true required: true
}, },
hpml: { hpml: {
type: Object as PropType<Hpml>,
required: true required: true
} }
}, },
data() { setup(props, ctx) {
return { const value = computed(() => {
v: this.value.default, return props.hpml.vars.value[props.block.name];
}; });
},
watch: { function updateValue(newValue: boolean) {
v() { props.hpml.updatePageVar(props.block.name, newValue);
this.hpml.updatePageVar(this.value.name, this.v); props.hpml.eval();
this.hpml.eval();
} }
return {
value,
updateValue
};
} }
}); });
</script> </script>

View File

@ -1,36 +1,44 @@
<template> <template>
<div> <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> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent, PropType } from 'vue';
import MkInput from '../ui/input.vue'; import MkInput from '../ui/input.vue';
import * as os from '@/os'; import * as os from '@/os';
import { Hpml } from '@/scripts/hpml/evaluator';
import { TextInputVarBlock } from '@/scripts/hpml/block';
export default defineComponent({ export default defineComponent({
components: { components: {
MkInput MkInput
}, },
props: { props: {
value: { block: {
type: Object as PropType<TextInputVarBlock>,
required: true required: true
}, },
hpml: { hpml: {
type: Object as PropType<Hpml>,
required: true required: true
} }
}, },
data() { setup(props, ctx) {
return { const value = computed(() => {
v: this.value.default, return props.hpml.vars.value[props.block.name];
}; });
},
watch: { function updateValue(newValue) {
v() { props.hpml.updatePageVar(props.block.name, newValue);
this.hpml.updatePageVar(this.value.name, this.v); props.hpml.eval();
this.hpml.eval();
} }
return {
value,
updateValue
};
} }
}); });
</script> </script>

View File

@ -6,7 +6,9 @@
</template> </template>
<script lang="ts"> <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 { parse } from '../../../mfm/parse';
import { unique } from '../../../prelude/array'; import { unique } from '../../../prelude/array';
@ -15,16 +17,18 @@ export default defineComponent({
MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')), MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')),
}, },
props: { props: {
value: { block: {
type: Object as PropType<TextBlock>,
required: true required: true
}, },
hpml: { hpml: {
type: Object as PropType<Hpml>,
required: true required: true
} }
}, },
data() { data() {
return { return {
text: this.hpml.interpolate(this.value.text), text: this.hpml.interpolate(this.block.text),
}; };
}, },
computed: { computed: {
@ -43,7 +47,7 @@ export default defineComponent({
watch: { watch: {
'hpml.vars': { 'hpml.vars': {
handler() { handler() {
this.text = this.hpml.interpolate(this.value.text); this.text = this.hpml.interpolate(this.block.text);
}, },
deep: true deep: true
} }

View File

@ -1,36 +1,45 @@
<template> <template>
<div> <div>
<MkTextarea v-model:value="v">{{ hpml.interpolate(value.text) }}</MkTextarea> <MkTextarea :value="value" @update:value="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkTextarea>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent, PropType } from 'vue';
import MkTextarea from '../ui/textarea.vue'; import MkTextarea from '../ui/textarea.vue';
import * as os from '@/os'; 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({ export default defineComponent({
components: { components: {
MkTextarea MkTextarea
}, },
props: { props: {
value: { block: {
type: Object as PropType<TextInputVarBlock>,
required: true required: true
}, },
hpml: { hpml: {
type: Object as PropType<Hpml>,
required: true required: true
} }
}, },
data() { setup(props, ctx) {
return { const value = computed(() => {
v: this.value.default, return props.hpml.vars.value[props.block.name];
}; });
},
watch: { function updateValue(newValue) {
v() { props.hpml.updatePageVar(props.block.name, newValue);
this.hpml.updatePageVar(this.value.name, this.v); props.hpml.eval();
this.hpml.eval();
} }
return {
value,
updateValue
};
} }
}); });
</script> </script>

View File

@ -3,7 +3,9 @@
</template> </template>
<script lang="ts"> <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'; import MkTextarea from '../ui/textarea.vue';
export default defineComponent({ export default defineComponent({
@ -11,22 +13,24 @@ export default defineComponent({
MkTextarea MkTextarea
}, },
props: { props: {
value: { block: {
type: Object as PropType<TextBlock>,
required: true required: true
}, },
hpml: { hpml: {
type: Object as PropType<Hpml>,
required: true required: true
} }
}, },
data() { data() {
return { return {
text: this.hpml.interpolate(this.value.text), text: this.hpml.interpolate(this.block.text),
}; };
}, },
watch: { watch: {
'hpml.vars': { 'hpml.vars': {
handler() { handler() {
this.text = this.hpml.interpolate(this.value.text); this.text = this.hpml.interpolate(this.block.text);
}, },
deep: true deep: true
} }

View File

@ -1,52 +1,43 @@
<template> <template>
<div class="iroscrza" :class="{ center: page.alignCenter, serif: page.font === 'serif' }" v-if="hpml"> <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> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, onMounted, nextTick, onUnmounted, PropType } from 'vue';
import { parse } from '@syuilo/aiscript'; 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 XBlock from './page.block.vue';
import { Hpml } from '@/scripts/hpml/evaluator'; import { Hpml } from '@/scripts/hpml/evaluator';
import { url } from '@/config'; import { url } from '@/config';
import { $i } from '@/account';
import { defaultStore } from '@/store';
export default defineComponent({ export default defineComponent({
components: { components: {
XBlock XBlock
}, },
props: { props: {
page: { page: {
type: Object, type: Object as PropType<Record<string, any>>,
required: true required: true
}, },
}, },
setup(props, ctx) {
data() { const hpml = new Hpml(props.page, {
return {
hpml: null,
faHeartS, faHeart
};
},
created() {
this.hpml = new Hpml(this.page, {
randomSeed: Math.random(), randomSeed: Math.random(),
visitor: this.$i, visitor: $i,
url: url, url: url,
enableAiScript: !this.$store.state.disablePagesScript enableAiScript: !defaultStore.state.disablePagesScript
}); });
},
mounted() { onMounted(() => {
this.$nextTick(() => { nextTick(() => {
if (this.page.script && this.hpml.aiscript) { if (props.page.script && hpml.aiscript) {
let ast; let ast;
try { try {
ast = parse(this.page.script); ast = parse(props.page.script);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
/*os.dialog({ /*os.dialog({
@ -55,8 +46,8 @@ export default defineComponent({
});*/ });*/
return; return;
} }
this.hpml.aiscript.exec(ast).then(() => { hpml.aiscript.exec(ast).then(() => {
this.hpml.eval(); hpml.eval();
}).catch(e => { }).catch(e => {
console.error(e); console.error(e);
/*os.dialog({ /*os.dialog({
@ -65,13 +56,17 @@ export default defineComponent({
});*/ });*/
}); });
} else { } else {
this.hpml.eval(); hpml.eval();
} }
}); });
}, onUnmounted(() => {
if (hpml.aiscript) hpml.aiscript.abort();
});
});
beforeUnmount() { return {
if (this.hpml.aiscript) this.hpml.aiscript.abort(); hpml,
};
}, },
}); });
</script> </script>

View File

@ -61,8 +61,10 @@ import { faPencilAlt, faPlug } from '@fortawesome/free-solid-svg-icons';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import XContainer from './page-editor.container.vue'; import XContainer from './page-editor.container.vue';
import MkTextarea from '@/components/ui/textarea.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 * as os from '@/os';
import { isLiteralValue } from '@/scripts/hpml/expr';
import { funcDefs } from '@/scripts/hpml/lib';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -166,7 +168,7 @@ export default defineComponent({
return; return;
} }
if (isLiteralBlock(this.value)) return; if (isLiteralValue(this.value)) return;
const empties = []; const empties = [];
for (let i = 0; i < funcDefs[this.value.type].in.length; i++) { for (let i = 0; i < funcDefs[this.value.type].in.length; i++) {

View 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);
}

View File

@ -1,12 +1,13 @@
import autobind from 'autobind-decorator'; 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 { version } from '@/config';
import { AiScript, utils, values } from '@syuilo/aiscript'; import { AiScript, utils, values } from '@syuilo/aiscript';
import { createAiScriptEnv } from '../aiscript/api'; import { createAiScriptEnv } from '../aiscript/api';
import { collectPageVars } from '../collect-page-vars'; import { collectPageVars } from '../collect-page-vars';
import { initHpmlLib, initAiLib } from './lib'; import { initHpmlLib, initAiLib } from './lib';
import * as os from '@/os'; 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 * Hpml evaluator
@ -94,7 +95,7 @@ export class Hpml {
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 = unref(this.vars)[match.slice(1, -1).trim()];
return v == null ? 'NULL' : v.toString(); return v == null ? 'NULL' : v.toString();
}); });
} }
@ -158,31 +159,33 @@ export class Hpml {
} }
@autobind @autobind
private evaluate(block: Block, scope: HpmlScope): any { private evaluate(expr: Expr, scope: HpmlScope): any {
if (block.type === null) {
if (isLiteralValue(expr)) {
if (expr.type === null) {
return null; return null;
} }
if (block.type === 'number') { if (expr.type === 'number') {
return parseInt(block.value, 10); return parseInt((expr.value as any), 10);
} }
if (block.type === 'text' || block.type === 'multiLineText') { if (expr.type === 'text' || expr.type === 'multiLineText') {
return this._interpolateScope(block.value || '', scope); return this._interpolateScope(expr.value || '', scope);
} }
if (block.type === 'textList') { if (expr.type === 'textList') {
return this._interpolateScope(block.value || '', scope).trim().split('\n'); return this._interpolateScope(expr.value || '', scope).trim().split('\n');
} }
if (block.type === 'ref') { if (expr.type === 'ref') {
return scope.getState(block.value); return scope.getState(expr.value);
} }
if (block.type === 'aiScriptVar') { if (expr.type === 'aiScriptVar') {
if (this.aiscript) { if (this.aiscript) {
try { try {
return utils.valToJs(this.aiscript.scope.get(block.value)); return utils.valToJs(this.aiscript.scope.get(expr.value));
} catch (e) { } catch (e) {
return null; return null;
} }
@ -192,38 +195,40 @@ export class Hpml {
} }
// Define user function // Define user function
if (isFnBlock(block)) { if (expr.type == 'fn') {
return { return {
slots: block.value.slots.map(x => x.name), slots: expr.value.slots.map(x => x.name),
exec: (slotArg: Record<string, any>) => { exec: (slotArg: Record<string, any>) => {
return this.evaluate(block.value.expression, scope.createChildScope(slotArg, block.id)); return this.evaluate(expr.value.expression, scope.createChildScope(slotArg, expr.id));
} }
} as Fn; } as Fn;
} }
return;
}
// Call user function // Call user function
if (block.type.startsWith('fn:')) { if (expr.type.startsWith('fn:')) {
const fnName = block.type.split(':')[1]; const fnName = expr.type.split(':')[1];
const fn = scope.getState(fnName); const fn = scope.getState(fnName);
const args = {} as Record<string, any>; const args = {} as Record<string, any>;
for (let i = 0; i < fn.slots.length; i++) { for (let i = 0; i < fn.slots.length; i++) {
const name = fn.slots[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); 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 // Call function
const fnName = block.type; const fnName = expr.type;
const fn = (funcs as any)[fnName]; const fn = (funcs as any)[fnName];
if (fn == null) { if (fn == null) {
throw new HpmlError(`No such function '${fnName}'`); throw new HpmlError(`No such function '${fnName}'`);
} else { } else {
return fn(...block.args.map(x => this.evaluate(x, scope))); return fn(...expr.args.map(x => this.evaluate(x, scope)));
} }
} }
} }

View 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;

View File

@ -3,52 +3,16 @@
*/ */
import autobind from 'autobind-decorator'; import autobind from 'autobind-decorator';
import { import {
faMagic, faMagic,
faSquareRootAlt, faSquareRootAlt,
faAlignLeft, faAlignLeft,
faShareAlt,
faPlus,
faMinus,
faTimes,
faDivide,
faList, faList,
faQuoteRight, faQuoteRight,
faEquals,
faGreaterThan,
faLessThan,
faGreaterThanEqual,
faLessThanEqual,
faNotEqual,
faDice,
faSortNumericUp, faSortNumericUp,
faExchangeAlt,
faRecycle,
faIndent,
faCalculator,
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import { faFlag } from '@fortawesome/free-regular-svg-icons';
import { Hpml } from './evaluator'; import { Hpml } from './evaluator';
import { funcDefs } from './lib';
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;
};
export type Fn = { export type Fn = {
slots: string[]; slots: string[];
@ -57,46 +21,6 @@ export type Fn = {
export type Type = 'string' | 'number' | 'boolean' | 'stringArray' | null; 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; }> = { export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = {
text: { out: 'string', category: 'value', icon: faQuoteRight, }, text: { out: 'string', category: 'value', icon: faQuoteRight, },
multiLineText: { out: 'string', category: 'value', icon: faAlignLeft, }, 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 type PageVar = { name: string; value: any; type: Type; };
export const envVarsDef: Record<string, Type> = { export const envVarsDef: Record<string, Type> = {
@ -140,12 +60,6 @@ export const envVarsDef: Record<string, Type> = {
NULL: null, NULL: null,
}; };
export function isLiteralBlock(v: Block) {
if (v.type === null) return true;
if (literalDefs[v.type]) return true;
return false;
}
export class HpmlScope { export class HpmlScope {
private layerdStates: Record<string, any>[]; private layerdStates: Record<string, any>[];
public name: string; public name: string;

View File

@ -2,9 +2,31 @@ import * as tinycolor from 'tinycolor2';
import Chart from 'chart.js'; import Chart from 'chart.js';
import { Hpml } from './evaluator'; import { Hpml } from './evaluator';
import { values, utils } from '@syuilo/aiscript'; 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 * 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 // https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs
Chart.pluginService.register({ Chart.pluginService.register({
beforeDraw: (chart, easing) => { 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 date = new Date();
const day = `${visitor ? visitor.id : ''} ${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`; 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'), splitStrByLine: (a: string) => a.split('\n'),
pick: (list: any[], i: number) => list[i - 1], pick: (list: any[], i: number) => list[i - 1],
listLen: (list: any[]) => list.length, listLen: (list: any[]) => list.length,
random: (probability: number) => Math.floor(seedrandom(`${randomSeed}:${block.id}`)() * 100) < probability, random: (probability: number) => Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * 100) < probability,
rannum: (min: number, max: number) => min + Math.floor(seedrandom(`${randomSeed}:${block.id}`)() * (max - min + 1)), rannum: (min: number, max: number) => min + Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * (max - min + 1)),
randomPick: (list: any[]) => list[Math.floor(seedrandom(`${randomSeed}:${block.id}`)() * list.length)], randomPick: (list: any[]) => list[Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * list.length)],
dailyRandom: (probability: number) => Math.floor(seedrandom(`${day}:${block.id}`)() * 100) < probability, dailyRandom: (probability: number) => Math.floor(seedrandom(`${day}:${expr.id}`)() * 100) < probability,
dailyRannum: (min: number, max: number) => min + Math.floor(seedrandom(`${day}:${block.id}`)() * (max - min + 1)), dailyRannum: (min: number, max: number) => min + Math.floor(seedrandom(`${day}:${expr.id}`)() * (max - min + 1)),
dailyRandomPick: (list: any[]) => list[Math.floor(seedrandom(`${day}:${block.id}`)() * list.length)], dailyRandomPick: (list: any[]) => list[Math.floor(seedrandom(`${day}:${expr.id}`)() * list.length)],
seedRandom: (seed: any, probability: number) => Math.floor(seedrandom(seed)() * 100) < probability, 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)), 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)], 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; totalFactor += factor;
xs.push({ factor, text }); xs.push({ factor, text });
} }
const r = seedrandom(`${day}:${block.id}`)() * totalFactor; const r = seedrandom(`${day}:${expr.id}`)() * totalFactor;
let stackedFactor = 0; let stackedFactor = 0;
for (const x of xs) { for (const x of xs) {
if (r >= stackedFactor && r <= stackedFactor + x.factor) { if (r >= stackedFactor && r <= stackedFactor + x.factor) {

View File

@ -1,5 +1,7 @@
import autobind from 'autobind-decorator'; 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 = { type TypeError = {
arg: number; arg: number;
@ -20,10 +22,10 @@ export class HpmlTypeChecker {
} }
@autobind @autobind
public typeCheck(v: Block): TypeError | null { public typeCheck(v: Expr): TypeError | null {
if (isLiteralBlock(v)) return null; if (isLiteralValue(v)) return null;
const def = funcDefs[v.type]; const def = funcDefs[v.type || ''];
if (def == null) { if (def == null) {
throw new Error('Unknown type: ' + v.type); throw new Error('Unknown type: ' + v.type);
} }
@ -58,8 +60,8 @@ export class HpmlTypeChecker {
} }
@autobind @autobind
public getExpectedType(v: Block, slot: number): Type { public getExpectedType(v: Expr, slot: number): Type {
const def = funcDefs[v.type]; const def = funcDefs[v.type || ''];
if (def == null) { if (def == null) {
throw new Error('Unknown type: ' + v.type); throw new Error('Unknown type: ' + v.type);
} }
@ -86,7 +88,7 @@ export class HpmlTypeChecker {
} }
@autobind @autobind
public infer(v: Block): Type { public infer(v: Expr): Type {
if (v.type === null) return null; if (v.type === null) return null;
if (v.type === 'text') return 'string'; if (v.type === 'text') return 'string';
if (v.type === 'multiLineText') return 'string'; if (v.type === 'multiLineText') return 'string';
@ -103,7 +105,7 @@ export class HpmlTypeChecker {
return pageVar.type; return pageVar.type;
} }
const envVar = envVarsDef[v.value]; const envVar = envVarsDef[v.value || ''];
if (envVar !== undefined) { if (envVar !== undefined) {
return envVar; return envVar;
} }