| 'use client' |
|
|
| export type Platform = 'mac' | 'windows' | 'linux' | 'unknown' |
|
|
| let cachedPlatform: Platform | null = null |
|
|
| export function getPlatform(): Platform { |
| if (cachedPlatform) return cachedPlatform |
| if (typeof navigator === 'undefined') return 'unknown' |
| const nav = navigator as any |
| const platform = (nav.userAgentData?.platform || nav.platform || '').toLowerCase() |
| if (platform.includes('mac')) cachedPlatform = 'mac' |
| else if (platform.includes('win')) cachedPlatform = 'windows' |
| else if (platform.includes('linux')) cachedPlatform = 'linux' |
| else cachedPlatform = 'unknown' |
|
|
| return cachedPlatform |
| } |
|
|
| export interface ShortcutEvent { |
| key: string |
| ctrlKey: boolean |
| metaKey: boolean |
| altKey: boolean |
| shiftKey: boolean |
| } |
|
|
| const MODIFIER_NAMES = new Set([ |
| 'Control', |
| 'Alt', |
| 'Shift', |
| 'Meta', |
| 'Process', |
| 'AltGraph', |
| 'CapsLock', |
| 'OS', |
| 'Command', |
| ]) |
|
|
| |
| |
| |
| export function formatModifierCombination(event: ShortcutEvent, isMac: boolean): string { |
| const parts: string[] = [] |
| if (event.ctrlKey) parts.push('Ctrl') |
| if (event.altKey) parts.push(isMac ? 'Opt' : 'Alt') |
| if (event.shiftKey) parts.push('Shift') |
| if (event.metaKey) parts.push(isMac ? 'Cmd' : 'Win') |
| return parts.join('+') |
| } |
|
|
| |
| |
| |
| |
| export function formatShortcut(event: ShortcutEvent, isMac: boolean): string { |
| const parts: string[] = [] |
|
|
| |
| if (event.ctrlKey) parts.push('Ctrl') |
| if (event.altKey) parts.push(isMac ? 'Opt' : 'Alt') |
| if (event.shiftKey) parts.push('Shift') |
| if (event.metaKey) parts.push(isMac ? 'Cmd' : 'Win') |
|
|
| const key = event.key |
|
|
| |
| if (MODIFIER_NAMES.has(key)) { |
| return '' |
| } |
|
|
| |
| let displayKey = key.length === 1 ? key.toUpperCase() : key |
| if (displayKey === ' ') displayKey = 'Space' |
| parts.push(displayKey) |
|
|
| return parts.join('+') |
| } |
|
|
| const BLOCKED_KEYS = new Set([ |
| 'Escape', |
| 'Tab', |
| 'CapsLock', |
| 'Meta', |
| 'ContextMenu', |
| 'ScrollLock', |
| 'NumLock', |
| 'Pause', |
| 'Insert', |
| ]) |
|
|
| |
| |
| |
| export function isKeyBlocked(key: string): boolean { |
| const isFunctionKey = key.startsWith('F') && key.length > 1 && !isNaN(Number(key.slice(1))) |
|
|
| return isFunctionKey || BLOCKED_KEYS.has(key) |
| } |
|
|
| |
| |
| |
| export function areShortcutsEqual(a: Record<string, string>, b: Record<string, string>): boolean { |
| const keysA = Object.keys(a) |
| const keysB = Object.keys(b) |
|
|
| if (keysA.length !== keysB.length) return false |
|
|
| for (let i = 0; i < keysA.length; i++) { |
| const key = keysA[i] |
| if (a[key] !== b[key]) return false |
| } |
|
|
| return true |
| } |
|
|
| |
| |
| |
| export function isModifierKey(key: string): boolean { |
| return MODIFIER_NAMES.has(key) |
| } |
|
|
| |
| |
| |
| |
| |
| export function formatShortcutForDisplay(shortcut: string, isMac: boolean): string { |
| if (!shortcut) return '' |
|
|
| const parts = shortcut.split('+') |
| if (!isMac) return parts.join('+') |
|
|
| |
| const symbols: Record<string, string> = { |
| Ctrl: '⌃', |
| Opt: '⌥', |
| Shift: '⇧', |
| Cmd: '⌘', |
| } |
|
|
| |
| const mappedParts = parts.map((part) => symbols[part] || part) |
|
|
| |
| |
| const ORDER = ['⌃', '⌥', '⇧', '⌘'] |
| const modifiers = mappedParts.filter((p) => ORDER.includes(p)) |
| const key = mappedParts.find((p) => !ORDER.includes(p)) |
|
|
| modifiers.sort((a, b) => ORDER.indexOf(a) - ORDER.indexOf(b)) |
|
|
| return modifiers.join('') + (key || '') |
| } |
|
|