(frontend) add settings quick access from device controls

Enable opening settings dialog directly from device controls while
inside a conference for quick access to device configuration.

Improves UX by providing immediate settings access without
enhancing convenience during meetings.

Requested by users.
This commit is contained in:
lebaudantoine
2025-08-22 01:18:35 +02:00
committed by aleb_the_flash
parent a49893696b
commit 42107f4698
10 changed files with 73 additions and 30 deletions

View File

@@ -11,6 +11,8 @@ import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice'
import Source = Track.Source
import * as React from 'react'
import { SelectDevice } from './SelectDevice'
import { SettingsButton } from './SettingsButton'
import { SettingsDialogExtendedKey } from '@/features/settings/type'
type AudioDevicesControlProps = Omit<
UseTrackToggleProps<Source.Microphone>,
@@ -114,6 +116,7 @@ export const AudioDevicesControl = ({
onSubmit={saveAudioOutputDeviceId}
/>
</div>
<SettingsButton settingTab={SettingsDialogExtendedKey.AUDIO} />
</div>
</Popover>
)}

View File

@@ -0,0 +1,30 @@
import { useSettingsDialog } from '../SettingsDialogContext'
import { Button } from '@/primitives'
import { RiSettings3Line } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { SettingsDialogExtendedKey } from '@/features/settings/type'
export const SettingsButton = ({
settingTab,
}: {
settingTab: SettingsDialogExtendedKey
}) => {
const { t } = useTranslation('rooms', { keyPrefix: 'selectDevice' })
const { setDialogOpen, setDefaultSelectedKey } = useSettingsDialog()
return (
<Button
size="sm"
square
tooltip={t('settings')}
aria-label={t('settings')}
variant="primaryDark"
onPress={() => {
setDefaultSelectedKey(settingTab)
setDialogOpen(true)
}}
>
<RiSettings3Line size={24} />
</Button>
)
}

View File

@@ -12,6 +12,8 @@ import { BackgroundProcessorFactory } from '../../blur'
import Source = Track.Source
import * as React from 'react'
import { SelectDevice } from './SelectDevice'
import { SettingsButton } from './SettingsButton'
import { SettingsDialogExtendedKey } from '@/features/settings/type'
type VideoDeviceControlProps = Omit<
UseTrackToggleProps<Source.Camera>,
@@ -126,6 +128,7 @@ export const VideoDeviceControl = ({
onSubmit={saveVideoInputDeviceId}
/>
</div>
<SettingsButton settingTab={SettingsDialogExtendedKey.VIDEO} />
</div>
</Popover>
)}

View File

@@ -1,15 +1,13 @@
import {
SettingsDialogExtended,
SettingsDialogExtendedKeys,
} from '@/features/settings/components/SettingsDialogExtended'
import React, { createContext, useContext, useState } from 'react'
import { SettingsDialogExtended } from '@/features/settings/components/SettingsDialogExtended'
import { SettingsDialogExtendedKey } from '@/features/settings/type'
const SettingsDialogContext = createContext<
| {
dialogOpen: boolean
defaultSelectedKey?: SettingsDialogExtendedKeys
defaultSelectedKey?: SettingsDialogExtendedKey
setDefaultSelectedKey: React.Dispatch<
React.SetStateAction<SettingsDialogExtendedKeys | undefined>
React.SetStateAction<SettingsDialogExtendedKey | undefined>
>
setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>
}
@@ -20,7 +18,7 @@ export const SettingsDialogProvider: React.FC<{
children: React.ReactNode
}> = ({ children }) => {
const [defaultSelectedKey, setDefaultSelectedKey] = useState<
SettingsDialogExtendedKeys | undefined
SettingsDialogExtendedKey | undefined
>(undefined)
const [dialogOpen, setDialogOpen] = useState(false)
return (

View File

@@ -18,6 +18,7 @@ import { AudioTab } from './tabs/AudioTab'
import { VideoTab } from './tabs/VideoTab'
import { useRef } from 'react'
import { useMediaQuery } from '@/features/rooms/livekit/hooks/useMediaQuery'
import { SettingsDialogExtendedKey } from '@/features/settings/type'
const tabsStyle = css({
maxHeight: '40.625rem', // fixme size copied from meet settings modal
@@ -43,18 +44,11 @@ const tabPanelContainerStyle = css({
minWidth: 0,
})
export type SettingsDialogExtendedKeys =
| 'account'
| 'audio'
| 'video'
| 'general'
| 'notifications'
export type SettingsDialogExtended = Pick<
DialogProps,
'isOpen' | 'onOpenChange'
> & {
defaultSelectedKey?: SettingsDialogExtendedKeys
defaultSelectedKey?: SettingsDialogExtendedKey
}
export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
@@ -85,34 +79,38 @@ export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
</Heading>
)}
<TabList border={false}>
<Tab icon highlight id="account">
<Tab icon highlight id={SettingsDialogExtendedKey.ACCOUNT}>
<RiAccountCircleLine />
{isWideScreen && t('tabs.account')}
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.ACCOUNT}`)}
</Tab>
<Tab icon highlight id="audio">
<Tab icon highlight id={SettingsDialogExtendedKey.AUDIO}>
<RiSpeakerLine />
{isWideScreen && t('tabs.audio')}
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.AUDIO}`)}
</Tab>
<Tab icon highlight id="video">
<Tab icon highlight id={SettingsDialogExtendedKey.VIDEO}>
<RiVideoOnLine />
{isWideScreen && t('tabs.video')}
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.VIDEO}`)}
</Tab>
<Tab icon highlight id="general">
<Tab icon highlight id={SettingsDialogExtendedKey.GENERAL}>
<RiSettings3Line />
{isWideScreen && t('tabs.general')}
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.GENERAL}`)}
</Tab>
<Tab icon highlight id="notifications">
<Tab icon highlight id={SettingsDialogExtendedKey.NOTIFICATIONS}>
<RiNotification3Line />
{isWideScreen && t('tabs.notifications')}
{isWideScreen &&
t(`tabs.${SettingsDialogExtendedKey.NOTIFICATIONS}`)}
</Tab>
</TabList>
</div>
<div className={tabPanelContainerStyle}>
<AccountTab id="account" onOpenChange={props.onOpenChange} />
<AudioTab id="audio" />
<VideoTab id="video" />
<GeneralTab id="general" />
<NotificationsTab id="notifications" />
<AccountTab
id={SettingsDialogExtendedKey.ACCOUNT}
onOpenChange={props.onOpenChange}
/>
<AudioTab id={SettingsDialogExtendedKey.AUDIO} />
<VideoTab id={SettingsDialogExtendedKey.VIDEO} />
<GeneralTab id={SettingsDialogExtendedKey.GENERAL} />
<NotificationsTab id={SettingsDialogExtendedKey.NOTIFICATIONS} />
</div>
</Tabs>
</Dialog>

View File

@@ -0,0 +1,7 @@
export enum SettingsDialogExtendedKey {
ACCOUNT = 'account',
AUDIO = 'audio',
VIDEO = 'video',
GENERAL = 'general',
NOTIFICATIONS = 'notifications',
}

View File

@@ -11,6 +11,7 @@
"loading": "Laden…",
"select": "Wählen Sie einen Wert",
"permissionsNeeded": "Genehmigung erforderlich",
"settings": "Einstellungen",
"videoinput": {
"choose": "Kamera auswählen",
"permissionsNeeded": "Kamera auswählen - genehmigung erforderlich",

View File

@@ -11,6 +11,7 @@
"loading": "Loading…",
"select": "Select a value",
"permissionsNeeded": "Permission needed",
"settings": "Settings",
"videoinput": {
"choose": "Select camera",
"permissionsNeeded": "Select camera - permission needed",

View File

@@ -11,6 +11,7 @@
"loading": "Chargement…",
"select": "Sélectionnez une valeur",
"permissionsNeeded": "Autorisations nécessaires",
"settings": "Paramètres",
"videoinput": {
"choose": "Choisir la webcam",
"permissionsNeeded": "Choisir la webcam - autorisations nécessaires",

View File

@@ -11,6 +11,7 @@
"loading": "Bezig met laden…",
"select": "Selecteer een waarde",
"permissionsNeeded": "Toestemming vereist",
"settings": "Instellingen",
"videoinput": {
"choose": "Selecteer camera",
"permissionsNeeded": "Selecteer camera - Toestemming vereist",