♻️(frontend) generalize recording state badge for all recording types

Extend recording state badge component to work with all types of recordings
instead of just transcripts. Create flexible implementation that supports
screen recordings and future recording formats while maintaining consistent
visual indicators.
This commit is contained in:
lebaudantoine
2025-04-07 11:44:17 +02:00
committed by aleb_the_flash
parent f596aae1e8
commit 9734df9d5d
9 changed files with 106 additions and 49 deletions

View File

@@ -8,22 +8,22 @@ import { Text } from '@/primitives'
import { RemoteParticipant, RoomEvent } from 'livekit-client' import { RemoteParticipant, RoomEvent } from 'livekit-client'
import { decodeNotificationDataReceived } from '@/features/notifications/utils' import { decodeNotificationDataReceived } from '@/features/notifications/utils'
import { NotificationType } from '@/features/notifications/NotificationType' import { NotificationType } from '@/features/notifications/NotificationType'
import { TranscriptionStatus, transcriptionStore } from '@/stores/transcription' import { RecordingStatus, recordingStore } from '@/stores/recording'
export const TranscriptStateToast = () => { export const RecordingStateBadge = () => {
const { t } = useTranslation('rooms', { const { t } = useTranslation('rooms', {
keyPrefix: 'recordingBadge.transcript', keyPrefix: 'recordingBadge',
}) })
const room = useRoomContext() const room = useRoomContext()
const transcriptionSnap = useSnapshot(transcriptionStore) const recordingSnap = useSnapshot(recordingStore)
useEffect(() => { useEffect(() => {
if (room.isRecording) { if (room.isRecording && recordingSnap.status == RecordingStatus.STOPPED) {
transcriptionStore.status = TranscriptionStatus.STARTED recordingStore.status = RecordingStatus.ANY_STARTED
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [room.isRecording])
useEffect(() => { useEffect(() => {
const handleDataReceived = ( const handleDataReceived = (
@@ -36,10 +36,16 @@ export const TranscriptStateToast = () => {
switch (notification.type) { switch (notification.type) {
case NotificationType.TranscriptionStarted: case NotificationType.TranscriptionStarted:
transcriptionStore.status = TranscriptionStatus.STARTING recordingStore.status = RecordingStatus.TRANSCRIPT_STARTING
break break
case NotificationType.TranscriptionStopped: case NotificationType.TranscriptionStopped:
transcriptionStore.status = TranscriptionStatus.STOPPING recordingStore.status = RecordingStatus.TRANSCRIPT_STOPPING
break
case NotificationType.ScreenRecordingStarted:
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STARTING
break
case NotificationType.ScreenRecordingStopped:
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STOPPING
break break
default: default:
return return
@@ -47,9 +53,17 @@ export const TranscriptStateToast = () => {
} }
const handleRecordingStatusChanged = (status: boolean) => { const handleRecordingStatusChanged = (status: boolean) => {
transcriptionStore.status = status if (!status) {
? TranscriptionStatus.STARTED recordingStore.status = RecordingStatus.STOPPED
: TranscriptionStatus.STOPPED } else if (recordingSnap.status == RecordingStatus.TRANSCRIPT_STARTING) {
recordingStore.status = RecordingStatus.TRANSCRIPT_STARTED
} else if (
recordingSnap.status == RecordingStatus.SCREEN_RECORDING_STARTING
) {
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STARTED
} else {
recordingStore.status = RecordingStatus.ANY_STARTED
}
} }
room.on(RoomEvent.DataReceived, handleDataReceived) room.on(RoomEvent.DataReceived, handleDataReceived)
@@ -59,20 +73,30 @@ export const TranscriptStateToast = () => {
room.off(RoomEvent.DataReceived, handleDataReceived) room.off(RoomEvent.DataReceived, handleDataReceived)
room.off(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged) room.off(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
} }
}, [room]) }, [room, recordingSnap])
const key = useMemo(() => { const key = useMemo(() => {
switch (transcriptionSnap.status) { switch (recordingSnap.status) {
case TranscriptionStatus.STOPPING: case RecordingStatus.TRANSCRIPT_STARTED:
return 'stopping' return 'transcript.started'
case TranscriptionStatus.STARTING: case RecordingStatus.TRANSCRIPT_STOPPING:
return 'starting' return 'transcript.stopping'
case RecordingStatus.TRANSCRIPT_STARTING:
return 'transcript.starting'
case RecordingStatus.SCREEN_RECORDING_STARTED:
return 'screenRecording.started'
case RecordingStatus.SCREEN_RECORDING_STOPPING:
return 'screenRecording.stopping'
case RecordingStatus.SCREEN_RECORDING_STARTING:
return 'screenRecording.starting'
case RecordingStatus.ANY_STARTED:
return 'any.started'
default: default:
return 'started' return
} }
}, [transcriptionSnap]) }, [recordingSnap])
if (transcriptionSnap.status == TranscriptionStatus.STOPPED) return if (!key) return
return ( return (
<div <div

View File

@@ -15,10 +15,7 @@ import { useTranslation } from 'react-i18next'
import { NotificationPayload } from '@/features/notifications/NotificationPayload' import { NotificationPayload } from '@/features/notifications/NotificationPayload'
import { NotificationType } from '@/features/notifications/NotificationType' import { NotificationType } from '@/features/notifications/NotificationType'
import { useSnapshot } from 'valtio/index' import { useSnapshot } from 'valtio/index'
import { import { RecordingStatus, recordingStore } from '@/stores/recording'
TranscriptionStatus,
transcriptionStore,
} from '@/stores/transcription.ts'
import { useHasTranscriptAccess } from '../hooks/useHasTranscriptAccess' import { useHasTranscriptAccess } from '../hooks/useHasTranscriptAccess'
import { import {
BETA_USERS_FORM_URL, BETA_USERS_FORM_URL,
@@ -35,7 +32,7 @@ export const Transcript = () => {
const { mutateAsync: startRecordingRoom } = useStartRecording() const { mutateAsync: startRecordingRoom } = useStartRecording()
const { mutateAsync: stopRecordingRoom } = useStopRecording() const { mutateAsync: stopRecordingRoom } = useStopRecording()
const transcriptionSnap = useSnapshot(transcriptionStore) const recordingSnap = useSnapshot(recordingStore)
const room = useRoomContext() const room = useRoomContext()
@@ -70,11 +67,11 @@ export const Transcript = () => {
if (room.isRecording) { if (room.isRecording) {
await stopRecordingRoom({ id: roomId }) await stopRecordingRoom({ id: roomId })
await notifyParticipant(NotificationType.TranscriptionStopped) await notifyParticipant(NotificationType.TranscriptionStopped)
transcriptionStore.status = TranscriptionStatus.STOPPING recordingStore.status = RecordingStatus.TRANSCRIPT_STOPPING
} else { } else {
await startRecordingRoom({ id: roomId, mode: RecordingMode.Transcript }) await startRecordingRoom({ id: roomId, mode: RecordingMode.Transcript })
await notifyParticipant(NotificationType.TranscriptionStarted) await notifyParticipant(NotificationType.TranscriptionStarted)
transcriptionStore.status = TranscriptionStatus.STARTING recordingStore.status = RecordingStatus.TRANSCRIPT_STARTING
} }
} catch (error) { } catch (error) {
console.error('Failed to handle transcript:', error) console.error('Failed to handle transcript:', error)
@@ -85,9 +82,9 @@ export const Transcript = () => {
const isDisabled = useMemo( const isDisabled = useMemo(
() => () =>
isLoading || isLoading ||
transcriptionSnap.status === TranscriptionStatus.STARTING || recordingSnap.status === RecordingStatus.TRANSCRIPT_STARTING ||
transcriptionSnap.status === TranscriptionStatus.STOPPING, recordingSnap.status === RecordingStatus.TRANSCRIPT_STOPPING,
[isLoading, transcriptionSnap] [isLoading, recordingSnap]
) )
return ( return (

View File

@@ -28,7 +28,7 @@ import { FocusLayout } from '../components/FocusLayout'
import { ParticipantTile } from '../components/ParticipantTile' import { ParticipantTile } from '../components/ParticipantTile'
import { SidePanel } from '../components/SidePanel' import { SidePanel } from '../components/SidePanel'
import { useSidePanel } from '../hooks/useSidePanel' import { useSidePanel } from '../hooks/useSidePanel'
import { TranscriptStateToast } from '../components/TranscriptStateToast' import { RecordingStateBadge } from '../components/RecordingStateBadge'
import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal' import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal'
const LayoutWrapper = styled( const LayoutWrapper = styled(
@@ -231,7 +231,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
)} )}
<RoomAudioRenderer /> <RoomAudioRenderer />
<ConnectionStateToast /> <ConnectionStateToast />
<TranscriptStateToast /> <RecordingStateBadge />
</div> </div>
) )
} }

View File

@@ -297,6 +297,14 @@
"started": "", "started": "",
"starting": "", "starting": "",
"stopping": "" "stopping": ""
},
"screenRecording": {
"started": "",
"starting": "",
"stopping": ""
},
"any": {
"started": ""
} }
}, },
"participantTileFocus": { "participantTileFocus": {

View File

@@ -296,6 +296,14 @@
"started": "Transcribing", "started": "Transcribing",
"starting": "Transcription starting", "starting": "Transcription starting",
"stopping": "Transcription stopping" "stopping": "Transcription stopping"
},
"screenRecording": {
"started": "Recording in progress",
"starting": "Starting recording",
"stopping": "Stopping recording"
},
"any": {
"started": "Recording in progress"
} }
}, },
"participantTileFocus": { "participantTileFocus": {

View File

@@ -296,6 +296,14 @@
"started": "Transcription en cours", "started": "Transcription en cours",
"starting": "Démarrage de la transcription", "starting": "Démarrage de la transcription",
"stopping": "Arrêt de la transcription" "stopping": "Arrêt de la transcription"
},
"screenRecording": {
"started": "Enregistrement en cours",
"starting": "Démarrage de l'enregistrement",
"stopping": "Arrêt de l'enregistrement"
},
"any": {
"started": "Enregistrement en cours"
} }
}, },
"participantTileFocus": { "participantTileFocus": {

View File

@@ -296,6 +296,14 @@
"started": "Transcriptie bezig", "started": "Transcriptie bezig",
"starting": "Transcriptie begint", "starting": "Transcriptie begint",
"stopping": "Transcriptie stopt" "stopping": "Transcriptie stopt"
},
"screenRecording": {
"started": "Opname bezig",
"starting": "Opname starten",
"stopping": "Opname stoppen"
},
"any": {
"started": "Opname bezig"
} }
}, },
"participantTileFocus": { "participantTileFocus": {

View File

@@ -0,0 +1,20 @@
import { proxy } from 'valtio'
export enum RecordingStatus {
TRANSCRIPT_STARTING,
TRANSCRIPT_STARTED,
TRANSCRIPT_STOPPING,
STOPPED,
SCREEN_RECORDING_STARTING,
SCREEN_RECORDING_STARTED,
SCREEN_RECORDING_STOPPING,
ANY_STARTED,
}
type State = {
status: RecordingStatus
}
export const recordingStore = proxy<State>({
status: RecordingStatus.STOPPED,
})

View File

@@ -1,16 +0,0 @@
import { proxy } from 'valtio'
export enum TranscriptionStatus {
STARTING,
STARTED,
STOPPING,
STOPPED,
}
type State = {
status: TranscriptionStatus
}
export const transcriptionStore = proxy<State>({
status: TranscriptionStatus.STOPPED,
})