diff --git a/desktop/generated/menu.json b/desktop/generated/menu.json index 761e58671..3f394aeaf 100644 --- a/desktop/generated/menu.json +++ b/desktop/generated/menu.json @@ -83,6 +83,11 @@ "label": "Copy", "accelerator": "CmdOrCtrl+C" }, + { + "id": "cut", + "label": "Cut", + "accelerator": "CmdOrCtrl+X" + }, { "id": "paste", "label": "Paste", @@ -170,8 +175,7 @@ }, { "id": "dev-tools", - "label": "Developer Tools", - "accelerator": "CmdOrCtrl+Alt+I" + "label": "Developer Tools" } ] }, diff --git a/scripts/generate-tauri-menu.ts b/scripts/generate-tauri-menu.ts index b42c1e5d0..dada21fb4 100644 --- a/scripts/generate-tauri-menu.ts +++ b/scripts/generate-tauri-menu.ts @@ -3,6 +3,7 @@ import { dirname } from 'node:path' import { APP_MENU_SCHEMA } from '../src/app/shell/menu/schema' import type { AppMenuEntry, AppMenuGroupSchema } from '../src/app/shell/menu/schema' +import { shortcutTokenToAccelerator } from '../src/app/shell/menu/shortcut' function isNativeVisible(entry: { target?: string }): boolean { return entry.target !== 'browser' @@ -14,7 +15,7 @@ function cleanEntry(entry: AppMenuEntry): unknown | null { return { id: entry.id, label: entry.label, - accelerator: entry.accelerator, + accelerator: entry.accelerator ?? shortcutTokenToAccelerator(entry.shortcut), checkbox: entry.checkbox, sub: entry.sub?.map(cleanEntry).filter(Boolean) } diff --git a/src/app/shell/menu/app-menu.ts b/src/app/shell/menu/app-menu.ts index 8007793c1..d79d18b18 100644 --- a/src/app/shell/menu/app-menu.ts +++ b/src/app/shell/menu/app-menu.ts @@ -1,12 +1,13 @@ import { computed } from 'vue' -import { useEditorCommands, useI18n, formatShortcut } from '@open-pencil/vue' +import { useEditorCommands, useI18n } from '@open-pencil/vue' import type { MenuEntry } from '@open-pencil/vue' import { useEditorStore } from '@/app/editor/active-store' import { createSharedEditorMenuActions } from '@/app/shell/menu/editor-actions' import { APP_MENU_SCHEMA } from '@/app/shell/menu/schema' import type { AppMenuActionItem, AppMenuEntry, AppMenuGroupSchema } from '@/app/shell/menu/schema' +import { appMenuShortcutLabel } from '@/app/shell/menu/shortcut' import { openFileDialog } from '@/app/shell/menu/use' import { useAppTheme } from '@/app/shell/theme' @@ -107,12 +108,12 @@ export function useAppMenu() { } if (entry.command) { - return commandMenuItem(entry.command, formatShortcut(entry.shortcut)) + return commandMenuItem(entry.command, appMenuShortcutLabel(entry.id)) } return { label: entry.label, - shortcut: formatShortcut(entry.shortcut), + shortcut: appMenuShortcutLabel(entry.id), action: itemAction(entry), checked: checked(entry), onCheckedChange: onCheckedChange(entry), diff --git a/src/app/shell/menu/schema.ts b/src/app/shell/menu/schema.ts index a514e0efc..17d69ee07 100644 --- a/src/app/shell/menu/schema.ts +++ b/src/app/shell/menu/schema.ts @@ -31,17 +31,16 @@ export const APP_MENU_SCHEMA = [ { label: 'File', items: [ - { id: 'new', label: 'New', shortcut: 'MOD+N', accelerator: 'CmdOrCtrl+N' }, - { id: 'open', label: 'Open…', shortcut: 'MOD+O', accelerator: 'CmdOrCtrl+O' }, + { id: 'new', label: 'New', shortcut: 'MOD+N' }, + { id: 'open', label: 'Open…', shortcut: 'MOD+O' }, { type: 'separator' }, - { id: 'save', label: 'Save', shortcut: 'MOD+S', accelerator: 'CmdOrCtrl+S' }, - { id: 'save-as', label: 'Save As…', shortcut: 'MOD+SHIFT+S', accelerator: 'CmdOrCtrl+Shift+S' }, + { id: 'save', label: 'Save', shortcut: 'MOD+S' }, + { id: 'save-as', label: 'Save As…', shortcut: 'MOD+SHIFT+S' }, { type: 'separator' }, { id: 'export-selection', label: 'Export Selection', shortcut: 'MOD+SHIFT+E', - accelerator: 'CmdOrCtrl+Shift+E', sub: [ { id: 'export-png', label: 'PNG' }, { id: 'export-svg', label: 'SVG' }, @@ -50,7 +49,7 @@ export const APP_MENU_SCHEMA = [ }, { type: 'separator' }, { id: 'autosave', label: 'Autosave', checkbox: true }, - { id: 'close', label: 'Close Tab', shortcut: 'MOD+W', accelerator: 'CmdOrCtrl+W' } + { id: 'close', label: 'Close Tab', shortcut: 'MOD+W' } ] }, { @@ -60,32 +59,28 @@ export const APP_MENU_SCHEMA = [ id: 'edit.undo', label: 'Undo', shortcut: 'MOD+Z', - accelerator: 'CmdOrCtrl+Z', command: 'edit.undo' }, { id: 'edit.redo', label: 'Redo', shortcut: 'MOD+SHIFT+Z', - accelerator: 'CmdOrCtrl+Shift+Z', command: 'edit.redo' }, { type: 'separator' }, - { id: 'copy', label: 'Copy', shortcut: 'MOD+C', accelerator: 'CmdOrCtrl+C' }, - { id: 'cut', label: 'Cut', shortcut: 'MOD+X', accelerator: 'CmdOrCtrl+X' }, - { id: 'paste', label: 'Paste', shortcut: 'MOD+V', accelerator: 'CmdOrCtrl+V' }, + { id: 'copy', label: 'Copy', shortcut: 'MOD+C' }, + { id: 'cut', label: 'Cut', shortcut: 'MOD+X' }, + { id: 'paste', label: 'Paste', shortcut: 'MOD+V' }, { id: 'selection.duplicate', label: 'Duplicate', shortcut: 'MOD+D', - accelerator: 'CmdOrCtrl+D', command: 'selection.duplicate' }, { id: 'selection.delete', label: 'Delete', shortcut: '⌫', - accelerator: 'Backspace', command: 'selection.delete' }, { type: 'separator' }, @@ -93,7 +88,6 @@ export const APP_MENU_SCHEMA = [ id: 'selection.selectAll', label: 'Select All', shortcut: 'MOD+A', - accelerator: 'CmdOrCtrl+A', command: 'selection.selectAll' } ] @@ -105,25 +99,22 @@ export const APP_MENU_SCHEMA = [ id: 'view.zoom100', label: 'Zoom to 100%', shortcut: 'MOD+0', - accelerator: 'CmdOrCtrl+0', command: 'view.zoom100' }, { id: 'view.zoomFit', label: 'Zoom to Fit', shortcut: 'MOD+1', - accelerator: 'CmdOrCtrl+1', command: 'view.zoomFit' }, { id: 'view.zoomSelection', label: 'Zoom to Selection', shortcut: 'MOD+2', - accelerator: 'CmdOrCtrl+2', command: 'view.zoomSelection' }, - { id: 'zoom-in', label: 'Zoom In', shortcut: 'MOD+=', accelerator: 'CmdOrCtrl+=' }, - { id: 'zoom-out', label: 'Zoom Out', shortcut: 'MOD+-', accelerator: 'CmdOrCtrl+-' }, + { id: 'zoom-in', label: 'Zoom In', shortcut: 'MOD+=' }, + { id: 'zoom-out', label: 'Zoom Out', shortcut: 'MOD+-' }, { type: 'separator' }, { id: 'theme', @@ -136,12 +127,11 @@ export const APP_MENU_SCHEMA = [ }, { id: 'language', label: 'Language', target: 'browser' }, { type: 'separator' }, - { id: 'toggle-ui', label: 'Toggle UI', shortcut: 'MOD+\\', accelerator: 'CmdOrCtrl+\\' }, + { id: 'toggle-ui', label: 'Toggle UI', shortcut: 'MOD+\\' }, { id: 'profiler', label: 'Profiler', checkbox: true, target: 'browser' }, { id: 'dev-tools', label: 'Developer Tools', - accelerator: 'CmdOrCtrl+Alt+I', target: 'native' } ] @@ -153,14 +143,12 @@ export const APP_MENU_SCHEMA = [ id: 'selection.group', label: 'Group Selection', shortcut: 'MOD+G', - accelerator: 'CmdOrCtrl+G', command: 'selection.group' }, { id: 'selection.ungroup', label: 'Ungroup Selection', shortcut: 'MOD+SHIFT+G', - accelerator: 'CmdOrCtrl+Shift+G', command: 'selection.ungroup' }, { type: 'separator' }, @@ -168,7 +156,6 @@ export const APP_MENU_SCHEMA = [ id: 'selection.createComponent', label: 'Create Component', shortcut: 'MOD+ALT+K', - accelerator: 'CmdOrCtrl+Alt+K', command: 'selection.createComponent' }, { @@ -186,14 +173,12 @@ export const APP_MENU_SCHEMA = [ id: 'selection.bringToFront', label: 'Bring to Front', shortcut: ']', - accelerator: ']', command: 'selection.bringToFront' }, { id: 'selection.sendToBack', label: 'Send to Back', shortcut: '[', - accelerator: '[', command: 'selection.sendToBack' } ] @@ -201,9 +186,9 @@ export const APP_MENU_SCHEMA = [ { label: 'Text', items: [ - { id: 'text.bold', label: 'Bold', shortcut: 'MOD+B', accelerator: 'CmdOrCtrl+B' }, - { id: 'text.italic', label: 'Italic', shortcut: 'MOD+I', accelerator: 'CmdOrCtrl+I' }, - { id: 'text.underline', label: 'Underline', shortcut: 'MOD+U', accelerator: 'CmdOrCtrl+U' } + { id: 'text.bold', label: 'Bold', shortcut: 'MOD+B' }, + { id: 'text.italic', label: 'Italic', shortcut: 'MOD+I' }, + { id: 'text.underline', label: 'Underline', shortcut: 'MOD+U' } ] }, { @@ -213,17 +198,16 @@ export const APP_MENU_SCHEMA = [ id: 'selection.wrapInAutoLayout', label: 'Wrap in Auto Layout', shortcut: 'SHIFT+A', - accelerator: 'Shift+A', command: 'selection.wrapInAutoLayout' }, { type: 'separator' }, - { id: 'align-left', label: 'Align Left', shortcut: 'ALT+A', accelerator: 'Alt+A' }, - { id: 'align-center', label: 'Align Center', shortcut: 'ALT+H', accelerator: 'Alt+H' }, - { id: 'align-right', label: 'Align Right', shortcut: 'ALT+D', accelerator: 'Alt+D' }, + { id: 'align-left', label: 'Align Left', shortcut: 'ALT+A' }, + { id: 'align-center', label: 'Align Center', shortcut: 'ALT+H' }, + { id: 'align-right', label: 'Align Right', shortcut: 'ALT+D' }, { type: 'separator' }, - { id: 'align-top', label: 'Align Top', shortcut: 'ALT+W', accelerator: 'Alt+W' }, - { id: 'align-middle', label: 'Align Middle', shortcut: 'ALT+V', accelerator: 'Alt+V' }, - { id: 'align-bottom', label: 'Align Bottom', shortcut: 'ALT+S', accelerator: 'Alt+S' } + { id: 'align-top', label: 'Align Top', shortcut: 'ALT+W' }, + { id: 'align-middle', label: 'Align Middle', shortcut: 'ALT+V' }, + { id: 'align-bottom', label: 'Align Bottom', shortcut: 'ALT+S' } ] } ] satisfies AppMenuGroupSchema[] diff --git a/src/app/shell/menu/shortcut.ts b/src/app/shell/menu/shortcut.ts index 8fc4c5a7d..9de2ccab6 100644 --- a/src/app/shell/menu/shortcut.ts +++ b/src/app/shell/menu/shortcut.ts @@ -1,3 +1,5 @@ +import { formatShortcut } from '@open-pencil/vue' + import type { AppMenuActionItem, AppMenuEntry } from '@/app/shell/menu/schema' import { APP_MENU_SCHEMA } from '@/app/shell/menu/schema' @@ -23,10 +25,36 @@ export function appMenuShortcut(id: string): string | undefined { return undefined } -export function appMenuTinykeysShortcut(id: string): string | string[] | undefined { - const shortcut = appMenuShortcut(id) - return shortcut - ?.replaceAll('MOD', '$mod') - .replaceAll('SHIFT', 'Shift') - .replaceAll('ALT', 'Alt') +function normalizeShortcutToken(shortcut: string): string { + return shortcut === '⌫' ? 'Backspace' : shortcut +} + +export function shortcutTokenToTinykeys(shortcut: string | undefined): string | undefined { + return shortcut + ? normalizeShortcutToken(shortcut) + .replaceAll('MOD', '$mod') + .replaceAll('SHIFT', 'Shift') + .replaceAll('ALT', 'Alt') + : undefined +} + +export function shortcutTokenToAccelerator(shortcut: string | undefined): string | undefined { + return shortcut + ? normalizeShortcutToken(shortcut) + .replaceAll('MOD', 'CmdOrCtrl') + .replaceAll('SHIFT', 'Shift') + .replaceAll('ALT', 'Alt') + : undefined +} + +export function appMenuShortcutLabel(id: string): string | undefined { + return formatShortcut(appMenuShortcut(id)) +} + +export function appMenuTinykeysShortcut(id: string): string | undefined { + return shortcutTokenToTinykeys(appMenuShortcut(id)) +} + +export function appMenuAccelerator(id: string): string | undefined { + return shortcutTokenToAccelerator(appMenuShortcut(id)) } diff --git a/src/components/AppMenu.vue b/src/components/AppMenu.vue index 2690eb915..3e1e7e998 100644 --- a/src/components/AppMenu.vue +++ b/src/components/AppMenu.vue @@ -19,12 +19,12 @@ import { import IconChevronRight from '~icons/lucide/chevron-right' -import { vTestId, useI18n, formatShortcut } from '@open-pencil/vue' +import { vTestId, useI18n } from '@open-pencil/vue' import { useMenuUI } from '@/components/ui/menu' import { IS_TAURI } from '@/constants' import { useAppMenu } from '@/app/shell/menu/app-menu' import { useDocumentNameRename } from '@/app/shell/menu/document-name' -import { appMenuShortcut } from '@/app/shell/menu/shortcut' +import { appMenuShortcutLabel } from '@/app/shell/menu/shortcut' import { hasMenuSubItems, isMenuCheckbox, @@ -76,7 +76,7 @@ const subMenuCls = useMenuUI({ content: 'min-w-44' }) @dblclick="startRename" >{{ store.state.documentName }} - +