From 188c47026f6f675d5a6af482954fedf1767fc21d Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Fri, 12 Dec 2025 20:50:11 -0500 Subject: [PATCH] fix(desktop): show platform-specific hotkey symbols MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mac: ⌘ ⌃ ⌥ ⇧ - Windows: Win Ctrl Alt Shift - Linux: Super Ctrl Alt Shift Refactored hotkey system to pre-compute display symbols at build time using PLATFORM constants instead of runtime navigator parsing. This: - Adds `.display` property to all HOTKEYS entries - Removes `formatKeysForDisplay` export (now internal) - Removes `useIsMac` hook from components - Centralizes all platform logic in hotkeys.ts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../KeyboardShortcutsSettings.tsx | 31 ++- .../components/TopBar/HelpMenu/HelpMenu.tsx | 5 +- .../TopBar/SettingsButton/SettingsButton.tsx | 5 +- .../main/components/TopBar/SidebarControl.tsx | 5 +- .../ContentView/TabsContent/EmptyTabView.tsx | 4 +- apps/desktop/src/shared/hotkeys.ts | 182 +++++++++--------- 6 files changed, 115 insertions(+), 117 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/SettingsView/KeyboardShortcutsSettings.tsx b/apps/desktop/src/renderer/screens/main/components/SettingsView/KeyboardShortcutsSettings.tsx index fc9fa2c6168..5cdcff698ea 100644 --- a/apps/desktop/src/renderer/screens/main/components/SettingsView/KeyboardShortcutsSettings.tsx +++ b/apps/desktop/src/renderer/screens/main/components/SettingsView/KeyboardShortcutsSettings.tsx @@ -1,22 +1,14 @@ import { Input } from "@superset/ui/input"; import { Kbd, KbdGroup } from "@superset/ui/kbd"; -import { useMemo, useState } from "react"; +import { useState } from "react"; import { HiMagnifyingGlass } from "react-icons/hi2"; import { - formatKeysForDisplay, getHotkeysByCategory, + HOTKEYS, type HotkeyCategory, - type HotkeyDefinition, + type HotkeyWithDisplay, } from "shared/hotkeys"; -function useIsMac(): boolean { - return useMemo(() => { - const platform = navigator.platform?.toUpperCase() ?? ""; - const userAgent = navigator.userAgent?.toUpperCase() ?? ""; - return platform.includes("MAC") || userAgent.includes("MAC"); - }, []); -} - const CATEGORY_ORDER: HotkeyCategory[] = [ "Workspace", "Terminal", @@ -29,11 +21,9 @@ function HotkeyRow({ hotkey, isEven, }: { - hotkey: HotkeyDefinition; + hotkey: HotkeyWithDisplay; isEven: boolean; }) { - const keys = formatKeysForDisplay(hotkey.keys); - return (
{hotkey.label} - {keys.map((key) => ( + {hotkey.display.map((key) => ( {key} ))} @@ -54,8 +44,8 @@ function HotkeyRow({ * Consolidate individual workspace jump shortcuts (1-9) into a single entry */ function consolidateWorkspaceJumps( - hotkeys: HotkeyDefinition[], -): HotkeyDefinition[] { + hotkeys: HotkeyWithDisplay[], +): HotkeyWithDisplay[] { const workspaceJumpPattern = /^Switch to Workspace \d$/; const hasWorkspaceJumps = hotkeys.some((h) => workspaceJumpPattern.test(h.label), @@ -64,10 +54,13 @@ function consolidateWorkspaceJumps( if (!hasWorkspaceJumps) return hotkeys; const filtered = hotkeys.filter((h) => !workspaceJumpPattern.test(h.label)); + // Reuse the meta key symbol from an existing hotkey's display + const [metaKey] = HOTKEYS.JUMP_TO_WORKSPACE_1.display; filtered.unshift({ keys: "meta+1-9", label: "Switch to Workspace 1-9", category: "Workspace", + display: [metaKey, "1-9"], }); return filtered; @@ -76,8 +69,8 @@ function consolidateWorkspaceJumps( export function KeyboardShortcutsSettings() { const [searchQuery, setSearchQuery] = useState(""); const hotkeysByCategory = getHotkeysByCategory(); - const isMac = useIsMac(); - const modifierKey = isMac ? "⌘" : "Ctrl"; + // Reuse the meta key symbol from SHOW_HOTKEYS display + const [modifierKey] = HOTKEYS.SHOW_HOTKEYS.display; // Flatten and consolidate all hotkeys const allHotkeys = CATEGORY_ORDER.flatMap((category) => diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/HelpMenu/HelpMenu.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/HelpMenu/HelpMenu.tsx index d4ca820c0bc..344da7ad6c8 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/HelpMenu/HelpMenu.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/HelpMenu/HelpMenu.tsx @@ -16,11 +16,10 @@ import { } from "react-icons/hi2"; import { useOpenSettings } from "renderer/stores"; import { HELP_MENU } from "shared/constants"; -import { formatKeysForDisplay, HOTKEYS } from "shared/hotkeys"; +import { HOTKEYS } from "shared/hotkeys"; export function HelpMenu() { const openSettings = useOpenSettings(); - const hotkeyKeys = formatKeysForDisplay(HOTKEYS.SHOW_HOTKEYS.keys); const handleContactUs = () => { window.open(HELP_MENU.CONTACT_URL, "_blank"); @@ -74,7 +73,7 @@ export function HelpMenu() { Keyboard Shortcuts - {hotkeyKeys.map((key) => ( + {HOTKEYS.SHOW_HOTKEYS.display.map((key) => ( {key} ))} diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/SettingsButton/SettingsButton.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/SettingsButton/SettingsButton.tsx index 43ced9d205f..9ddaf78358c 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/SettingsButton/SettingsButton.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/SettingsButton/SettingsButton.tsx @@ -2,11 +2,10 @@ import { Kbd, KbdGroup } from "@superset/ui/kbd"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { HiOutlineCog6Tooth } from "react-icons/hi2"; import { useOpenSettings } from "renderer/stores"; -import { formatKeysForDisplay, HOTKEYS } from "shared/hotkeys"; +import { HOTKEYS } from "shared/hotkeys"; export function SettingsButton() { const openSettings = useOpenSettings(); - const keys = formatKeysForDisplay(HOTKEYS.SHOW_HOTKEYS.keys); return ( @@ -24,7 +23,7 @@ export function SettingsButton() { Settings - {keys.map((key) => ( + {HOTKEYS.SHOW_HOTKEYS.display.map((key) => ( {key} ))} diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/SidebarControl.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/SidebarControl.tsx index c2b71e587c5..e729d806578 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/SidebarControl.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/SidebarControl.tsx @@ -3,11 +3,10 @@ import { Kbd, KbdGroup } from "@superset/ui/kbd"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { HiMiniBars3, HiMiniBars3BottomLeft } from "react-icons/hi2"; import { useSidebarStore } from "renderer/stores"; -import { formatKeysForDisplay, HOTKEYS } from "shared/hotkeys"; +import { HOTKEYS } from "shared/hotkeys"; export function SidebarControl() { const { isSidebarOpen, toggleSidebar } = useSidebarStore(); - const keys = formatKeysForDisplay(HOTKEYS.TOGGLE_SIDEBAR.keys); return ( @@ -30,7 +29,7 @@ export function SidebarControl() { Toggle sidebar - {keys.map((key) => ( + {HOTKEYS.TOGGLE_SIDEBAR.display.map((key) => ( {key} ))} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx index c1204cb51d1..d59d010c0d5 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/EmptyTabView.tsx @@ -1,6 +1,6 @@ import { Kbd, KbdGroup } from "@superset/ui/kbd"; import { HiMiniCommandLine } from "react-icons/hi2"; -import { formatKeysForDisplay, HOTKEYS } from "shared/hotkeys"; +import { HOTKEYS } from "shared/hotkeys"; const shortcuts = [HOTKEYS.NEW_TERMINAL, HOTKEYS.OPEN_IN_APP]; @@ -17,7 +17,7 @@ export function EmptyTabView() { {shortcuts.map((shortcut) => (
- {formatKeysForDisplay(shortcut.keys).map((key) => ( + {shortcut.display.map((key) => ( {key} ))} diff --git a/apps/desktop/src/shared/hotkeys.ts b/apps/desktop/src/shared/hotkeys.ts index 2093ee940e7..02d7a1cbbcf 100644 --- a/apps/desktop/src/shared/hotkeys.ts +++ b/apps/desktop/src/shared/hotkeys.ts @@ -3,6 +3,43 @@ * Used both for registering shortcuts and displaying in the hotkey modal. */ +import { PLATFORM } from "./constants"; + +// Platform-specific modifier key symbols +const MODIFIER_MAP = PLATFORM.IS_MAC + ? { meta: "⌘", ctrl: "⌃", alt: "⌥", shift: "⇧" } + : PLATFORM.IS_WINDOWS + ? { meta: "Win", ctrl: "Ctrl", alt: "Alt", shift: "Shift" } + : { meta: "Super", ctrl: "Ctrl", alt: "Alt", shift: "Shift" }; + +const KEY_MAP: Record = { + ...MODIFIER_MAP, + enter: "↵", + backspace: "⌫", + delete: "⌦", + escape: "⎋", + tab: "⇥", + up: "↑", + down: "↓", + left: "←", + right: "→", + space: "␣", + slash: "/", +}; + +/** Format a key string for display (e.g., "meta+shift+d" -> ["⌘", "⇧", "D"]) */ +function formatKeys(keys: string): string[] { + return keys.split("+").map((key) => { + const lower = key.toLowerCase(); + return KEY_MAP[lower] || key.toUpperCase(); + }); +} + +/** Helper to define a hotkey with pre-computed display */ +function hotkey(def: T): T & { display: string[] } { + return { ...def, display: formatKeys(def.keys) }; +} + export type HotkeyCategory = | "Workspace" | "Layout" @@ -10,7 +47,7 @@ export type HotkeyCategory = | "Window" | "Help"; -export interface HotkeyDefinition { +interface HotkeyDefinition { /** Key combination for react-hotkeys-hook (e.g., "meta+s") */ keys: string; /** Human-readable label for display */ @@ -27,156 +64,158 @@ export interface HotkeyDefinition { */ export const HOTKEYS = { // Workspace - switch with ⌘+1-9 - JUMP_TO_WORKSPACE_1: { + JUMP_TO_WORKSPACE_1: hotkey({ keys: "meta+1", label: "Switch to Workspace 1", category: "Workspace", - }, - JUMP_TO_WORKSPACE_2: { + }), + JUMP_TO_WORKSPACE_2: hotkey({ keys: "meta+2", label: "Switch to Workspace 2", category: "Workspace", - }, - JUMP_TO_WORKSPACE_3: { + }), + JUMP_TO_WORKSPACE_3: hotkey({ keys: "meta+3", label: "Switch to Workspace 3", category: "Workspace", - }, - JUMP_TO_WORKSPACE_4: { + }), + JUMP_TO_WORKSPACE_4: hotkey({ keys: "meta+4", label: "Switch to Workspace 4", category: "Workspace", - }, - JUMP_TO_WORKSPACE_5: { + }), + JUMP_TO_WORKSPACE_5: hotkey({ keys: "meta+5", label: "Switch to Workspace 5", category: "Workspace", - }, - JUMP_TO_WORKSPACE_6: { + }), + JUMP_TO_WORKSPACE_6: hotkey({ keys: "meta+6", label: "Switch to Workspace 6", category: "Workspace", - }, - JUMP_TO_WORKSPACE_7: { + }), + JUMP_TO_WORKSPACE_7: hotkey({ keys: "meta+7", label: "Switch to Workspace 7", category: "Workspace", - }, - JUMP_TO_WORKSPACE_8: { + }), + JUMP_TO_WORKSPACE_8: hotkey({ keys: "meta+8", label: "Switch to Workspace 8", category: "Workspace", - }, - JUMP_TO_WORKSPACE_9: { + }), + JUMP_TO_WORKSPACE_9: hotkey({ keys: "meta+9", label: "Switch to Workspace 9", category: "Workspace", - }, - PREV_WORKSPACE: { + }), + PREV_WORKSPACE: hotkey({ keys: "meta+left", label: "Previous Workspace", category: "Workspace", - }, - NEXT_WORKSPACE: { + }), + NEXT_WORKSPACE: hotkey({ keys: "meta+right", label: "Next Workspace", category: "Workspace", - }, + }), // Layout - TOGGLE_SIDEBAR: { + TOGGLE_SIDEBAR: hotkey({ keys: "meta+b", label: "Toggle Sidebar", category: "Layout", - }, - SPLIT_RIGHT: { + }), + SPLIT_RIGHT: hotkey({ keys: "meta+d", label: "Split Right", category: "Layout", description: "Split the current pane to the right", - }, - SPLIT_DOWN: { + }), + SPLIT_DOWN: hotkey({ keys: "meta+shift+d", label: "Split Down", category: "Layout", description: "Split the current pane downward", - }, - SPLIT_AUTO: { + }), + SPLIT_AUTO: hotkey({ keys: "meta+e", label: "Split Pane Auto", category: "Layout", description: "Split the current pane along its longer side", - }, + }), // Terminal - FIND_IN_TERMINAL: { + FIND_IN_TERMINAL: hotkey({ keys: "meta+f", label: "Find in Terminal", category: "Terminal", description: "Search text in the active terminal", - }, - NEW_TERMINAL: { + }), + NEW_TERMINAL: hotkey({ keys: "meta+t", label: "New Terminal", category: "Terminal", - }, - CLOSE_TERMINAL: { + }), + CLOSE_TERMINAL: hotkey({ keys: "meta+w", label: "Close Terminal", category: "Terminal", - }, - CLEAR_TERMINAL: { + }), + CLEAR_TERMINAL: hotkey({ keys: "meta+k", label: "Clear Terminal", category: "Terminal", - }, - PREV_TERMINAL: { + }), + PREV_TERMINAL: hotkey({ keys: "meta+up", label: "Previous Terminal", category: "Terminal", - }, - NEXT_TERMINAL: { + }), + NEXT_TERMINAL: hotkey({ keys: "meta+down", label: "Next Terminal", category: "Terminal", - }, + }), // Window - NEW_WINDOW: { + NEW_WINDOW: hotkey({ keys: "meta+shift+n", label: "New Window", category: "Window", - }, - CLOSE_WINDOW: { + }), + CLOSE_WINDOW: hotkey({ keys: "meta+shift+w", label: "Close Window", category: "Window", - }, - OPEN_IN_APP: { + }), + OPEN_IN_APP: hotkey({ keys: "meta+o", label: "Open in App", category: "Window", description: "Open workspace in external app (Cursor, VS Code, etc.)", - }, + }), // Help - SHOW_HOTKEYS: { + SHOW_HOTKEYS: hotkey({ keys: "meta+slash", label: "Show Keyboard Shortcuts", category: "Help", - }, -} as const satisfies Record; + }), +} as const satisfies Record; export type HotkeyId = keyof typeof HOTKEYS; +export type HotkeyWithDisplay = HotkeyDefinition & { display: string[] }; + /** * Get all hotkeys grouped by category for display purposes. */ export function getHotkeysByCategory(): Record< HotkeyCategory, - HotkeyDefinition[] + HotkeyWithDisplay[] > { - const grouped: Record = { + const grouped: Record = { Workspace: [], Layout: [], Terminal: [], @@ -191,41 +230,10 @@ export function getHotkeysByCategory(): Record< return grouped; } -/** - * Format a key string for display (e.g., "meta+shift+d" -> ["⌘", "⇧", "D"]) - */ -export function formatKeysForDisplay(keys: string): string[] { - const keyMap: Record = { - meta: "⌘", - ctrl: "⌃", - alt: "⌥", - shift: "⇧", - enter: "↵", - backspace: "⌫", - delete: "⌦", - escape: "⎋", - tab: "⇥", - up: "↑", - down: "↓", - left: "←", - right: "→", - space: "␣", - slash: "/", - }; - - return keys.split("+").map((key) => { - const lower = key.toLowerCase(); - return keyMap[lower] || key.toUpperCase(); - }); -} - /** * Check if a keyboard event matches a hotkey string like "meta+shift+d" */ -export function matchesHotkey( - event: KeyboardEvent, - hotkeyString: string, -): boolean { +function matchesHotkey(event: KeyboardEvent, hotkeyString: string): boolean { const parts = hotkeyString.toLowerCase().split("+"); const requiresMeta = parts.includes("meta"); @@ -274,7 +282,7 @@ export function matchesHotkey( /** * Find which hotkey ID matches the keyboard event, if any */ -export function findMatchingHotkey(event: KeyboardEvent): HotkeyId | null { +function findMatchingHotkey(event: KeyboardEvent): HotkeyId | null { for (const [id, hotkey] of Object.entries(HOTKEYS)) { if (matchesHotkey(event, hotkey.keys)) { return id as HotkeyId;