diff --git a/.gitignore b/.gitignore index 9ab26495a5..91690b4d2f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ temp /utsuho /build/*.js -/packages/plugin-webui todo.md yarn.lock diff --git a/packages/koishi-core/src/context.ts b/packages/koishi-core/src/context.ts index 27e1c48c6d..170f21f8ca 100644 --- a/packages/koishi-core/src/context.ts +++ b/packages/koishi-core/src/context.ts @@ -182,6 +182,7 @@ export class Context { } this.state.children.push(plugin) + this.emit('registry', this.app.registry) return this } @@ -196,6 +197,7 @@ export class Context { this.app.registry.delete(plugin) const index = state.parent.children.indexOf(plugin) if (index >= 0) state.parent.children.splice(index, 1) + this.emit('registry', this.app.registry) } async parallel(name: K, ...args: Parameters): Promise>[]> @@ -471,6 +473,7 @@ export interface EventMap extends SessionEventMap { 'before-command'(argv: Argv): Awaitable 'command'(argv: Argv): Awaitable 'middleware'(session: Session): void + 'registry'(registry: Map): void 'before-connect'(): Awaitable 'connect'(): void 'before-disconnect'(): Awaitable diff --git a/packages/plugin-webui/README.md b/packages/plugin-webui/README.md new file mode 100644 index 0000000000..47c68837ab --- /dev/null +++ b/packages/plugin-webui/README.md @@ -0,0 +1 @@ +plugin-webui diff --git a/packages/plugin-webui/client/app.vue b/packages/plugin-webui/client/app.vue new file mode 100644 index 0000000000..619466ce97 --- /dev/null +++ b/packages/plugin-webui/client/app.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/packages/plugin-webui/client/assets/koishi.png b/packages/plugin-webui/client/assets/koishi.png new file mode 100644 index 0000000000..64abcf09db Binary files /dev/null and b/packages/plugin-webui/client/assets/koishi.png differ diff --git a/packages/plugin-webui/client/components/plugin-view.vue b/packages/plugin-webui/client/components/plugin-view.vue new file mode 100644 index 0000000000..5becdce9a1 --- /dev/null +++ b/packages/plugin-webui/client/components/plugin-view.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/plugin-webui/client/index.html b/packages/plugin-webui/client/index.html new file mode 100644 index 0000000000..fcd54cb7b0 --- /dev/null +++ b/packages/plugin-webui/client/index.html @@ -0,0 +1,13 @@ + + + + + + + Koishi 控制台 + + +
+ + + diff --git a/packages/plugin-webui/client/main.ts b/packages/plugin-webui/client/main.ts new file mode 100644 index 0000000000..8aef29becd --- /dev/null +++ b/packages/plugin-webui/client/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './app.vue' + +createApp(App).mount('#app') diff --git a/packages/plugin-webui/client/tsconfig.json b/packages/plugin-webui/client/tsconfig.json new file mode 100644 index 0000000000..8b4835b4ac --- /dev/null +++ b/packages/plugin-webui/client/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../tsconfig.base", + "compilerOptions": { + "rootDir": ".", + "noEmit": true, + "target": "es2020", + "module": "esnext", + "emitDeclarationOnly": false, + "types": [ + "vite/client", + ], + }, + "include": [ + ".", + ], +} \ No newline at end of file diff --git a/packages/plugin-webui/package.json b/packages/plugin-webui/package.json new file mode 100644 index 0000000000..d1aac14f06 --- /dev/null +++ b/packages/plugin-webui/package.json @@ -0,0 +1,38 @@ +{ + "name": "koishi-plugin-webui", + "version": "0.1.2", + "description": "Web UI for Koishi", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "author": "Shigma <1700011071@pku.edu.cn>", + "license": "MIT", + "files": [ + "dist", + "client" + ], + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/koishijs/koishi.git" + }, + "bugs": { + "url": "https://github.com/koishijs/koishi/issues" + }, + "homepage": "https://github.com/koishijs/koishi#readme", + "peerDependencies": { + "koishi-core": "^3.0.0" + }, + "dependencies": { + "vue": "^3.0.6" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^1.1.4", + "@vue/compiler-sfc": "^3.0.6", + "sass": "^1.32.8", + "vite": "^2.0.3" + } +} diff --git a/packages/plugin-webui/server/index.ts b/packages/plugin-webui/server/index.ts new file mode 100644 index 0000000000..f1b3292bdc --- /dev/null +++ b/packages/plugin-webui/server/index.ts @@ -0,0 +1,67 @@ +import { Context, Plugin } from 'koishi-core' +import { assertProperty } from 'koishi-utils' +import { resolve } from 'path' +import { createServer, ViteDevServer } from 'vite' +import vuePlugin from '@vitejs/plugin-vue' + +declare module 'koishi-core' { + interface App { + vite: ViteDevServer + } +} + +export interface Config { + port?: number + server?: string +} + +export interface PluginData { + name: string + sideEffect: boolean + children: PluginData[] +} + +export const name = 'webui' + +export function apply(ctx: Context, config: Config = {}) { + const koishiPort = assertProperty(ctx.app.options, 'port') + const { port = 8080, server = `http://localhost:${koishiPort}` } = config + + ctx.on('connect', async () => { + const vite = await createServer({ + root: resolve(__dirname, '../client'), + plugins: [vuePlugin()], + define: { + KOISHI_SERVER: JSON.stringify(server), + }, + }) + + await vite.listen(port) + ctx.app.vite = vite + }) + + function traverse(plugin: Plugin): PluginData[] { + const state = ctx.app.registry.get(plugin) + const children = state.children.flatMap(traverse, 1) + if (typeof plugin === 'function' || !plugin?.name) return children + const { name } = plugin, { sideEffect } = state + return [{ name, sideEffect, children }] + } + + ctx.router.get('/plugins', (ctx) => { + ctx.set('access-control-allow-origin', '*') + ctx.body = traverse(null) + }) + + ctx.on('registry', () => { + ctx.app.vite?.ws.send({ + type: 'custom', + event: 'registry-update', + data: traverse(null), + }) + }) + + ctx.before('disconnect', async () => { + await ctx.app.vite?.close() + }) +} diff --git a/packages/plugin-webui/tsconfig.json b/packages/plugin-webui/tsconfig.json new file mode 100644 index 0000000000..8d4cc75f5b --- /dev/null +++ b/packages/plugin-webui/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "rootDir": "server", + "outDir": "dist", + }, + "include": [ + "server" + ], +} \ No newline at end of file