mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-22 08:07:26 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9c9a36ac7 | |||
| 35f2d3da86 | |||
| e204cd7f38 | |||
| 8d28343e80 | |||
| e1eb30d798 | |||
| 1bd2325ad2 | |||
| ebaf24c795 |
@@ -8,6 +8,10 @@ and this project adheres to
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- 📈(frontend) track errors when starting or stopping a recording
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- ✨(backend) accept form-urlencoded on the user token endpoint
|
- ✨(backend) accept form-urlencoded on the user token endpoint
|
||||||
@@ -15,12 +19,18 @@ and this project adheres to
|
|||||||
- ⬆️(frontend) upgrade i18next and react-i18next patch versions
|
- ⬆️(frontend) upgrade i18next and react-i18next patch versions
|
||||||
- ⬆️(frontend) upgrade posthog-js from 1.395.0 to 1.404.1
|
- ⬆️(frontend) upgrade posthog-js from 1.395.0 to 1.404.1
|
||||||
- ⬆️(frontend) upgrade livekit-client and @livekit/components-react
|
- ⬆️(frontend) upgrade livekit-client and @livekit/components-react
|
||||||
|
- 💄(frontend) increase the blur intensity
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- 📝(docs) fix minor typos in comments and docstrings
|
- 📝(docs) fix minor typos in comments and docstrings
|
||||||
- ⬆️(backend) bump sqlparse from 0.5.5 to 0.6.0
|
- ⬆️(backend) bump sqlparse from 0.5.5 to 0.6.0
|
||||||
- ⬆️(mail) bump @html-to/text-cli from 0.6.0 to 0.6.1
|
- ⬆️(mail) bump @html-to/text-cli from 0.6.0 to 0.6.1
|
||||||
|
- 🐛(frontend) treat client-initiated connect aborts as events
|
||||||
|
- 🐛(frontend) use state instead of a ref for MoreControls container
|
||||||
|
- 🐛(frontend) stop init_virtual_background from firing on blur updates
|
||||||
|
- 🐛(frontend) hoist mute confirmation dialog to VideoConference level
|
||||||
|
- 🐛(frontend) fix joined notification tile no longer rendering properly
|
||||||
|
|
||||||
## [1.27.0] - 2026-08-14
|
## [1.27.0] - 2026-08-14
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useToast } from 'react-aria'
|
import { useToast } from 'react-aria'
|
||||||
import { useRef } from 'react'
|
import { useMemo, useRef } from 'react'
|
||||||
import { Button as RACButton } from 'react-aria-components'
|
import { Button as RACButton } from 'react-aria-components'
|
||||||
import { Track } from 'livekit-client'
|
import { Track } from 'livekit-client'
|
||||||
import Source = Track.Source
|
import Source = Track.Source
|
||||||
@@ -11,6 +11,7 @@ import { Div } from '@/primitives'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyledToastContainer } from './StyledToastContainer'
|
import { StyledToastContainer } from './StyledToastContainer'
|
||||||
import { setPinnedTrack } from '@/stores/layout'
|
import { setPinnedTrack } from '@/stores/layout'
|
||||||
|
import { useParticipantTracks } from '@livekit/components-react'
|
||||||
|
|
||||||
const ClickableToast = styled(RACButton, {
|
const ClickableToast = styled(RACButton, {
|
||||||
base: {
|
base: {
|
||||||
@@ -30,13 +31,17 @@ export function ToastJoined({ state, ...props }: Readonly<ToastProps>) {
|
|||||||
)
|
)
|
||||||
const participant = props.toast.content.participant
|
const participant = props.toast.content.participant
|
||||||
|
|
||||||
if (!participant) return
|
const [cameraTrack] = useParticipantTracks(
|
||||||
|
[Source.Camera],
|
||||||
|
participant?.identity
|
||||||
|
)
|
||||||
|
|
||||||
const trackReference = {
|
const trackReference = useMemo(
|
||||||
participant,
|
() => cameraTrack ?? { participant, source: Source.Camera },
|
||||||
publication: participant.getTrackPublication(Source.Camera),
|
[cameraTrack, participant]
|
||||||
source: Source.Camera,
|
)
|
||||||
}
|
|
||||||
|
if (!participant) return
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledToastContainer {...toastProps} ref={ref}>
|
<StyledToastContainer {...toastProps} ref={ref}>
|
||||||
|
|||||||
+11
-26
@@ -1,11 +1,9 @@
|
|||||||
import { Participant, Track } from 'livekit-client'
|
import { Participant, Track } from 'livekit-client'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useTrackMutedIndicator } from '@livekit/components-react'
|
import { useTrackMutedIndicator } from '@livekit/components-react'
|
||||||
import { useMuteParticipant } from '@/features/rooms/api/muteParticipant'
|
|
||||||
import { useState } from 'react'
|
|
||||||
import { Button } from '@/primitives'
|
import { Button } from '@/primitives'
|
||||||
import { RiMicLine, RiMicOffLine } from '@remixicon/react'
|
import { RiMicLine, RiMicOffLine } from '@remixicon/react'
|
||||||
import { MuteAlertDialog } from '@/features/rooms/livekit/components/MuteAlertDialog'
|
import { openMuteDialog } from '@/stores/muteDialog'
|
||||||
|
|
||||||
export const MuteButton = ({ participant }: { participant: Participant }) => {
|
export const MuteButton = ({ participant }: { participant: Participant }) => {
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'participantTileFocus' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'participantTileFocus' })
|
||||||
@@ -15,31 +13,18 @@ export const MuteButton = ({ participant }: { participant: Participant }) => {
|
|||||||
source: Track.Source.Microphone,
|
source: Track.Source.Microphone,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { muteParticipant } = useMuteParticipant()
|
|
||||||
const [isAlertOpen, setIsAlertOpen] = useState(false)
|
|
||||||
|
|
||||||
const name = participant.name || participant.identity
|
const name = participant.name || participant.identity
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Button
|
||||||
<Button
|
isDisabled={isMuted}
|
||||||
isDisabled={isMuted}
|
size={'sm'}
|
||||||
size={'sm'}
|
variant={'primaryTextDark'}
|
||||||
variant={'primaryTextDark'}
|
square
|
||||||
square
|
onPress={() => openMuteDialog(participant)}
|
||||||
onPress={() => setIsAlertOpen(true)}
|
tooltip={t('muteParticipant', { name })}
|
||||||
tooltip={t('muteParticipant', { name })}
|
>
|
||||||
>
|
{!isMuted ? <RiMicLine /> : <RiMicOffLine />}
|
||||||
{!isMuted ? <RiMicLine /> : <RiMicOffLine />}
|
</Button>
|
||||||
</Button>
|
|
||||||
<MuteAlertDialog
|
|
||||||
isOpen={isAlertOpen}
|
|
||||||
onSubmit={() =>
|
|
||||||
muteParticipant(participant).then(() => setIsAlertOpen(false))
|
|
||||||
}
|
|
||||||
onClose={() => setIsAlertOpen(false)}
|
|
||||||
name={name}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,11 @@ import {
|
|||||||
import Source = Track.Source
|
import Source = Track.Source
|
||||||
import { RiMicFill, RiMicOffFill } from '@remixicon/react'
|
import { RiMicFill, RiMicOffFill } from '@remixicon/react'
|
||||||
import { Button } from '@/primitives'
|
import { Button } from '@/primitives'
|
||||||
import { useState } from 'react'
|
|
||||||
import { useMuteParticipant } from '@/features/rooms/api/muteParticipant'
|
|
||||||
import { useCanMute } from '@/features/rooms/livekit/hooks/useCanMute'
|
import { useCanMute } from '@/features/rooms/livekit/hooks/useCanMute'
|
||||||
import { ParticipantMenuButton } from './menu/ParticipantMenuButton'
|
import { ParticipantMenuButton } from './menu/ParticipantMenuButton'
|
||||||
import { PinBadge } from './PinBadge'
|
import { PinBadge } from './PinBadge'
|
||||||
import { UnauthenticatedBadge } from './UnauthenticatedBadge'
|
import { UnauthenticatedBadge } from './UnauthenticatedBadge'
|
||||||
import { MuteAlertDialog } from '@/features/rooms/livekit/components/MuteAlertDialog'
|
import { openMuteDialog } from '@/stores/muteDialog'
|
||||||
import { ParticipantName } from './ParticipantName'
|
import { ParticipantName } from './ParticipantName'
|
||||||
|
|
||||||
type MicIndicatorProps = {
|
type MicIndicatorProps = {
|
||||||
@@ -34,7 +32,6 @@ type MicIndicatorProps = {
|
|||||||
|
|
||||||
const MicIndicator = ({ participant }: MicIndicatorProps) => {
|
const MicIndicator = ({ participant }: MicIndicatorProps) => {
|
||||||
const { t } = useTranslation('rooms')
|
const { t } = useTranslation('rooms')
|
||||||
const { muteParticipant } = useMuteParticipant()
|
|
||||||
const { isMuted } = useTrackMutedIndicator({
|
const { isMuted } = useTrackMutedIndicator({
|
||||||
participant: participant,
|
participant: participant,
|
||||||
source: Source.Microphone,
|
source: Source.Microphone,
|
||||||
@@ -42,7 +39,6 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => {
|
|||||||
|
|
||||||
const canMute = useCanMute(participant)
|
const canMute = useCanMute(participant)
|
||||||
const isSpeaking = useIsSpeaking(participant)
|
const isSpeaking = useIsSpeaking(participant)
|
||||||
const [isAlertOpen, setIsAlertOpen] = useState(false)
|
|
||||||
const name = participant.name || participant.identity
|
const name = participant.name || participant.identity
|
||||||
|
|
||||||
const label = isLocal(participant)
|
const label = isLocal(participant)
|
||||||
@@ -52,46 +48,34 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Button
|
||||||
<Button
|
square
|
||||||
square
|
variant="greyscale"
|
||||||
variant="greyscale"
|
size="sm"
|
||||||
size="sm"
|
tooltip={label}
|
||||||
tooltip={label}
|
aria-label={label}
|
||||||
aria-label={label}
|
isDisabled={isMuted || !canMute}
|
||||||
isDisabled={isMuted || !canMute}
|
onPress={async () =>
|
||||||
onPress={async () =>
|
!isMuted && isLocal(participant)
|
||||||
!isMuted && isLocal(participant)
|
? await (participant as LocalParticipant)?.setMicrophoneEnabled(false)
|
||||||
? await (participant as LocalParticipant)?.setMicrophoneEnabled(
|
: openMuteDialog(participant)
|
||||||
false
|
}
|
||||||
)
|
data-attr="participants-mute"
|
||||||
: setIsAlertOpen(true)
|
>
|
||||||
}
|
{isMuted ? (
|
||||||
data-attr="participants-mute"
|
<RiMicOffFill color={'gray'} aria-hidden={true} />
|
||||||
>
|
) : (
|
||||||
{isMuted ? (
|
<RiMicFill
|
||||||
<RiMicOffFill color={'gray'} aria-hidden={true} />
|
className={css({
|
||||||
) : (
|
color: isSpeaking ? 'primaryDark.300' : 'primaryDark.50',
|
||||||
<RiMicFill
|
animation: isSpeaking
|
||||||
className={css({
|
? 'pulse_background 800ms infinite'
|
||||||
color: isSpeaking ? 'primaryDark.300' : 'primaryDark.50',
|
: undefined,
|
||||||
animation: isSpeaking
|
})}
|
||||||
? 'pulse_background 800ms infinite'
|
aria-hidden={true}
|
||||||
: undefined,
|
/>
|
||||||
})}
|
)}
|
||||||
aria-hidden={true}
|
</Button>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
<MuteAlertDialog
|
|
||||||
isOpen={isAlertOpen}
|
|
||||||
onSubmit={() =>
|
|
||||||
muteParticipant(participant).then(() => setIsAlertOpen(false))
|
|
||||||
}
|
|
||||||
onClose={() => setIsAlertOpen(false)}
|
|
||||||
name={name}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import { useStartRecording, useStopRecording } from '@/features/recording'
|
import { useStartRecording, useStopRecording } from '@/features/recording'
|
||||||
import { recordingStore } from '@/stores/recording'
|
import { recordingStore } from '@/stores/recording'
|
||||||
|
import { captureEvent } from '@/features/analytics/telemetry'
|
||||||
|
|
||||||
export const useMutateRecording = () => {
|
export const useMutateRecording = () => {
|
||||||
const { mutateAsync: startRecording, isPending: isPendingToStart } =
|
const { mutateAsync: startRecording, isPending: isPendingToStart } =
|
||||||
useStartRecording({
|
useStartRecording({
|
||||||
onError: () => {
|
onError: () => {
|
||||||
recordingStore.isErrorDialogOpen = 'start'
|
recordingStore.isErrorDialogOpen = 'start'
|
||||||
|
captureEvent('error-starting-recording')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { mutateAsync: stopRecording, isPending: isPendingToStop } =
|
const { mutateAsync: stopRecording, isPending: isPendingToStop } =
|
||||||
useStopRecording({
|
useStopRecording({
|
||||||
onError: () => {
|
onError: () => {
|
||||||
recordingStore.isErrorDialogOpen = 'stop'
|
recordingStore.isErrorDialogOpen = 'stop'
|
||||||
|
captureEvent('error-stopping-recording')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
usePersistentUserChoices,
|
usePersistentUserChoices,
|
||||||
} from '@livekit/components-react'
|
} from '@livekit/components-react'
|
||||||
import {
|
import {
|
||||||
|
ConnectionError,
|
||||||
|
ConnectionErrorReason,
|
||||||
DisconnectReason,
|
DisconnectReason,
|
||||||
MediaDeviceFailure,
|
MediaDeviceFailure,
|
||||||
Room,
|
Room,
|
||||||
@@ -25,7 +27,11 @@ import { VideoConference } from '../livekit/prefabs/VideoConference'
|
|||||||
import { css } from '@/styled-system/css'
|
import { css } from '@/styled-system/css'
|
||||||
import { BackgroundProcessorFactory } from '../livekit/components/blur'
|
import { BackgroundProcessorFactory } from '../livekit/components/blur'
|
||||||
import { LocalUserChoices } from '@/stores/userChoices'
|
import { LocalUserChoices } from '@/stores/userChoices'
|
||||||
import { captureMediaEvent, reportError } from '@/features/analytics/telemetry'
|
import {
|
||||||
|
captureEvent,
|
||||||
|
captureMediaEvent,
|
||||||
|
reportError,
|
||||||
|
} from '@/features/analytics/telemetry'
|
||||||
import { useConfig } from '@/api/useConfig'
|
import { useConfig } from '@/api/useConfig'
|
||||||
import { isFireFox } from '@/utils/livekit'
|
import { isFireFox } from '@/utils/livekit'
|
||||||
import { useIsMobile } from '@/utils/useIsMobile'
|
import { useIsMobile } from '@/utils/useIsMobile'
|
||||||
@@ -227,6 +233,16 @@ export const Conference = ({
|
|||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
const failure = MediaDeviceFailure.getFailure(e)
|
const failure = MediaDeviceFailure.getFailure(e)
|
||||||
if (failure && failure !== MediaDeviceFailure.Other) return
|
if (failure && failure !== MediaDeviceFailure.Other) return
|
||||||
|
|
||||||
|
// connect() was aborted by a disconnect() before the join completed
|
||||||
|
if (
|
||||||
|
e instanceof ConnectionError &&
|
||||||
|
e.reason === ConnectionErrorReason.Cancelled
|
||||||
|
) {
|
||||||
|
void captureEvent('connection-cancelled')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
reportError('livekit_room_error', e, {
|
reportError('livekit_room_error', e, {
|
||||||
path: 'connect_publish',
|
path: 'connect_publish',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { useRef } from 'react'
|
||||||
|
import { useSnapshot } from 'valtio'
|
||||||
|
import { useMuteParticipant } from '@/features/rooms/api/muteParticipant'
|
||||||
|
import { closeMuteDialog, muteDialogStore } from '@/stores/muteDialog'
|
||||||
|
import { MuteAlertDialog } from './MuteAlertDialog'
|
||||||
|
|
||||||
|
export const MuteAlertDialogProvider = () => {
|
||||||
|
const { participant } = useSnapshot(muteDialogStore)
|
||||||
|
const { muteParticipant } = useMuteParticipant()
|
||||||
|
|
||||||
|
const lastNameRef = useRef('')
|
||||||
|
if (participant) {
|
||||||
|
lastNameRef.current = participant.name || participant.identity
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MuteAlertDialog
|
||||||
|
isOpen={!!participant}
|
||||||
|
name={lastNameRef.current}
|
||||||
|
onClose={closeMuteDialog}
|
||||||
|
onSubmit={() => {
|
||||||
|
const target = muteDialogStore.participant
|
||||||
|
if (!target) return
|
||||||
|
muteParticipant(target).then(closeMuteDialog)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
-3
@@ -105,9 +105,7 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
|
|||||||
|
|
||||||
_initVirtualBackgroundImage() {
|
_initVirtualBackgroundImage() {
|
||||||
if (this.options.type !== 'virtual') {
|
if (this.options.type !== 'virtual') {
|
||||||
throw new Error(
|
return
|
||||||
'Virtual background is only supported for virtual background'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const needsUpdate =
|
const needsUpdate =
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ import { reportError } from '@/features/analytics/telemetry'
|
|||||||
|
|
||||||
enum BlurRadius {
|
enum BlurRadius {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
LIGHT = 5,
|
LIGHT = 10,
|
||||||
NORMAL = 10,
|
NORMAL = 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSupported = BackgroundProcessorFactory.isSupported()
|
const isSupported = BackgroundProcessorFactory.isSupported()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { SubtitlesToggle } from '../../components/controls/SubtitlesToggle'
|
|||||||
import { OptionsButton } from '../../components/controls/Options/OptionsButton'
|
import { OptionsButton } from '../../components/controls/Options/OptionsButton'
|
||||||
import { StartMediaButton } from '../../components/controls/StartMediaButton'
|
import { StartMediaButton } from '../../components/controls/StartMediaButton'
|
||||||
import { MoreOptions } from './MoreOptions'
|
import { MoreOptions } from './MoreOptions'
|
||||||
import { useRef } from 'react'
|
import { RefObject, useMemo, useState } from 'react'
|
||||||
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
|
import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKeyboardShortcut'
|
||||||
import { useFullScreen } from '../../hooks/useFullScreen'
|
import { useFullScreen } from '../../hooks/useFullScreen'
|
||||||
import { VideoDeviceControl } from '../../components/controls/Device/VideoDeviceControl'
|
import { VideoDeviceControl } from '../../components/controls/Device/VideoDeviceControl'
|
||||||
@@ -21,7 +21,14 @@ export function DesktopControlBar({
|
|||||||
onDeviceError,
|
onDeviceError,
|
||||||
}: Readonly<ControlBarAuxProps>) {
|
}: Readonly<ControlBarAuxProps>) {
|
||||||
const browserSupportsScreenSharing = supportsScreenSharing()
|
const browserSupportsScreenSharing = supportsScreenSharing()
|
||||||
const desktopControlBarEl = useRef<HTMLDivElement>(null)
|
|
||||||
|
const [controlBarElement, setControlBarElement] =
|
||||||
|
useState<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
|
const desktopControlBarEl = useMemo<RefObject<HTMLDivElement>>(
|
||||||
|
() => ({ current: controlBarElement }),
|
||||||
|
[controlBarElement]
|
||||||
|
)
|
||||||
|
|
||||||
const { toggleFullScreen, isFullscreenAvailable } = useFullScreen({})
|
const { toggleFullScreen, isFullscreenAvailable } = useFullScreen({})
|
||||||
|
|
||||||
@@ -45,7 +52,7 @@ export function DesktopControlBar({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={desktopControlBarEl}
|
ref={setControlBarElement}
|
||||||
className={css({
|
className={css({
|
||||||
width: '100vw',
|
width: '100vw',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { RoomMetadataSynchronizer } from '../components/RoomMetadataSynchronizer
|
|||||||
import { useNoiseReduction } from '../hooks/useNoiseReduction'
|
import { useNoiseReduction } from '../hooks/useNoiseReduction'
|
||||||
import { VideoResolutionSubscription } from '../components/VideoResolutionSubscription'
|
import { VideoResolutionSubscription } from '../components/VideoResolutionSubscription'
|
||||||
import { SettingsDialogProvider } from '@/features/settings/components/SettingsDialogProvider'
|
import { SettingsDialogProvider } from '@/features/settings/components/SettingsDialogProvider'
|
||||||
|
import { MuteAlertDialogProvider } from '@/features/rooms/livekit/components/MuteAlertDialogProvider'
|
||||||
import { IsIdleDisconnectModal } from '../components/IsIdleDisconnectModal'
|
import { IsIdleDisconnectModal } from '../components/IsIdleDisconnectModal'
|
||||||
import { ReactionPortals } from '@/features/reactions/components/ReactionPortals'
|
import { ReactionPortals } from '@/features/reactions/components/ReactionPortals'
|
||||||
import { RoomContentArea } from '@/features/layout/components/RoomContentArea'
|
import { RoomContentArea } from '@/features/layout/components/RoomContentArea'
|
||||||
@@ -144,6 +145,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
|
|||||||
<ConnectionStateToast />
|
<ConnectionStateToast />
|
||||||
<RecordingProvider />
|
<RecordingProvider />
|
||||||
<SettingsDialogProvider />
|
<SettingsDialogProvider />
|
||||||
|
<MuteAlertDialogProvider />
|
||||||
<ReactionPortals />
|
<ReactionPortals />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { proxy, ref } from 'valtio'
|
||||||
|
import type { Participant } from 'livekit-client'
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
participant: Participant | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const muteDialogStore = proxy<State>({
|
||||||
|
participant: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const openMuteDialog = (participant: Participant) => {
|
||||||
|
muteDialogStore.participant = ref(participant)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const closeMuteDialog = () => {
|
||||||
|
muteDialogStore.participant = null
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user