refactor(vue): format command shortcuts by platform
This commit is contained in:
parent
cc90c63952
commit
433d812b9d
|
|
@ -1,4 +1,5 @@
|
|||
import { editorCommandMetadata } from './registry'
|
||||
import { formatShortcut } from './shortcut'
|
||||
import type { EditorCommand, EditorCommandId, EditorCommandMenuItem } from './types'
|
||||
|
||||
export function createEditorCommandActions(commands: Record<EditorCommandId, EditorCommand>) {
|
||||
|
|
@ -19,7 +20,7 @@ export function createEditorCommandActions(commands: Record<EditorCommandId, Edi
|
|||
return {
|
||||
id,
|
||||
label: command.label,
|
||||
shortcut,
|
||||
shortcut: formatShortcut(shortcut),
|
||||
get disabled() {
|
||||
return !command.enabled.value
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,27 +7,27 @@ export interface EditorCommandMetadata {
|
|||
}
|
||||
|
||||
export const EDITOR_COMMAND_METADATA = {
|
||||
'edit.undo': { shortcut: '⌘Z', keybinding: '$mod+KeyZ' },
|
||||
'edit.redo': { shortcut: '⇧⌘Z', keybinding: ['$mod+Shift+KeyZ', '$mod+KeyY'] },
|
||||
'selection.selectAll': { shortcut: '⌘A', keybinding: '$mod+KeyA' },
|
||||
'edit.undo': { shortcut: 'MOD+Z', keybinding: '$mod+KeyZ' },
|
||||
'edit.redo': { shortcut: 'MOD+SHIFT+Z', keybinding: ['$mod+Shift+KeyZ', '$mod+KeyY'] },
|
||||
'selection.selectAll': { shortcut: 'MOD+A', keybinding: '$mod+KeyA' },
|
||||
'selection.duplicate': {
|
||||
shortcut: '⌘D',
|
||||
shortcut: 'MOD+D',
|
||||
keybinding: '$mod+KeyD',
|
||||
contextTestId: 'context-duplicate'
|
||||
},
|
||||
'selection.delete': { shortcut: '⌫', contextTestId: 'context-delete' },
|
||||
'selection.group': { shortcut: '⌘G', keybinding: '$mod+KeyG', contextTestId: 'context-group' },
|
||||
'selection.ungroup': { shortcut: '⇧⌘G', keybinding: '$mod+Shift+KeyG' },
|
||||
'selection.group': { shortcut: 'MOD+G', keybinding: '$mod+KeyG', contextTestId: 'context-group' },
|
||||
'selection.ungroup': { shortcut: 'MOD+SHIFT+G', keybinding: '$mod+Shift+KeyG' },
|
||||
'selection.createComponent': {
|
||||
shortcut: '⌥⌘K',
|
||||
shortcut: 'MOD+ALT+K',
|
||||
keybinding: '$mod+Alt+KeyK',
|
||||
contextTestId: 'context-create-component'
|
||||
},
|
||||
'selection.createComponentSet': { shortcut: '⇧⌘K', keybinding: '$mod+Shift+KeyK' },
|
||||
'selection.detachInstance': { shortcut: '⌥⌘B', keybinding: '$mod+Alt+KeyB' },
|
||||
'selection.createComponentSet': { shortcut: 'MOD+SHIFT+K', keybinding: '$mod+Shift+KeyK' },
|
||||
'selection.detachInstance': { shortcut: 'MOD+ALT+B', keybinding: '$mod+Alt+KeyB' },
|
||||
'selection.goToMainComponent': {},
|
||||
'selection.createInstance': {},
|
||||
'selection.wrapInAutoLayout': { shortcut: '⇧A', keybinding: 'Shift+KeyA' },
|
||||
'selection.wrapInAutoLayout': { shortcut: 'SHIFT+A', keybinding: 'Shift+KeyA' },
|
||||
'selection.bringToFront': {
|
||||
shortcut: ']',
|
||||
keybinding: 'BracketRight',
|
||||
|
|
@ -39,22 +39,22 @@ export const EDITOR_COMMAND_METADATA = {
|
|||
contextTestId: 'context-send-to-back'
|
||||
},
|
||||
'selection.toggleVisibility': {
|
||||
shortcut: '⇧⌘H',
|
||||
shortcut: 'MOD+SHIFT+H',
|
||||
keybinding: '$mod+Shift+KeyH',
|
||||
contextTestId: 'context-toggle-visibility'
|
||||
},
|
||||
'selection.toggleLock': {
|
||||
shortcut: '⇧⌘L',
|
||||
shortcut: 'MOD+SHIFT+L',
|
||||
keybinding: '$mod+Shift+KeyL',
|
||||
contextTestId: 'context-toggle-lock'
|
||||
},
|
||||
'selection.flipHorizontal': {
|
||||
shortcut: '⇧H',
|
||||
shortcut: 'SHIFT+H',
|
||||
keybinding: 'Shift+KeyH',
|
||||
contextTestId: 'context-flip-horizontal'
|
||||
},
|
||||
'selection.flipVertical': {
|
||||
shortcut: '⇧V',
|
||||
shortcut: 'SHIFT+V',
|
||||
keybinding: 'Shift+KeyV',
|
||||
contextTestId: 'context-flip-vertical'
|
||||
},
|
||||
|
|
|
|||
57
packages/vue/src/editor/commands/shortcut.ts
Normal file
57
packages/vue/src/editor/commands/shortcut.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
export type ShortcutPlatform = 'mac' | 'windows' | 'linux'
|
||||
|
||||
const MODIFIER_DISPLAY: Record<ShortcutPlatform, Record<string, string>> = {
|
||||
mac: {
|
||||
MOD: '⌘',
|
||||
SHIFT: '⇧',
|
||||
ALT: '⌥',
|
||||
CTRL: '⌃'
|
||||
},
|
||||
windows: {
|
||||
MOD: 'Ctrl',
|
||||
SHIFT: 'Shift',
|
||||
ALT: 'Alt',
|
||||
CTRL: 'Ctrl'
|
||||
},
|
||||
linux: {
|
||||
MOD: 'Ctrl',
|
||||
SHIFT: 'Shift',
|
||||
ALT: 'Alt',
|
||||
CTRL: 'Ctrl'
|
||||
}
|
||||
}
|
||||
|
||||
const MAC_MODIFIER_ORDER = ['CTRL', 'ALT', 'SHIFT', 'MOD']
|
||||
const STANDARD_MODIFIER_ORDER = ['MOD', 'CTRL', 'ALT', 'SHIFT']
|
||||
|
||||
export function shortcutPlatform(userAgent = navigator.userAgent): ShortcutPlatform {
|
||||
if (/Mac|iPhone|iPad|iPod/u.test(userAgent)) return 'mac'
|
||||
if (/Win/u.test(userAgent)) return 'windows'
|
||||
return 'linux'
|
||||
}
|
||||
|
||||
function sortModifiers(modifiers: string[], platform: ShortcutPlatform): string[] {
|
||||
const order = platform === 'mac' ? MAC_MODIFIER_ORDER : STANDARD_MODIFIER_ORDER
|
||||
return [...modifiers].sort((a, b) => order.indexOf(a) - order.indexOf(b))
|
||||
}
|
||||
|
||||
export function formatShortcut(
|
||||
shortcut: string | undefined,
|
||||
platform = shortcutPlatform()
|
||||
): string | undefined {
|
||||
if (!shortcut) return undefined
|
||||
|
||||
return shortcut
|
||||
.split(' ')
|
||||
.map((combo) => {
|
||||
const parts = combo.split('+').filter(Boolean)
|
||||
const modifiers = parts.filter((part) => part in MODIFIER_DISPLAY[platform])
|
||||
const keys = parts.filter((part) => !(part in MODIFIER_DISPLAY[platform]))
|
||||
const formattedModifiers = sortModifiers(modifiers, platform).map(
|
||||
(part) => MODIFIER_DISPLAY[platform][part]
|
||||
)
|
||||
const separator = platform === 'mac' ? '' : '+'
|
||||
return [...formattedModifiers, ...keys].join(separator)
|
||||
})
|
||||
.join(' ')
|
||||
}
|
||||
|
|
@ -36,7 +36,9 @@ export { useSelectionCapabilities } from '#vue/editor/selection-capabilities/use
|
|||
/** Command and menu composition helpers. */
|
||||
export { useEditorCommands } from '#vue/editor/commands/use'
|
||||
export { EDITOR_COMMAND_METADATA, editorCommandMetadata } from '#vue/editor/commands/registry'
|
||||
export { formatShortcut, shortcutPlatform } from '#vue/editor/commands/shortcut'
|
||||
export type { EditorCommandMetadata } from '#vue/editor/commands/registry'
|
||||
export type { ShortcutPlatform } from '#vue/editor/commands/shortcut'
|
||||
export type { EditorCommand, EditorCommandId } from '#vue/editor/commands/use'
|
||||
export { useMenuModel } from '#vue/editor/menu-model/use'
|
||||
export type { MenuActionNode, MenuEntry, MenuSeparatorNode } from '#vue/editor/menu-model/use'
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ import {
|
|||
useI18n,
|
||||
useMenuModel,
|
||||
useSelectionState,
|
||||
editorCommandMetadata
|
||||
editorCommandMetadata,
|
||||
formatShortcut
|
||||
} from '@open-pencil/vue'
|
||||
import type { EditorCommandId } from '@open-pencil/vue'
|
||||
|
||||
|
|
@ -62,7 +63,7 @@ function contextCommandTestId(id: EditorCommandId | undefined): string | undefin
|
|||
@select="execCommand('copy')"
|
||||
>
|
||||
<span>{{ t.copy }}</span
|
||||
><span class="text-[11px] text-muted">⌘C</span>
|
||||
><span class="text-[11px] text-muted">{{ formatShortcut('MOD+C') }}</span>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
data-test-id="context-cut"
|
||||
|
|
@ -71,11 +72,11 @@ function contextCommandTestId(id: EditorCommandId | undefined): string | undefin
|
|||
@select="execCommand('cut')"
|
||||
>
|
||||
<span>{{ t.cut }}</span
|
||||
><span class="text-[11px] text-muted">⌘X</span>
|
||||
><span class="text-[11px] text-muted">{{ formatShortcut('MOD+X') }}</span>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem data-test-id="context-paste" :class="cls.item" @select="execCommand('paste')">
|
||||
<span>{{ t.pasteHere }}</span
|
||||
><span class="text-[11px] text-muted">⌘V</span>
|
||||
><span class="text-[11px] text-muted">{{ formatShortcut('MOD+V') }}</span>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
data-test-id="context-duplicate"
|
||||
|
|
@ -83,7 +84,7 @@ function contextCommandTestId(id: EditorCommandId | undefined): string | undefin
|
|||
:disabled="!hasSelection"
|
||||
@select="getCommand('selection.duplicate').run()"
|
||||
>
|
||||
<span>Duplicate</span><span class="text-[11px] text-muted">⌘D</span>
|
||||
<span>Duplicate</span><span class="text-[11px] text-muted">{{ formatShortcut('MOD+D') }}</span>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
data-test-id="context-delete"
|
||||
|
|
@ -159,7 +160,7 @@ function contextCommandTestId(id: EditorCommandId | undefined): string | undefin
|
|||
>
|
||||
<ContextMenuItem :class="cls.item" @select="copyAsPNG">
|
||||
<span>{{ t.copyAsPNG }}</span
|
||||
><span class="text-[11px] text-muted">⇧⌘C</span>
|
||||
><span class="text-[11px] text-muted">{{ formatShortcut('MOD+SHIFT+C') }}</span>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
data-test-id="context-copy-as-jsx"
|
||||
|
|
|
|||
25
tests/engine/vue/editor/commands/shortcut.test.ts
Normal file
25
tests/engine/vue/editor/commands/shortcut.test.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { describe, expect, test } from 'bun:test'
|
||||
|
||||
import { formatShortcut, shortcutPlatform } from '#vue/editor/commands/shortcut'
|
||||
|
||||
describe('formatShortcut', () => {
|
||||
test('formats shortcuts for macOS', () => {
|
||||
expect(formatShortcut('MOD+D', 'mac')).toBe('⌘D')
|
||||
expect(formatShortcut('MOD+SHIFT+H', 'mac')).toBe('⇧⌘H')
|
||||
expect(formatShortcut('MOD+ALT+K', 'mac')).toBe('⌥⌘K')
|
||||
expect(formatShortcut('SHIFT+A', 'mac')).toBe('⇧A')
|
||||
})
|
||||
|
||||
test('formats shortcuts for Windows and Linux', () => {
|
||||
expect(formatShortcut('MOD+D', 'windows')).toBe('Ctrl+D')
|
||||
expect(formatShortcut('MOD+SHIFT+H', 'windows')).toBe('Ctrl+Shift+H')
|
||||
expect(formatShortcut('MOD+ALT+K', 'linux')).toBe('Ctrl+Alt+K')
|
||||
expect(formatShortcut('SHIFT+A', 'linux')).toBe('Shift+A')
|
||||
})
|
||||
|
||||
test('detects platform from user agent', () => {
|
||||
expect(shortcutPlatform('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)')).toBe('mac')
|
||||
expect(shortcutPlatform('Mozilla/5.0 (Windows NT 10.0; Win64; x64)')).toBe('windows')
|
||||
expect(shortcutPlatform('Mozilla/5.0 (X11; Linux x86_64)')).toBe('linux')
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue