✨(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:
committed by
aleb_the_flash
parent
a49893696b
commit
42107f4698
@@ -11,6 +11,8 @@ import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice'
|
|||||||
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'
|
||||||
|
import { SettingsButton } from './SettingsButton'
|
||||||
|
import { SettingsDialogExtendedKey } from '@/features/settings/type'
|
||||||
|
|
||||||
type AudioDevicesControlProps = Omit<
|
type AudioDevicesControlProps = Omit<
|
||||||
UseTrackToggleProps<Source.Microphone>,
|
UseTrackToggleProps<Source.Microphone>,
|
||||||
@@ -114,6 +116,7 @@ export const AudioDevicesControl = ({
|
|||||||
onSubmit={saveAudioOutputDeviceId}
|
onSubmit={saveAudioOutputDeviceId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<SettingsButton settingTab={SettingsDialogExtendedKey.AUDIO} />
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ import { BackgroundProcessorFactory } from '../../blur'
|
|||||||
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'
|
||||||
|
import { SettingsButton } from './SettingsButton'
|
||||||
|
import { SettingsDialogExtendedKey } from '@/features/settings/type'
|
||||||
|
|
||||||
type VideoDeviceControlProps = Omit<
|
type VideoDeviceControlProps = Omit<
|
||||||
UseTrackToggleProps<Source.Camera>,
|
UseTrackToggleProps<Source.Camera>,
|
||||||
@@ -126,6 +128,7 @@ export const VideoDeviceControl = ({
|
|||||||
onSubmit={saveVideoInputDeviceId}
|
onSubmit={saveVideoInputDeviceId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<SettingsButton settingTab={SettingsDialogExtendedKey.VIDEO} />
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import {
|
|
||||||
SettingsDialogExtended,
|
|
||||||
SettingsDialogExtendedKeys,
|
|
||||||
} from '@/features/settings/components/SettingsDialogExtended'
|
|
||||||
import React, { createContext, useContext, useState } from 'react'
|
import React, { createContext, useContext, useState } from 'react'
|
||||||
|
import { SettingsDialogExtended } from '@/features/settings/components/SettingsDialogExtended'
|
||||||
|
import { SettingsDialogExtendedKey } from '@/features/settings/type'
|
||||||
|
|
||||||
const SettingsDialogContext = createContext<
|
const SettingsDialogContext = createContext<
|
||||||
| {
|
| {
|
||||||
dialogOpen: boolean
|
dialogOpen: boolean
|
||||||
defaultSelectedKey?: SettingsDialogExtendedKeys
|
defaultSelectedKey?: SettingsDialogExtendedKey
|
||||||
setDefaultSelectedKey: React.Dispatch<
|
setDefaultSelectedKey: React.Dispatch<
|
||||||
React.SetStateAction<SettingsDialogExtendedKeys | undefined>
|
React.SetStateAction<SettingsDialogExtendedKey | undefined>
|
||||||
>
|
>
|
||||||
setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>
|
setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
}
|
}
|
||||||
@@ -20,7 +18,7 @@ export const SettingsDialogProvider: React.FC<{
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}> = ({ children }) => {
|
}> = ({ children }) => {
|
||||||
const [defaultSelectedKey, setDefaultSelectedKey] = useState<
|
const [defaultSelectedKey, setDefaultSelectedKey] = useState<
|
||||||
SettingsDialogExtendedKeys | undefined
|
SettingsDialogExtendedKey | undefined
|
||||||
>(undefined)
|
>(undefined)
|
||||||
const [dialogOpen, setDialogOpen] = useState(false)
|
const [dialogOpen, setDialogOpen] = useState(false)
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { AudioTab } from './tabs/AudioTab'
|
|||||||
import { VideoTab } from './tabs/VideoTab'
|
import { VideoTab } from './tabs/VideoTab'
|
||||||
import { useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
import { useMediaQuery } from '@/features/rooms/livekit/hooks/useMediaQuery'
|
import { useMediaQuery } from '@/features/rooms/livekit/hooks/useMediaQuery'
|
||||||
|
import { SettingsDialogExtendedKey } from '@/features/settings/type'
|
||||||
|
|
||||||
const tabsStyle = css({
|
const tabsStyle = css({
|
||||||
maxHeight: '40.625rem', // fixme size copied from meet settings modal
|
maxHeight: '40.625rem', // fixme size copied from meet settings modal
|
||||||
@@ -43,18 +44,11 @@ const tabPanelContainerStyle = css({
|
|||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
export type SettingsDialogExtendedKeys =
|
|
||||||
| 'account'
|
|
||||||
| 'audio'
|
|
||||||
| 'video'
|
|
||||||
| 'general'
|
|
||||||
| 'notifications'
|
|
||||||
|
|
||||||
export type SettingsDialogExtended = Pick<
|
export type SettingsDialogExtended = Pick<
|
||||||
DialogProps,
|
DialogProps,
|
||||||
'isOpen' | 'onOpenChange'
|
'isOpen' | 'onOpenChange'
|
||||||
> & {
|
> & {
|
||||||
defaultSelectedKey?: SettingsDialogExtendedKeys
|
defaultSelectedKey?: SettingsDialogExtendedKey
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
|
export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
|
||||||
@@ -85,34 +79,38 @@ export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
|
|||||||
</Heading>
|
</Heading>
|
||||||
)}
|
)}
|
||||||
<TabList border={false}>
|
<TabList border={false}>
|
||||||
<Tab icon highlight id="account">
|
<Tab icon highlight id={SettingsDialogExtendedKey.ACCOUNT}>
|
||||||
<RiAccountCircleLine />
|
<RiAccountCircleLine />
|
||||||
{isWideScreen && t('tabs.account')}
|
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.ACCOUNT}`)}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab icon highlight id="audio">
|
<Tab icon highlight id={SettingsDialogExtendedKey.AUDIO}>
|
||||||
<RiSpeakerLine />
|
<RiSpeakerLine />
|
||||||
{isWideScreen && t('tabs.audio')}
|
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.AUDIO}`)}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab icon highlight id="video">
|
<Tab icon highlight id={SettingsDialogExtendedKey.VIDEO}>
|
||||||
<RiVideoOnLine />
|
<RiVideoOnLine />
|
||||||
{isWideScreen && t('tabs.video')}
|
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.VIDEO}`)}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab icon highlight id="general">
|
<Tab icon highlight id={SettingsDialogExtendedKey.GENERAL}>
|
||||||
<RiSettings3Line />
|
<RiSettings3Line />
|
||||||
{isWideScreen && t('tabs.general')}
|
{isWideScreen && t(`tabs.${SettingsDialogExtendedKey.GENERAL}`)}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab icon highlight id="notifications">
|
<Tab icon highlight id={SettingsDialogExtendedKey.NOTIFICATIONS}>
|
||||||
<RiNotification3Line />
|
<RiNotification3Line />
|
||||||
{isWideScreen && t('tabs.notifications')}
|
{isWideScreen &&
|
||||||
|
t(`tabs.${SettingsDialogExtendedKey.NOTIFICATIONS}`)}
|
||||||
</Tab>
|
</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
</div>
|
</div>
|
||||||
<div className={tabPanelContainerStyle}>
|
<div className={tabPanelContainerStyle}>
|
||||||
<AccountTab id="account" onOpenChange={props.onOpenChange} />
|
<AccountTab
|
||||||
<AudioTab id="audio" />
|
id={SettingsDialogExtendedKey.ACCOUNT}
|
||||||
<VideoTab id="video" />
|
onOpenChange={props.onOpenChange}
|
||||||
<GeneralTab id="general" />
|
/>
|
||||||
<NotificationsTab id="notifications" />
|
<AudioTab id={SettingsDialogExtendedKey.AUDIO} />
|
||||||
|
<VideoTab id={SettingsDialogExtendedKey.VIDEO} />
|
||||||
|
<GeneralTab id={SettingsDialogExtendedKey.GENERAL} />
|
||||||
|
<NotificationsTab id={SettingsDialogExtendedKey.NOTIFICATIONS} />
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
7
src/frontend/src/features/settings/type.ts
Normal file
7
src/frontend/src/features/settings/type.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export enum SettingsDialogExtendedKey {
|
||||||
|
ACCOUNT = 'account',
|
||||||
|
AUDIO = 'audio',
|
||||||
|
VIDEO = 'video',
|
||||||
|
GENERAL = 'general',
|
||||||
|
NOTIFICATIONS = 'notifications',
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
"loading": "Laden…",
|
"loading": "Laden…",
|
||||||
"select": "Wählen Sie einen Wert",
|
"select": "Wählen Sie einen Wert",
|
||||||
"permissionsNeeded": "Genehmigung erforderlich",
|
"permissionsNeeded": "Genehmigung erforderlich",
|
||||||
|
"settings": "Einstellungen",
|
||||||
"videoinput": {
|
"videoinput": {
|
||||||
"choose": "Kamera auswählen",
|
"choose": "Kamera auswählen",
|
||||||
"permissionsNeeded": "Kamera auswählen - genehmigung erforderlich",
|
"permissionsNeeded": "Kamera auswählen - genehmigung erforderlich",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"loading": "Loading…",
|
"loading": "Loading…",
|
||||||
"select": "Select a value",
|
"select": "Select a value",
|
||||||
"permissionsNeeded": "Permission needed",
|
"permissionsNeeded": "Permission needed",
|
||||||
|
"settings": "Settings",
|
||||||
"videoinput": {
|
"videoinput": {
|
||||||
"choose": "Select camera",
|
"choose": "Select camera",
|
||||||
"permissionsNeeded": "Select camera - permission needed",
|
"permissionsNeeded": "Select camera - permission needed",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"loading": "Chargement…",
|
"loading": "Chargement…",
|
||||||
"select": "Sélectionnez une valeur",
|
"select": "Sélectionnez une valeur",
|
||||||
"permissionsNeeded": "Autorisations nécessaires",
|
"permissionsNeeded": "Autorisations nécessaires",
|
||||||
|
"settings": "Paramètres",
|
||||||
"videoinput": {
|
"videoinput": {
|
||||||
"choose": "Choisir la webcam",
|
"choose": "Choisir la webcam",
|
||||||
"permissionsNeeded": "Choisir la webcam - autorisations nécessaires",
|
"permissionsNeeded": "Choisir la webcam - autorisations nécessaires",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"loading": "Bezig met laden…",
|
"loading": "Bezig met laden…",
|
||||||
"select": "Selecteer een waarde",
|
"select": "Selecteer een waarde",
|
||||||
"permissionsNeeded": "Toestemming vereist",
|
"permissionsNeeded": "Toestemming vereist",
|
||||||
|
"settings": "Instellingen",
|
||||||
"videoinput": {
|
"videoinput": {
|
||||||
"choose": "Selecteer camera",
|
"choose": "Selecteer camera",
|
||||||
"permissionsNeeded": "Selecteer camera - Toestemming vereist",
|
"permissionsNeeded": "Selecteer camera - Toestemming vereist",
|
||||||
|
|||||||
Reference in New Issue
Block a user