From 9b73e897df134ba57d4ac4d0e6e6924f8ddbc23d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 12 Jul 2020 00:38:55 +0900 Subject: Plugin system (#6479) * wip * wip * wip * wip * Update store.ts --- src/client/components/note.vue | 15 +++- src/client/components/post-form.vue | 21 ++++- src/client/components/user-menu.vue | 12 ++- src/client/init.ts | 31 +++++++ src/client/pages/preferences/index.vue | 6 +- src/client/pages/preferences/plugins.vue | 134 ++++++++++++++++++++++++++++++ src/client/pages/scratchpad.vue | 2 +- src/client/scripts/aiscript/api.ts | 57 +++++++++++++ src/client/scripts/create-aiscript-env.ts | 42 ---------- src/client/scripts/hpml/evaluator.ts | 2 +- src/client/store.ts | 59 ++++++++++++- 11 files changed, 327 insertions(+), 54 deletions(-) create mode 100644 src/client/pages/preferences/plugins.vue create mode 100644 src/client/scripts/aiscript/api.ts delete mode 100644 src/client/scripts/create-aiscript-env.ts (limited to 'src/client') diff --git a/src/client/components/note.vue b/src/client/components/note.vue index badb9f12f3..63a803c7f4 100644 --- a/src/client/components/note.vue +++ b/src/client/components/note.vue @@ -89,7 +89,7 @@ diff --git a/src/client/components/user-menu.vue b/src/client/components/user-menu.vue index 25937fb3c0..cbfa7b346d 100644 --- a/src/client/components/user-menu.vue +++ b/src/client/components/user-menu.vue @@ -4,7 +4,7 @@ + + diff --git a/src/client/pages/scratchpad.vue b/src/client/pages/scratchpad.vue index 81d4e60459..025505295b 100644 --- a/src/client/pages/scratchpad.vue +++ b/src/client/pages/scratchpad.vue @@ -30,7 +30,7 @@ import PrismEditor from 'vue-prism-editor'; import { AiScript, parse, utils, values } from '@syuilo/aiscript'; import MkContainer from '../components/ui/container.vue'; import MkButton from '../components/ui/button.vue'; -import { createAiScriptEnv } from '../scripts/create-aiscript-env'; +import { createAiScriptEnv } from '../scripts/aiscript/api'; export default Vue.extend({ metaInfo() { diff --git a/src/client/scripts/aiscript/api.ts b/src/client/scripts/aiscript/api.ts new file mode 100644 index 0000000000..29baa25b1a --- /dev/null +++ b/src/client/scripts/aiscript/api.ts @@ -0,0 +1,57 @@ +import { utils, values } from '@syuilo/aiscript'; + +export function createAiScriptEnv(vm, opts) { + let apiRequests = 0; + return { + USER_ID: vm.$store.getters.isSignedIn ? values.STR(vm.$store.state.i.id) : values.NULL, + USER_NAME: vm.$store.getters.isSignedIn ? values.STR(vm.$store.state.i.name) : values.NULL, + USER_USERNAME: vm.$store.getters.isSignedIn ? values.STR(vm.$store.state.i.username) : values.NULL, + 'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => { + await vm.$root.dialog({ + type: type ? type.value : 'info', + title: title.value, + text: text.value, + }); + }), + 'Mk:confirm': values.FN_NATIVE(async ([title, text]) => { + const confirm = await vm.$root.dialog({ + type: 'warning', + showCancelButton: true, + title: title.value, + text: text.value, + }); + return confirm.canceled ? values.FALSE : values.TRUE; + }), + 'Mk:api': values.FN_NATIVE(async ([ep, param, token]) => { + if (token) utils.assertString(token); + apiRequests++; + if (apiRequests > 16) return values.NULL; + const res = await vm.$root.api(ep.value, utils.valToJs(param), token ? token.value : null); + return utils.jsToVal(res); + }), + 'Mk:save': values.FN_NATIVE(([key, value]) => { + utils.assertString(key); + localStorage.setItem('aiscript:' + opts.storageKey + ':' + key.value, JSON.stringify(utils.valToJs(value))); + return values.NULL; + }), + 'Mk:load': values.FN_NATIVE(([key]) => { + utils.assertString(key); + return utils.jsToVal(JSON.parse(localStorage.getItem('aiscript:' + opts.storageKey + ':' + key.value))); + }), + }; +} + +export function createPluginEnv(vm, opts) { + return { + ...createAiScriptEnv(vm, opts), + 'Mk:register_post_form_action': values.FN_NATIVE(([title, handler]) => { + vm.$store.commit('registerPostFormAction', { pluginId: opts.plugin.id, title: title.value, handler }); + }), + 'Mk:register_user_action': values.FN_NATIVE(([title, handler]) => { + vm.$store.commit('registerUserAction', { pluginId: opts.plugin.id, title: title.value, handler }); + }), + 'Mk:register_note_action': values.FN_NATIVE(([title, handler]) => { + vm.$store.commit('registerNoteAction', { pluginId: opts.plugin.id, title: title.value, handler }); + }), + }; +} diff --git a/src/client/scripts/create-aiscript-env.ts b/src/client/scripts/create-aiscript-env.ts deleted file mode 100644 index dfa38be385..0000000000 --- a/src/client/scripts/create-aiscript-env.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { utils, values } from '@syuilo/aiscript'; - -export function createAiScriptEnv(vm, opts) { - let apiRequests = 0; - return { - USER_ID: vm.$store.getters.isSignedIn ? values.STR(vm.$store.state.i.id) : values.NULL, - USER_NAME: vm.$store.getters.isSignedIn ? values.STR(vm.$store.state.i.name) : values.NULL, - USER_USERNAME: vm.$store.getters.isSignedIn ? values.STR(vm.$store.state.i.username) : values.NULL, - 'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => { - await vm.$root.dialog({ - type: type ? type.value : 'info', - title: title.value, - text: text.value, - }); - }), - 'Mk:confirm': values.FN_NATIVE(async ([title, text]) => { - const confirm = await vm.$root.dialog({ - type: 'warning', - showCancelButton: true, - title: title.value, - text: text.value, - }); - return confirm.canceled ? values.FALSE : values.TRUE; - }), - 'Mk:api': values.FN_NATIVE(async ([ep, param, token]) => { - if (token) utils.assertString(token); - apiRequests++; - if (apiRequests > 16) return values.NULL; - const res = await vm.$root.api(ep.value, utils.valToJs(param), token ? token.value : null); - return utils.jsToVal(res); - }), - 'Mk:save': values.FN_NATIVE(([key, value]) => { - utils.assertString(key); - localStorage.setItem('aiscript:' + opts.storageKey + ':' + key.value, JSON.stringify(utils.valToJs(value))); - return values.NULL; - }), - 'Mk:load': values.FN_NATIVE(([key]) => { - utils.assertString(key); - return utils.jsToVal(JSON.parse(localStorage.getItem('aiscript:' + opts.storageKey + ':' + key.value))); - }), - }; -} diff --git a/src/client/scripts/hpml/evaluator.ts b/src/client/scripts/hpml/evaluator.ts index f1fcdde0e5..a056884368 100644 --- a/src/client/scripts/hpml/evaluator.ts +++ b/src/client/scripts/hpml/evaluator.ts @@ -3,7 +3,7 @@ import * as seedrandom from 'seedrandom'; import { Variable, PageVar, envVarsDef, funcDefs, Block, isFnBlock } from '.'; import { version } from '../../config'; import { AiScript, utils, values } from '@syuilo/aiscript'; -import { createAiScriptEnv } from '../create-aiscript-env'; +import { createAiScriptEnv } from '../aiscript/api'; import { collectPageVars } from '../collect-page-vars'; import { initLib } from './lib'; diff --git a/src/client/store.ts b/src/client/store.ts index eaa8ea6a69..31febc782b 100644 --- a/src/client/store.ts +++ b/src/client/store.ts @@ -3,6 +3,7 @@ import createPersistedState from 'vuex-persistedstate'; import * as nestedProperty from 'nested-property'; import { faTerminal, faHashtag, faBroadcastTower, faFireAlt, faSearch, faStar, faAt, faListUl, faUserClock, faUsers, faCloud, faGamepad, faFileAlt, faSatellite, faDoorClosed, faColumns } from '@fortawesome/free-solid-svg-icons'; import { faBell, faEnvelope, faComments } from '@fortawesome/free-regular-svg-icons'; +import { AiScript, utils, values } from '@syuilo/aiscript'; import { apiUrl, deckmode } from './config'; import { erase } from '../prelude/array'; @@ -43,6 +44,7 @@ export const defaultDeviceUserSettings = { columns: [], layout: [], }, + plugins: [], }; export const defaultDeviceSettings = { @@ -93,7 +95,13 @@ export default () => new Vuex.Store({ state: { i: null, pendingApiRequestsCount: 0, - spinner: null + spinner: null, + + // Plugin + pluginContexts: new Map(), + postFormActions: [], + userActions: [], + noteActions: [], }, getters: { @@ -224,8 +232,38 @@ export default () => new Vuex.Store({ state.i = x; }, - updateIKeyValue(state, x) { - state.i[x.key] = x.value; + updateIKeyValue(state, { key, value }) { + state.i[key] = value; + }, + + initPlugin(state, { plugin, aiscript }) { + state.pluginContexts.set(plugin.id, aiscript); + }, + + registerPostFormAction(state, { pluginId, title, handler }) { + state.postFormActions.push({ + title, handler: (form, update) => { + state.pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(form), values.FN_NATIVE(([key, value]) => { + update(key.value, value.value); + })]); + } + }); + }, + + registerUserAction(state, { pluginId, title, handler }) { + state.userActions.push({ + title, handler: (user) => { + state.pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(user)]); + } + }); + }, + + registerNoteAction(state, { pluginId, title, handler }) { + state.noteActions.push({ + title, handler: (note) => { + state.pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(note)]); + } + }); }, }, @@ -546,6 +584,21 @@ export default () => new Vuex.Store({ column = x; }, //#endregion + + installPlugin(state, { meta, ast }) { + state.plugins.push({ + id: meta.id, + name: meta.name, + version: meta.version, + author: meta.author, + description: meta.description, + ast: ast + }); + }, + + uninstallPlugin(state, id) { + state.plugins = state.plugins.filter(x => x.id != id); + }, } }, -- cgit v1.2.3-freya