♻️(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 { decodeNotificationDataReceived } from '@/features/notifications/utils'
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', {
keyPrefix: 'recordingBadge.transcript',
keyPrefix: 'recordingBadge',
})
const room = useRoomContext()
const transcriptionSnap = useSnapshot(transcriptionStore)
const recordingSnap = useSnapshot(recordingStore)
useEffect(() => {
if (room.isRecording) {
transcriptionStore.status = TranscriptionStatus.STARTED
if (room.isRecording && recordingSnap.status == RecordingStatus.STOPPED) {
recordingStore.status = RecordingStatus.ANY_STARTED
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
}, [room.isRecording])
useEffect(() => {
const handleDataReceived = (
@@ -36,10 +36,16 @@ export const TranscriptStateToast = () => {
switch (notification.type) {
case NotificationType.TranscriptionStarted:
transcriptionStore.status = TranscriptionStatus.STARTING
recordingStore.status = RecordingStatus.TRANSCRIPT_STARTING
break
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
default:
return
@@ -47,9 +53,17 @@ export const TranscriptStateToast = () => {
}
const handleRecordingStatusChanged = (status: boolean) => {
transcriptionStore.status = status
? TranscriptionStatus.STARTED
: TranscriptionStatus.STOPPED
if (!status) {
recordingStore.status = RecordingStatus.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)
@@ -59,20 +73,30 @@ export const TranscriptStateToast = () => {
room.off(RoomEvent.DataReceived, handleDataReceived)
room.off(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
}
}, [room])
}, [room, recordingSnap])
const key = useMemo(() => {
switch (transcriptionSnap.status) {
case TranscriptionStatus.STOPPING:
return 'stopping'
case TranscriptionStatus.STARTING:
return 'starting'
switch (recordingSnap.status) {
case RecordingStatus.TRANSCRIPT_STARTED:
return 'transcript.started'
case RecordingStatus.TRANSCRIPT_STOPPING:
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:
return 'started'
return
}
}, [transcriptionSnap])
}, [recordingSnap])
if (transcriptionSnap.status == TranscriptionStatus.STOPPED) return
if (!key) return
return (
<div

View File

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

View File

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

View File

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

View File

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

View File

@@ -296,6 +296,14 @@
"started": "Transcription en cours",
"starting": "Démarrage 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": {

View File

@@ -296,6 +296,14 @@
"started": "Transcriptie bezig",
"starting": "Transcriptie begint",
"stopping": "Transcriptie stopt"
},
"screenRecording": {
"started": "Opname bezig",
"starting": "Opname starten",
"stopping": "Opname stoppen"
},
"any": {
"started": "Opname bezig"
}
},
"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,
})