Skip to content

Commit

Permalink
feat(commands): support updating display name and aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Mar 2, 2023
1 parent 042fd6b commit 5c5fd46
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 89 deletions.
74 changes: 71 additions & 3 deletions packages/commands/client/commands.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</template>

<template #menu>
<span class="menu-item" @click.stop.prevent="send('command/update', command.name, current)">
<span class="menu-item" @click.stop.prevent="updateConfig">
<k-icon class="menu-icon" name="check"></k-icon>
</span>
</template>
Expand Down Expand Up @@ -47,6 +47,34 @@
>前往本地化</router-link>
</div>

<div class="aliases">
<h2 class="k-schema-header">名称设置</h2>
<ul>
<li v-for="(item, index) in current.aliases" :key="item">
<span>{{ item }}</span>
<span class="button"
v-if="index > 0"
@click.stop.prevent="setDefault(index)"
>设置为默认</span>
<span class="button"
v-if="!command.initial.aliases.includes(item)"
@click.stop.prevent="deleteAlias(index)"
>删除别名</span>
</li>
<li>
<span class="button" @click.stop.prevent="dialog = true">添加别名</span>
</li>
</ul>
</div>

<el-dialog destroy-on-close v-model="dialog" title="编辑别名">
<el-input v-model="alias" @keydown.enter.stop.prevent="addAlias"></el-input>
<template #footer>
<el-button @click="dialog = false">取消</el-button>
<el-button type="primary" @click="addAlias">确定</el-button>
</template>
</el-dialog>

<k-form
:schema="schema.config"
:initial="command.override.config"
Expand All @@ -72,7 +100,7 @@

<script lang="ts" setup>
import { clone, Dict, Schema, send, store, valueMap } from '@koishijs/client'
import { clone, Dict, pick, Schema, send, store, valueMap } from '@koishijs/client'
import { useRoute, useRouter } from 'vue-router'
import { computed, ref, watch } from 'vue'
import { CommandData, CommandState } from '@koishijs/plugin-commands'
Expand All @@ -83,6 +111,8 @@ import { commands, createSchema } from './utils'
const route = useRoute()
const router = useRouter()
const dialog = ref(false)
const alias = ref('')
const tree = ref(null)
const keyword = ref('')
const current = ref<CommandState>()
Expand Down Expand Up @@ -145,7 +175,32 @@ function handleClick(data: CommandData) {
function handleDrop(source: Node, target: Node, position: 'before' | 'after' | 'inner', event: DragEvent) {
const parent = position === 'inner' ? target : target.parent
send('command/rename', source.data.name, (parent.data.name || '') + '/' + source.data.name)
send('command/teleport', source.data.name, parent.data.name)
}
function addAlias() {
if (alias.value && !current.value.aliases.includes(alias.value)) {
current.value.aliases.push(alias.value)
}
alias.value = ''
dialog.value = false
send('command/aliases', command.value.name, current.value.aliases)
}
function updateConfig() {
send('command/update', command.value.name, pick(current.value, ['config', 'options']))
}
function setDefault(index: number) {
const item = current.value.aliases[index]
current.value.aliases.splice(index, 1)
current.value.aliases.unshift(item)
send('command/aliases', command.value.name, current.value.aliases)
}
function deleteAlias(index: number) {
current.value.aliases.splice(index, 1)
send('command/aliases', command.value.name, current.value.aliases)
}
</script>
Expand Down Expand Up @@ -177,6 +232,19 @@ function handleDrop(source: Node, target: Node, position: 'before' | 'after' | '
gap: 0.5rem 1rem;
flex-wrap: wrap;
}
.aliases {
margin-bottom: 2rem;
* + .button {
margin-left: 0.5rem;
}
.button:hover {
cursor: pointer;
text-decoration: underline;
}
}
}
</style>
174 changes: 98 additions & 76 deletions packages/commands/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,15 @@ import CommandProvider from './service'

export * from './service'

interface Override {
interface Override extends Partial<CommandState> {
name?: string
aliases?: string[]
create?: boolean
options?: Dict<Argv.OptionDeclaration>
config?: Command.Config
}

const Override: Schema<Override> = Schema.object({
name: Schema.string(),
aliases: Schema.array(String),
create: Schema.boolean(),
aliases: Schema.any(),
options: Schema.dict(null),
config: Schema.any(),
})
Expand All @@ -26,7 +23,7 @@ export interface CommandState {
}

export interface Snapshot {
name: string
command: Command
parent: Command
initial: CommandState
override: CommandState
Expand All @@ -36,7 +33,7 @@ interface Config extends Override {}

const Config: Schema<string | Config, Config> = Schema.union([
Override,
Schema.transform(String, (name) => ({ name, alias: [] })),
Schema.transform(String, (name) => ({ name, aliases: [] })),
])

const logger = new Logger('commands')
Expand Down Expand Up @@ -71,104 +68,91 @@ export class CommandManager {

ctx.on('dispose', () => {
for (const key in this.snapshots) {
const { name, parent, initial, override } = this.snapshots[key]
const cmd = ctx.$commander.resolve(name)
cmd.config = initial.config
cmd._aliases = initial.aliases
Object.assign(cmd._options, initial.options)
const { command, parent, initial, override } = this.snapshots[key]
command.config = initial.config
command._aliases = initial.aliases
Object.assign(command._options, initial.options)
for (const alias of override.aliases) {
if (cmd._aliases.includes(alias)) continue
if (initial.aliases.includes(alias)) continue
ctx.$commander._commands.delete(alias)
}
this.teleport(cmd, parent)
this._teleport(command, parent)
}
}, true)

ctx.plugin(CommandProvider, this)
}

teleport(command: Command, parent: Command = null) {
if (command.parent === parent) return
if (command.parent) {
remove(command.parent.children, command)
}
command.parent = parent
parent?.children.push(command)
}

ensure(command: Command) {
ensure(name: string) {
const command = this.ctx.$commander.resolve(name)
return this.snapshots[command.name] ||= {
name: command.name,
command,
parent: command.parent,
initial: {
aliases: command._aliases,
options: command._options,
config: command.config,
},
} as Snapshot
override: {
aliases: command._aliases,
options: {},
config: {},
},
}
}

locate(command: Command, path: string, write = false) {
const capture = path.match(/.*(?=[./])/)
let name = path
if (capture) {
const parent = this.ctx.$commander.resolve(capture[0])
if (capture[0] && !parent) {
logger.warn('cannot find parent command', capture[0])
return
}
this.teleport(command, parent)
const rest = path.slice(capture[0].length)
name = rest[0] === '.' ? path : rest.slice(1)
_teleport(command: Command, parent: Command = null) {
if (command.parent === parent) return
if (command.parent) {
remove(command.parent.children, command)
}
command.parent = parent
parent?.children.push(command)
}

teleport(command: Command, name: string, write = false) {
const parent = this.ctx.$commander.resolve(name)
if (name && !parent) {
logger.warn('cannot find parent command', name)
return
}
command.displayName = name
this._teleport(command, parent)

if (write) {
this.config[command.name] ||= {}
this.config[command.name].name = path
this.config[command.name].name = `${command.parent?.name || ''}/${command.displayName}`
this.write(command)
}
}

write(command: Command) {
const snapshot = this.ensure(command)
const override = this.config[command.name]
if (override.config && !Object.keys(override.config).length) {
delete override.config
}
if (override.options && !Object.keys(override.options).length) {
delete override.options
}
if (override.aliases && !override.aliases.length) {
delete override.aliases
}
if (override.name) {
const initial = (snapshot.parent?.name || '') + '/' + command.name
if (override.name === initial || override.name === command.name) {
delete this.config[command.name].name
}
alias(command: Command, aliases: string[], write = false) {
const { initial, override } = this.snapshots[command.name]
command._aliases = override.aliases = aliases
for (const alias of aliases) {
this.ctx.$commander._commands.set(alias, command)
}
if (!Object.keys(override).length) {
delete this.config[command.name]

if (write) {
this.config[command.name] ||= {}
this.config[command.name].name = `${command.parent?.name || ''}/${command.displayName}`
this.config[command.name].aliases = aliases.filter((name) => {
return command.displayName !== name && !initial.aliases.includes(name)
})
this.write(command)
}
this.ctx.scope.update(this.config, false)
}

update(name: string, override: CommandState, write = false) {
// create snapshot for restoration
const command = this.ctx.$commander.resolve(name)
this.ensure(command)

// override command config
const { initial } = this.snapshots[name]
this.snapshots[name].override = override
update(command: Command, data: Pick<CommandState, 'config' | 'options'>, write = false) {
const { initial, override } = this.snapshots[command.name]
override.config = data.config || {}
override.options = data.options || {}
command.config = Object.assign({ ...initial.config }, override.config)
for (const key in override.options) {
const option = initial.options[key]
if (!option) continue
command._options[key] = Object.assign({ ...initial.options[key] }, override.options[key])
}

// update config
if (write) {
this.config[command.name] ||= {}
this.config[command.name].config = override.config
Expand All @@ -178,15 +162,53 @@ export class CommandManager {
}

accept(target: Command, override: Override) {
const { name, aliases = [], options = {}, config = {} } = override
const { options = {}, config = {} } = override

this.update(target.name, {
options,
config,
aliases,
})
// create snapshot for restoration
this.ensure(target.name)

// override config and options
this.update(target, { options, config })

// teleport to new parent
let name = override.name
if (name?.includes('/')) {
const [parent, child] = name.split('/')
name = child
this.teleport(target, parent)
}

if (name) this.locate(target, name)
// extend aliases and display name
const aliases = [...new Set([
...name ? [name] : [],
...target._aliases,
...override.aliases || [],
])]
this.alias(target, aliases)
}

write(command: Command) {
const snapshot = this.ensure(command.name)
const override = this.config[command.name]
if (override.config && !Object.keys(override.config).length) {
delete override.config
}
if (override.options && !Object.keys(override.options).length) {
delete override.options
}
if (override.aliases && !override.aliases.length) {
delete override.aliases
}
if (override.name) {
const initial = (snapshot.parent?.name || '') + '/' + command.name
if (override.name === initial || override.name === command.name) {
delete this.config[command.name].name
}
}
if (!Object.keys(override).length) {
delete this.config[command.name]
}
this.ctx.scope.update(this.config, false)
}
}

Expand Down
Loading

0 comments on commit 5c5fd46

Please sign in to comment.