Skip to content

Commit

Permalink
feat(manager): support tree view for plugin groups
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Jun 6, 2022
1 parent cd512d3 commit d8584c3
Show file tree
Hide file tree
Showing 14 changed files with 299 additions and 153 deletions.
6 changes: 0 additions & 6 deletions packages/cli/src/worker/daemon.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { Context, noop } from 'koishi'

declare module 'koishi' {
interface EventMap {
'exit'(signal: NodeJS.Signals): Promise<void>
}
}

export interface Config {
exitCommand?: boolean
autoRestart?: boolean
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import Watcher from './watcher'
export { Loader, Watcher }

declare module 'koishi' {
interface EventMap {
'exit'(signal: NodeJS.Signals): Promise<void>
'config'(): void
}

interface App {
prologue: string[]
watcher: Watcher
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/worker/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export default class Loader extends ConfigLoader<App.Config> {
// 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) {
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/worker/watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
4 changes: 2 additions & 2 deletions plugins/frontend/manager/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})

Expand Down
25 changes: 25 additions & 0 deletions plugins/frontend/manager/client/settings/global.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<template>
<h1 class="config-header">
全局设置
<k-button solid @click="send('manager/app-reload', data.config)">应用配置</k-button>
</h1>
<k-form :schema="store.packages[''].schema" :initial="data.config" v-model="config"></k-form>
</template>

<script lang="ts" setup>
import { send, store, clone } from '@koishijs/client'
import { ref, watch } from 'vue'
import { Tree } from './utils'
const props = defineProps<{
data: Tree
}>()
const config = ref()
watch(() => props.data.config, (value) => {
config.value = clone(value)
}, { immediate: true })
</script>
17 changes: 17 additions & 0 deletions plugins/frontend/manager/client/settings/group.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<h1 class="config-header">
{{ data.label }}
</h1>
</template>

<script lang="ts" setup>
import { send, store, clone } from '@koishijs/client'
import { ref, watch } from 'vue'
import { Tree } from './utils'
const props = defineProps<{
data: Tree
}>()
</script>
24 changes: 16 additions & 8 deletions plugins/frontend/manager/client/settings/index.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
<template>
<k-card-aside class="page-settings">
<template #aside>
<plugin-select v-model="current"></plugin-select>
<plugin-select v-model="path"></plugin-select>
</template>
<plugin-settings :key="current" :current="current"></plugin-settings>
<k-content class="plugin-view">
<global-settings v-if="current.path === '@global'" :data="current"></global-settings>
<group-settings v-else-if="current.children" :data="current"></group-settings>
<plugin-settings v-else :current="current"></plugin-settings>
</k-content>
</k-card-aside>
</template>

<script setup lang="ts">
import { store } from '@koishijs/client'
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { plugins } from './utils'
import GlobalSettings from './global.vue'
import GroupSettings from './group.vue'
import PluginSelect from './select.vue'
import PluginSettings from './settings.vue'
import PluginSettings from './plugin.vue'
function join(source: string | string[]) {
return Array.isArray(source) ? source.join('/') : source || ''
Expand All @@ -22,17 +28,19 @@ function join(source: string | string[]) {
const route = useRoute()
const router = useRouter()
const current = computed<string>({
const path = computed<string>({
get() {
const name = join(route.params.name)
return store.packages[name] ? name : ''
return name in plugins.value.map ? name : '@global'
},
set(name) {
if (!store.packages[name]) name = ''
router.replace('/settings/' + name)
if (!(name in plugins.value.map)) name = '@global'
router.replace('/plugins/' + name)
},
})
const current = computed(() => plugins.value.map[path.value])
</script>

<style lang="scss">
Expand Down
119 changes: 119 additions & 0 deletions plugins/frontend/manager/client/settings/plugin.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<template>
<h1 class="config-header">
{{ data.shortname }}
<span class="version">({{ data.workspace ? '工作区' : data.version }})</span>
<template v-if="data.id">
<k-button solid type="error" @click="execute('unload')">停用插件</k-button>
<k-button solid :disabled="env.invalid" @click="execute('reload')">重载配置</k-button>
</template>
<template v-else>
<k-button solid :disabled="env.invalid" @click="execute('reload')">启用插件</k-button>
<k-button solid @click="execute('unload')">保存配置</k-button>
</template>
</h1>

<!-- latest -->
<k-comment v-if="hasUpdate">
当前的插件版本不是最新,<router-link to="/dependencies">点击前往依赖管理</router-link>。
</k-comment>

<!-- external -->
<k-comment type="warning" v-if="!data.workspace && !store.dependencies[name]">
尚未将当前插件列入依赖,<a @click="send('market/patch', name, data.version)">点击添加</a>。
</k-comment>

<!-- impl -->
<template v-for="name in env.impl" :key="name">
<k-comment v-if="name in store.services && !data.id" type="warning">
此插件将会提供 {{ name }} 服务,但此服务已被其他插件实现。
</k-comment>
<k-comment v-else :type="data.id ? 'success' : 'primary'">
此插件{{ data.id ? '提供了' : '将会提供' }} {{ name }} 服务。
</k-comment>
</template>

<!-- using -->
<k-comment
v-for="({ fulfilled, required, available, name }) in env.using" :key="name"
:type="fulfilled ? 'success' : required ? 'error' : 'primary'">
{{ required ? '必需' : '可选' }}服务:{{ name }}
{{ fulfilled ? '(已加载)' : '(未加载,启用下列任一插件可实现此服务)' }}
<template v-if="!fulfilled" #body>
<ul>
<li v-for="name in available">
<k-dep-link :name="name"></k-dep-link> (点击{{ name in store.packages ? '配置' : '添加' }})
</li>
</ul>
</template>
</k-comment>

<!-- dep -->
<k-comment
v-for="({ fulfilled, required, local, name }) in env.deps" :key="name"
:type="fulfilled ? 'success' : required ? 'error' : 'primary'">
{{ required ? '必需' : '可选' }}依赖:<k-dep-link :name="name"></k-dep-link>
({{ local ? `${fulfilled ? '已' : '未'}启用` : '未安装,点击添加' }})
</k-comment>

<!-- schema -->
<k-comment v-if="!data.schema" type="warning">
此插件未声明配置项,这可能并非预期行为{{ hint }}。
</k-comment>
<k-form :schema="data.schema" :initial="current.config" v-model="config">
<template #hint>{{ hint }}</template>
</k-form>
</template>

<script lang="ts" setup>
import { send, store, clone } from '@koishijs/client'
import { computed, ref, watch } from 'vue'
import { getMixedMeta } from '../utils'
import { envMap, Tree } from './utils'
const props = defineProps<{
current: Tree
}>()
const config = ref()
watch(() => props.current.config, (value) => {
config.value = clone(value)
}, { immediate: true })
const name = computed(() => {
const { label } = props.current
if (label.includes('/')) {
const [left, right] = label.split('/')
return `${left}/koishi-plugin-${right}`
}
return [`@koishijs/plugin-${label}`, `koishi-plugin-${label}`].find(name => name in store.packages)
})
const data = computed(() => getMixedMeta(name.value))
const env = computed(() => envMap.value[name.value])
const hint = computed(() => data.value.workspace ? ',请检查源代码' : ',请联系插件作者')
const hasUpdate = computed(() => {
if (!data.value.versions || data.value.workspace) return
return data.value.versions[0].version !== data.value.version
})
function execute(event: 'unload' | 'reload') {
send(`manager/plugin-${event}`, props.current.path, config.value)
}
</script>

<style lang="scss">
.plugin-view {
a {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
}
</style>
63 changes: 59 additions & 4 deletions plugins/frontend/manager/client/settings/select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
<k-icon name="search"></k-icon>
</el-input>
</div>
<k-tab-item class="k-tab-group-title" label="" v-model="model">
<k-tab-item class="k-tab-group-title" label="@global" v-model="model">
全局设置
</k-tab-item>
<div class="k-tab-group-title">
<el-tree :data="plugins.data" :expand-on-click-node="false" default-expand-all :props="{ class: getClass }"
@node-click="handleClick"
#="{ node }">
{{ node.label }}
</el-tree>
<!-- <div class="k-tab-group-title">
运行中的插件
</div>
<k-tab-group
Expand All @@ -31,15 +36,15 @@
:data="packages" v-model="model"
:filter="data => !data.id && data.name && (!config.showDepsOnly || store.dependencies[data.name])" #="data">
<span :class="{ readonly: isReadonly(data) }">{{ data.shortname }}</span>
</k-tab-group>
</k-tab-group> -->
</el-scrollbar>
</template>

<script lang="ts" setup>
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<{
Expand All @@ -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))
Expand All @@ -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)
})
Expand Down Expand Up @@ -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;
}
}
</style>
Loading

0 comments on commit d8584c3

Please sign in to comment.