<template> <section class="_card"> <div class="_title"><fa :icon="faPlug"/> {{ $t('plugins') }}</div> <div class="_content"> <details> <summary><fa :icon="faDownload"/> {{ $t('install') }}</summary> <mk-info warn>{{ $t('pluginInstallWarn') }}</mk-info> <mk-textarea v-model="script" tall> <span>{{ $t('script') }}</span> </mk-textarea> <mk-button @click="install()" primary><fa :icon="faSave"/> {{ $t('install') }}</mk-button> </details> </div> <div class="_content"> <details> <summary><fa :icon="faFolderOpen"/> {{ $t('manage') }}</summary> <mk-select v-model="selectedPluginId"> <option v-for="x in $store.state.deviceUser.plugins" :value="x.id" :key="x.id">{{ x.name }}</option> </mk-select> <template v-if="selectedPlugin"> <div style="margin: -8px 0 8px 0;"> <mk-switch :value="selectedPlugin.active" @change="changeActive(selectedPlugin, $event)">{{ $t('makeActive') }}</mk-switch> </div> <div class="_keyValue"> <div>{{ $t('version') }}:</div> <div>{{ selectedPlugin.version }}</div> </div> <div class="_keyValue"> <div>{{ $t('author') }}:</div> <div>{{ selectedPlugin.author }}</div> </div> <div class="_keyValue"> <div>{{ $t('description') }}:</div> <div>{{ selectedPlugin.description }}</div> </div> <div style="margin-top: 8px;"> <mk-button @click="config()" inline v-if="selectedPlugin.config"><fa :icon="faCog"/> {{ $t('settings') }}</mk-button> <mk-button @click="uninstall()" inline><fa :icon="faTrashAlt"/> {{ $t('uninstall') }}</mk-button> </div> </template> </details> </div> </section> </template> <script lang="ts"> import Vue from 'vue'; import { AiScript, parse } from '@syuilo/aiscript'; import { serialize } from '@syuilo/aiscript/built/serializer'; import { v4 as uuid } from 'uuid'; import { faPlug, faSave, faTrashAlt, faFolderOpen, faDownload, faCog } from '@fortawesome/free-solid-svg-icons'; import MkButton from '../../components/ui/button.vue'; import MkTextarea from '../../components/ui/textarea.vue'; import MkSelect from '../../components/ui/select.vue'; import MkInfo from '../../components/ui/info.vue'; import MkSwitch from '../../components/ui/switch.vue'; export default Vue.extend({ components: { MkButton, MkTextarea, MkSelect, MkInfo, MkSwitch, }, data() { return { script: '', selectedPluginId: null, faPlug, faSave, faTrashAlt, faFolderOpen, faDownload, faCog } }, computed: { selectedPlugin() { if (this.selectedPluginId == null) return null; return this.$store.state.deviceUser.plugins.find(x => x.id === this.selectedPluginId); }, }, methods: { async install() { let ast; try { ast = parse(this.script); } catch (e) { this.$root.dialog({ type: 'error', text: 'Syntax error :(' }); return; } const meta = AiScript.collectMetadata(ast); if (meta == null) { this.$root.dialog({ type: 'error', text: 'No metadata found :(' }); return; } const data = meta.get(null); if (data == null) { this.$root.dialog({ type: 'error', text: 'No metadata found :(' }); return; } const { name, version, author, description, permissions, config } = data; if (name == null || version == null || author == null) { this.$root.dialog({ type: 'error', text: 'Required property not found :(' }); return; } const token = permissions == null || permissions.length === 0 ? null : await new Promise(async (res, rej) => { this.$root.new(await import('../../components/token-generate-window.vue').then(m => m.default), { title: this.$t('tokenRequested'), information: this.$t('pluginTokenRequestedDescription'), initialName: name, initialPermissions: permissions }).$on('ok', async ({ name, permissions }) => { const { token } = await this.$root.api('miauth/gen-token', { session: null, name: name, permission: permissions, }); res(token); }); }); this.$store.commit('deviceUser/installPlugin', { id: uuid(), meta: { name, version, author, description, permissions, config }, token, ast: serialize(ast) }); this.$root.dialog({ type: 'success', iconOnly: true, autoClose: true }); this.$nextTick(() => { location.reload(); }); }, uninstall() { this.$store.commit('deviceUser/uninstallPlugin', this.selectedPluginId); this.$root.dialog({ type: 'success', iconOnly: true, autoClose: true }); this.$nextTick(() => { location.reload(); }); }, // TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする async config() { const config = this.selectedPlugin.config; for (const key in this.selectedPlugin.configData) { config[key].default = this.selectedPlugin.configData[key]; } const { canceled, result } = await this.$root.form(this.selectedPlugin.name, config); if (canceled) return; this.$store.commit('deviceUser/configPlugin', { id: this.selectedPluginId, config: result }); this.$nextTick(() => { location.reload(); }); }, changeActive(plugin, active) { this.$store.commit('deviceUser/changePluginActive', { id: plugin.id, active: active }); this.$nextTick(() => { location.reload(); }); } }, }); </script> <style lang="scss" scoped> </style>