refactor(frontend): use composition api

This commit is contained in:
syuilo 2023-05-14 11:43:56 +09:00
parent 238d0fa667
commit 3d4a90b08a
7 changed files with 125 additions and 381 deletions

View file

@ -1,118 +0,0 @@
<template>
<div class="">
<div class="">
<MkInput v-model="text">
<template #label>Text</template>
</MkInput>
<MkSwitch v-model="flag">
<span>Switch is now {{ flag ? 'on' : 'off' }}</span>
</MkSwitch>
<div style="margin: 32px 0;">
<MkRadio v-model="radio" value="misskey">Misskey</MkRadio>
<MkRadio v-model="radio" value="mastodon">Mastodon</MkRadio>
<MkRadio v-model="radio" value="pleroma">Pleroma</MkRadio>
</div>
<MkButton inline>This is</MkButton>
<MkButton inline primary>the button</MkButton>
</div>
<div class="" style="pointer-events: none;">
<Mfm :text="mfm"/>
</div>
<div class="">
<MkButton inline primary @click="openMenu">Open menu</MkButton>
<MkButton inline primary @click="openDialog">Open dialog</MkButton>
<MkButton inline primary @click="openForm">Open form</MkButton>
<MkButton inline primary @click="openDrive">Open drive</MkButton>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkRadio from '@/components/MkRadio.vue';
import * as os from '@/os';
import * as config from '@/config';
import { $i } from '@/account';
export default defineComponent({
components: {
MkButton,
MkInput,
MkSwitch,
MkTextarea,
MkRadio,
},
data() {
return {
text: '',
flag: true,
radio: 'misskey',
$i,
mfm: `Hello world! This is an @example mention. BTW you are @${this.$i ? this.$i.username : 'guest'}.\nAlso, here is ${config.url} and [example link](${config.url}). for more details, see https://example.com.\nAs you know #misskey is open-source software.`,
};
},
methods: {
async openDialog() {
os.alert({
type: 'warning',
title: 'Oh my Aichan',
text: 'Lorem ipsum dolor sit amet, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
});
},
async openForm() {
os.form('Example form', {
foo: {
type: 'boolean',
default: true,
label: 'This is a boolean property',
},
bar: {
type: 'number',
default: 300,
label: 'This is a number property',
},
baz: {
type: 'string',
default: 'Misskey makes you happy.',
label: 'This is a string property',
},
});
},
async openDrive() {
os.selectDriveFile(false);
},
async selectUser() {
os.selectUser();
},
async openMenu(ev) {
os.popupMenu([{
type: 'label',
text: 'Fruits',
}, {
text: 'Create some apples',
action: () => {},
}, {
text: 'Read some oranges',
action: () => {},
}, {
text: 'Update some melons',
action: () => {},
}, null, {
text: 'Delete some bananas',
danger: true,
action: () => {},
}], ev.currentTarget ?? ev.target);
},
},
});
</script>

View file

@ -23,22 +23,13 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { } from 'vue';
export default defineComponent({ defineProps<{
props: { def: any[];
def: { grid?: boolean;
type: Array, }>();
required: true,
},
grid: {
type: Boolean,
required: false,
default: false,
},
},
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -26,120 +26,71 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, onMounted, nextTick, ref, watch, computed, toRefs } from 'vue'; import { onMounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue';
import { debounce } from 'throttle-debounce'; import { debounce } from 'throttle-debounce';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
export default defineComponent({ const props = defineProps<{
components: { modelValue: string | null;
MkButton, required?: boolean;
}, readonly?: boolean;
disabled?: boolean;
pattern?: string;
placeholder?: string;
autofocus?: boolean;
autocomplete?: string;
spellcheck?: boolean;
debounce?: boolean;
manualSave?: boolean;
code?: boolean;
tall?: boolean;
pre?: boolean;
}>();
props: { const emit = defineEmits<{
modelValue: { (ev: 'change', _ev: KeyboardEvent): void;
required: true, (ev: 'keydown', _ev: KeyboardEvent): void;
}, (ev: 'enter'): void;
type: { (ev: 'update:modelValue', value: string): void;
type: String, }>();
required: false,
},
required: {
type: Boolean,
required: false,
},
readonly: {
type: Boolean,
required: false,
},
disabled: {
type: Boolean,
required: false,
},
pattern: {
type: String,
required: false,
},
placeholder: {
type: String,
required: false,
},
autofocus: {
type: Boolean,
required: false,
default: false,
},
autocomplete: {
required: false,
},
spellcheck: {
required: false,
},
code: {
type: Boolean,
required: false,
},
tall: {
type: Boolean,
required: false,
default: false,
},
pre: {
type: Boolean,
required: false,
default: false,
},
debounce: {
type: Boolean,
required: false,
default: false,
},
manualSave: {
type: Boolean,
required: false,
default: false,
},
},
emits: ['change', 'keydown', 'enter', 'update:modelValue'], const { modelValue, autofocus } = toRefs(props);
const v = ref<string>(modelValue.value ?? '');
const focused = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = shallowRef<HTMLTextAreaElement>();
setup(props, context) { const focus = () => inputEl.value.focus();
const { modelValue, autofocus } = toRefs(props); const onInput = (ev) => {
const v = ref(modelValue.value);
const focused = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = ref(null);
const focus = () => inputEl.value.focus();
const onInput = (ev) => {
changed.value = true; changed.value = true;
context.emit('change', ev); emit('change', ev);
}; };
const onKeydown = (ev: KeyboardEvent) => { const onKeydown = (ev: KeyboardEvent) => {
if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return; if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return;
context.emit('keydown', ev); emit('keydown', ev);
if (ev.code === 'Enter') { if (ev.code === 'Enter') {
context.emit('enter'); emit('enter');
} }
}; };
const updated = () => { const updated = () => {
changed.value = false; changed.value = false;
context.emit('update:modelValue', v.value); emit('update:modelValue', v.value ?? '');
}; };
const debouncedUpdated = debounce(1000, updated); const debouncedUpdated = debounce(1000, updated);
watch(modelValue, newValue => { watch(modelValue, newValue => {
v.value = newValue; v.value = newValue;
}); });
watch(v, newValue => { watch(v, newValue => {
if (!props.manualSave) { if (!props.manualSave) {
if (props.debounce) { if (props.debounce) {
debouncedUpdated(); debouncedUpdated();
@ -149,30 +100,14 @@ export default defineComponent({
} }
invalid.value = inputEl.value.validity.badInput; invalid.value = inputEl.value.validity.badInput;
}); });
onMounted(() => { onMounted(() => {
nextTick(() => { nextTick(() => {
if (autofocus.value) { if (autofocus.value) {
focus(); focus();
} }
}); });
});
return {
v,
focused,
invalid,
changed,
filled,
inputEl,
focus,
onInput,
onKeydown,
updated,
i18n,
};
},
}); });
</script> </script>

View file

@ -9,49 +9,41 @@
</Sortable> </Sortable>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, defineAsyncComponent } from 'vue'; import { defineAsyncComponent } from 'vue';
import XSection from './els/page-editor.el.section.vue'; import XSection from './els/page-editor.el.section.vue';
import XText from './els/page-editor.el.text.vue'; import XText from './els/page-editor.el.text.vue';
import XImage from './els/page-editor.el.image.vue'; import XImage from './els/page-editor.el.image.vue';
import XNote from './els/page-editor.el.note.vue'; import XNote from './els/page-editor.el.note.vue';
export default defineComponent({ const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
components: {
Sortable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
XSection, XText, XImage, XNote,
},
props: { const props = defineProps<{
modelValue: { modelValue: any[];
type: Array, }>();
required: true,
},
},
emits: ['update:modelValue'], const emit = defineEmits<{
(ev: 'update:modelValue', value: any[]): void;
}>();
methods: { function updateItem(v) {
updateItem(v) { const i = props.modelValue.findIndex(x => x.id === v.id);
const i = this.modelValue.findIndex(x => x.id === v.id);
const newValue = [ const newValue = [
...this.modelValue.slice(0, i), ...props.modelValue.slice(0, i),
v, v,
...this.modelValue.slice(i + 1), ...props.modelValue.slice(i + 1),
]; ];
this.$emit('update:modelValue', newValue); emit('update:modelValue', newValue);
}, }
removeItem(el) { function removeItem(el) {
const i = this.modelValue.findIndex(x => x.id === el.id); const i = props.modelValue.findIndex(x => x.id === el.id);
const newValue = [ const newValue = [
...this.modelValue.slice(0, i), ...props.modelValue.slice(0, i),
...this.modelValue.slice(i + 1), ...props.modelValue.slice(i + 1),
]; ];
this.$emit('update:modelValue', newValue); emit('update:modelValue', newValue);
}, }
},
});
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="cpjygsrt" :class="{ error: error != null, warn: warn != null }"> <div class="cpjygsrt">
<header> <header>
<div class="title"><slot name="header"></slot></div> <div class="title"><slot name="header"></slot></div>
<div class="buttons"> <div class="buttons">
@ -16,58 +16,40 @@
</button> </button>
</div> </div>
</header> </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"> <div v-show="showBody" class="body">
<slot></slot> <slot></slot>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { ref } from 'vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
export default defineComponent({ const props = withDefaults(defineProps<{
props: { expanded?: boolean;
expanded: { removable?: boolean;
type: Boolean, draggable?: boolean;
default: true, }>(), {
}, expanded: true,
removable: { removable: true,
type: Boolean,
default: true,
},
draggable: {
type: Boolean,
default: false,
},
error: {
required: false,
default: null,
},
warn: {
required: false,
default: null,
},
},
emits: ['toggle', 'remove'],
data() {
return {
showBody: this.expanded,
i18n,
};
},
methods: {
toggleContent(show: boolean) {
this.showBody = show;
this.$emit('toggle', show);
},
remove() {
this.$emit('remove');
},
},
}); });
const emit = defineEmits<{
(ev: 'toggle', show: boolean): void;
(ev: 'remove'): void;
}>();
const showBody = ref(props.expanded);
function toggleContent(show: boolean) {
showBody.value = show;
emit('toggle', show);
}
function remove() {
emit('remove');
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -128,20 +110,6 @@ export default defineComponent({
} }
} }
> .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 { > .body {
::v-deep(.juejbjww), ::v-deep(.eiipwacr) { ::v-deep(.juejbjww), ::v-deep(.eiipwacr) {
&:not(.inline):first-child { &:not(.inline):first-child {

View file

@ -1,21 +0,0 @@
<template>
<div>
<MkSample/>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import MkSample from '@/components/MkSample.vue';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata(computed(() => ({
title: i18n.ts.preview,
icon: 'ti ti-eye',
})));
</script>

View file

@ -242,9 +242,6 @@ export const routes = [{
}, { }, {
path: '/scratchpad', path: '/scratchpad',
component: page(() => import('./pages/scratchpad.vue')), component: page(() => import('./pages/scratchpad.vue')),
}, {
path: '/preview',
component: page(() => import('./pages/preview.vue')),
}, { }, {
path: '/auth/:token', path: '/auth/:token',
component: page(() => import('./pages/auth.vue')), component: page(() => import('./pages/auth.vue')),