♻️(frontend) unify toggle components into single flexible implementation
Replace separate prejoin and room toggle components with unified component that's adaptable and easier to evolve without overfitting. Adds responsibilities to join component but eliminates duplication. Join component needs future refactoring as complexity is growing significantly.
This commit is contained in:
committed by
aleb_the_flash
parent
f17e0a3ba0
commit
6f3339fbdc
@@ -21,6 +21,7 @@ import {
|
||||
EffectsConfigurationProps,
|
||||
} from '../livekit/components/effects/EffectsConfiguration'
|
||||
import { SelectDevice } from '../livekit/components/controls/Device/SelectDevice'
|
||||
import { ToggleDevice } from '../livekit/components/controls/Device/ToggleDevice'
|
||||
import { usePersistentUserChoices } from '../livekit/hooks/usePersistentUserChoices'
|
||||
import { BackgroundProcessorFactory } from '../livekit/components/blur'
|
||||
import { isMobileBrowser } from '@livekit/components-core'
|
||||
@@ -34,7 +35,6 @@ import { Spinner } from '@/primitives/Spinner'
|
||||
import { ApiAccessLevel } from '../api/ApiRoom'
|
||||
import { useLoginHint } from '@/hooks/useLoginHint'
|
||||
import { openPermissionsDialog } from '@/stores/permissions'
|
||||
import { ToggleDevice } from './join/ToggleDevice'
|
||||
import { useResolveInitiallyDefaultDeviceId } from '../livekit/hooks/useResolveInitiallyDefaultDeviceId'
|
||||
import { isSafari } from '@/utils/livekit'
|
||||
import type { LocalUserChoices } from '@/stores/userChoices'
|
||||
@@ -661,15 +661,29 @@ export const Join = ({
|
||||
>
|
||||
<ToggleDevice
|
||||
kind="audioinput"
|
||||
initialState={audioEnabled}
|
||||
track={audioTrack}
|
||||
onChange={(enabled) => saveAudioInputEnabled(enabled)}
|
||||
context="join"
|
||||
enabled={audioEnabled}
|
||||
toggle={async () => {
|
||||
saveAudioInputEnabled(!audioEnabled)
|
||||
if (audioEnabled) {
|
||||
await audioTrack?.mute()
|
||||
} else {
|
||||
await audioTrack?.unmute()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ToggleDevice
|
||||
kind="videoinput"
|
||||
initialState={videoEnabled}
|
||||
track={videoTrack}
|
||||
onChange={(enabled) => saveVideoInputEnabled(enabled)}
|
||||
context="join"
|
||||
enabled={videoEnabled}
|
||||
toggle={async () => {
|
||||
saveVideoInputEnabled(!videoEnabled)
|
||||
if (videoEnabled) {
|
||||
await videoTrack?.mute()
|
||||
} else {
|
||||
await videoTrack?.unmute()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import { UseTrackToggleProps } from '@livekit/components-react'
|
||||
import { ToggleDevice as BaseToggleDevice } from '../../livekit/components/controls/Device/ToggleDevice'
|
||||
import { LocalAudioTrack, LocalVideoTrack, Track } from 'livekit-client'
|
||||
import { ButtonRecipeProps } from '@/primitives/buttonRecipe'
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
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
|
||||
kind: MediaDeviceKind
|
||||
variant?: NonNullable<ButtonRecipeProps>['variant']
|
||||
}
|
||||
|
||||
export const ToggleDevice = <T extends ToggleSource>({
|
||||
track,
|
||||
kind,
|
||||
onChange,
|
||||
initialState,
|
||||
}: ToggleDeviceProps<T>) => {
|
||||
const [isTrackEnabled, setIsTrackEnabled] = useState(initialState ?? false)
|
||||
|
||||
const toggle = useCallback(async () => {
|
||||
try {
|
||||
if (isTrackEnabled) {
|
||||
setIsTrackEnabled(false)
|
||||
onChange?.(false, true)
|
||||
await track?.mute()
|
||||
} else {
|
||||
setIsTrackEnabled(true)
|
||||
onChange?.(true, true)
|
||||
await track?.unmute()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle track:', error)
|
||||
}
|
||||
}, [track, onChange, isTrackEnabled])
|
||||
|
||||
return (
|
||||
<BaseToggleDevice
|
||||
enabled={isTrackEnabled}
|
||||
toggle={toggle}
|
||||
kind={kind}
|
||||
variant="whiteCircle"
|
||||
errorVariant="errorCircle"
|
||||
toggleButtonProps={{
|
||||
groupPosition: undefined,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -57,8 +57,7 @@ export const AudioDevicesControl = ({
|
||||
<ToggleDevice
|
||||
{...trackProps}
|
||||
kind="audioinput"
|
||||
variant="primaryDark"
|
||||
toggle={trackProps.toggle}
|
||||
toggle={trackProps.toggle as () => Promise<void>}
|
||||
toggleButtonProps={{
|
||||
...(hideMenu
|
||||
? {
|
||||
|
||||
@@ -17,26 +17,52 @@ import { openPermissionsDialog } from '@/stores/permissions'
|
||||
import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice'
|
||||
import { useDeviceIcons } from '../../../hooks/useDeviceIcons'
|
||||
import { useDeviceShortcut } from '../../../hooks/useDeviceShortcut'
|
||||
import { ToggleSource, CaptureOptionsBySource } from '@livekit/components-core'
|
||||
|
||||
export type ToggleDeviceProps = {
|
||||
enabled: boolean
|
||||
toggle: () => void
|
||||
kind: 'audioinput' | 'videoinput'
|
||||
type ToggleDeviceStyleProps = {
|
||||
variant?: NonNullable<ButtonRecipeProps>['variant']
|
||||
errorVariant?: NonNullable<ButtonRecipeProps>['variant']
|
||||
toggleButtonProps?: Partial<ToggleButtonProps>
|
||||
}
|
||||
|
||||
export const ToggleDevice = ({
|
||||
export type ToggleDeviceProps<T extends ToggleSource> = {
|
||||
enabled: boolean
|
||||
toggle: (
|
||||
forceState?: boolean,
|
||||
captureOptions?: CaptureOptionsBySource<T>
|
||||
) => Promise<void | boolean | undefined> | void
|
||||
context?: 'room' | 'join'
|
||||
kind: 'audioinput' | 'videoinput'
|
||||
toggleButtonProps?: Partial<ToggleButtonProps>
|
||||
}
|
||||
|
||||
export const ToggleDevice = <T extends ToggleSource>({
|
||||
kind,
|
||||
enabled,
|
||||
toggle,
|
||||
variant = 'primaryDark',
|
||||
errorVariant = 'error2',
|
||||
toggleButtonProps,
|
||||
}: ToggleDeviceProps) => {
|
||||
context = 'room',
|
||||
...props
|
||||
}: ToggleDeviceProps<T>) => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
|
||||
|
||||
const { variant, errorVariant, toggleButtonProps } =
|
||||
useMemo<ToggleDeviceStyleProps>(() => {
|
||||
if (context === 'join') {
|
||||
return {
|
||||
variant: 'whiteCircle',
|
||||
errorVariant: 'errorCircle',
|
||||
toggleButtonProps: {
|
||||
groupPosition: undefined,
|
||||
},
|
||||
} as ToggleDeviceStyleProps
|
||||
}
|
||||
return {
|
||||
variant: 'primaryDark',
|
||||
errorVariant: 'error2',
|
||||
toggleButtonProps: undefined,
|
||||
} as ToggleDeviceStyleProps
|
||||
}, [context])
|
||||
|
||||
const [pushToTalk, setPushToTalk] = useState(false)
|
||||
|
||||
const onKeyDown = () => {
|
||||
@@ -71,8 +97,8 @@ export const ToggleDevice = ({
|
||||
const Icon =
|
||||
enabled && !cannotUseDevice ? deviceIcons.toggleOn : deviceIcons.toggleOff
|
||||
|
||||
const context = useMaybeRoomContext()
|
||||
if (kind === 'audioinput' && pushToTalk && context) {
|
||||
const roomContext = useMaybeRoomContext()
|
||||
if (kind === 'audioinput' && pushToTalk && roomContext) {
|
||||
return <ActiveSpeakerWrapper />
|
||||
}
|
||||
|
||||
@@ -92,6 +118,7 @@ export const ToggleDevice = ({
|
||||
}
|
||||
groupPosition="left"
|
||||
{...toggleButtonProps}
|
||||
{...props}
|
||||
>
|
||||
<Icon />
|
||||
</ToggleButton>
|
||||
|
||||
@@ -82,7 +82,6 @@ export const VideoDeviceControl = ({
|
||||
<ToggleDevice
|
||||
{...trackProps}
|
||||
kind="videoinput"
|
||||
variant="primaryDark"
|
||||
toggle={toggle}
|
||||
toggleButtonProps={{
|
||||
...(hideMenu
|
||||
|
||||
Reference in New Issue
Block a user