♻️(frontend) refactor settings from context to valtio global store
Replace settings context provider with valtio global store for easier access outside room components and better long-term maintainability. Prepares for upcoming prejoin screen settings access by making settings globally available without React context limitations.
This commit is contained in:
committed by
aleb_the_flash
parent
e80b9c2485
commit
c1c2d0260d
@@ -1,8 +1,8 @@
|
|||||||
import { useSettingsDialog } from '../SettingsDialogContext'
|
|
||||||
import { Button } from '@/primitives'
|
import { Button } from '@/primitives'
|
||||||
import { RiSettings3Line } from '@remixicon/react'
|
import { RiSettings3Line } from '@remixicon/react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { SettingsDialogExtendedKey } from '@/features/settings/type'
|
import { SettingsDialogExtendedKey } from '@/features/settings/type'
|
||||||
|
import { useSettingsDialog } from '@/features/settings/hook/useSettingsDialog'
|
||||||
|
|
||||||
export const SettingsButton = ({
|
export const SettingsButton = ({
|
||||||
settingTab,
|
settingTab,
|
||||||
@@ -12,7 +12,7 @@ export const SettingsButton = ({
|
|||||||
onPress?: () => void
|
onPress?: () => void
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'selectDevice' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'selectDevice' })
|
||||||
const { setDialogOpen, setDefaultSelectedKey } = useSettingsDialog()
|
const { openSettingsDialog } = useSettingsDialog()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -22,8 +22,7 @@ export const SettingsButton = ({
|
|||||||
aria-label={t(`settings.${settingTab}`)}
|
aria-label={t(`settings.${settingTab}`)}
|
||||||
variant="primaryDark"
|
variant="primaryDark"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setDefaultSelectedKey(settingTab)
|
openSettingsDialog(settingTab)
|
||||||
setDialogOpen(true)
|
|
||||||
onPress?.()
|
onPress?.()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ import { RiSettings3Line } from '@remixicon/react'
|
|||||||
import { MenuItem } from 'react-aria-components'
|
import { MenuItem } from 'react-aria-components'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { menuRecipe } from '@/primitives/menuRecipe'
|
import { menuRecipe } from '@/primitives/menuRecipe'
|
||||||
import { useSettingsDialog } from '../SettingsDialogContext'
|
import { useSettingsDialog } from '@/features/settings/hook/useSettingsDialog'
|
||||||
|
|
||||||
export const SettingsMenuItem = () => {
|
export const SettingsMenuItem = () => {
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'options.items' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'options.items' })
|
||||||
const { setDialogOpen } = useSettingsDialog()
|
const { openSettingsDialog } = useSettingsDialog()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
className={menuRecipe({ icon: true, variant: 'dark' }).item}
|
className={menuRecipe({ icon: true, variant: 'dark' }).item}
|
||||||
onAction={() => setDialogOpen(true)}
|
onAction={() => openSettingsDialog()}
|
||||||
>
|
>
|
||||||
<RiSettings3Line size={20} />
|
<RiSettings3Line size={20} />
|
||||||
{t('settings')}
|
{t('settings')}
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
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?: SettingsDialogExtendedKey
|
|
||||||
setDefaultSelectedKey: React.Dispatch<
|
|
||||||
React.SetStateAction<SettingsDialogExtendedKey | undefined>
|
|
||||||
>
|
|
||||||
setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>
|
|
||||||
}
|
|
||||||
| undefined
|
|
||||||
>(undefined)
|
|
||||||
|
|
||||||
export const SettingsDialogProvider: React.FC<{
|
|
||||||
children: React.ReactNode
|
|
||||||
}> = ({ children }) => {
|
|
||||||
const [defaultSelectedKey, setDefaultSelectedKey] = useState<
|
|
||||||
SettingsDialogExtendedKey | undefined
|
|
||||||
>(undefined)
|
|
||||||
const [dialogOpen, setDialogOpen] = useState(false)
|
|
||||||
return (
|
|
||||||
<SettingsDialogContext.Provider
|
|
||||||
value={{
|
|
||||||
dialogOpen,
|
|
||||||
setDialogOpen,
|
|
||||||
defaultSelectedKey,
|
|
||||||
setDefaultSelectedKey,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<SettingsDialogExtended
|
|
||||||
isOpen={dialogOpen}
|
|
||||||
defaultSelectedKey={defaultSelectedKey}
|
|
||||||
onOpenChange={(v) => {
|
|
||||||
if (!v) {
|
|
||||||
setDefaultSelectedKey(undefined)
|
|
||||||
}
|
|
||||||
setDialogOpen(v)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</SettingsDialogContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-refresh/only-export-components
|
|
||||||
export const useSettingsDialog = () => {
|
|
||||||
const context = useContext(SettingsDialogContext)
|
|
||||||
if (!context) {
|
|
||||||
throw new Error(
|
|
||||||
'useSettingsDialog must be used within a SettingsDialogProvider'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ import * as React from 'react'
|
|||||||
|
|
||||||
import { MobileControlBar } from './MobileControlBar'
|
import { MobileControlBar } from './MobileControlBar'
|
||||||
import { DesktopControlBar } from './DesktopControlBar'
|
import { DesktopControlBar } from './DesktopControlBar'
|
||||||
import { SettingsDialogProvider } from '../../components/controls/SettingsDialogContext'
|
|
||||||
import { useIsMobile } from '@/utils/useIsMobile'
|
import { useIsMobile } from '@/utils/useIsMobile'
|
||||||
|
|
||||||
export interface ControlBarProps extends React.HTMLAttributes<HTMLDivElement> {
|
export interface ControlBarProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
@@ -16,15 +15,11 @@ export interface ControlBarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
*/
|
*/
|
||||||
export function ControlBar({ onDeviceError }: ControlBarProps) {
|
export function ControlBar({ onDeviceError }: ControlBarProps) {
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
return (
|
|
||||||
<SettingsDialogProvider>
|
if (isMobile) {
|
||||||
{isMobile ? (
|
return <MobileControlBar onDeviceError={onDeviceError} />
|
||||||
<MobileControlBar onDeviceError={onDeviceError} />
|
}
|
||||||
) : (
|
return <DesktopControlBar onDeviceError={onDeviceError} />
|
||||||
<DesktopControlBar onDeviceError={onDeviceError} />
|
|
||||||
)}
|
|
||||||
</SettingsDialogProvider>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ControlBarAuxProps = Pick<ControlBarProps, 'onDeviceError'>
|
export type ControlBarAuxProps = Pick<ControlBarProps, 'onDeviceError'>
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ import { ChatToggle } from '../../components/controls/ChatToggle'
|
|||||||
import { ParticipantsToggle } from '../../components/controls/Participants/ParticipantsToggle'
|
import { ParticipantsToggle } from '../../components/controls/Participants/ParticipantsToggle'
|
||||||
import { useSidePanel } from '../../hooks/useSidePanel'
|
import { useSidePanel } from '../../hooks/useSidePanel'
|
||||||
import { LinkButton } from '@/primitives'
|
import { LinkButton } from '@/primitives'
|
||||||
import { useSettingsDialog } from '../../components/controls/SettingsDialogContext'
|
|
||||||
import { ResponsiveMenu } from './ResponsiveMenu'
|
import { ResponsiveMenu } from './ResponsiveMenu'
|
||||||
import { ToolsToggle } from '../../components/controls/ToolsToggle'
|
import { ToolsToggle } from '../../components/controls/ToolsToggle'
|
||||||
import { CameraSwitchButton } from '../../components/controls/CameraSwitchButton'
|
import { CameraSwitchButton } from '../../components/controls/CameraSwitchButton'
|
||||||
import { useConfig } from '@/api/useConfig'
|
import { useConfig } from '@/api/useConfig'
|
||||||
import { AudioDevicesControl } from '../../components/controls/Device/AudioDevicesControl'
|
import { AudioDevicesControl } from '../../components/controls/Device/AudioDevicesControl'
|
||||||
import { VideoDeviceControl } from '../../components/controls/Device/VideoDeviceControl'
|
import { VideoDeviceControl } from '../../components/controls/Device/VideoDeviceControl'
|
||||||
|
import { useSettingsDialog } from '@/features/settings/hook/useSettingsDialog'
|
||||||
|
|
||||||
export function MobileControlBar({
|
export function MobileControlBar({
|
||||||
onDeviceError,
|
onDeviceError,
|
||||||
@@ -33,7 +33,7 @@ export function MobileControlBar({
|
|||||||
const [isMenuOpened, setIsMenuOpened] = React.useState(false)
|
const [isMenuOpened, setIsMenuOpened] = React.useState(false)
|
||||||
const browserSupportsScreenSharing = supportsScreenSharing()
|
const browserSupportsScreenSharing = supportsScreenSharing()
|
||||||
const { toggleEffects } = useSidePanel()
|
const { toggleEffects } = useSidePanel()
|
||||||
const { setDialogOpen } = useSettingsDialog()
|
const { openSettingsDialog } = useSettingsDialog()
|
||||||
|
|
||||||
const { data } = useConfig()
|
const { data } = useConfig()
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ export function MobileControlBar({
|
|||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setDialogOpen(true)
|
openSettingsDialog()
|
||||||
setIsMenuOpened(false)
|
setIsMenuOpened(false)
|
||||||
}}
|
}}
|
||||||
variant="primaryTextDark"
|
variant="primaryTextDark"
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
|
|||||||
import { useConnectionObserver } from '../hooks/useConnectionObserver'
|
import { useConnectionObserver } from '../hooks/useConnectionObserver'
|
||||||
import { useNoiseReduction } from '../hooks/useNoiseReduction'
|
import { useNoiseReduction } from '../hooks/useNoiseReduction'
|
||||||
import { useVideoResolutionSubscription } from '../hooks/useVideoResolutionSubscription'
|
import { useVideoResolutionSubscription } from '../hooks/useVideoResolutionSubscription'
|
||||||
|
import { SettingsDialogProvider } from '@/features/settings/components/SettingsDialogProvider'
|
||||||
|
|
||||||
const LayoutWrapper = styled(
|
const LayoutWrapper = styled(
|
||||||
'div',
|
'div',
|
||||||
@@ -240,6 +241,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
|
|||||||
<RoomAudioRenderer />
|
<RoomAudioRenderer />
|
||||||
<ConnectionStateToast />
|
<ConnectionStateToast />
|
||||||
<RecordingStateToast />
|
<RecordingStateToast />
|
||||||
|
<SettingsDialogProvider />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export type SettingsDialogExtended = Pick<
|
|||||||
DialogProps,
|
DialogProps,
|
||||||
'isOpen' | 'onOpenChange'
|
'isOpen' | 'onOpenChange'
|
||||||
> & {
|
> & {
|
||||||
defaultSelectedKey?: SettingsDialogExtendedKey
|
defaultSelectedTab?: SettingsDialogExtendedKey
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
|
export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
|
||||||
@@ -63,7 +63,7 @@ export const SettingsDialogExtended = (props: SettingsDialogExtended) => {
|
|||||||
<Tabs
|
<Tabs
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
className={tabsStyle}
|
className={tabsStyle}
|
||||||
defaultSelectedKey={props.defaultSelectedKey}
|
defaultSelectedKey={props.defaultSelectedTab}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={tabListContainerStyle}
|
className={tabListContainerStyle}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { SettingsDialogExtended } from './SettingsDialogExtended'
|
||||||
|
import { useSnapshot } from 'valtio'
|
||||||
|
import { settingsStore } from '@/stores/settings'
|
||||||
|
|
||||||
|
export const SettingsDialogProvider = () => {
|
||||||
|
const { areSettingsOpen, defaultSelectedTab } = useSnapshot(settingsStore)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsDialogExtended
|
||||||
|
isOpen={areSettingsOpen}
|
||||||
|
defaultSelectedTab={defaultSelectedTab}
|
||||||
|
onOpenChange={(v) => {
|
||||||
|
if (!v && settingsStore.defaultSelectedTab) {
|
||||||
|
settingsStore.defaultSelectedTab = undefined
|
||||||
|
}
|
||||||
|
settingsStore.areSettingsOpen = v
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
20
src/frontend/src/features/settings/hook/useSettingsDialog.ts
Normal file
20
src/frontend/src/features/settings/hook/useSettingsDialog.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { useSnapshot } from 'valtio/index'
|
||||||
|
import { settingsStore } from '@/stores/settings'
|
||||||
|
import { SettingsDialogExtendedKey } from '@/features/settings/type'
|
||||||
|
|
||||||
|
export const useSettingsDialog = () => {
|
||||||
|
const { areSettingsOpen } = useSnapshot(settingsStore)
|
||||||
|
|
||||||
|
const openSettingsDialog = (
|
||||||
|
defaultSelectedTab?: SettingsDialogExtendedKey
|
||||||
|
) => {
|
||||||
|
if (areSettingsOpen) return
|
||||||
|
if (defaultSelectedTab)
|
||||||
|
settingsStore.defaultSelectedTab = defaultSelectedTab
|
||||||
|
settingsStore.areSettingsOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
openSettingsDialog,
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/frontend/src/stores/settings.ts
Normal file
12
src/frontend/src/stores/settings.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { proxy } from 'valtio'
|
||||||
|
import { SettingsDialogExtendedKey } from '@/features/settings/type'
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
areSettingsOpen: boolean
|
||||||
|
defaultSelectedTab?: SettingsDialogExtendedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
export const settingsStore = proxy<State>({
|
||||||
|
areSettingsOpen: false,
|
||||||
|
defaultSelectedTab: undefined,
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user