From a2c7becaf42cf3e1b2fad71e97d5f60637d79d9f Mon Sep 17 00:00:00 2001 From: Ovgodd Date: Tue, 10 Feb 2026 19:02:41 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F(frontend)=20centralize=20sho?= =?UTF-8?q?rtcuts=20in=20a=20catalog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Centralize shortcuts into a single source of truth, making them easier to discover and manage, and laying the groundwork for future override support and the ability to revert to default definitions if needed. Shortcuts are now retrieved by identifier, while leaving each component responsible for declaring when a shortcut should be enabled and which handler should be called; --- .../controls/Device/ToggleDevice.tsx | 11 +++-- .../rooms/livekit/hooks/useDeviceShortcut.ts | 17 +++---- .../prefabs/ControlBar/DesktopControlBar.tsx | 2 +- .../src/features/shortcuts/catalog.ts | 47 +++++++++++++++++++ .../shortcuts/useRegisterKeyboardShortcut.ts | 14 +++--- 5 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 src/frontend/src/features/shortcuts/catalog.ts diff --git a/src/frontend/src/features/rooms/livekit/components/controls/Device/ToggleDevice.tsx b/src/frontend/src/features/rooms/livekit/components/controls/Device/ToggleDevice.tsx index 84ce2277..96359e1b 100644 --- a/src/frontend/src/features/rooms/livekit/components/controls/Device/ToggleDevice.tsx +++ b/src/frontend/src/features/rooms/livekit/components/controls/Device/ToggleDevice.tsx @@ -18,6 +18,7 @@ import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice' import { useDeviceIcons } from '../../../hooks/useDeviceIcons' import { useDeviceShortcut } from '../../../hooks/useDeviceShortcut' import { ToggleSource, CaptureOptionsBySource } from '@livekit/components-core' +import { getShortcutDescriptorById } from '@/features/shortcuts/catalog' type ToggleDeviceStyleProps = { variant?: NonNullable['variant'] @@ -88,12 +89,14 @@ export const ToggleDevice = ({ const deviceShortcut = useDeviceShortcut(kind) useRegisterKeyboardShortcut({ - shortcut: deviceShortcut, + id: deviceShortcut?.id, handler: async () => await toggle(), isDisabled: cannotUseDevice, }) + + const pushToTalkShortcut = getShortcutDescriptorById('push-to-talk') useLongPress({ - keyCode: kind === 'audioinput' ? 'KeyV' : undefined, + keyCode: kind === 'audioinput' ? pushToTalkShortcut?.code : undefined, onKeyDown, onKeyUp, isDisabled: cannotUseDevice, @@ -103,7 +106,9 @@ export const ToggleDevice = ({ const label = t(enabled ? 'disable' : 'enable', { keyPrefix: `selectDevice.${kind}`, }) - return deviceShortcut ? appendShortcutLabel(label, deviceShortcut) : label + return deviceShortcut?.shortcut + ? appendShortcutLabel(label, deviceShortcut.shortcut) + : label }, [enabled, kind, deviceShortcut, t]) const Icon = diff --git a/src/frontend/src/features/rooms/livekit/hooks/useDeviceShortcut.ts b/src/frontend/src/features/rooms/livekit/hooks/useDeviceShortcut.ts index b47ed3f2..f7ae37aa 100644 --- a/src/frontend/src/features/rooms/livekit/hooks/useDeviceShortcut.ts +++ b/src/frontend/src/features/rooms/livekit/hooks/useDeviceShortcut.ts @@ -1,19 +1,16 @@ import { useMemo } from 'react' -import { Shortcut } from '@/features/shortcuts/types' +import { + getShortcutDescriptorById, + ShortcutDescriptor, +} from '@/features/shortcuts/catalog' export const useDeviceShortcut = (kind: MediaDeviceKind) => { - return useMemo(() => { + return useMemo(() => { switch (kind) { case 'audioinput': - return { - key: 'd', - ctrlKey: true, - } + return getShortcutDescriptorById('toggle-microphone') case 'videoinput': - return { - key: 'e', - ctrlKey: true, - } + return getShortcutDescriptorById('toggle-camera') default: return undefined } diff --git a/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/DesktopControlBar.tsx b/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/DesktopControlBar.tsx index dce26463..de7897f2 100644 --- a/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/DesktopControlBar.tsx +++ b/src/frontend/src/features/rooms/livekit/prefabs/ControlBar/DesktopControlBar.tsx @@ -22,7 +22,7 @@ export function DesktopControlBar({ const desktopControlBarEl = useRef(null) useRegisterKeyboardShortcut({ - shortcut: { key: 'F2' }, + id: 'focus-toolbar', handler: () => { const root = desktopControlBarEl.current if (!root) return diff --git a/src/frontend/src/features/shortcuts/catalog.ts b/src/frontend/src/features/shortcuts/catalog.ts new file mode 100644 index 00000000..ff85de56 --- /dev/null +++ b/src/frontend/src/features/shortcuts/catalog.ts @@ -0,0 +1,47 @@ +import { Shortcut } from './types' + +// Central list of current keyboard shortcuts. +// Keep a single source of truth for display and, later, customization +export type ShortcutCategory = 'navigation' | 'media' | 'interaction' + +export type ShortcutId = + | 'focus-toolbar' + | 'toggle-microphone' + | 'toggle-camera' + | 'push-to-talk' + +export const getShortcutDescriptorById = (id: ShortcutId) => + shortcutCatalog.find((item) => item.id === id) + +export type ShortcutDescriptor = { + id: ShortcutId + category: ShortcutCategory + shortcut?: Shortcut + kind?: 'press' | 'longPress' + code?: string // used when kind === 'longPress' (KeyboardEvent.code) + description?: string +} + +export const shortcutCatalog: ShortcutDescriptor[] = [ + { + id: 'focus-toolbar', + category: 'navigation', + shortcut: { key: 'F2' }, + }, + { + id: 'toggle-microphone', + category: 'media', + shortcut: { key: 'd', ctrlKey: true }, + }, + { + id: 'toggle-camera', + category: 'media', + shortcut: { key: 'e', ctrlKey: true }, + }, + { + id: 'push-to-talk', + category: 'media', + kind: 'longPress', + code: 'KeyV', + }, +] diff --git a/src/frontend/src/features/shortcuts/useRegisterKeyboardShortcut.ts b/src/frontend/src/features/shortcuts/useRegisterKeyboardShortcut.ts index f39b1141..0e8ab03c 100644 --- a/src/frontend/src/features/shortcuts/useRegisterKeyboardShortcut.ts +++ b/src/frontend/src/features/shortcuts/useRegisterKeyboardShortcut.ts @@ -1,26 +1,28 @@ import { useEffect } from 'react' import { keyboardShortcutsStore } from '@/stores/keyboardShortcuts' import { formatShortcutKey } from '@/features/shortcuts/utils' -import { Shortcut } from '@/features/shortcuts/types' +import { ShortcutId, getShortcutDescriptorById } from './catalog' export type useRegisterKeyboardShortcutProps = { - shortcut?: Shortcut + id?: ShortcutId handler: () => Promise | void isDisabled?: boolean } export const useRegisterKeyboardShortcut = ({ - shortcut, + id, handler, isDisabled = false, }: useRegisterKeyboardShortcutProps) => { useEffect(() => { - if (!shortcut) return - const formattedKey = formatShortcutKey(shortcut) + if (!id) return + const descriptor = getShortcutDescriptorById(id) + if (!descriptor?.shortcut) return + const formattedKey = formatShortcutKey(descriptor.shortcut) if (isDisabled) { keyboardShortcutsStore.shortcuts.delete(formattedKey) } else { keyboardShortcutsStore.shortcuts.set(formattedKey, handler) } - }, [handler, shortcut, isDisabled]) + }, [handler, id, isDisabled]) }