♻️(frontend) replace toggle device config with keyboard shortcut hook
Remove ugly toggle device configuration and implement hook to determine appropriate keyboard shortcuts based on media device kind. Cleaner approach that encapsulates shortcut logic in reusable hook instead of scattered configuration objects.
This commit is contained in:
committed by
aleb_the_flash
parent
2367750395
commit
f17e0a3ba0
@@ -660,18 +660,16 @@ export const Join = ({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ToggleDevice
|
<ToggleDevice
|
||||||
source={Track.Source.Microphone}
|
kind="audioinput"
|
||||||
initialState={audioEnabled}
|
initialState={audioEnabled}
|
||||||
track={audioTrack}
|
track={audioTrack}
|
||||||
onChange={(enabled) => saveAudioInputEnabled(enabled)}
|
onChange={(enabled) => saveAudioInputEnabled(enabled)}
|
||||||
onDeviceError={(error) => console.error(error)}
|
|
||||||
/>
|
/>
|
||||||
<ToggleDevice
|
<ToggleDevice
|
||||||
source={Track.Source.Camera}
|
kind="videoinput"
|
||||||
initialState={videoEnabled}
|
initialState={videoEnabled}
|
||||||
track={videoTrack}
|
track={videoTrack}
|
||||||
onChange={(enabled) => saveVideoInputEnabled(enabled)}
|
onChange={(enabled) => saveVideoInputEnabled(enabled)}
|
||||||
onDeviceError={(error) => console.error(error)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,33 +1,32 @@
|
|||||||
import { UseTrackToggleProps } from '@livekit/components-react'
|
import { UseTrackToggleProps } from '@livekit/components-react'
|
||||||
import { ToggleDevice as BaseToggleDevice } from '../../livekit/components/controls/Device/ToggleDevice'
|
import { ToggleDevice as BaseToggleDevice } from '../../livekit/components/controls/Device/ToggleDevice'
|
||||||
import {
|
import { LocalAudioTrack, LocalVideoTrack, Track } from 'livekit-client'
|
||||||
TOGGLE_DEVICE_CONFIG,
|
|
||||||
ToggleSource,
|
|
||||||
} from '../../livekit/config/ToggleDeviceConfig'
|
|
||||||
import { LocalAudioTrack, LocalVideoTrack } from 'livekit-client'
|
|
||||||
import { ButtonRecipeProps } from '@/primitives/buttonRecipe'
|
import { ButtonRecipeProps } from '@/primitives/buttonRecipe'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
|
|
||||||
type ToggleDeviceProps<T extends ToggleSource> = UseTrackToggleProps<T> & {
|
type ToggleSource = Exclude<
|
||||||
|
Track.Source,
|
||||||
|
| Track.Source.ScreenShareAudio
|
||||||
|
| Track.Source.Unknown
|
||||||
|
| Track.Source.ScreenShare
|
||||||
|
>
|
||||||
|
|
||||||
|
type ToggleDeviceProps<T extends ToggleSource> = Pick<
|
||||||
|
UseTrackToggleProps<T>,
|
||||||
|
'onChange' | 'initialState'
|
||||||
|
> & {
|
||||||
track?: LocalAudioTrack | LocalVideoTrack
|
track?: LocalAudioTrack | LocalVideoTrack
|
||||||
source: ToggleSource
|
kind: MediaDeviceKind
|
||||||
variant?: NonNullable<ButtonRecipeProps>['variant']
|
variant?: NonNullable<ButtonRecipeProps>['variant']
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ToggleDevice = <T extends ToggleSource>({
|
export const ToggleDevice = <T extends ToggleSource>({
|
||||||
track,
|
track,
|
||||||
|
kind,
|
||||||
onChange,
|
onChange,
|
||||||
...props
|
initialState,
|
||||||
}: ToggleDeviceProps<T>) => {
|
}: ToggleDeviceProps<T>) => {
|
||||||
const config = TOGGLE_DEVICE_CONFIG[props.source]
|
const [isTrackEnabled, setIsTrackEnabled] = useState(initialState ?? false)
|
||||||
|
|
||||||
if (!config) {
|
|
||||||
throw new Error('Invalid source')
|
|
||||||
}
|
|
||||||
|
|
||||||
const [isTrackEnabled, setIsTrackEnabled] = useState(
|
|
||||||
props.initialState ?? false
|
|
||||||
)
|
|
||||||
|
|
||||||
const toggle = useCallback(async () => {
|
const toggle = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -49,7 +48,7 @@ export const ToggleDevice = <T extends ToggleSource>({
|
|||||||
<BaseToggleDevice
|
<BaseToggleDevice
|
||||||
enabled={isTrackEnabled}
|
enabled={isTrackEnabled}
|
||||||
toggle={toggle}
|
toggle={toggle}
|
||||||
config={config}
|
kind={kind}
|
||||||
variant="whiteCircle"
|
variant="whiteCircle"
|
||||||
errorVariant="errorCircle"
|
errorVariant="errorCircle"
|
||||||
toggleButtonProps={{
|
toggleButtonProps={{
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { ToggleDevice } from './ToggleDevice'
|
|||||||
import { css } from '@/styled-system/css'
|
import { css } from '@/styled-system/css'
|
||||||
import { usePersistentUserChoices } from '../../../hooks/usePersistentUserChoices'
|
import { usePersistentUserChoices } from '../../../hooks/usePersistentUserChoices'
|
||||||
import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice'
|
import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice'
|
||||||
import { ToggleDeviceConfig } from '../../../config/ToggleDeviceConfig'
|
|
||||||
import Source = Track.Source
|
import Source = Track.Source
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { SelectDevice } from './SelectDevice'
|
import { SelectDevice } from './SelectDevice'
|
||||||
@@ -24,16 +23,6 @@ export const AudioDevicesControl = ({
|
|||||||
hideMenu,
|
hideMenu,
|
||||||
...props
|
...props
|
||||||
}: AudioDevicesControlProps) => {
|
}: AudioDevicesControlProps) => {
|
||||||
const config: ToggleDeviceConfig = {
|
|
||||||
kind: 'audioinput',
|
|
||||||
shortcut: {
|
|
||||||
key: 'd',
|
|
||||||
ctrlKey: true,
|
|
||||||
},
|
|
||||||
longPress: {
|
|
||||||
key: 'Space',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -67,7 +56,7 @@ export const AudioDevicesControl = ({
|
|||||||
>
|
>
|
||||||
<ToggleDevice
|
<ToggleDevice
|
||||||
{...trackProps}
|
{...trackProps}
|
||||||
config={config}
|
kind="audioinput"
|
||||||
variant="primaryDark"
|
variant="primaryDark"
|
||||||
toggle={trackProps.toggle}
|
toggle={trackProps.toggle}
|
||||||
toggleButtonProps={{
|
toggleButtonProps={{
|
||||||
|
|||||||
@@ -14,21 +14,21 @@ import {
|
|||||||
import { ButtonRecipeProps } from '@/primitives/buttonRecipe'
|
import { ButtonRecipeProps } from '@/primitives/buttonRecipe'
|
||||||
import { ToggleButtonProps } from '@/primitives/ToggleButton'
|
import { ToggleButtonProps } from '@/primitives/ToggleButton'
|
||||||
import { openPermissionsDialog } from '@/stores/permissions'
|
import { openPermissionsDialog } from '@/stores/permissions'
|
||||||
import { ToggleDeviceConfig } from '../../../config/ToggleDeviceConfig'
|
|
||||||
import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice'
|
import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice'
|
||||||
import { useDeviceIcons } from '../../../hooks/useDeviceIcons'
|
import { useDeviceIcons } from '../../../hooks/useDeviceIcons'
|
||||||
|
import { useDeviceShortcut } from '../../../hooks/useDeviceShortcut'
|
||||||
|
|
||||||
export type ToggleDeviceProps = {
|
export type ToggleDeviceProps = {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
toggle: () => void
|
toggle: () => void
|
||||||
config: ToggleDeviceConfig
|
kind: 'audioinput' | 'videoinput'
|
||||||
variant?: NonNullable<ButtonRecipeProps>['variant']
|
variant?: NonNullable<ButtonRecipeProps>['variant']
|
||||||
errorVariant?: NonNullable<ButtonRecipeProps>['variant']
|
errorVariant?: NonNullable<ButtonRecipeProps>['variant']
|
||||||
toggleButtonProps?: Partial<ToggleButtonProps>
|
toggleButtonProps?: Partial<ToggleButtonProps>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ToggleDevice = ({
|
export const ToggleDevice = ({
|
||||||
config,
|
kind,
|
||||||
enabled,
|
enabled,
|
||||||
toggle,
|
toggle,
|
||||||
variant = 'primaryDark',
|
variant = 'primaryDark',
|
||||||
@@ -37,8 +37,6 @@ export const ToggleDevice = ({
|
|||||||
}: ToggleDeviceProps) => {
|
}: ToggleDeviceProps) => {
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
||||||
|
|
||||||
const { kind, shortcut, longPress } = config
|
|
||||||
|
|
||||||
const [pushToTalk, setPushToTalk] = useState(false)
|
const [pushToTalk, setPushToTalk] = useState(false)
|
||||||
|
|
||||||
const onKeyDown = () => {
|
const onKeyDown = () => {
|
||||||
@@ -54,16 +52,21 @@ export const ToggleDevice = ({
|
|||||||
|
|
||||||
const deviceIcons = useDeviceIcons(kind)
|
const deviceIcons = useDeviceIcons(kind)
|
||||||
const cannotUseDevice = useCannotUseDevice(kind)
|
const cannotUseDevice = useCannotUseDevice(kind)
|
||||||
|
const deviceShortcut = useDeviceShortcut(kind)
|
||||||
|
|
||||||
useRegisterKeyboardShortcut({ shortcut, handler: toggle })
|
useRegisterKeyboardShortcut({ shortcut: deviceShortcut, handler: toggle })
|
||||||
useLongPress({ keyCode: longPress?.key, onKeyDown, onKeyUp })
|
useLongPress({
|
||||||
|
keyCode: kind === 'audioinput' ? 'Space' : undefined,
|
||||||
|
onKeyDown,
|
||||||
|
onKeyUp,
|
||||||
|
})
|
||||||
|
|
||||||
const toggleLabel = useMemo(() => {
|
const toggleLabel = useMemo(() => {
|
||||||
const label = t(enabled ? 'disable' : 'enable', {
|
const label = t(enabled ? 'disable' : 'enable', {
|
||||||
keyPrefix: `join.${kind}`,
|
keyPrefix: `join.${kind}`,
|
||||||
})
|
})
|
||||||
return shortcut ? appendShortcutLabel(label, shortcut) : label
|
return deviceShortcut ? appendShortcutLabel(label, deviceShortcut) : label
|
||||||
}, [enabled, kind, shortcut, t])
|
}, [enabled, kind, deviceShortcut, t])
|
||||||
|
|
||||||
const Icon =
|
const Icon =
|
||||||
enabled && !cannotUseDevice ? deviceIcons.toggleOn : deviceIcons.toggleOff
|
enabled && !cannotUseDevice ? deviceIcons.toggleOn : deviceIcons.toggleOff
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { css } from '@/styled-system/css'
|
|||||||
import { usePersistentUserChoices } from '../../../hooks/usePersistentUserChoices'
|
import { usePersistentUserChoices } from '../../../hooks/usePersistentUserChoices'
|
||||||
import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice'
|
import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice'
|
||||||
import { BackgroundProcessorFactory } from '../../blur'
|
import { BackgroundProcessorFactory } from '../../blur'
|
||||||
import { ToggleDeviceConfig } from '../../../config/ToggleDeviceConfig'
|
|
||||||
import Source = Track.Source
|
import Source = Track.Source
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { SelectDevice } from './SelectDevice'
|
import { SelectDevice } from './SelectDevice'
|
||||||
@@ -25,14 +24,6 @@ export const VideoDeviceControl = ({
|
|||||||
hideMenu,
|
hideMenu,
|
||||||
...props
|
...props
|
||||||
}: VideoDeviceControlProps) => {
|
}: VideoDeviceControlProps) => {
|
||||||
const config: ToggleDeviceConfig = {
|
|
||||||
kind: 'videoinput',
|
|
||||||
shortcut: {
|
|
||||||
key: 'e',
|
|
||||||
ctrlKey: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
||||||
|
|
||||||
const { userChoices, saveVideoInputDeviceId, saveVideoInputEnabled } =
|
const { userChoices, saveVideoInputDeviceId, saveVideoInputEnabled } =
|
||||||
@@ -90,7 +81,7 @@ export const VideoDeviceControl = ({
|
|||||||
>
|
>
|
||||||
<ToggleDevice
|
<ToggleDevice
|
||||||
{...trackProps}
|
{...trackProps}
|
||||||
config={config}
|
kind="videoinput"
|
||||||
variant="primaryDark"
|
variant="primaryDark"
|
||||||
toggle={toggle}
|
toggle={toggle}
|
||||||
toggleButtonProps={{
|
toggleButtonProps={{
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import { Track } from 'livekit-client'
|
|
||||||
import { Shortcut } from '@/features/shortcuts/types'
|
|
||||||
|
|
||||||
export type ToggleSource = Exclude<
|
|
||||||
Track.Source,
|
|
||||||
| Track.Source.ScreenShareAudio
|
|
||||||
| Track.Source.Unknown
|
|
||||||
| Track.Source.ScreenShare
|
|
||||||
>
|
|
||||||
|
|
||||||
export type ToggleDeviceConfig = {
|
|
||||||
kind: MediaDeviceKind
|
|
||||||
shortcut?: Shortcut
|
|
||||||
longPress?: Shortcut
|
|
||||||
}
|
|
||||||
|
|
||||||
type ToggleDeviceConfigMap = {
|
|
||||||
[key in ToggleSource]: ToggleDeviceConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TOGGLE_DEVICE_CONFIG = {
|
|
||||||
[Track.Source.Microphone]: {
|
|
||||||
kind: 'audioinput',
|
|
||||||
shortcut: {
|
|
||||||
key: 'd',
|
|
||||||
ctrlKey: true,
|
|
||||||
},
|
|
||||||
longPress: {
|
|
||||||
key: 'Space',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[Track.Source.Camera]: {
|
|
||||||
kind: 'videoinput',
|
|
||||||
shortcut: {
|
|
||||||
key: 'e',
|
|
||||||
ctrlKey: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as const satisfies ToggleDeviceConfigMap
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
import { Shortcut } from '@/features/shortcuts/types'
|
||||||
|
|
||||||
|
export const useDeviceShortcut = (kind: MediaDeviceKind) => {
|
||||||
|
return useMemo<Shortcut | undefined>(() => {
|
||||||
|
switch (kind) {
|
||||||
|
case 'audioinput':
|
||||||
|
return {
|
||||||
|
key: 'e',
|
||||||
|
ctrlKey: true,
|
||||||
|
}
|
||||||
|
case 'videoinput':
|
||||||
|
return {
|
||||||
|
key: 'd',
|
||||||
|
ctrlKey: true,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}, [kind])
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user