diff --git a/packages/cli/src/worker/daemon.ts b/packages/cli/src/worker/daemon.ts index d6ce9acae5..bd4ac40515 100644 --- a/packages/cli/src/worker/daemon.ts +++ b/packages/cli/src/worker/daemon.ts @@ -1,11 +1,5 @@ import { Context, noop } from 'koishi' -declare module 'koishi' { - interface EventMap { - 'exit'(signal: NodeJS.Signals): Promise - } -} - export interface Config { exitCommand?: boolean autoRestart?: boolean diff --git a/packages/cli/src/worker/index.ts b/packages/cli/src/worker/index.ts index 7ab0438498..eb1c5b0c9a 100644 --- a/packages/cli/src/worker/index.ts +++ b/packages/cli/src/worker/index.ts @@ -7,6 +7,11 @@ import Watcher from './watcher' export { Loader, Watcher } declare module 'koishi' { + interface EventMap { + 'exit'(signal: NodeJS.Signals): Promise + 'config'(): void + } + interface App { prologue: string[] watcher: Watcher diff --git a/packages/cli/src/worker/loader.ts b/packages/cli/src/worker/loader.ts index aad27dc55e..1ba6e4183a 100644 --- a/packages/cli/src/worker/loader.ts +++ b/packages/cli/src/worker/loader.ts @@ -82,6 +82,7 @@ export default class Loader extends ConfigLoader { // prevent hot reload when it's being written if (this.app.watcher) this.app.watcher.suspend = true super.writeConfig() + this.app.emit('config') } resolvePlugin(name: string) { diff --git a/packages/cli/src/worker/watcher.ts b/packages/cli/src/worker/watcher.ts index a96c440b03..9c6863c6c4 100644 --- a/packages/cli/src/worker/watcher.ts +++ b/packages/cli/src/worker/watcher.ts @@ -136,6 +136,7 @@ class Watcher { // check plugin changes this.triggerGroupReload(neo.plugins || {}, old.plugins || {}, this.ctx.loader.runtime) + this.ctx.emit('config') } private triggerGroupReload(neo: Dict, old: Dict, runtime: Plugin.Runtime) { diff --git a/packages/core/package.json b/packages/core/package.json index e75e44a807..8b2cd6d9df 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -36,7 +36,7 @@ }, "dependencies": { "@koishijs/utils": "^5.4.5", - "cordis": "^1.3.0", + "cordis": "^1.3.1", "fastest-levenshtein": "^1.0.12", "minato": "^1.1.0" } diff --git a/plugins/frontend/manager/client/index.ts b/plugins/frontend/manager/client/index.ts index f70318a507..81507e597d 100644 --- a/plugins/frontend/manager/client/index.ts +++ b/plugins/frontend/manager/client/index.ts @@ -31,12 +31,12 @@ export default (ctx: Context) => { }) ctx.addPage({ - path: '/settings/:name*', + path: '/plugins/:name*', name: '插件配置', icon: 'cog', order: 630, authority: 4, - fields: ['packages', 'services', 'dependencies'], + fields: ['config', 'packages', 'services', 'dependencies'], component: Settings, }) diff --git a/plugins/frontend/manager/client/settings/global.vue b/plugins/frontend/manager/client/settings/global.vue new file mode 100644 index 0000000000..6a11c0e3e6 --- /dev/null +++ b/plugins/frontend/manager/client/settings/global.vue @@ -0,0 +1,25 @@ + + + diff --git a/plugins/frontend/manager/client/settings/group.vue b/plugins/frontend/manager/client/settings/group.vue new file mode 100644 index 0000000000..70eb437c24 --- /dev/null +++ b/plugins/frontend/manager/client/settings/group.vue @@ -0,0 +1,17 @@ + + + diff --git a/plugins/frontend/manager/client/settings/index.vue b/plugins/frontend/manager/client/settings/index.vue index f47967f1ff..41e6621da0 100644 --- a/plugins/frontend/manager/client/settings/index.vue +++ b/plugins/frontend/manager/client/settings/index.vue @@ -1,19 +1,25 @@ diff --git a/plugins/frontend/manager/client/settings/select.vue b/plugins/frontend/manager/client/settings/select.vue index d0a9602d7c..72f59580f5 100644 --- a/plugins/frontend/manager/client/settings/select.vue +++ b/plugins/frontend/manager/client/settings/select.vue @@ -5,10 +5,15 @@ - + 全局设置 -
+ + {{ node.label }} + + @@ -39,7 +44,7 @@ import { ref, computed, onActivated, nextTick } from 'vue' import { store } from '@koishijs/client' -import { envMap } from './utils' +import { Tree, envMap, plugins } from './utils' import { config } from '../utils' const props = defineProps<{ @@ -53,6 +58,18 @@ const model = computed({ set: val => emits('update:modelValue', val), }) +function handleClick(tree: Tree) { + model.value = tree.path +} + +function getClass(tree: Tree) { + const words: string[] = [] + if (tree.children) words.push('is-group') + if (tree.disabled) words.push('is-disabled') + if (tree.path === model.value) words.push('is-active') + return words.join(' ') +} + const packages = computed(() => { return Object.fromEntries(Object.values(store.packages) .filter(data => data.shortname.includes(keyword.value)) @@ -71,6 +88,7 @@ onActivated(async () => { const container = root.value.$el await nextTick() const element = container.querySelector('.k-tab-item.active') as HTMLElement + if (!element) return root.value['setScrollTop'](element.offsetTop - (container.offsetHeight - element.offsetHeight) / 2) }) @@ -122,6 +140,43 @@ onActivated(async () => { .k-tab-item:hover i.remove { opacity: 0.4; } + + .el-tree { + margin-top: 0.5rem; + user-select: none; + } + + .el-tree-node__expand-icon { + margin-left: 8px; + } + + .el-tree-node.is-group { + > .el-tree-node__content { + font-weight: bold; + } + } + + .el-tree-node.is-disabled { + > .el-tree-node__content { + color: var(--fg3t); + } + } + + .el-tree-node.is-active { + > .el-tree-node__content { + background-color: var(--hover-bg); + color: var(--active); + } + } + + .el-tree-node__content { + line-height: 2.25rem; + height: 2.25rem; + } + + .el-tree-node__label { + font-size: 16px; + } } diff --git a/plugins/frontend/manager/client/settings/settings.vue b/plugins/frontend/manager/client/settings/settings.vue deleted file mode 100644 index b2c8689272..0000000000 --- a/plugins/frontend/manager/client/settings/settings.vue +++ /dev/null @@ -1,126 +0,0 @@ - - - - - diff --git a/plugins/frontend/manager/client/settings/utils.ts b/plugins/frontend/manager/client/settings/utils.ts index 16394c0c52..b5a3a7e57f 100644 --- a/plugins/frontend/manager/client/settings/utils.ts +++ b/plugins/frontend/manager/client/settings/utils.ts @@ -4,6 +4,7 @@ import { PackageJson } from '@koishijs/market' import { MarketProvider } from '@koishijs/plugin-manager' import { store } from '@koishijs/client' import { getMixedMeta } from '../utils' +import {} from '@koishijs/cli' interface DepInfo { name: string @@ -61,11 +62,6 @@ function getEnvInfo(name: string) { const data = getMixedMeta(name) const result: EnvInfo = { impl: [], using: {}, deps: {} } - // nested plugins - if (!data.root && data.id) { - result.invalid = true - } - // check implementations for (const name of getKeywords('impl', data)) { if (name === 'adapter') continue @@ -99,3 +95,52 @@ function getEnvInfo(name: string) { export const envMap = computed(() => { return Object.fromEntries(Object.keys(store.packages).map(name => [name, getEnvInfo(name)])) }) + +export interface Tree { + label: string + path: string + config?: any + disabled?: boolean + children?: Tree[] +} + +function getTree(prefix: string, plugins: any, map: Dict): Tree[] { + const trees: Tree[] = [] + for (const key in plugins) { + if (key.startsWith('$')) continue + const label = key.replace(/^~/, '') + const path = prefix + label + const config = plugins[key] + const node: Tree = { label, path, config } + if (key.startsWith('~')) { + node.disabled = true + } + if (key.startsWith('+')) { + node.label = '分组:' + label.slice(1) + node.children = getTree(path + '/', config, map) + } + map[path] = node + trees.push(node) + } + return trees +} + +export const plugins = computed(() => { + const map: Dict = { + '@global': { + label: '全局设置', + path: '@global', + config: store.config, + }, + '': { + label: '所有插件', + path: '', + config: store.config.plugins, + }, + } + map[''].children = getTree('', store.config.plugins, map) + return { + map, + data: [map['']], + } +}) diff --git a/plugins/frontend/manager/src/writer.ts b/plugins/frontend/manager/src/writer.ts index f087213ad1..2457afdde7 100644 --- a/plugins/frontend/manager/src/writer.ts +++ b/plugins/frontend/manager/src/writer.ts @@ -40,6 +40,8 @@ class ConfigWriter extends DataService { ctx.console.addListener('manager/bot-remove', (id) => { this.removeBot(id) }, { authority: 4 }) + + ctx.on('config', () => this.refresh()) } async get() { @@ -54,7 +56,7 @@ class ConfigWriter extends DataService { } private resolve(path: string) { - const segments = path.split('.') + const segments = path.split('/') let runtime = this.loader.runtime let name = segments.shift() while (segments.length) {