♻️(frontend) centralize shortcuts in a catalog

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;
This commit is contained in:
Ovgodd
2026-02-10 19:02:41 +01:00
committed by aleb_the_flash
parent 89031abb63
commit a2c7becaf4
5 changed files with 71 additions and 20 deletions

View File

@@ -18,6 +18,7 @@ import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice'
import { useDeviceIcons } from '../../../hooks/useDeviceIcons' import { useDeviceIcons } from '../../../hooks/useDeviceIcons'
import { useDeviceShortcut } from '../../../hooks/useDeviceShortcut' import { useDeviceShortcut } from '../../../hooks/useDeviceShortcut'
import { ToggleSource, CaptureOptionsBySource } from '@livekit/components-core' import { ToggleSource, CaptureOptionsBySource } from '@livekit/components-core'
import { getShortcutDescriptorById } from '@/features/shortcuts/catalog'
type ToggleDeviceStyleProps = { type ToggleDeviceStyleProps = {
variant?: NonNullable<ButtonRecipeProps>['variant'] variant?: NonNullable<ButtonRecipeProps>['variant']
@@ -88,12 +89,14 @@ export const ToggleDevice = <T extends ToggleSource>({
const deviceShortcut = useDeviceShortcut(kind) const deviceShortcut = useDeviceShortcut(kind)
useRegisterKeyboardShortcut({ useRegisterKeyboardShortcut({
shortcut: deviceShortcut, id: deviceShortcut?.id,
handler: async () => await toggle(), handler: async () => await toggle(),
isDisabled: cannotUseDevice, isDisabled: cannotUseDevice,
}) })
const pushToTalkShortcut = getShortcutDescriptorById('push-to-talk')
useLongPress({ useLongPress({
keyCode: kind === 'audioinput' ? 'KeyV' : undefined, keyCode: kind === 'audioinput' ? pushToTalkShortcut?.code : undefined,
onKeyDown, onKeyDown,
onKeyUp, onKeyUp,
isDisabled: cannotUseDevice, isDisabled: cannotUseDevice,
@@ -103,7 +106,9 @@ export const ToggleDevice = <T extends ToggleSource>({
const label = t(enabled ? 'disable' : 'enable', { const label = t(enabled ? 'disable' : 'enable', {
keyPrefix: `selectDevice.${kind}`, keyPrefix: `selectDevice.${kind}`,
}) })
return deviceShortcut ? appendShortcutLabel(label, deviceShortcut) : label return deviceShortcut?.shortcut
? appendShortcutLabel(label, deviceShortcut.shortcut)
: label
}, [enabled, kind, deviceShortcut, t]) }, [enabled, kind, deviceShortcut, t])
const Icon = const Icon =

View File

@@ -1,19 +1,16 @@
import { useMemo } from 'react' import { useMemo } from 'react'
import { Shortcut } from '@/features/shortcuts/types' import {
getShortcutDescriptorById,
ShortcutDescriptor,
} from '@/features/shortcuts/catalog'
export const useDeviceShortcut = (kind: MediaDeviceKind) => { export const useDeviceShortcut = (kind: MediaDeviceKind) => {
return useMemo<Shortcut | undefined>(() => { return useMemo<ShortcutDescriptor | undefined>(() => {
switch (kind) { switch (kind) {
case 'audioinput': case 'audioinput':
return { return getShortcutDescriptorById('toggle-microphone')
key: 'd',
ctrlKey: true,
}
case 'videoinput': case 'videoinput':
return { return getShortcutDescriptorById('toggle-camera')
key: 'e',
ctrlKey: true,
}
default: default:
return undefined return undefined
} }

View File

@@ -22,7 +22,7 @@ export function DesktopControlBar({
const desktopControlBarEl = useRef<HTMLDivElement>(null) const desktopControlBarEl = useRef<HTMLDivElement>(null)
useRegisterKeyboardShortcut({ useRegisterKeyboardShortcut({
shortcut: { key: 'F2' }, id: 'focus-toolbar',
handler: () => { handler: () => {
const root = desktopControlBarEl.current const root = desktopControlBarEl.current
if (!root) return if (!root) return

View File

@@ -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',
},
]

View File

@@ -1,26 +1,28 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { keyboardShortcutsStore } from '@/stores/keyboardShortcuts' import { keyboardShortcutsStore } from '@/stores/keyboardShortcuts'
import { formatShortcutKey } from '@/features/shortcuts/utils' import { formatShortcutKey } from '@/features/shortcuts/utils'
import { Shortcut } from '@/features/shortcuts/types' import { ShortcutId, getShortcutDescriptorById } from './catalog'
export type useRegisterKeyboardShortcutProps = { export type useRegisterKeyboardShortcutProps = {
shortcut?: Shortcut id?: ShortcutId
handler: () => Promise<void | boolean | undefined> | void handler: () => Promise<void | boolean | undefined> | void
isDisabled?: boolean isDisabled?: boolean
} }
export const useRegisterKeyboardShortcut = ({ export const useRegisterKeyboardShortcut = ({
shortcut, id,
handler, handler,
isDisabled = false, isDisabled = false,
}: useRegisterKeyboardShortcutProps) => { }: useRegisterKeyboardShortcutProps) => {
useEffect(() => { useEffect(() => {
if (!shortcut) return if (!id) return
const formattedKey = formatShortcutKey(shortcut) const descriptor = getShortcutDescriptorById(id)
if (!descriptor?.shortcut) return
const formattedKey = formatShortcutKey(descriptor.shortcut)
if (isDisabled) { if (isDisabled) {
keyboardShortcutsStore.shortcuts.delete(formattedKey) keyboardShortcutsStore.shortcuts.delete(formattedKey)
} else { } else {
keyboardShortcutsStore.shortcuts.set(formattedKey, handler) keyboardShortcutsStore.shortcuts.set(formattedKey, handler)
} }
}, [handler, shortcut, isDisabled]) }, [handler, id, isDisabled])
} }