1import {
2 isModifierPairShortcut,
3 snapShotModifierPairLabel,
4 snapShotShortcutModifierPair,
5 type KeybindingShortcut,
6 type SnapShotModifier,
7 type SnapShotShortcut,
8} from "@t3tools/contracts";
9import { parseKeybindingShortcut } from "@t3tools/shared/keybindings";
10
11import { formatShortcutKeyLabel, formatShortcutLabel, shortcutConflictKey } from "../keybindings";
12import { isMacPlatform, isWindowsPlatform } from "./utils";
13
14const DESKTOP_KEY_ALIASES: Readonly<Record<string, string>> = {
15 return: "enter",
16 page_up: "pageup",
17 page_down: "pagedown",
18 left: "arrowleft",
19 right: "arrowright",
20 up: "arrowup",
21 down: "arrowdown",
22 plus: "+",
23 minus: "-",
24};
25
26/** Portal labels are descriptions, not a keybinding protocol. Only parse known key notation. */
27export function parseDesktopSnapShotShortcut(label: string): KeybindingShortcut | null {
28 const text = label.trim().replace(/^Press\s+/i, "");
29 if (!text) return null;
30 const gtk = text.match(/^((?:<[^<>]+>)+)([^<>]+)$/);
31 const input = (gtk ? gtk[1]!.replace(/<([^<>]+)>/g, "$1+") + gtk[2]! : text).replace(
32 /\b(super|win|primary)\b/gi,
33 (modifier) => (modifier.toLowerCase() === "primary" ? "ctrl" : "meta"),
34 );
35 if (input !== "+" && input.endsWith("+") && !input.endsWith("++")) return null;
36 const shortcut = parseKeybindingShortcut(input);
37 if (!shortcut) return null;
38 const key = DESKTOP_KEY_ALIASES[shortcut.key] ?? shortcut.key;
39 if (
40 key !== " " &&
41 !/^[^\s<>]$/u.test(key) &&
42 !/^(?:f(?:[1-9]|1\d|2[0-4])|enter|tab|escape|backspace|delete|insert|home|end|pageup|pagedown|arrow(?:left|right|up|down)|pause|printscreen)$/i.test(
43 key,
44 )
45 ) {
46 return null;
47 }
48 return { ...shortcut, key };
49}
50
51export function formatSnapShotShortcutLabel(
52 shortcut: SnapShotShortcut,
53 platform = navigator.platform,
54): string {
55 return isModifierPairShortcut(shortcut)
56 ? snapShotModifierPairLabel(snapShotShortcutModifierPair(shortcut), isMacPlatform(platform))
57 : formatShortcutLabel(shortcut, platform);
58}
59
60function modifierKeyLabel(modifier: SnapShotModifier, platform: string): string {
61 if (modifier === "shift") return "⇧";
62 if (isMacPlatform(platform)) {
63 if (modifier === "meta") return "⌘";
64 if (modifier === "control") return "⌃";
65 return "⌥";
66 }
67 if (modifier === "meta") return isWindowsPlatform(platform) ? "⊞" : "Super";
68 return modifier === "control" ? "Ctrl" : "Alt";
69}
70
71export function snapShotShortcutKeyLabels(
72 shortcut: SnapShotShortcut,
73 platform = navigator.platform,
74): readonly string[] {
75 if (isModifierPairShortcut(shortcut)) {
76 const label = modifierKeyLabel(snapShotShortcutModifierPair(shortcut), platform);
77 return [label, label];
78 }
79
80 const useMetaForMod = isMacPlatform(platform);
81 const modifiers: readonly [boolean, SnapShotModifier][] = [
82 [shortcut.ctrlKey || (shortcut.modKey && !useMetaForMod), "control"],
83 [shortcut.altKey, "alt"],
84 [shortcut.shiftKey, "shift"],
85 [shortcut.metaKey || (shortcut.modKey && useMetaForMod), "meta"],
86 ];
87 return [
88 ...modifiers
89 .filter(([enabled]) => enabled)
90 .map(([, modifier]) => modifierKeyLabel(modifier, platform)),
91 formatShortcutKeyLabel(shortcut.key),
92 ];
93}
94
95export function sameSnapShotShortcut(
96 left: SnapShotShortcut,
97 right: SnapShotShortcut,
98 platform = navigator.platform,
99): boolean {
100 if (isModifierPairShortcut(left) || isModifierPairShortcut(right)) {
101 return (
102 isModifierPairShortcut(left) &&
103 isModifierPairShortcut(right) &&
104 snapShotShortcutModifierPair(left) === snapShotShortcutModifierPair(right)
105 );
106 }
107 return shortcutConflictKey(left, platform) === shortcutConflictKey(right, platform);
108}
109
110export function snapShotKeybindingConflict<Command extends string>(
111 shortcut: SnapShotShortcut,
112 keybindings: ReadonlyArray<{ readonly command: Command; readonly shortcut: KeybindingShortcut }>,
113 platform = navigator.platform,
114): Command | null {
115 if (isModifierPairShortcut(shortcut)) return null;
116 const key = shortcutConflictKey(shortcut, platform);
117 return (
118 keybindings.find((binding) => shortcutConflictKey(binding.shortcut, platform) === key)
119 ?.command ?? null
120 );
121}