remove client code related to pages

This commit is contained in:
Johann150 2022-07-31 21:25:56 +02:00
parent 1ca6ad080c
commit bc79d721c8
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS.
GPG key ID: 9EE6577A2A06F8F1
41 changed files with 0 additions and 3036 deletions

View file

@ -1,44 +0,0 @@
<template>
<component :is="'x-' + block.type" :key="block.id" :block="block" :hpml="hpml" :h="h"/>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import XText from './page.text.vue';
import XSection from './page.section.vue';
import XImage from './page.image.vue';
import XButton from './page.button.vue';
import XNumberInput from './page.number-input.vue';
import XTextInput from './page.text-input.vue';
import XTextareaInput from './page.textarea-input.vue';
import XSwitch from './page.switch.vue';
import XIf from './page.if.vue';
import XTextarea from './page.textarea.vue';
import XPost from './page.post.vue';
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: {
block: {
type: Object as PropType<Block>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
h: {
type: Number,
required: true,
},
},
});
</script>

View file

@ -1,66 +0,0 @@
<template>
<div>
<MkButton class="kudkigyw" :primary="block.primary" @click="click()">{{ hpml.interpolate(block.text) }}</MkButton>
</div>
</template>
<script lang="ts">
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: {
block: {
type: Object as PropType<ButtonBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
methods: {
click() {
if (this.block.action === 'dialog') {
this.hpml.eval();
os.alert({
text: this.hpml.interpolate(this.block.content),
});
} else if (this.block.action === 'resetRandom') {
this.hpml.updateRandomSeed(Math.random());
this.hpml.eval();
} else if (this.block.action === 'pushEvent') {
os.api('page-push', {
pageId: this.hpml.page.id,
event: this.block.event,
...(this.block.var ? {
var: unref(this.hpml.vars)[this.block.var],
} : {}),
});
os.alert({
type: 'success',
text: this.hpml.interpolate(this.block.message),
});
} else if (this.block.action === 'callAiScript') {
this.hpml.callAiScript(this.block.fn);
}
},
},
});
</script>
<style lang="scss" scoped>
.kudkigyw {
display: inline-block;
min-width: 200px;
max-width: 450px;
margin: 8px 0;
}
</style>

View file

@ -1,49 +0,0 @@
<template>
<div class="ysrxegms">
<canvas ref="canvas" :width="block.width" :height="block.height"/>
</div>
</template>
<script lang="ts">
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: {
block: {
type: Object as PropType<CanvasBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
setup(props, ctx) {
const canvas: Ref<any> = ref(null);
onMounted(() => {
props.hpml.registerCanvas(props.block.name, canvas.value);
});
return {
canvas,
};
},
});
</script>
<style lang="scss" scoped>
.ysrxegms {
display: inline-block;
vertical-align: bottom;
overflow: auto;
max-width: 100%;
> canvas {
display: block;
}
}
</style>

View file

@ -1,52 +0,0 @@
<template>
<div>
<MkButton class="llumlmnx" @click="click()">{{ hpml.interpolate(block.text) }}</MkButton>
</div>
</template>
<script lang="ts">
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: {
block: {
type: Object as PropType<CounterVarBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
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 {
click,
};
},
});
</script>
<style lang="scss" scoped>
.llumlmnx {
display: inline-block;
min-width: 300px;
max-width: 450px;
margin: 8px 0;
}
</style>

View file

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

View file

@ -1,28 +0,0 @@
<template>
<div class="lzyxtsnt">
<ImgWithBlurhash v-if="image" :hash="image.blurhash" :src="image.url" :alt="image.comment" :title="image.comment" :cover="false"/>
</div>
</template>
<script lang="ts" setup>
import { defineComponent, PropType } from 'vue';
import ImgWithBlurhash from '@/components/img-with-blurhash.vue';
import * as os from '@/os';
import { ImageBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
const props = defineProps<{
block: PropType<ImageBlock>,
hpml: PropType<Hpml>,
}>();
const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId);
</script>
<style lang="scss" scoped>
.lzyxtsnt {
> img {
max-width: 100%;
}
}
</style>

View file

@ -1,47 +0,0 @@
<template>
<div class="voxdxuby">
<XNote v-if="note && !block.detailed" :key="note.id + ':normal'" v-model:note="note"/>
<XNoteDetailed v-if="note && block.detailed" :key="note.id + ':detail'" v-model:note="note"/>
</div>
</template>
<script lang="ts">
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: {
XNote,
XNoteDetailed,
},
props: {
block: {
type: Object as PropType<NoteBlock>,
required: true,
},
},
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,
};
},
});
</script>
<style lang="scss" scoped>
.voxdxuby {
margin: 1em 0;
}
</style>

View file

@ -1,55 +0,0 @@
<template>
<div>
<MkInput class="kudkigyw" :model-value="value" type="number" @update:modelValue="updateValue($event)">
<template #label>{{ hpml.interpolate(block.text) }}</template>
</MkInput>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkInput from '../form/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: {
block: {
type: Object as PropType<NumberInputVarBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
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>
<style lang="scss" scoped>
.kudkigyw {
display: inline-block;
min-width: 300px;
max-width: 450px;
margin: 8px 0;
}
</style>

View file

@ -1,111 +0,0 @@
<template>
<div class="ngbfujlo">
<MkTextarea :model-value="text" readonly style="margin: 0;"></MkTextarea>
<MkButton class="button" primary :disabled="posting || posted" @click="post()">
<i v-if="posted" class="fas fa-check"></i>
<i v-else class="fas fa-paper-plane"></i>
</MkButton>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import MkTextarea from '../form/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: {
MkTextarea,
MkButton,
},
props: {
block: {
type: Object as PropType<PostBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
data() {
return {
text: this.hpml.interpolate(this.block.text),
posted: false,
posting: false,
};
},
watch: {
'hpml.vars': {
handler() {
this.text = this.hpml.interpolate(this.block.text);
},
deep: true,
},
},
methods: {
upload() {
const promise = new Promise((ok) => {
const canvas = this.hpml.canvases[this.block.canvasId];
canvas.toBlob(blob => {
const formData = new FormData();
formData.append('file', blob);
if (this.$store.state.uploadFolder) {
formData.append('folderId', this.$store.state.uploadFolder);
}
fetch(apiUrl + '/drive/files/create', {
method: 'POST',
body: formData,
headers: {
authorization: `Bearer ${this.$i.token}`,
},
})
.then(response => response.json())
.then(f => {
ok(f);
});
});
});
os.promiseDialog(promise);
return promise;
},
async post() {
this.posting = true;
const file = this.block.attachCanvasImage ? await this.upload() : null;
os.apiWithDialog('notes/create', {
text: this.text === '' ? null : this.text,
fileIds: file ? [file.id] : undefined,
}).then(() => {
this.posted = true;
});
},
},
});
</script>
<style lang="scss" scoped>
.ngbfujlo {
position: relative;
padding: 32px;
border-radius: 6px;
box-shadow: 0 2px 8px var(--shadow);
z-index: 1;
> .button {
margin-top: 32px;
}
@media (max-width: 600px) {
padding: 16px;
> .button {
margin-top: 16px;
}
}
}
</style>

View file

@ -1,45 +0,0 @@
<template>
<div>
<div>{{ hpml.interpolate(block.title) }}</div>
<MkRadio v-for="item in block.values" :key="item" :model-value="value" :value="item" @update:modelValue="updateValue($event)">{{ item }}</MkRadio>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkRadio from '../form/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: {
block: {
type: Object as PropType<RadioButtonVarBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
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>

View file

@ -1,60 +0,0 @@
<template>
<section class="sdgxphyu">
<component :is="'h' + h">{{ block.title }}</component>
<div class="children">
<XBlock v-for="child in block.children" :key="child.id" :block="child" :hpml="hpml" :h="h + 1"/>
</div>
</section>
</template>
<script lang="ts">
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: {
block: {
type: Object as PropType<SectionBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
h: {
required: true,
},
},
});
</script>
<style lang="scss" scoped>
.sdgxphyu {
margin: 1.5em 0;
> h2 {
font-size: 1.35em;
margin: 0 0 0.5em 0;
}
> h3 {
font-size: 1em;
margin: 0 0 0.5em 0;
}
> h4 {
font-size: 1em;
margin: 0 0 0.5em 0;
}
> .children {
//padding 16px
}
}
</style>

View file

@ -1,55 +0,0 @@
<template>
<div class="hkcxmtwj">
<MkSwitch :model-value="value" @update:modelValue="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkSwitch>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkSwitch from '../form/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: {
block: {
type: Object as PropType<SwitchVarBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
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>
<style lang="scss" scoped>
.hkcxmtwj {
display: inline-block;
margin: 16px auto;
& + .hkcxmtwj {
margin-left: 16px;
}
}
</style>

View file

@ -1,55 +0,0 @@
<template>
<div>
<MkInput class="kudkigyw" :model-value="value" type="text" @update:modelValue="updateValue($event)">
<template #label>{{ hpml.interpolate(block.text) }}</template>
</MkInput>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkInput from '../form/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: {
block: {
type: Object as PropType<TextInputVarBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
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>
<style lang="scss" scoped>
.kudkigyw {
display: inline-block;
min-width: 300px;
max-width: 450px;
margin: 8px 0;
}
</style>

View file

@ -1,54 +0,0 @@
<template>
<div class="mrdgzndn">
<Mfm :key="text" :text="text" :is-note="false"/>
<MkUrlPreview v-for="url in urls" :key="url" :url="url" class="url"/>
</div>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, watch, computed } from 'vue';
import * as mfm from 'mfm-js';
import { TextBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm';
const props = defineProps<{
block: TextBlock;
hpml: Hpml;
}>();
const MkUrlPreview = defineAsyncComponent(() => import('@/components/url-preview.vue'));
let text: string = $ref('');
const urls = computed((): string[] => {
if (text) {
return extractUrlFromMfm(mfm.parse(text));
} else {
return [];
}
});
watch(props.hpml.vars, () => {
text = props.hpml.interpolate(props.block.text) as string;
}, {
deep: true,
immediate: true,
});
</script>
<style lang="scss" scoped>
.mrdgzndn {
&:not(:first-child) {
margin-top: 0.5em;
}
&:not(:last-child) {
margin-bottom: 0.5em;
}
> .url {
margin: 0.5em 0;
}
}
</style>

View file

@ -1,47 +0,0 @@
<template>
<div>
<MkTextarea :model-value="value" @update:modelValue="updateValue($event)">
<template #label>{{ hpml.interpolate(block.text) }}</template>
</MkTextarea>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkTextarea from '../form/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: {
block: {
type: Object as PropType<TextInputVarBlock>,
required: true,
},
hpml: {
type: Object as PropType<Hpml>,
required: true,
},
},
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>

View file

@ -1,24 +0,0 @@
<template>
<MkTextarea :model-value="text" readonly></MkTextarea>
</template>
<script lang="ts" setup>
import { watch } from 'vue';
import MkTextarea from '../form/textarea.vue';
import { TextBlock } from '@/scripts/hpml/block';
import { Hpml } from '@/scripts/hpml/evaluator';
const props = defineProps<{
block: TextBlock;
hpml: Hpml;
}>();
let text = $ref('');
watch(props.hpml.vars, () => {
text = props.hpml.interpolate(props.block.text) as string;
}, {
deep: true,
immediate: true,
});
</script>

View file

@ -1,85 +0,0 @@
<template>
<div v-if="hpml" class="iroscrza" :class="{ center: page.alignCenter, serif: page.font === 'serif' }">
<XBlock v-for="child in page.content" :key="child.id" :block="child" :hpml="hpml" :h="2"/>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, nextTick, onUnmounted, PropType } from 'vue';
import { parse } from '@syuilo/aiscript';
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 as PropType<Record<string, any>>,
required: true,
},
},
setup(props, ctx) {
const hpml = new Hpml(props.page, {
randomSeed: Math.random(),
visitor: $i,
url,
enableAiScript: !defaultStore.state.disablePagesScript,
});
onMounted(() => {
nextTick(() => {
if (props.page.script && hpml.aiscript) {
let ast;
try {
ast = parse(props.page.script);
} catch (err) {
console.error(err);
/*os.alert({
type: 'error',
text: 'Syntax error :('
});*/
return;
}
hpml.aiscript.exec(ast).then(() => {
hpml.eval();
}).catch(err => {
console.error(err);
/*os.alert({
type: 'error',
text: err
});*/
});
} else {
hpml.eval();
}
});
onUnmounted(() => {
if (hpml.aiscript) hpml.aiscript.abort();
});
});
return {
hpml,
};
},
});
</script>
<style lang="scss" scoped>
.iroscrza {
&.serif {
> div {
font-family: serif;
}
}
&.center {
text-align: center;
}
}
</style>

View file

@ -1,69 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.button }}</template>
<section class="xfhsjczc">
<MkInput v-model="value.text"><template #label>{{ $ts._pages.blocks._button.text }}</template></MkInput>
<MkSwitch v-model="value.primary"><span>{{ $ts._pages.blocks._button.colored }}</span></MkSwitch>
<MkSelect v-model="value.action">
<template #label>{{ $ts._pages.blocks._button.action }}</template>
<option value="dialog">{{ $ts._pages.blocks._button._action.dialog }}</option>
<option value="resetRandom">{{ $ts._pages.blocks._button._action.resetRandom }}</option>
<option value="pushEvent">{{ $ts._pages.blocks._button._action.pushEvent }}</option>
<option value="callAiScript">{{ $ts._pages.blocks._button._action.callAiScript }}</option>
</MkSelect>
<template v-if="value.action === 'dialog'">
<MkInput v-model="value.content"><template #label>{{ $ts._pages.blocks._button._action._dialog.content }}</template></MkInput>
</template>
<template v-else-if="value.action === 'pushEvent'">
<MkInput v-model="value.event"><template #label>{{ $ts._pages.blocks._button._action._pushEvent.event }}</template></MkInput>
<MkInput v-model="value.message"><template #label>{{ $ts._pages.blocks._button._action._pushEvent.message }}</template></MkInput>
<MkSelect v-model="value.var">
<template #label>{{ $ts._pages.blocks._button._action._pushEvent.variable }}</template>
<option :value="null">{{ $t('_pages.blocks._button._action._pushEvent.no-variable') }}</option>
<option v-for="v in hpml.getVarsByType()" :value="v.name">{{ v.name }}</option>
<optgroup :label="$ts._pages.script.pageVariables">
<option v-for="v in hpml.getPageVarsByType()" :value="v">{{ v }}</option>
</optgroup>
<optgroup :label="$ts._pages.script.enviromentVariables">
<option v-for="v in hpml.getEnvVarsByType()" :value="v">{{ v }}</option>
</optgroup>
</MkSelect>
</template>
<template v-else-if="value.action === 'callAiScript'">
<MkInput v-model="value.fn"><template #label>{{ $ts._pages.blocks._button._action._callAiScript.functionName }}</template></MkInput>
</template>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkSelect from '@/components/form/select.vue';
import MkInput from '@/components/form/input.vue';
import MkSwitch from '@/components/form/switch.vue';
withDefaults(defineProps<{
value: any,
hpml: any
}>(), {
value: {
text: '',
action: 'dialog',
content: null,
event: null,
message: null,
primary: false,
var: null,
fn: null,
},
});
</script>
<style lang="scss" scoped>
.xfhsjczc {
padding: 0 16px 0 16px;
}
</style>

View file

@ -1,37 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-paint-brush"></i> {{ $ts._pages.blocks.canvas }}</template>
<section style="padding: 0 16px 0 16px;">
<MkInput v-model="value.name">
<template #prefix><i class="fas fa-magic"></i></template>
<template #label>{{ $ts._pages.blocks._canvas.id }}</template>
</MkInput>
<MkInput v-model="value.width" type="number">
<template #label>{{ $ts._pages.blocks._canvas.width }}</template>
<template #suffix>px</template>
</MkInput>
<MkInput v-model="value.height" type="number">
<template #label>{{ $ts._pages.blocks._canvas.height }}</template>
<template #suffix>px</template>
</MkInput>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkInput from '@/components/form/input.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
width: 300,
height: 200,
},
});
</script>

View file

@ -1,33 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.counter }}</template>
<section style="padding: 0 16px 0 16px;">
<MkInput v-model="value.name">
<template #prefix><i class="fas fa-magic"></i></template>
<template #label>{{ $ts._pages.blocks._counter.name }}</template>
</MkInput>
<MkInput v-model="value.text">
<template #label>{{ $ts._pages.blocks._counter.text }}</template>
</MkInput>
<MkInput v-model="value.inc" type="number">
<template #label>{{ $ts._pages.blocks._counter.inc }}</template>
</MkInput>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkInput from '@/components/form/input.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
},
});
</script>

View file

@ -1,67 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-question"></i> {{ $ts._pages.blocks.if }}</template>
<template #func>
<button class="_button" @click="add()">
<i class="fas fa-plus"></i>
</button>
</template>
<section class="romcojzs">
<MkSelect v-model="value.var">
<template #label>{{ $ts._pages.blocks._if.variable }}</template>
<option v-for="v in hpml.getVarsByType('boolean')" :value="v.name">{{ v.name }}</option>
<optgroup :label="$ts._pages.script.pageVariables">
<option v-for="v in hpml.getPageVarsByType('boolean')" :value="v">{{ v }}</option>
</optgroup>
<optgroup :label="$ts._pages.script.enviromentVariables">
<option v-for="v in hpml.getEnvVarsByType('boolean')" :value="v">{{ v }}</option>
</optgroup>
</MkSelect>
<XBlocks v-model="value.children" class="children" :hpml="hpml"/>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import { defineAsyncComponent, inject } from 'vue';
import { v4 as uuid } from 'uuid';
import XContainer from '../page-editor.container.vue';
import MkSelect from '@/components/form/select.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
const XBlocks = defineAsyncComponent(() => import('../page-editor.blocks.vue'));
const props = withDefaults(defineProps<{
value: any,
hpml: any
}>(), {
value: {
children: [],
var: null,
},
});
const getPageBlockList = inject<(any) => any>('getPageBlockList');
async function add() {
const { canceled, result: type } = await os.select({
title: i18n.ts._pages.chooseBlock,
groupedItems: getPageBlockList(),
});
if (canceled) return;
const id = uuid();
props.value.children.push({ id, type });
}
</script>
<style lang="scss" scoped>
.romcojzs {
padding: 0 16px 16px 16px;
}
</style>

View file

@ -1,60 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-image"></i> {{ $ts._pages.blocks.image }}</template>
<template #func>
<button @click="choose()">
<i class="fas fa-folder-open"></i>
</button>
</template>
<section class="oyyftmcf">
<MkDriveFileThumbnail v-if="file" class="preview" :file="file" fit="contain" @click="choose()"/>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import { onMounted } from 'vue';
import XContainer from '../page-editor.container.vue';
import MkDriveFileThumbnail from '@/components/drive-file-thumbnail.vue';
import * as os from '@/os';
const props = withDefaults(defineProps<{
value: any
}>(), {
value: {
fileId: null,
},
});
let file: any = $ref(null);
async function choose() {
os.selectDriveFile(false).then((fileResponse: any) => {
file = fileResponse;
props.value.fileId = fileResponse.id;
});
}
onMounted(async () => {
if (props.value.fileId == null) {
await choose();
} else {
os.api('drive/files/show', {
fileId: props.value.fileId,
}).then(fileResponse => {
file = fileResponse;
});
}
});
</script>
<style lang="scss" scoped>
.oyyftmcf {
> .preview {
height: 150px;
}
}
</style>

View file

@ -1,52 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-sticky-note"></i> {{ $ts._pages.blocks.note }}</template>
<section style="padding: 0 16px 0 16px;">
<MkInput v-model="id">
<template #label>{{ $ts._pages.blocks._note.id }}</template>
<template #caption>{{ $ts._pages.blocks._note.idDescription }}</template>
</MkInput>
<MkSwitch v-model="value.detailed"><span>{{ $ts._pages.blocks._note.detailed }}</span></MkSwitch>
<XNote v-if="note && !value.detailed" :key="note.id + ':normal'" v-model:note="note" style="margin-bottom: 16px;"/>
<XNoteDetailed v-if="note && value.detailed" :key="note.id + ':detail'" v-model:note="note" style="margin-bottom: 16px;"/>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import { watch } from 'vue';
import XContainer from '../page-editor.container.vue';
import MkInput from '@/components/form/input.vue';
import MkSwitch from '@/components/form/switch.vue';
import XNote from '@/components/note.vue';
import XNoteDetailed from '@/components/note-detailed.vue';
import * as os from '@/os';
const props = withDefaults(defineProps<{
value: any
}>(), {
value: {
note: null,
detailed: false,
},
});
let id: any = $ref(props.value.note);
let note: any = $ref(null);
watch(id, async () => {
if (id && (id.startsWith('http://') || id.startsWith('https://'))) {
props.value.note = (id.endsWith('/') ? id.slice(0, -1) : id).split('/').pop();
} else {
props.value.note = id;
}
note = await os.api('notes/show', { noteId: props.value.note });
}, {
immediate: true,
});
</script>

View file

@ -1,33 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.numberInput }}</template>
<section style="padding: 0 16px 0 16px;">
<MkInput v-model="value.name">
<template #prefix><i class="fas fa-magic"></i></template>
<template #label>{{ $ts._pages.blocks._numberInput.name }}</template>
</MkInput>
<MkInput v-model="value.text">
<template #label>{{ $ts._pages.blocks._numberInput.text }}</template>
</MkInput>
<MkInput v-model="value.default" type="number">
<template #label>{{ $ts._pages.blocks._numberInput.default }}</template>
</MkInput>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkInput from '@/components/form/input.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
},
});
</script>

View file

@ -1,30 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-paper-plane"></i> {{ $ts._pages.blocks.post }}</template>
<section style="padding: 16px;">
<MkTextarea v-model="value.text"><template #label>{{ $ts._pages.blocks._post.text }}</template></MkTextarea>
<MkSwitch v-model="value.attachCanvasImage"><span>{{ $ts._pages.blocks._post.attachCanvasImage }}</span></MkSwitch>
<MkInput v-if="value.attachCanvasImage" v-model="value.canvasId"><template #label>{{ $ts._pages.blocks._post.canvasId }}</template></MkInput>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkTextarea from '@/components/form/textarea.vue';
import MkInput from '@/components/form/input.vue';
import MkSwitch from '@/components/form/switch.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
text: '',
attachCanvasImage: false,
canvasId: '',
},
});
</script>

View file

@ -1,39 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.radioButton }}</template>
<section style="padding: 0 16px 16px 16px;">
<MkInput v-model="value.name"><template #prefix><i class="fas fa-magic"></i></template><template #label>{{ $ts._pages.blocks._radioButton.name }}</template></MkInput>
<MkInput v-model="value.title"><template #label>{{ $ts._pages.blocks._radioButton.title }}</template></MkInput>
<MkTextarea v-model="values"><template #label>{{ $ts._pages.blocks._radioButton.values }}</template></MkTextarea>
<MkInput v-model="value.default"><template #label>{{ $ts._pages.blocks._radioButton.default }}</template></MkInput>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import { watch } from 'vue';
import XContainer from '../page-editor.container.vue';
import MkTextarea from '@/components/form/textarea.vue';
import MkInput from '@/components/form/input.vue';
const props = withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
title: '',
values: [],
},
});
let values: string = $ref(props.value.values.join('\n'));
watch(values, () => {
props.value.values = values.split('\n');
}, {
deep: true,
});
</script>

View file

@ -1,75 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-sticky-note"></i> {{ value.title }}</template>
<template #func>
<button class="_button" @click="rename()">
<i class="fas fa-pencil-alt"></i>
</button>
<button class="_button" @click="add()">
<i class="fas fa-plus"></i>
</button>
</template>
<section class="ilrvjyvi">
<XBlocks v-model="value.children" class="children" :hpml="hpml"/>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import { defineAsyncComponent, inject, onMounted } from 'vue';
import { v4 as uuid } from 'uuid';
import XContainer from '../page-editor.container.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
const XBlocks = defineAsyncComponent(() => import('../page-editor.blocks.vue'));
const props = withDefaults(defineProps<{
value: any,
hpml: any
}>(), {
value: {
title: null,
children: [],
},
});
const getPageBlockList = inject<(any) => any>('getPageBlockList');
async function rename() {
const { canceled, result: title } = await os.inputText({
title: 'Enter title',
default: props.value.title,
});
if (canceled) return;
props.value.title = title;
}
async function add() {
const { canceled, result: type } = await os.select({
title: i18n.ts._pages.chooseBlock,
groupedItems: getPageBlockList(),
});
if (canceled) return;
const id = uuid();
props.value.children.push({ id, type });
}
onMounted(() => {
if (props.value.title == null) {
rename();
}
});
</script>
<style lang="scss" scoped>
.ilrvjyvi {
> .children {
padding: 16px;
}
}
</style>

View file

@ -1,33 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.switch }}</template>
<section class="kjuadyyj">
<MkInput v-model="value.name"><template #prefix><i class="fas fa-magic"></i></template><template #label>{{ $ts._pages.blocks._switch.name }}</template></MkInput>
<MkInput v-model="value.text"><template #label>{{ $ts._pages.blocks._switch.text }}</template></MkInput>
<MkSwitch v-model="value.default"><span>{{ $ts._pages.blocks._switch.default }}</span></MkSwitch>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkSwitch from '@/components/form/switch.vue';
import MkInput from '@/components/form/input.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
},
});
</script>
<style lang="scss" scoped>
.kjuadyyj {
padding: 0 16px 16px 16px;
}
</style>

View file

@ -1,26 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.textInput }}</template>
<section style="padding: 0 16px 0 16px;">
<MkInput v-model="value.name"><template #prefix><i class="fas fa-magic"></i></template><template #label>{{ $ts._pages.blocks._textInput.name }}</template></MkInput>
<MkInput v-model="value.text"><template #label>{{ $ts._pages.blocks._textInput.text }}</template></MkInput>
<MkInput v-model="value.default" type="text"><template #label>{{ $ts._pages.blocks._textInput.default }}</template></MkInput>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkInput from '@/components/form/input.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
},
});
</script>

View file

@ -1,44 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-align-left"></i> {{ $ts._pages.blocks.text }}</template>
<section class="vckmsadr">
<textarea v-model="value.text"></textarea>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
text: '',
},
});
</script>
<style lang="scss" scoped>
.vckmsadr {
> textarea {
display: block;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 100%;
min-width: 100%;
min-height: 150px;
border: none;
box-shadow: none;
padding: 16px;
background: transparent;
color: var(--fg);
font-size: 14px;
box-sizing: border-box;
}
}
</style>

View file

@ -1,27 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-bolt"></i> {{ $ts._pages.blocks.textareaInput }}</template>
<section style="padding: 0 16px 16px 16px;">
<MkInput v-model="value.name"><template #prefix><i class="fas fa-magic"></i></template><template #label>{{ $ts._pages.blocks._textareaInput.name }}</template></MkInput>
<MkInput v-model="value.text"><template #label>{{ $ts._pages.blocks._textareaInput.text }}</template></MkInput>
<MkTextarea v-model="value.default"><template #label>{{ $ts._pages.blocks._textareaInput.default }}</template></MkTextarea>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
import MkTextarea from '@/components/form/textarea.vue';
import MkInput from '@/components/form/input.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
name: '',
},
});
</script>

View file

@ -1,44 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :draggable="true" @remove="() => $emit('remove')">
<template #header><i class="fas fa-align-left"></i> {{ $ts._pages.blocks.textarea }}</template>
<section class="ihymsbbe">
<textarea v-model="value.text"></textarea>
</section>
</XContainer>
</template>
<script lang="ts" setup>
/* eslint-disable vue/no-mutating-props */
import XContainer from '../page-editor.container.vue';
withDefaults(defineProps<{
value: any
}>(), {
value: {
text: '',
},
});
</script>
<style lang="scss" scoped>
.ihymsbbe {
> textarea {
display: block;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 100%;
min-width: 100%;
min-height: 150px;
border: none;
box-shadow: none;
padding: 16px;
background: transparent;
color: var(--fg);
font-size: 14px;
box-sizing: border-box;
}
}
</style>

View file

@ -1,78 +0,0 @@
<template>
<XDraggable v-model="blocks" tag="div" item-key="id" handle=".drag-handle" :group="{ name: 'blocks' }" animation="150" swap-threshold="0.5">
<template #item="{element}">
<component :is="'x-' + element.type" :value="element" :hpml="hpml" @update:value="updateItem" @remove="() => removeItem(element)"/>
</template>
</XDraggable>
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent } from 'vue';
import XSection from './els/page-editor.el.section.vue';
import XText from './els/page-editor.el.text.vue';
import XTextarea from './els/page-editor.el.textarea.vue';
import XImage from './els/page-editor.el.image.vue';
import XButton from './els/page-editor.el.button.vue';
import XTextInput from './els/page-editor.el.text-input.vue';
import XTextareaInput from './els/page-editor.el.textarea-input.vue';
import XNumberInput from './els/page-editor.el.number-input.vue';
import XSwitch from './els/page-editor.el.switch.vue';
import XIf from './els/page-editor.el.if.vue';
import XPost from './els/page-editor.el.post.vue';
import XCounter from './els/page-editor.el.counter.vue';
import XRadioButton from './els/page-editor.el.radio-button.vue';
import XCanvas from './els/page-editor.el.canvas.vue';
import XNote from './els/page-editor.el.note.vue';
import * as os from '@/os';
export default defineComponent({
components: {
XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter, XRadioButton, XCanvas, XNote,
},
props: {
modelValue: {
type: Array,
required: true,
},
hpml: {
required: true,
},
},
emits: ['update:modelValue'],
computed: {
blocks: {
get() {
return this.modelValue;
},
set(value) {
this.$emit('update:modelValue', value);
},
},
},
methods: {
updateItem(v) {
const i = this.blocks.findIndex(x => x.id === v.id);
const newValue = [
...this.blocks.slice(0, i),
v,
...this.blocks.slice(i + 1),
];
this.$emit('update:modelValue', newValue);
},
removeItem(el) {
const i = this.blocks.findIndex(x => x.id === el.id);
const newValue = [
...this.blocks.slice(0, i),
...this.blocks.slice(i + 1),
];
this.$emit('update:modelValue', newValue);
},
},
});
</script>

View file

@ -1,143 +0,0 @@
<template>
<div class="cpjygsrt" :class="{ error: error != null, warn: warn != null }">
<header>
<div class="title"><slot name="header"></slot></div>
<div class="buttons">
<slot name="func"></slot>
<button v-if="removable" class="_button" @click="emit('remove')">
<i class="fas fa-trash-alt"></i>
</button>
<button v-if="draggable" class="drag-handle _button">
<i class="fas fa-bars"></i>
</button>
<button class="_button" @click="toggleContent(!showBody)">
<template v-if="showBody"><i class="fas fa-angle-up"></i></template>
<template v-else><i class="fas fa-angle-down"></i></template>
</button>
</div>
</header>
<p v-show="showBody" v-if="error != null" class="error">{{ i18n.t('_pages.script.typeError', { slot: error.arg + 1, expect: i18n.t(`script.types.${error.expect}`), actual: i18n.t(`script.types.${error.actual}`) }) }}</p>
<p v-show="showBody" v-if="warn != null" class="warn">{{ i18n.t('_pages.script.thereIsEmptySlot', { slot: warn.slot + 1 }) }}</p>
<div v-show="showBody" class="body">
<slot></slot>
</div>
</div>
</template>
<script lang="ts" setup>
import { i18n } from '@/i18n';
const emit = defineEmits<{
(ev: 'toggle', v: boolean): void;
(ev: 'remove'): void;
}>();
const props = withDefaults(defineProps<{
expanded?: boolean;
removable?: boolean;
draggable?: boolean;
error?: { arg: number; expect: string; actual: string; };
warn?: { slot: number; };
}>(), {
expanded: true,
removable: true,
draggable: false,
});
let showBody = $ref(props.expanded);
function toggleContent(show: boolean) {
showBody = show;
emit('toggle', show);
}
</script>
<style lang="scss" scoped>
.cpjygsrt {
position: relative;
overflow: hidden;
background: var(--panel);
border: solid 2px var(--X12);
border-radius: 6px;
&:hover {
border: solid 2px var(--X13);
}
&.warn {
border: solid 2px #dec44c;
}
&.error {
border: solid 2px #f00;
}
& + .cpjygsrt {
margin-top: 16px;
}
> header {
> .title {
z-index: 1;
margin: 0;
padding: 0 16px;
line-height: 42px;
font-size: 0.9em;
font-weight: bold;
box-shadow: 0 1px rgba(#000, 0.07);
> i {
margin-right: 6px;
}
&:empty {
display: none;
}
}
> .buttons {
position: absolute;
z-index: 2;
top: 0;
right: 0;
> button {
padding: 0;
width: 42px;
font-size: 0.9em;
line-height: 42px;
}
.drag-handle {
cursor: move;
}
}
}
> .warn {
color: #b19e49;
margin: 0;
padding: 16px 16px 0 16px;
font-size: 14px;
}
> .error {
color: #f00;
margin: 0;
padding: 16px 16px 0 16px;
font-size: 14px;
}
> .body {
::v-deep(.juejbjww), ::v-deep(.eiipwacr) {
&:not(.inline):first-child {
margin-top: 28px;
}
&:not(.inline):last-child {
margin-bottom: 20px;
}
}
}
}
</style>

View file

@ -1,279 +0,0 @@
<template>
<!-- eslint-disable vue/no-mutating-props -->
<XContainer :removable="removable" :error="error" :warn="warn" :draggable="draggable" @remove="() => $emit('remove')">
<template #header><i v-if="icon" :class="icon"></i> <template v-if="title">{{ title }} <span v-if="typeText" class="turmquns">({{ typeText }})</span></template><template v-else-if="typeText">{{ typeText }}</template></template>
<template #func>
<button class="_button" @click="changeType()">
<i class="fas fa-pencil-alt"></i>
</button>
</template>
<section v-if="modelValue.type === null" class="pbglfege" @click="changeType()">
{{ $ts._pages.script.emptySlot }}
</section>
<section v-else-if="modelValue.type === 'text'" class="tbwccoaw">
<input v-model="modelValue.value"/>
</section>
<section v-else-if="modelValue.type === 'multiLineText'" class="tbwccoaw">
<textarea v-model="modelValue.value"></textarea>
</section>
<section v-else-if="modelValue.type === 'textList'" class="tbwccoaw">
<textarea v-model="modelValue.value" :placeholder="$ts._pages.script.blocks._textList.info"></textarea>
</section>
<section v-else-if="modelValue.type === 'number'" class="tbwccoaw">
<input v-model="modelValue.value" type="number"/>
</section>
<section v-else-if="modelValue.type === 'ref'" class="hpdwcrvs">
<select v-model="modelValue.value">
<option v-for="v in hpml.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option>
<optgroup :label="$ts._pages.script.argVariables">
<option v-for="v in fnSlots" :value="v.name">{{ v.name }}</option>
</optgroup>
<optgroup :label="$ts._pages.script.pageVariables">
<option v-for="v in hpml.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
</optgroup>
<optgroup :label="$ts._pages.script.enviromentVariables">
<option v-for="v in hpml.getEnvVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
</optgroup>
</select>
</section>
<section v-else-if="modelValue.type === 'aiScriptVar'" class="tbwccoaw">
<input v-model="modelValue.value"/>
</section>
<section v-else-if="modelValue.type === 'fn'" class="" style="padding:0 16px 16px 16px;">
<MkTextarea v-model="slots">
<template #label>{{ $ts._pages.script.blocks._fn.slots }}</template>
<template #caption>{{ $t('_pages.script.blocks._fn.slots-info') }}</template>
</MkTextarea>
<XV v-if="modelValue.value.expression" v-model="modelValue.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :hpml="hpml" :fn-slots="modelValue.value.slots" :name="name"/>
</section>
<section v-else-if="modelValue.type.startsWith('fn:')" class="" style="padding:16px;">
<XV v-for="(x, i) in modelValue.args" :key="i" v-model="modelValue.args[i]" :title="hpml.getVarByName(modelValue.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :hpml="hpml" :name="name"/>
</section>
<section v-else class="" style="padding:16px;">
<XV v-for="(x, i) in modelValue.args" :key="i" v-model="modelValue.args[i]" :title="$t(`_pages.script.blocks._${modelValue.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :hpml="hpml" :name="name" :fn-slots="fnSlots"/>
</section>
</XContainer>
</template>
<script lang="ts">
/* eslint-disable vue/no-mutating-props */
import { defineAsyncComponent, defineComponent } from 'vue';
import { v4 as uuid } from 'uuid';
import XContainer from './page-editor.container.vue';
import MkTextarea from '@/components/form/textarea.vue';
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: {
XContainer, MkTextarea,
XV: defineAsyncComponent(() => import('./page-editor.script-block.vue')),
},
inject: ['getScriptBlockList'],
props: {
getExpectedType: {
required: false,
default: null,
},
modelValue: {
required: true,
},
title: {
required: false,
},
removable: {
required: false,
default: false,
},
hpml: {
required: true,
},
name: {
required: true,
},
fnSlots: {
required: false,
},
draggable: {
required: false,
default: false,
},
},
data() {
return {
error: null,
warn: null,
slots: '',
};
},
computed: {
icon(): any {
if (this.modelValue.type === null) return null;
if (this.modelValue.type.startsWith('fn:')) return 'fas fa-plug';
return blockDefs.find(x => x.type === this.modelValue.type).icon;
},
typeText(): any {
if (this.modelValue.type === null) return null;
if (this.modelValue.type.startsWith('fn:')) return this.modelValue.type.split(':')[1];
return this.$t(`_pages.script.blocks.${this.modelValue.type}`);
},
},
watch: {
slots: {
handler() {
this.modelValue.value.slots = this.slots.split('\n').map(x => ({
name: x,
type: null,
}));
},
deep: true,
},
},
created() {
if (this.modelValue.value == null) this.modelValue.value = null;
if (this.modelValue.value && this.modelValue.value.slots) this.slots = this.modelValue.value.slots.map(x => x.name).join('\n');
this.$watch(() => this.modelValue.type, (t) => {
this.warn = null;
if (this.modelValue.type === 'fn') {
const id = uuid();
this.modelValue.value = {
slots: [],
expression: { id, type: null },
};
return;
}
if (this.modelValue.type && this.modelValue.type.startsWith('fn:')) {
const fnName = this.modelValue.type.split(':')[1];
const fn = this.hpml.getVarByName(fnName);
const empties = [];
for (let i = 0; i < fn.value.slots.length; i++) {
const id = uuid();
empties.push({ id, type: null });
}
this.modelValue.args = empties;
return;
}
if (isLiteralValue(this.modelValue)) return;
const empties = [];
for (let i = 0; i < funcDefs[this.modelValue.type].in.length; i++) {
const id = uuid();
empties.push({ id, type: null });
}
this.modelValue.args = empties;
for (let i = 0; i < funcDefs[this.modelValue.type].in.length; i++) {
const inType = funcDefs[this.modelValue.type].in[i];
if (typeof inType !== 'number') {
if (inType === 'number') this.modelValue.args[i].type = 'number';
if (inType === 'string') this.modelValue.args[i].type = 'text';
}
}
});
this.$watch(() => this.modelValue.args, (args) => {
if (args == null) {
this.warn = null;
return;
}
const emptySlotIndex = args.findIndex(x => x.type === null);
if (emptySlotIndex !== -1 && emptySlotIndex < args.length) {
this.warn = {
slot: emptySlotIndex,
};
} else {
this.warn = null;
}
}, {
deep: true,
});
this.$watch(() => this.hpml.variables, () => {
if (this.type != null && this.modelValue) {
this.error = this.hpml.typeCheck(this.modelValue);
}
}, {
deep: true,
});
},
methods: {
async changeType() {
const { canceled, result: type } = await os.select({
title: this.$ts._pages.selectType,
groupedItems: this.getScriptBlockList(this.getExpectedType ? this.getExpectedType() : null),
});
if (canceled) return;
this.modelValue.type = type;
},
_getExpectedType(slot: number) {
return this.hpml.getExpectedType(this.modelValue, slot);
},
},
});
</script>
<style lang="scss" scoped>
.turmquns {
opacity: 0.7;
}
.pbglfege {
opacity: 0.5;
padding: 16px;
text-align: center;
cursor: pointer;
color: var(--fg);
}
.tbwccoaw {
> input,
> textarea {
display: block;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 100%;
max-width: 100%;
min-width: 100%;
border: none;
box-shadow: none;
padding: 16px;
font-size: 16px;
background: transparent;
color: var(--fg);
box-sizing: border-box;
}
> textarea {
min-height: 100px;
}
}
.hpdwcrvs {
padding: 16px;
> select {
display: block;
padding: 4px;
font-size: 16px;
width: 100%;
}
}
</style>

View file

@ -1,109 +0,0 @@
// 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,232 +0,0 @@
import autobind from 'autobind-decorator';
import { AiScript, utils, values } from '@syuilo/aiscript';
import { markRaw, ref, Ref, unref } from 'vue';
import { createAiScriptEnv } from '../aiscript/api';
import { collectPageVars } from '../collect-page-vars';
import { initHpmlLib, initAiLib } from './lib';
import { Expr, isLiteralValue, Variable } from './expr';
import { PageVar, envVarsDef, Fn, HpmlScope, HpmlError } from '.';
import { version } from '@/config';
import * as os from '@/os';
/**
* Hpml evaluator
*/
export class Hpml {
private variables: Variable[];
private pageVars: PageVar[];
private envVars: Record<keyof typeof envVarsDef, any>;
public aiscript?: AiScript;
public pageVarUpdatedCallback?: values.VFn;
public canvases: Record<string, HTMLCanvasElement> = {};
public vars: Ref<Record<string, any>> = ref({});
public page: Record<string, any>;
private opts: {
randomSeed: string; visitor?: any; url?: string;
enableAiScript: boolean;
};
constructor(page: Hpml['page'], opts: Hpml['opts']) {
this.page = page;
this.variables = this.page.variables;
this.pageVars = collectPageVars(this.page.content);
this.opts = opts;
if (this.opts.enableAiScript) {
this.aiscript = markRaw(new AiScript({ ...createAiScriptEnv({
storageKey: 'pages:' + this.page.id,
}), ...initAiLib(this) }, {
in: (q) => {
return new Promise(ok => {
os.inputText({
title: q,
}).then(({ canceled, result: a }) => {
ok(a);
});
});
},
out: (value) => {
console.log(value);
},
log: (type, params) => {
},
}));
this.aiscript.scope.opts.onUpdated = (name, value) => {
this.eval();
};
}
const date = new Date();
this.envVars = {
AI: 'kawaii',
VERSION: version,
URL: this.page ? `${opts.url}/@${this.page.user.username}/pages/${this.page.name}` : '',
LOGIN: opts.visitor != null,
NAME: opts.visitor ? opts.visitor.name || opts.visitor.username : '',
USERNAME: opts.visitor ? opts.visitor.username : '',
USERID: opts.visitor ? opts.visitor.id : '',
NOTES_COUNT: opts.visitor ? opts.visitor.notesCount : 0,
FOLLOWERS_COUNT: opts.visitor ? opts.visitor.followersCount : 0,
FOLLOWING_COUNT: opts.visitor ? opts.visitor.followingCount : 0,
IS_CAT: opts.visitor ? opts.visitor.isCat : false,
SEED: opts.randomSeed ? opts.randomSeed : '',
YMD: `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`,
AISCRIPT_DISABLED: !this.opts.enableAiScript,
NULL: null,
};
this.eval();
}
@autobind
public eval() {
try {
this.vars.value = this.evaluateVars();
} catch (err) {
//this.onError(e);
}
}
@autobind
public interpolate(str: string) {
if (str == null) return null;
return str.replace(/{(.+?)}/g, match => {
const v = unref(this.vars)[match.slice(1, -1).trim()];
return v == null ? 'NULL' : v.toString();
});
}
@autobind
public callAiScript(fn: string) {
try {
if (this.aiscript) this.aiscript.execFn(this.aiscript.scope.get(fn), []);
} catch (err) {}
}
@autobind
public registerCanvas(id: string, canvas: any) {
this.canvases[id] = canvas;
}
@autobind
public updatePageVar(name: string, value: any) {
const pageVar = this.pageVars.find(v => v.name === name);
if (pageVar !== undefined) {
pageVar.value = value;
if (this.pageVarUpdatedCallback) {
if (this.aiscript) this.aiscript.execFn(this.pageVarUpdatedCallback, [values.STR(name), utils.jsToVal(value)]);
}
} else {
throw new HpmlError(`No such page var '${name}'`);
}
}
@autobind
public updateRandomSeed(seed: string) {
this.opts.randomSeed = seed;
this.envVars.SEED = seed;
}
@autobind
private _interpolateScope(str: string, scope: HpmlScope) {
return str.replace(/{(.+?)}/g, match => {
const v = scope.getState(match.slice(1, -1).trim());
return v == null ? 'NULL' : v.toString();
});
}
@autobind
public evaluateVars(): Record<string, any> {
const values: Record<string, any> = {};
for (const [k, v] of Object.entries(this.envVars)) {
values[k] = v;
}
for (const v of this.pageVars) {
values[v.name] = v.value;
}
for (const v of this.variables) {
values[v.name] = this.evaluate(v, new HpmlScope([values]));
}
return values;
}
@autobind
private evaluate(expr: Expr, scope: HpmlScope): any {
if (isLiteralValue(expr)) {
if (expr.type === null) {
return null;
}
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 (err) {
return null;
}
} else {
return null;
}
}
// 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 (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(expr.args[i], scope);
}
return fn.exec(args);
}
if (expr.args === undefined) return null;
const funcs = initHpmlLib(expr, scope, this.opts.randomSeed, this.opts.visitor);
// Call function
const fnName = expr.type;
const fn = (funcs as any)[fnName];
if (fn == null) {
throw new HpmlError(`No such function '${fnName}'`);
} else {
return fn(...expr.args.map(x => this.evaluate(x, scope)));
}
}
}

View file

@ -1,79 +0,0 @@
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

@ -1,103 +0,0 @@
/**
* Hpml
*/
import autobind from 'autobind-decorator';
import { Hpml } from './evaluator';
import { funcDefs } from './lib';
export type Fn = {
slots: string[];
exec: (args: Record<string, any>) => ReturnType<Hpml['evaluate']>;
};
export type Type = 'string' | 'number' | 'boolean' | 'stringArray' | null;
export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = {
text: { out: 'string', category: 'value', icon: 'fas fa-quote-right' },
multiLineText: { out: 'string', category: 'value', icon: 'fas fa-align-left' },
textList: { out: 'stringArray', category: 'value', icon: 'fas fa-list' },
number: { out: 'number', category: 'value', icon: 'fas fa-sort-numeric-up' },
ref: { out: null, category: 'value', icon: 'fas fa-magic' },
aiScriptVar: { out: null, category: 'value', icon: 'fas fa-magic' },
fn: { out: 'function', category: 'value', icon: 'fas fa-square-root-alt' },
};
export const blockDefs = [
...Object.entries(literalDefs).map(([k, v]) => ({
type: k, out: v.out, category: v.category, icon: v.icon,
})),
...Object.entries(funcDefs).map(([k, v]) => ({
type: k, out: v.out, category: v.category, icon: v.icon,
})),
];
export type PageVar = { name: string; value: any; type: Type; };
export const envVarsDef: Record<string, Type> = {
AI: 'string',
URL: 'string',
VERSION: 'string',
LOGIN: 'boolean',
NAME: 'string',
USERNAME: 'string',
USERID: 'string',
NOTES_COUNT: 'number',
FOLLOWERS_COUNT: 'number',
FOLLOWING_COUNT: 'number',
IS_CAT: 'boolean',
SEED: null,
YMD: 'string',
AISCRIPT_DISABLED: 'boolean',
NULL: null,
};
export class HpmlScope {
private layerdStates: Record<string, any>[];
public name: string;
constructor(layerdStates: HpmlScope['layerdStates'], name?: HpmlScope['name']) {
this.layerdStates = layerdStates;
this.name = name || 'anonymous';
}
@autobind
public createChildScope(states: Record<string, any>, name?: HpmlScope['name']): HpmlScope {
const layer = [states, ...this.layerdStates];
return new HpmlScope(layer, name);
}
/**
*
* @param name
*/
@autobind
public getState(name: string): any {
for (const later of this.layerdStates) {
const state = later[name];
if (state !== undefined) {
return state;
}
}
throw new HpmlError(
`No such variable '${name}' in scope '${this.name}'`, {
scope: this.layerdStates,
});
}
}
export class HpmlError extends Error {
public info?: any;
constructor(message: string, info?: any) {
super(message);
this.info = info;
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, HpmlError);
}
}
}

View file

@ -1,247 +0,0 @@
import tinycolor from 'tinycolor2';
import { values, utils } from '@syuilo/aiscript';
import seedrandom from 'seedrandom';
import { Hpml } from './evaluator';
import { Expr } from './expr';
import { Fn, HpmlScope } from '.';
/* TODO: https://www.chartjs.org/docs/latest/configuration/canvas-background.html#color
// https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs
Chart.pluginService.register({
beforeDraw: (chart, easing) => {
if (chart.config.options.chartArea && chart.config.options.chartArea.backgroundColor) {
const ctx = chart.chart.ctx;
ctx.save();
ctx.fillStyle = chart.config.options.chartArea.backgroundColor;
ctx.fillRect(0, 0, chart.chart.width, chart.chart.height);
ctx.restore();
}
}
});
*/
export function initAiLib(hpml: Hpml) {
return {
'MkPages:updated': values.FN_NATIVE(([callback]) => {
hpml.pageVarUpdatedCallback = (callback as values.VFn);
}),
'MkPages:get_canvas': values.FN_NATIVE(([id]) => {
utils.assertString(id);
const canvas = hpml.canvases[id.value];
const ctx = canvas.getContext('2d');
return values.OBJ(new Map([
['clear_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.clearRect(x.value, y.value, width.value, height.value); })],
['fill_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.fillRect(x.value, y.value, width.value, height.value); })],
['stroke_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.strokeRect(x.value, y.value, width.value, height.value); })],
['fill_text', values.FN_NATIVE(([text, x, y, width]) => { ctx.fillText(text.value, x.value, y.value, width ? width.value : undefined); })],
['stroke_text', values.FN_NATIVE(([text, x, y, width]) => { ctx.strokeText(text.value, x.value, y.value, width ? width.value : undefined); })],
['set_line_width', values.FN_NATIVE(([width]) => { ctx.lineWidth = width.value; })],
['set_font', values.FN_NATIVE(([font]) => { ctx.font = font.value; })],
['set_fill_style', values.FN_NATIVE(([style]) => { ctx.fillStyle = style.value; })],
['set_stroke_style', values.FN_NATIVE(([style]) => { ctx.strokeStyle = style.value; })],
['begin_path', values.FN_NATIVE(() => { ctx.beginPath(); })],
['close_path', values.FN_NATIVE(() => { ctx.closePath(); })],
['move_to', values.FN_NATIVE(([x, y]) => { ctx.moveTo(x.value, y.value); })],
['line_to', values.FN_NATIVE(([x, y]) => { ctx.lineTo(x.value, y.value); })],
['arc', values.FN_NATIVE(([x, y, radius, startAngle, endAngle]) => { ctx.arc(x.value, y.value, radius.value, startAngle.value, endAngle.value); })],
['rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.rect(x.value, y.value, width.value, height.value); })],
['fill', values.FN_NATIVE(() => { ctx.fill(); })],
['stroke', values.FN_NATIVE(() => { ctx.stroke(); })],
]));
}),
'MkPages:chart': values.FN_NATIVE(([id, opts]) => {
/* TODO
utils.assertString(id);
utils.assertObject(opts);
const canvas = hpml.canvases[id.value];
const color = getComputedStyle(document.documentElement).getPropertyValue('--accent');
Chart.defaults.color = '#555';
const chart = new Chart(canvas, {
type: opts.value.get('type').value,
data: {
labels: opts.value.get('labels').value.map(x => x.value),
datasets: opts.value.get('datasets').value.map(x => ({
label: x.value.has('label') ? x.value.get('label').value : '',
data: x.value.get('data').value.map(x => x.value),
pointRadius: 0,
lineTension: 0,
borderWidth: 2,
borderColor: x.value.has('color') ? x.value.get('color') : color,
backgroundColor: tinycolor(x.value.has('color') ? x.value.get('color') : color).setAlpha(0.1).toRgbString(),
}))
},
options: {
responsive: false,
devicePixelRatio: 1.5,
title: {
display: opts.value.has('title'),
text: opts.value.has('title') ? opts.value.get('title').value : '',
fontSize: 14,
},
layout: {
padding: {
left: 32,
right: 32,
top: opts.value.has('title') ? 16 : 32,
bottom: 16
}
},
legend: {
display: opts.value.get('datasets').value.filter(x => x.value.has('label') && x.value.get('label').value).length === 0 ? false : true,
position: 'bottom',
labels: {
boxWidth: 16,
}
},
tooltips: {
enabled: false,
},
chartArea: {
backgroundColor: '#fff'
},
...(opts.value.get('type').value === 'radar' ? {
scale: {
ticks: {
display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : false,
min: opts.value.has('min') ? opts.value.get('min').value : undefined,
max: opts.value.has('max') ? opts.value.get('max').value : undefined,
maxTicksLimit: 8,
},
pointLabels: {
fontSize: 12
}
}
} : {
scales: {
yAxes: [{
ticks: {
display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : true,
min: opts.value.has('min') ? opts.value.get('min').value : undefined,
max: opts.value.has('max') ? opts.value.get('max').value : undefined,
}
}]
}
})
}
});
*/
}),
};
}
export const funcDefs: Record<string, { in: any[]; out: any; category: string; icon: any; }> = {
if: { in: ['boolean', 0, 0], out: 0, category: 'flow', icon: 'fas fa-share-alt' },
for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'fas fa-recycle' },
not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
add: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-plus' },
subtract: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-minus' },
multiply: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-times' },
divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' },
mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' },
round: { in: ['number'], out: 'number', category: 'operation', icon: 'fas fa-calculator' },
eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-equals' },
notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-not-equal' },
gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than' },
lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than' },
gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than-equal' },
ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than-equal' },
strLen: { in: ['string'], out: 'number', category: 'text', icon: 'fas fa-quote-right' },
strPick: { in: ['string', 'number'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
strReplace: { in: ['string', 'string', 'string'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
strReverse: { in: ['string'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
join: { in: ['stringArray', 'string'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'fas fa-exchange-alt' },
numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'fas fa-exchange-alt' },
splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'fas fa-exchange-alt' },
pick: { in: [null, 'number'], out: null, category: 'list', icon: 'fas fa-indent' },
listLen: { in: [null], out: 'number', category: 'list', icon: 'fas fa-indent' },
rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
random: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
randomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' },
dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' },
seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'fas fa-dice' },
DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'fas fa-dice' }, // 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()}`;
// SHOULD be fine to ignore since it's intended + function shape isn't defined
// eslint-disable-next-line @typescript-eslint/ban-types
const funcs: Record<string, Function> = {
not: (a: boolean) => !a,
or: (a: boolean, b: boolean) => a || b,
and: (a: boolean, b: boolean) => a && b,
eq: (a: any, b: any) => a === b,
notEq: (a: any, b: any) => a !== b,
gt: (a: number, b: number) => a > b,
lt: (a: number, b: number) => a < b,
gtEq: (a: number, b: number) => a >= b,
ltEq: (a: number, b: number) => a <= b,
if: (bool: boolean, a: any, b: any) => bool ? a : b,
for: (times: number, fn: Fn) => {
const result: any[] = [];
for (let i = 0; i < times; i++) {
result.push(fn.exec({
[fn.slots[0]]: i + 1,
}));
}
return result;
},
add: (a: number, b: number) => a + b,
subtract: (a: number, b: number) => a - b,
multiply: (a: number, b: number) => a * b,
divide: (a: number, b: number) => a / b,
mod: (a: number, b: number) => a % b,
round: (a: number) => Math.round(a),
strLen: (a: string) => a.length,
strPick: (a: string, b: number) => a[b - 1],
strReplace: (a: string, b: string, c: string) => a.split(b).join(c),
strReverse: (a: string) => a.split('').reverse().join(''),
join: (texts: string[], separator: string) => texts.join(separator || ''),
stringToNumber: (a: string) => parseInt(a),
numberToString: (a: number) => a.toString(),
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}:${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)],
DRPWPM: (list: string[]) => {
const xs: any[] = [];
let totalFactor = 0;
for (const x of list) {
const parts = x.split(' ');
const factor = parseInt(parts.pop()!, 10);
const text = parts.join(' ');
totalFactor += factor;
xs.push({ factor, text });
}
const r = seedrandom(`${day}:${expr.id}`)() * totalFactor;
let stackedFactor = 0;
for (const x of xs) {
if (r >= stackedFactor && r <= stackedFactor + x.factor) {
return x.text;
} else {
stackedFactor += x.factor;
}
}
return xs[0].text;
},
};
return funcs;
}

View file

@ -1,189 +0,0 @@
import autobind from 'autobind-decorator';
import { Expr, isLiteralValue, Variable } from './expr';
import { funcDefs } from './lib';
import { Type, envVarsDef, PageVar } from '.';
type TypeError = {
arg: number;
expect: Type;
actual: Type;
};
/**
* Hpml type checker
*/
export class HpmlTypeChecker {
public variables: Variable[];
public pageVars: PageVar[];
constructor(variables: HpmlTypeChecker['variables'] = [], pageVars: HpmlTypeChecker['pageVars'] = []) {
this.variables = variables;
this.pageVars = pageVars;
}
@autobind
public typeCheck(v: Expr): TypeError | null {
if (isLiteralValue(v)) return null;
const def = funcDefs[v.type || ''];
if (def == null) {
throw new Error('Unknown type: ' + v.type);
}
const generic: Type[] = [];
for (let i = 0; i < def.in.length; i++) {
const arg = def.in[i];
const type = this.infer(v.args[i]);
if (type === null) continue;
if (typeof arg === 'number') {
if (generic[arg] === undefined) {
generic[arg] = type;
} else if (type !== generic[arg]) {
return {
arg: i,
expect: generic[arg],
actual: type,
};
}
} else if (type !== arg) {
return {
arg: i,
expect: arg,
actual: type,
};
}
}
return null;
}
@autobind
public getExpectedType(v: Expr, slot: number): Type {
const def = funcDefs[v.type || ''];
if (def == null) {
throw new Error('Unknown type: ' + v.type);
}
const generic: Type[] = [];
for (let i = 0; i < def.in.length; i++) {
const arg = def.in[i];
const type = this.infer(v.args[i]);
if (type === null) continue;
if (typeof arg === 'number') {
if (generic[arg] === undefined) {
generic[arg] = type;
}
}
}
if (typeof def.in[slot] === 'number') {
return generic[def.in[slot]] || null;
} else {
return def.in[slot];
}
}
@autobind
public infer(v: Expr): Type {
if (v.type === null) return null;
if (v.type === 'text') return 'string';
if (v.type === 'multiLineText') return 'string';
if (v.type === 'textList') return 'stringArray';
if (v.type === 'number') return 'number';
if (v.type === 'ref') {
const variable = this.variables.find(va => va.name === v.value);
if (variable) {
return this.infer(variable);
}
const pageVar = this.pageVars.find(va => va.name === v.value);
if (pageVar) {
return pageVar.type;
}
const envVar = envVarsDef[v.value || ''];
if (envVar !== undefined) {
return envVar;
}
return null;
}
if (v.type === 'aiScriptVar') return null;
if (v.type === 'fn') return null; // todo
if (v.type.startsWith('fn:')) return null; // todo
const generic: Type[] = [];
const def = funcDefs[v.type];
for (let i = 0; i < def.in.length; i++) {
const arg = def.in[i];
if (typeof arg === 'number') {
const type = this.infer(v.args[i]);
if (generic[arg] === undefined) {
generic[arg] = type;
} else {
if (type !== generic[arg]) {
generic[arg] = null;
}
}
}
}
if (typeof def.out === 'number') {
return generic[def.out];
} else {
return def.out;
}
}
@autobind
public getVarByName(name: string): Variable {
const v = this.variables.find(x => x.name === name);
if (v !== undefined) {
return v;
} else {
throw new Error(`No such variable '${name}'`);
}
}
@autobind
public getVarsByType(type: Type): Variable[] {
if (type == null) return this.variables;
return this.variables.filter(x => (this.infer(x) === null) || (this.infer(x) === type));
}
@autobind
public getEnvVarsByType(type: Type): string[] {
if (type == null) return Object.keys(envVarsDef);
return Object.entries(envVarsDef).filter(([k, v]) => v === null || type === v).map(([k, v]) => k);
}
@autobind
public getPageVarsByType(type: Type): string[] {
if (type == null) return this.pageVars.map(v => v.name);
return this.pageVars.filter(v => type === v.type).map(v => v.name);
}
@autobind
public isUsedName(name: string) {
if (this.variables.some(v => v.name === name)) {
return true;
}
if (this.pageVars.some(v => v.name === name)) {
return true;
}
if (envVarsDef[name]) {
return true;
}
return false;
}
}