openpencil/packages/vue/src/commands/useMenuModel.ts
Danila Poyarkov 405a3f2ecf fix(vue): show Create Instance instead of Create Component for components
When right-clicking a component, the context menu now shows
'Create Instance' instead of 'Create Component'. Previously both
were shown simultaneously.
2026-04-22 14:21:18 +03:00

130 lines
4.4 KiB
TypeScript

import { useStore } from '@nanostores/vue'
import { computed } from 'vue'
import { useEditorCommands } from '@open-pencil/vue/commands/useEditorCommands'
import { useEditor } from '@open-pencil/vue/context/editorContext'
import { menuMessages } from '@open-pencil/vue/i18n'
import { useSelectionState } from '@open-pencil/vue/selection/useSelectionState'
/**
* Action entry used by menu models returned from {@link useMenuModel}.
*/
export interface MenuActionNode {
separator?: false
label: string
shortcut?: string
action?: () => void
disabled?: boolean
checked?: boolean
onCheckedChange?: (checked: boolean) => void
sub?: MenuEntry[]
}
export interface MenuSeparatorNode {
separator: true
}
export type MenuEntry = MenuActionNode | MenuSeparatorNode
/**
* Returns ready-to-render menu models derived from the current editor state.
*
* This is a higher-level API than {@link useEditorCommands}: it groups
* commands into app and canvas menu structures and computes context-sensitive
* labels like Hide/Show and Lock/Unlock.
*/
export function useMenuModel() {
const editor = useEditor()
const { menuItem: commandMenuItem, otherPages, moveSelectionToPage } = useEditorCommands()
const { hasSelection, isGroup, isInstance, isComponent, canCreateComponentSet } =
useSelectionState()
const t = useStore(menuMessages)
const editMenu = computed<MenuEntry[]>(() => [
commandMenuItem('edit.undo'),
commandMenuItem('edit.redo'),
{ separator: true },
commandMenuItem('selection.duplicate'),
commandMenuItem('selection.delete'),
{ separator: true },
commandMenuItem('selection.selectAll')
])
const viewMenu = computed<MenuEntry[]>(() => [
commandMenuItem('view.zoom100'),
commandMenuItem('view.zoomFit'),
commandMenuItem('view.zoomSelection')
])
const objectMenu = computed<MenuEntry[]>(() => [
commandMenuItem('selection.group'),
commandMenuItem('selection.ungroup'),
{ separator: true },
commandMenuItem('selection.createComponent'),
commandMenuItem('selection.createComponentSet'),
commandMenuItem('selection.detachInstance'),
{ separator: true },
commandMenuItem('selection.bringToFront'),
commandMenuItem('selection.sendToBack')
])
const arrangeMenu = computed<MenuEntry[]>(() => [commandMenuItem('selection.wrapInAutoLayout')])
const appMenu = computed(() => [
{ label: t.value.edit, items: editMenu.value },
{ label: t.value.view, items: viewMenu.value },
{ label: t.value.object, items: objectMenu.value },
{ label: t.value.arrange, items: arrangeMenu.value }
])
const canvasMenu = computed<MenuEntry[]>(() => {
const moveToPageSubmenu: MenuEntry[] = otherPages.value.map((page) => ({
label: page.name,
action: () => moveSelectionToPage(page.id)
}))
return [
commandMenuItem('selection.duplicate', '⌘D'),
commandMenuItem('selection.delete', '⌫'),
{ separator: true },
...(moveToPageSubmenu.length > 0 && hasSelection.value
? [{ label: t.value.moveToPage, sub: moveToPageSubmenu } satisfies MenuActionNode]
: []),
commandMenuItem('selection.bringToFront', ']'),
commandMenuItem('selection.sendToBack', '['),
{ separator: true },
commandMenuItem('selection.group', '⌘G'),
...(isGroup.value ? [commandMenuItem('selection.ungroup', '⇧⌘G')] : []),
...(hasSelection.value ? [commandMenuItem('selection.wrapInAutoLayout', '⇧A')] : []),
{ separator: true },
...(isComponent.value
? [commandMenuItem('selection.createInstance')]
: [commandMenuItem('selection.createComponent', '⌥⌘K')]),
...(canCreateComponentSet.value
? [commandMenuItem('selection.createComponentSet', '⇧⌘K')]
: []),
...(isInstance.value ? [commandMenuItem('selection.goToMainComponent')] : []),
...(isInstance.value ? [commandMenuItem('selection.detachInstance', '⌥⌘B')] : []),
...(hasSelection.value
? [
{ separator: true } as MenuSeparatorNode,
commandMenuItem('selection.toggleVisibility', '⇧⌘H'),
commandMenuItem('selection.toggleLock', '⇧⌘L')
]
: [])
]
})
const selectionLabelMenu = computed(() => ({
visibility: (editor.getSelectedNode()?.visible ?? true) ? t.value.hide : t.value.show,
lock: (editor.getSelectedNode()?.locked ?? false) ? t.value.unlock : t.value.lock
}))
return {
appMenu,
canvasMenu,
selectionLabelMenu
}
}