feat(variables): add mode management and collection deletion
Core: - Add addMode, removeMode, renameMode, setDefaultMode to SceneGraph - Add editor actions with full undo: addMode, removeMode, renameMode, setDefaultMode, duplicateMode, setActiveMode Vue SDK: - Wire all mode and collection actions through SDK helpers - Add mode rename state to dialog composable - Remove HTMLInputElement casts from commit handlers UI: - Mode management lives in the table column headers (Figma-style): double-click to rename, right-click for context menu (rename, duplicate, set as default, delete), + button after last mode column - Collection ⋯ dropdown menu with rename and delete - Use shared menu UI styles throughout - i18n for en, ru, zh-CN
This commit is contained in:
parent
2147aeb327
commit
107e9f3d51
|
|
@ -1,3 +1,4 @@
|
|||
import { randomHex } from '#core/random'
|
||||
import type { Variable, VariableCollection, VariableType, VariableValue } from '#core/scene-graph'
|
||||
|
||||
import type { EditorContext } from './types'
|
||||
|
|
@ -156,6 +157,133 @@ export function createVariableActions(ctx: EditorContext) {
|
|||
ctx.requestRender()
|
||||
}
|
||||
|
||||
function addMode(collectionId: string, name?: string): string | undefined {
|
||||
const collection = ctx.graph.variableCollections.get(collectionId)
|
||||
if (!collection) return undefined
|
||||
const modeId = `mode:${randomHex(8)}`
|
||||
const modeName = name ?? `Mode ${collection.modes.length + 1}`
|
||||
ctx.graph.addMode(collectionId, modeId, modeName)
|
||||
ctx.undo.push({
|
||||
label: 'Add mode',
|
||||
forward: () => {
|
||||
ctx.graph.addMode(collectionId, modeId, modeName)
|
||||
ctx.requestRender()
|
||||
},
|
||||
inverse: () => {
|
||||
ctx.graph.removeMode(collectionId, modeId)
|
||||
ctx.requestRender()
|
||||
}
|
||||
})
|
||||
ctx.requestRender()
|
||||
return modeId
|
||||
}
|
||||
|
||||
function removeMode(collectionId: string, modeId: string) {
|
||||
const collection = ctx.graph.variableCollections.get(collectionId)
|
||||
if (!collection || collection.modes.length <= 1) return
|
||||
const modeIndex = collection.modes.findIndex((m) => m.modeId === modeId)
|
||||
const modeName = collection.modes[modeIndex]?.name ?? ''
|
||||
const wasDefault = collection.defaultModeId === modeId
|
||||
const valueSnapshots = new Map<string, VariableValue>()
|
||||
for (const varId of collection.variableIds) {
|
||||
const v = ctx.graph.variables.get(varId)
|
||||
if (v?.valuesByMode[modeId] !== undefined) {
|
||||
valueSnapshots.set(varId, structuredClone(v.valuesByMode[modeId]))
|
||||
}
|
||||
}
|
||||
ctx.graph.removeMode(collectionId, modeId)
|
||||
ctx.undo.push({
|
||||
label: 'Remove mode',
|
||||
forward: () => {
|
||||
ctx.graph.removeMode(collectionId, modeId)
|
||||
ctx.requestRender()
|
||||
},
|
||||
inverse: () => {
|
||||
ctx.graph.addMode(collectionId, modeId, modeName)
|
||||
const col = ctx.graph.variableCollections.get(collectionId)
|
||||
if (col && modeIndex !== -1) {
|
||||
const mode = col.modes.pop()
|
||||
if (mode) col.modes.splice(modeIndex, 0, mode)
|
||||
}
|
||||
for (const [varId, value] of valueSnapshots) {
|
||||
const v = ctx.graph.variables.get(varId)
|
||||
if (v) v.valuesByMode[modeId] = structuredClone(value)
|
||||
}
|
||||
if (wasDefault) ctx.graph.setDefaultMode(collectionId, modeId)
|
||||
ctx.requestRender()
|
||||
}
|
||||
})
|
||||
ctx.requestRender()
|
||||
}
|
||||
|
||||
function renameMode(collectionId: string, modeId: string, newName: string) {
|
||||
const collection = ctx.graph.variableCollections.get(collectionId)
|
||||
if (!collection) return
|
||||
const mode = collection.modes.find((m) => m.modeId === modeId)
|
||||
if (!mode) return
|
||||
const prevName = mode.name
|
||||
ctx.graph.renameMode(collectionId, modeId, newName)
|
||||
ctx.undo.push({
|
||||
label: 'Rename mode',
|
||||
forward: () => {
|
||||
ctx.graph.renameMode(collectionId, modeId, newName)
|
||||
ctx.requestRender()
|
||||
},
|
||||
inverse: () => {
|
||||
ctx.graph.renameMode(collectionId, modeId, prevName)
|
||||
ctx.requestRender()
|
||||
}
|
||||
})
|
||||
ctx.requestRender()
|
||||
}
|
||||
|
||||
function setDefaultMode(collectionId: string, modeId: string) {
|
||||
const collection = ctx.graph.variableCollections.get(collectionId)
|
||||
if (!collection) return
|
||||
const prevDefault = collection.defaultModeId
|
||||
ctx.graph.setDefaultMode(collectionId, modeId)
|
||||
ctx.undo.push({
|
||||
label: 'Set default mode',
|
||||
forward: () => {
|
||||
ctx.graph.setDefaultMode(collectionId, modeId)
|
||||
ctx.requestRender()
|
||||
},
|
||||
inverse: () => {
|
||||
ctx.graph.setDefaultMode(collectionId, prevDefault)
|
||||
ctx.requestRender()
|
||||
}
|
||||
})
|
||||
ctx.requestRender()
|
||||
}
|
||||
|
||||
function duplicateMode(collectionId: string, sourceModeId: string): string | undefined {
|
||||
const collection = ctx.graph.variableCollections.get(collectionId)
|
||||
if (!collection) return undefined
|
||||
const sourceMode = collection.modes.find((m) => m.modeId === sourceModeId)
|
||||
if (!sourceMode) return undefined
|
||||
const modeId = `mode:${randomHex(8)}`
|
||||
const modeName = `${sourceMode.name} copy`
|
||||
ctx.graph.addMode(collectionId, modeId, modeName, sourceModeId)
|
||||
ctx.undo.push({
|
||||
label: 'Duplicate mode',
|
||||
forward: () => {
|
||||
ctx.graph.addMode(collectionId, modeId, modeName, sourceModeId)
|
||||
ctx.requestRender()
|
||||
},
|
||||
inverse: () => {
|
||||
ctx.graph.removeMode(collectionId, modeId)
|
||||
ctx.requestRender()
|
||||
}
|
||||
})
|
||||
ctx.requestRender()
|
||||
return modeId
|
||||
}
|
||||
|
||||
function setActiveMode(collectionId: string, modeId: string) {
|
||||
ctx.graph.setActiveMode(collectionId, modeId)
|
||||
ctx.requestRender()
|
||||
}
|
||||
|
||||
function updateVariableValue(id: string, modeId: string, value: VariableValue) {
|
||||
const variable = ctx.graph.variables.get(id)
|
||||
if (!variable) return
|
||||
|
|
@ -194,6 +322,12 @@ export function createVariableActions(ctx: EditorContext) {
|
|||
addVariable,
|
||||
removeVariable,
|
||||
renameVariable,
|
||||
updateVariableValue
|
||||
updateVariableValue,
|
||||
addMode,
|
||||
removeMode,
|
||||
renameMode,
|
||||
setDefaultMode,
|
||||
duplicateMode,
|
||||
setActiveMode
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,6 +160,22 @@ export class SceneGraph {
|
|||
Variables.setActiveMode(this, collectionId, modeId)
|
||||
}
|
||||
|
||||
addMode(collectionId: string, modeId: string, name: string, sourceMode?: string): void {
|
||||
Variables.addMode(this, collectionId, modeId, name, sourceMode)
|
||||
}
|
||||
|
||||
removeMode(collectionId: string, modeId: string): void {
|
||||
Variables.removeMode(this, collectionId, modeId)
|
||||
}
|
||||
|
||||
renameMode(collectionId: string, modeId: string, name: string): void {
|
||||
Variables.renameMode(this, collectionId, modeId, name)
|
||||
}
|
||||
|
||||
setDefaultMode(collectionId: string, modeId: string): void {
|
||||
Variables.setDefaultMode(this, collectionId, modeId)
|
||||
}
|
||||
|
||||
resolveVariable(
|
||||
variableId: string,
|
||||
modeId?: string,
|
||||
|
|
|
|||
|
|
@ -111,6 +111,61 @@ export function setActiveMode(graph: SceneGraph, collectionId: string, modeId: s
|
|||
graph.activeMode.set(collectionId, modeId)
|
||||
}
|
||||
|
||||
export function addMode(
|
||||
graph: SceneGraph,
|
||||
collectionId: string,
|
||||
modeId: string,
|
||||
name: string,
|
||||
sourceMode?: string
|
||||
): void {
|
||||
const collection = graph.variableCollections.get(collectionId)
|
||||
if (!collection) return
|
||||
collection.modes.push({ modeId, name })
|
||||
const sourceModeId = sourceMode ?? collection.defaultModeId
|
||||
for (const varId of collection.variableIds) {
|
||||
const variable = graph.variables.get(varId)
|
||||
if (!variable) continue
|
||||
variable.valuesByMode[modeId] = structuredClone(
|
||||
variable.valuesByMode[sourceModeId] ?? Object.values(variable.valuesByMode)[0]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function removeMode(graph: SceneGraph, collectionId: string, modeId: string): void {
|
||||
const collection = graph.variableCollections.get(collectionId)
|
||||
if (!collection || collection.modes.length <= 1) return
|
||||
collection.modes = collection.modes.filter((m) => m.modeId !== modeId)
|
||||
if (collection.defaultModeId === modeId) {
|
||||
collection.defaultModeId = collection.modes[0].modeId
|
||||
}
|
||||
for (const varId of collection.variableIds) {
|
||||
const variable = graph.variables.get(varId)
|
||||
if (variable) delete variable.valuesByMode[modeId]
|
||||
}
|
||||
if (graph.activeMode.get(collectionId) === modeId) {
|
||||
graph.activeMode.set(collectionId, collection.defaultModeId)
|
||||
}
|
||||
}
|
||||
|
||||
export function renameMode(
|
||||
graph: SceneGraph,
|
||||
collectionId: string,
|
||||
modeId: string,
|
||||
name: string
|
||||
): void {
|
||||
const collection = graph.variableCollections.get(collectionId)
|
||||
if (!collection) return
|
||||
const mode = collection.modes.find((m) => m.modeId === modeId)
|
||||
if (mode) mode.name = name
|
||||
}
|
||||
|
||||
export function setDefaultMode(graph: SceneGraph, collectionId: string, modeId: string): void {
|
||||
const collection = graph.variableCollections.get(collectionId)
|
||||
if (!collection) return
|
||||
if (!collection.modes.some((m) => m.modeId === modeId)) return
|
||||
collection.defaultModeId = modeId
|
||||
}
|
||||
|
||||
export function resolveVariable(
|
||||
graph: SceneGraph,
|
||||
variableId: string,
|
||||
|
|
|
|||
|
|
@ -260,8 +260,16 @@ export const dialogMessages = i18n('dialogs', {
|
|||
copy: 'Copy',
|
||||
copied: 'Copied',
|
||||
createCollection: 'Create collection',
|
||||
renameCollection: 'Rename collection',
|
||||
deleteCollection: 'Delete collection',
|
||||
localVariables: 'Local variables',
|
||||
noVariableCollections: 'No variable collections',
|
||||
modes: 'Modes',
|
||||
addMode: 'Add mode',
|
||||
renameMode: 'Rename mode',
|
||||
duplicateMode: 'Duplicate mode',
|
||||
deleteMode: 'Delete mode',
|
||||
setDefaultMode: 'Set as default',
|
||||
selectLayerForJSX: 'Select a layer to see its JSX code',
|
||||
connectAIProvider: 'Connect an AI provider to start chatting.',
|
||||
connect: 'Connect',
|
||||
|
|
|
|||
|
|
@ -225,7 +225,15 @@
|
|||
"updateInstalledTitle": "Обновление установлено",
|
||||
"updateInstalled": "OpenPencil {version} установлен{size}. Перезапуск.",
|
||||
"updateCheckFailed": "Не удалось проверить обновления: {error}",
|
||||
"updateUnavailable": "Обновления пока недоступны. Сначала опубликуйте подписанный релиз с latest.json."
|
||||
"updateUnavailable": "Обновления пока недоступны. Сначала опубликуйте подписанный релиз с latest.json.",
|
||||
"renameCollection": "Переименовать коллекцию",
|
||||
"deleteCollection": "Удалить коллекцию",
|
||||
"modes": "Режимы",
|
||||
"addMode": "Добавить режим",
|
||||
"renameMode": "Переименовать режим",
|
||||
"duplicateMode": "Дублировать режим",
|
||||
"deleteMode": "Удалить режим",
|
||||
"setDefaultMode": "Установить по умолчанию"
|
||||
},
|
||||
"variableTypes": {
|
||||
"color": "Цвет",
|
||||
|
|
|
|||
|
|
@ -225,7 +225,15 @@
|
|||
"updateInstalledTitle": "更新已安装",
|
||||
"updateInstalled": "OpenPencil {version} 已安装{size}。正在重启。",
|
||||
"updateCheckFailed": "无法检查更新:{error}",
|
||||
"updateUnavailable": "更新尚不可用。请先发布带有 latest.json 的已签名版本。"
|
||||
"updateUnavailable": "更新尚不可用。请先发布带有 latest.json 的已签名版本。",
|
||||
"renameCollection": "重命名集合",
|
||||
"deleteCollection": "删除集合",
|
||||
"modes": "模式",
|
||||
"addMode": "添加模式",
|
||||
"renameMode": "重命名模式",
|
||||
"duplicateMode": "复制模式",
|
||||
"deleteMode": "删除模式",
|
||||
"setDefaultMode": "设为默认"
|
||||
},
|
||||
"variableTypes": {
|
||||
"color": "颜色",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export function useVariablesDialogState() {
|
|||
const variables = useVariables()
|
||||
|
||||
const editingCollectionId = ref<string | null>(null)
|
||||
const editingModeId = ref<string | null>(null)
|
||||
async function focusCollectionInput(input: HTMLInputElement | null) {
|
||||
if (!input) return
|
||||
await nextTick()
|
||||
|
|
@ -17,8 +18,10 @@ export function useVariablesDialogState() {
|
|||
editingCollectionId.value = id
|
||||
}
|
||||
|
||||
function commitRenameCollection(id: string, input: HTMLInputElement) {
|
||||
function commitRenameCollection(id: string, event: Event) {
|
||||
if (editingCollectionId.value !== id) return
|
||||
const input = event.target
|
||||
if (!(input instanceof HTMLInputElement)) return
|
||||
const value = input.value.trim()
|
||||
const col = variables.collections.value.find((collection) => collection.id === id)
|
||||
if (col && value && value !== col.name) {
|
||||
|
|
@ -27,11 +30,38 @@ export function useVariablesDialogState() {
|
|||
editingCollectionId.value = null
|
||||
}
|
||||
|
||||
function startRenameMode(modeId: string) {
|
||||
editingModeId.value = modeId
|
||||
}
|
||||
|
||||
async function focusModeInput(input: HTMLInputElement | null) {
|
||||
if (!input) return
|
||||
await nextTick()
|
||||
input.focus()
|
||||
input.select()
|
||||
}
|
||||
|
||||
function commitRenameMode(modeId: string, event: Event) {
|
||||
if (editingModeId.value !== modeId) return
|
||||
const input = event.target
|
||||
if (!(input instanceof HTMLInputElement)) return
|
||||
const value = input.value.trim()
|
||||
const mode = variables.activeModes.value.find((m) => m.modeId === modeId)
|
||||
if (mode && value && value !== mode.name) {
|
||||
variables.renameMode(modeId, value)
|
||||
}
|
||||
editingModeId.value = null
|
||||
}
|
||||
|
||||
return {
|
||||
...variables,
|
||||
editingCollectionId,
|
||||
editingModeId,
|
||||
focusCollectionInput,
|
||||
focusModeInput,
|
||||
startRenameCollection,
|
||||
commitRenameCollection
|
||||
commitRenameCollection,
|
||||
startRenameMode,
|
||||
commitRenameMode
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,60 @@ export function createVariableCollectionActions(editor: Editor, activeCollection
|
|||
editor.renameCollection(id, newName)
|
||||
}
|
||||
|
||||
return { setActiveCollection, addCollection, renameCollection }
|
||||
function removeCollection(id: string) {
|
||||
editor.removeCollection(id)
|
||||
const cols = [...editor.getCollections()]
|
||||
activeCollectionId.value = cols[0]?.id ?? ''
|
||||
}
|
||||
|
||||
function addMode(): string | undefined {
|
||||
const colId = activeCollectionId.value
|
||||
if (!colId) return undefined
|
||||
return editor.addMode(colId)
|
||||
}
|
||||
|
||||
function removeMode(modeId: string) {
|
||||
const colId = activeCollectionId.value
|
||||
if (!colId) return
|
||||
editor.removeMode(colId, modeId)
|
||||
}
|
||||
|
||||
function renameMode(modeId: string, newName: string) {
|
||||
const colId = activeCollectionId.value
|
||||
if (!colId) return
|
||||
editor.renameMode(colId, modeId, newName)
|
||||
}
|
||||
|
||||
function setDefaultMode(modeId: string) {
|
||||
const colId = activeCollectionId.value
|
||||
if (!colId) return
|
||||
editor.setDefaultMode(colId, modeId)
|
||||
}
|
||||
|
||||
function duplicateMode(modeId: string): string | undefined {
|
||||
const colId = activeCollectionId.value
|
||||
if (!colId) return undefined
|
||||
return editor.duplicateMode(colId, modeId)
|
||||
}
|
||||
|
||||
function setActiveMode(modeId: string) {
|
||||
const colId = activeCollectionId.value
|
||||
if (!colId) return
|
||||
editor.setActiveMode(colId, modeId)
|
||||
}
|
||||
|
||||
return {
|
||||
setActiveCollection,
|
||||
addCollection,
|
||||
renameCollection,
|
||||
removeCollection,
|
||||
addMode,
|
||||
removeMode,
|
||||
renameMode,
|
||||
setDefaultMode,
|
||||
duplicateMode,
|
||||
setActiveMode
|
||||
}
|
||||
}
|
||||
|
||||
export function createVariableValueActions(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@
|
|||
import { watch, type Component } from 'vue'
|
||||
import { templateRef } from '@vueuse/core'
|
||||
import {
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuPortal,
|
||||
ContextMenuRoot,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuTrigger,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogOverlay,
|
||||
|
|
@ -12,6 +18,7 @@ import {
|
|||
DropdownMenuItem,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuRoot,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
|
|
@ -36,7 +43,7 @@ import type { VariableType } from '@open-pencil/core/scene-graph'
|
|||
|
||||
const open = defineModel<boolean>('open', { default: false })
|
||||
const cls = useDialogUI({ content: 'flex h-[75vh] w-[800px] max-w-[90vw] flex-col' })
|
||||
const menuCls = useMenuUI({ content: 'w-44' })
|
||||
const menuCls = useMenuUI({ content: 'w-40' })
|
||||
|
||||
const variableTypeIcons: Record<VariableType, Component> = {
|
||||
COLOR: IconPalette,
|
||||
|
|
@ -81,10 +88,26 @@ const ctx = useVariablesEditor({
|
|||
deleteIcon: IconX
|
||||
})
|
||||
const collectionInput = templateRef<HTMLInputElement>('collectionInput')
|
||||
const modeInput = templateRef<HTMLInputElement>('modeInput')
|
||||
|
||||
watch(collectionInput, (input) => {
|
||||
void ctx.focusCollectionInput(input)
|
||||
})
|
||||
watch(modeInput, (input) => {
|
||||
void ctx.focusModeInput(input)
|
||||
})
|
||||
|
||||
function blurOnEnter(e: KeyboardEvent) {
|
||||
if (e.target instanceof HTMLInputElement) e.target.blur()
|
||||
}
|
||||
|
||||
function getModeId(columnId: string): string | undefined {
|
||||
return columnId.startsWith('mode-') ? columnId.slice(5) : undefined
|
||||
}
|
||||
|
||||
function modeId(columnId: string): string {
|
||||
return columnId.slice(5)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -130,8 +153,8 @@ watch(collectionInput, (input) => {
|
|||
ref="collectionInput"
|
||||
class="w-24 rounded border border-accent bg-input px-2 py-0.5 text-xs text-surface outline-none"
|
||||
:value="col.name"
|
||||
@blur="ctx.commitRenameCollection(col.id, $event.target as HTMLInputElement)"
|
||||
@keydown.enter="($event.target as HTMLInputElement).blur()"
|
||||
@blur="ctx.commitRenameCollection(col.id, $event)"
|
||||
@keydown.enter="blurOnEnter"
|
||||
@keydown.escape="ctx.editingCollectionId.value = null"
|
||||
/>
|
||||
<TabsTrigger
|
||||
|
|
@ -147,6 +170,42 @@ watch(collectionInput, (input) => {
|
|||
</TabsList>
|
||||
|
||||
<div class="flex items-center gap-1.5 px-3">
|
||||
<DropdownMenuRoot>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<button
|
||||
data-test-id="variables-collection-menu"
|
||||
class="flex size-6 cursor-pointer items-center justify-center rounded border-none bg-transparent text-muted hover:bg-hover hover:text-surface"
|
||||
>
|
||||
<icon-lucide-ellipsis class="size-3.5" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent
|
||||
side="bottom"
|
||||
:side-offset="4"
|
||||
align="start"
|
||||
:class="menuCls.content"
|
||||
>
|
||||
<DropdownMenuItem
|
||||
:class="menuCls.item"
|
||||
@select="ctx.startRenameCollection(ctx.activeCollectionId.value)"
|
||||
>
|
||||
<icon-lucide-pencil :class="menuCls.icon" />
|
||||
{{ dialogs.renameCollection }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator class="mx-1.5 my-1 h-px bg-border" />
|
||||
<DropdownMenuItem
|
||||
:class="menuCls.item"
|
||||
class="text-red-500"
|
||||
data-test-id="variables-delete-collection"
|
||||
@select="ctx.removeCollection(ctx.activeCollectionId.value)"
|
||||
>
|
||||
<icon-lucide-trash-2 :class="menuCls.icon" />
|
||||
{{ dialogs.deleteCollection }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuRoot>
|
||||
<div class="flex items-center gap-1 rounded border border-border px-2 py-0.5">
|
||||
<icon-lucide-search class="size-3 text-muted" />
|
||||
<input
|
||||
|
|
@ -196,8 +255,69 @@ watch(collectionInput, (input) => {
|
|||
class="relative px-4 py-2 text-left text-[11px] font-medium text-muted"
|
||||
:style="{ width: `${header.getSize()}px` }"
|
||||
>
|
||||
<template v-if="getModeId(header.column.id)">
|
||||
<input
|
||||
v-if="ctx.editingModeId.value === getModeId(header.column.id)"
|
||||
ref="modeInput"
|
||||
class="-mx-1 w-full rounded border border-accent bg-input px-1 py-0 text-[11px] font-medium text-surface outline-none"
|
||||
:value="header.column.columnDef.header"
|
||||
@blur="ctx.commitRenameMode(modeId(header.column.id), $event)"
|
||||
@keydown.enter="blurOnEnter"
|
||||
@keydown.escape="ctx.editingModeId.value = null"
|
||||
/>
|
||||
<ContextMenuRoot v-else>
|
||||
<ContextMenuTrigger as-child>
|
||||
<span
|
||||
class="cursor-default"
|
||||
:class="
|
||||
getModeId(header.column.id) === col.defaultModeId
|
||||
? 'text-surface'
|
||||
: ''
|
||||
"
|
||||
@dblclick="ctx.startRenameMode(modeId(header.column.id))"
|
||||
>
|
||||
{{ header.column.columnDef.header }}
|
||||
</span>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuPortal>
|
||||
<ContextMenuContent :class="menuCls.content">
|
||||
<ContextMenuItem
|
||||
:class="menuCls.item"
|
||||
@select="ctx.startRenameMode(modeId(header.column.id))"
|
||||
>
|
||||
<icon-lucide-pencil :class="menuCls.icon" />
|
||||
{{ dialogs.renameMode }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
:class="menuCls.item"
|
||||
@select="ctx.duplicateMode(modeId(header.column.id))"
|
||||
>
|
||||
<icon-lucide-copy :class="menuCls.icon" />
|
||||
{{ dialogs.duplicateMode }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
v-if="getModeId(header.column.id) !== col.defaultModeId"
|
||||
:class="menuCls.item"
|
||||
@select="ctx.setDefaultMode(modeId(header.column.id))"
|
||||
>
|
||||
<icon-lucide-pin :class="menuCls.icon" />
|
||||
{{ dialogs.setDefaultMode }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator :class="menuCls.separator" />
|
||||
<ContextMenuItem
|
||||
:class="[menuCls.item, 'text-red-500']"
|
||||
:disabled="col.modes.length <= 1"
|
||||
@select="ctx.removeMode(modeId(header.column.id))"
|
||||
>
|
||||
<icon-lucide-trash-2 :class="menuCls.icon" />
|
||||
{{ dialogs.deleteMode }}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenuPortal>
|
||||
</ContextMenuRoot>
|
||||
</template>
|
||||
<FlexRender
|
||||
v-if="!header.isPlaceholder"
|
||||
v-else-if="!header.isPlaceholder"
|
||||
:render="header.column.columnDef.header"
|
||||
:props="header.getContext()"
|
||||
/>
|
||||
|
|
@ -214,6 +334,17 @@ watch(collectionInput, (input) => {
|
|||
@dblclick="header.column.resetSize()"
|
||||
/>
|
||||
</th>
|
||||
<th class="w-8 px-1 py-2">
|
||||
<Tip :label="dialogs.addMode">
|
||||
<button
|
||||
data-test-id="variables-add-mode"
|
||||
class="flex size-5 cursor-pointer items-center justify-center rounded border-none bg-transparent text-muted hover:bg-hover hover:text-surface"
|
||||
@click="ctx.addMode"
|
||||
>
|
||||
<icon-lucide-plus class="size-3" />
|
||||
</button>
|
||||
</Tip>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
|||
Loading…
Reference in a new issue