Skip to content

Commit

Permalink
feat(client): support context menu API
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed May 29, 2023
1 parent 0f8ccd4 commit b9b0f6d
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 15 deletions.
2 changes: 1 addition & 1 deletion packages/client/app/layout/layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<template #right>
<slot name="menu">
<template v-if="typeof menu === 'string'">
<layout-menu-item v-for="item in ctx.internal.menus[menu]" v-bind="{ ...item, ...ctx.internal.actions[item.id]?.[0] }"></layout-menu-item>
<layout-menu-item v-for="item in ctx.internal.menus[menu]" v-bind="{ ...item, ...ctx.internal.actions[item.id] }"></layout-menu-item>
</template>
<template v-else>
<layout-menu-item v-for="item in menu" v-bind="item" />
Expand Down
10 changes: 9 additions & 1 deletion packages/client/app/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ $white: #ffffff;

--k-card-bg: var(--bg0);
--k-card-border: var(--k-card-bg);
--k-card-shadow: 0 4px 8px -4px rgb(0 0 0 / 15%);
--k-menu-bg: var(--bg0);
--k-main-bg: var(--bg0);
--k-side-bg: var(--bg1);
--k-page-bg: var(--bg0);
Expand All @@ -36,7 +38,6 @@ $white: #ffffff;
--k-text-light: var(--fg3);
--k-text-active: var(--k-color-primary);

--k-card-shadow: 0 4px 8px -4px rgb(0 0 0 / 15%);
--loading-mask-bg: #f2f3f5bf;

@mixin apply-color($name, $base) {
Expand Down Expand Up @@ -67,6 +68,8 @@ html.dark, .theme-root.dark {

--k-card-bg: var(--bg2);
--k-card-border: var(--k-color-divider);
--k-card-shadow: 0 4px 8px -4px rgb(0 0 0 / 15%);
--k-menu-bg: var(--bg0);
--k-main-bg: var(--bg3);
--k-side-bg: var(--bg2);
--k-page-bg: var(--bg2);
Expand Down Expand Up @@ -107,6 +110,9 @@ html.dark, .theme-root.dark {
--font-family: PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
--font-family-code: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;

// layout
--k-menu-padding-vertical: 0.375rem;

// fallback
--k-color-border-dark: var(--k-color-border);
--k-color-border-light: var(--k-color-divider);
Expand All @@ -116,6 +122,8 @@ html.dark, .theme-root.dark {
--k-fill-normal: var(--k-color-primary);
--k-fill-disabled: var(--k-color-disabled);

--k-menu-shadow: var(--k-card-shadow);

// compatibility
--border: var(--k-color-border);
--divider: var(--k-color-divider);
Expand Down
2 changes: 1 addition & 1 deletion packages/client/app/styles/layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
}
}

.k-menu-item {
.k-tab-menu-item {
display: block;
position: relative;
cursor: pointer;
Expand Down
2 changes: 2 additions & 0 deletions packages/client/app/theme/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</router-view>
<div class="loading" v-else v-loading="true" element-loading-text="正在加载数据……"></div>
<status-bar></status-bar>
<menu-list></menu-list>
<k-slot name="global"></k-slot>
</template>

Expand All @@ -17,6 +18,7 @@ import { computed } from 'vue'
import { useRoute } from 'vue-router'
import ActivityBar from './activity/index.vue'
import StatusBar from './status.vue'
import MenuList from './menu/index.vue'
const route = useRoute()
Expand Down
30 changes: 30 additions & 0 deletions packages/client/app/theme/menu/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<template>
<template v-for="{ id, styles } of ctx.internal.activeMenus" :key="id">
<k-menu
:id="id"
:style="styles"
:ref="el => elements[id] = (el as ComponentPublicInstance)"
></k-menu>
</template>
</template>

<script lang="ts" setup>
import { Dict, useContext } from '@koishijs/client'
import { ComponentPublicInstance, shallowReactive } from 'vue'
import { useEventListener } from '@vueuse/core'
import KMenu from './menu.vue'
const ctx = useContext()
const elements = shallowReactive<Dict<ComponentPublicInstance>>({})
useEventListener('click', () => {
ctx.internal.activeMenus.splice(0)
})
useEventListener('contextmenu', () => {
ctx.internal.activeMenus.splice(0)
})
</script>
60 changes: 60 additions & 0 deletions packages/client/app/theme/menu/menu-item.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<template>
<div
class="k-menu-item"
v-if="forced || !disabled"
:class="{ disabled }"
@click.prevent="action?.action(ctx.internal.createScope())"
>
{{ toValue(label) }}
</div>
</template>

<script lang="ts" setup>
import { MaybeGetter, MenuItem, useContext } from '@koishijs/client'
import { computed } from 'vue'
const props = defineProps<MenuItem & { prefix: string }>()
const ctx = useContext()
const forced = computed(() => props.id.startsWith('!'))
const action = computed(() => {
let id = props.id.replace(/^!/, '')
if (id.startsWith('.')) id = props.prefix + id
return ctx.internal.actions[id]
})
const disabled = computed(() => {
if (!action.value) return true
if (!action.value.disabled) return false
return toValue(action.value.disabled)
})
function toValue<T>(getter: MaybeGetter<T>): T {
if (typeof getter !== 'function') return getter
return (getter as any)(ctx.internal.createScope())
}
</script>

<style lang="scss">
.k-menu-item {
user-select: none;
padding: 0.25rem 1.5rem;
cursor: pointer;
transition: var(--color-transition);
&.disabled {
color: var(--k-text-light);
pointer-events: none;
}
&:not(.disabled):hover {
background-color: var(--k-hover-bg);
}
}
</style>
50 changes: 50 additions & 0 deletions packages/client/app/theme/menu/menu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<template>
<div ref="menu" class="k-menu">
<template v-for="item of ctx.internal.menus[id]">
<div class="k-menu-separator" v-if="item.id === '@separator'"></div>
<menu-item v-else v-bind="{ prefix: id, ...item }"></menu-item>
</template>
</div>
</template>

<script lang="ts" setup>
import { useContext } from '@koishijs/client'
import MenuItem from './menu-item.vue'
defineProps<{
id: string
}>()
const ctx = useContext()
</script>

<style lang="scss">
.k-menu {
position: fixed;
z-index: 1000;
min-width: 12rem;
padding: var(--k-menu-padding-vertical) 0;
border-radius: 6px;
background-color: var(--k-menu-bg);
box-shadow: var(--k-menu-shadow);
transition: var(--color-transition);
font-size: 14px;
.k-menu-separator {
margin: var(--k-menu-padding-vertical) 0;
border-top: 1px solid var(--k-color-border);
&:first-child, &:last-child {
display: none;
}
& + & {
display: none;
}
}
}
</style>
2 changes: 1 addition & 1 deletion packages/client/client/components/layout/tab-item.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="k-tab-item k-menu-item"
<div class="k-tab-item k-tab-menu-item"
:class="{ active: label === modelValue }"
@click="$emit('update:modelValue', label)">
<slot>{{ label }}</slot>
Expand Down
20 changes: 15 additions & 5 deletions packages/client/client/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ export type LegacyMenuItem = Partial<ActionOptions> & Omit<MenuItem, 'id'>

export interface MenuItem {
id: string
label?: MaybeGetter<string>
type?: MaybeGetter<string>
label: MaybeGetter<string>
icon?: MaybeGetter<string>
order?: number
}
Expand Down Expand Up @@ -86,10 +86,11 @@ type Flatten<S extends {}> = Intersect<{
class Internal {
scope = shallowReactive<Store<ActionContext>>({})
menus = reactive<Dict<MenuItem[]>>({})
actions = reactive<Dict<ActionOptions[]>>({})
actions = reactive<Dict<ActionOptions>>({})
views = reactive<Dict<SlotOptions[]>>({})
themes = reactive<Dict<ThemeOptions>>({})
settings = reactive<Dict<SettingOptions[]>>({})
activeMenus = reactive<{ id: string; styles: Partial<CSSStyleDeclaration> }[]>([])

createScope(prefix = '') {
return new Proxy({}, {
Expand All @@ -106,6 +107,16 @@ class Internal {
}
}

export function useMenu<K extends keyof ActionContext>(id: K) {
const ctx = useContext()
return (event: MouseEvent, value: MaybeRefOrGetter<ActionContext[K]>) => {
ctx.define(id, value)
event.preventDefault()
const { clientX, clientY } = event
ctx.internal.activeMenus.splice(0, Infinity, { id, styles: { left: clientX + 'px', top: clientY + 'px' } })
}
}

export class Context extends cordis.Context {
app: App
internal = new Internal()
Expand Down Expand Up @@ -163,10 +174,9 @@ export class Context extends cordis.Context {
}

action(id: string, options: ActionOptions) {
const list = this.internal.actions[id] ||= []
markRaw(options)
list.push(options)
return this.scope.collect('actions', () => remove(list, options))
this.internal.actions[id] = options
return this.scope.collect('actions', () => delete this.internal.actions[id])
}

menu(id: string, items: MenuItem[]) {
Expand Down
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@koishijs/client",
"description": "Koishi Console Client",
"version": "5.10.0",
"version": "5.10.1",
"main": "client/index.ts",
"files": [
"app",
Expand Down
10 changes: 6 additions & 4 deletions plugins/config/client/components/plugin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,18 @@ const ctx = useContext()
ctx.action('config.save', {
disabled: () => !name.value,
action: async () => {
await execute(props.current.disabled ? 'unload' : 'reload')
message.success(props.current.disabled ? '配置已保存。' : '配置已重载。')
const { disabled } = props.current
await execute(disabled ? 'unload' : 'reload')
message.success(disabled ? '配置已保存。' : '配置已重载。')
},
})
ctx.action('config.toggle', {
disabled: () => !name.value || coreDeps.includes(name.value),
action: async () => {
await execute(props.current.disabled ? 'reload' : 'unload')
message.success(props.current.disabled ? '插件已启用。' : '插件已停用。')
const { disabled } = props.current
await execute(disabled ? 'reload' : 'unload')
message.success(disabled ? '插件已启用。' : '插件已停用。')
},
})
Expand Down
2 changes: 1 addition & 1 deletion plugins/sandbox/client/layout.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<k-layout class="page-sandbox">
<template #left>
<div class="card-header k-menu-item" @click="createUser">添加用户</div>
<div class="card-header k-tab-menu-item" @click="createUser">添加用户</div>
<div class="user-container">
<el-scrollbar>
<k-tab-group :data="userMap" v-model="config.user" #="{ name }">
Expand Down

0 comments on commit b9b0f6d

Please sign in to comment.