(frontend) update recording metadata alongside recording state changes

Following the previous commit, refactor the frontend to rely on room metadata to
track which recording is running and update the interface accordingly. This
implementation is not fully functional yet.

The limit-reached dialog triggering mechanism is currently broken and will be
fixed in upcoming commits. I also simplified the interface lifecycle, but some
edge cases are not yet handled—for example, transcription controls should be
disabled when a screen recording is started. This will be improved soon.

Controls were extracted into a reusable component using early returns. This
makes the logic easier to read, but slightly increases the overall complexity of
the recording side panel component.

Relying on literals to manage recording statuses is quite poor, feel free to
enhance this part.
This commit is contained in:
lebaudantoine
2026-01-01 00:25:06 +01:00
committed by aleb_the_flash
parent 16badde82d
commit da3dfedcbc
13 changed files with 258 additions and 295 deletions

View File

@@ -0,0 +1,116 @@
import { css } from '@/styled-system/css'
import { HStack } from '@/styled-system/jsx'
import { Spinner } from '@/primitives/Spinner'
import { Button, Text } from '@/primitives'
import { useTranslation } from 'react-i18next'
import { RecordingStatuses } from '../hooks/useRecordingStatuses'
import { ReactNode, useEffect, useRef, useState } from 'react'
import { useRoomContext } from '@livekit/components-react'
import { ConnectionState } from 'livekit-client'
const Layout = ({ children }: { children: ReactNode }) => (
<div
className={css({
marginBottom: '80px',
width: '100%',
})}
>
{children}
</div>
)
interface ControlsButtonProps {
i18nKeyPrefix: string
statuses: RecordingStatuses
handle: () => void
isPendingToStart: boolean
isPendingToStop: boolean
}
const MIN_SPINNER_DISPLAY_TIME = 2000
export const ControlsButton = ({
i18nKeyPrefix,
statuses,
handle,
isPendingToStart,
isPendingToStop,
}: ControlsButtonProps) => {
const { t } = useTranslation('rooms', { keyPrefix: i18nKeyPrefix })
const room = useRoomContext()
const isRoomConnected = room.state == ConnectionState.Connected
const [showSaving, setShowSaving] = useState(false)
const timeoutRef = useRef<NodeJS.Timeout>()
const isSaving = statuses.isSaving || isPendingToStop
const isDisabled = !isRoomConnected
useEffect(() => {
if (isSaving) {
clearTimeout(timeoutRef.current)
setShowSaving(true)
} else if (showSaving) {
timeoutRef.current = setTimeout(() => {
setShowSaving(false)
}, MIN_SPINNER_DISPLAY_TIME)
}
return () => clearTimeout(timeoutRef.current)
}, [isSaving, showSaving])
// Saving state
if (showSaving) {
return (
<Layout>
<HStack width="100%" height="46px" justify="center">
<Spinner size={30} />
<Text variant="body">{t('button.saving')}</Text>
</HStack>
</Layout>
)
}
// Starting state
if (statuses.isStarting || isPendingToStart) {
return (
<Layout>
<HStack width="100%" height="46px" justify="center">
<Spinner size={30} />
{t('button.starting')}
</HStack>
</Layout>
)
}
// Active state (Stop button)
if (statuses.isStarted) {
return (
<Layout>
<Button
variant="tertiary"
fullWidth
onPress={handle}
isDisabled={isDisabled}
>
{t('button.stop')}
</Button>
</Layout>
)
}
// Inactive state (Start button)
return (
<Layout>
<Button
variant="tertiary"
fullWidth
onPress={handle}
isDisabled={isDisabled}
>
{t('button.start')}
</Button>
</Layout>
)
}

View File

@@ -1,132 +1,69 @@
import { css } from '@/styled-system/css'
import { useTranslation } from 'react-i18next'
import { useSnapshot } from 'valtio'
import { useRoomContext } from '@livekit/components-react'
import { Spinner } from '@/primitives/Spinner'
import { useEffect, useMemo, useState } from 'react'
import { useMemo, useState } from 'react'
import { Text } from '@/primitives'
import { RoomEvent } from 'livekit-client'
import { decodeNotificationDataReceived } from '@/features/notifications/utils'
import { NotificationType } from '@/features/notifications/NotificationType'
import { RecordingStatus, recordingStore } from '@/stores/recording'
import { RiRecordCircleLine } from '@remixicon/react'
import {
RecordingMode,
useHasRecordingAccess,
useIsRecordingActive,
useRecordingStatuses,
} from '@/features/recording'
import { FeatureFlags } from '@/features/analytics/enums'
import { Button as RACButton } from 'react-aria-components'
import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
import { LimitReachedAlertDialog } from './LimitReachedAlertDialog'
import { useRoomMetadata } from '../hooks/useRoomMetadata'
export const RecordingStateToast = () => {
const { t } = useTranslation('rooms', {
keyPrefix: 'recordingStateToast',
})
const room = useRoomContext()
const isAdminOrOwner = useIsAdminOrOwner()
const { openTranscript, openScreenRecording } = useSidePanel()
const [isAlertOpen, setIsAlertOpen] = useState(false)
const recordingSnap = useSnapshot(recordingStore)
const hasTranscriptAccess = useHasRecordingAccess(
RecordingMode.Transcript,
FeatureFlags.Transcript
)
const isTranscriptActive = useIsRecordingActive(RecordingMode.Transcript)
const hasScreenRecordingAccess = useHasRecordingAccess(
RecordingMode.ScreenRecording,
FeatureFlags.ScreenRecording
)
const isScreenRecordingActive = useIsRecordingActive(
RecordingMode.ScreenRecording
)
const {
isStarted: isScreenRecordingStarted,
isStarting: isScreenRecordingStarting,
isActive: isScreenRecordingActive,
} = useRecordingStatuses(RecordingMode.ScreenRecording)
useEffect(() => {
if (room.isRecording && recordingSnap.status == RecordingStatus.STOPPED) {
recordingStore.status = RecordingStatus.ANY_STARTED
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [room.isRecording])
const {
isStarted: isTranscriptStarted,
isStarting: isTranscriptStarting,
isActive: isTranscriptActive,
} = useRecordingStatuses(RecordingMode.Transcript)
useEffect(() => {
const handleDataReceived = (payload: Uint8Array) => {
const notification = decodeNotificationDataReceived(payload)
const isStarted = isScreenRecordingStarted || isTranscriptStarted
const isStarting = isTranscriptStarting || isScreenRecordingStarting
if (!notification) return
switch (notification.type) {
case NotificationType.TranscriptionStarted:
recordingStore.status = RecordingStatus.TRANSCRIPT_STARTING
break
case NotificationType.TranscriptionStopped:
recordingStore.status = RecordingStatus.TRANSCRIPT_STOPPING
break
case NotificationType.TranscriptionLimitReached:
if (isAdminOrOwner) setIsAlertOpen(true)
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
case NotificationType.ScreenRecordingLimitReached:
if (isAdminOrOwner) setIsAlertOpen(true)
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STOPPING
break
default:
return
}
}
const handleRecordingStatusChanged = (status: boolean) => {
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)
room.on(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
return () => {
room.off(RoomEvent.DataReceived, handleDataReceived)
room.off(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
}
}, [room, recordingSnap, setIsAlertOpen, isAdminOrOwner])
const metadata = useRoomMetadata()
const key = useMemo(() => {
switch (recordingSnap.status) {
case RecordingStatus.TRANSCRIPT_STARTED:
return 'transcript.started'
case RecordingStatus.TRANSCRIPT_STARTING:
return 'transcript.starting'
case RecordingStatus.SCREEN_RECORDING_STARTED:
return 'screenRecording.started'
case RecordingStatus.SCREEN_RECORDING_STARTING:
return 'screenRecording.starting'
case RecordingStatus.ANY_STARTED:
return 'any.started'
default:
return
if (!metadata?.recording_status || !metadata?.recording_mode) {
return undefined
}
}, [recordingSnap])
if (!isStarting && !isStarted) {
return undefined
}
return `${metadata.recording_mode}.${metadata.recording_status}`
}, [metadata, isStarted, isStarting])
if (!key)
return isAdminOrOwner ? (
@@ -137,8 +74,6 @@ export const RecordingStateToast = () => {
/>
) : null
const isStarted = key?.includes('started')
const hasScreenRecordingAccessAndActive =
isScreenRecordingActive && hasScreenRecordingAccess
const hasTranscriptAccessAndActive = isTranscriptActive && hasTranscriptAccess

View File

@@ -9,11 +9,10 @@ import {
useStartRecording,
useStopRecording,
useHumanizeRecordingMaxDuration,
useRecordingStatuses,
} from '@/features/recording'
import { useEffect, useMemo, useState } from 'react'
import { ConnectionState, RoomEvent } from 'livekit-client'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RecordingStatus, recordingStore } from '@/stores/recording'
import {
NotificationType,
@@ -21,13 +20,12 @@ import {
useNotifyParticipants,
} from '@/features/notifications'
import posthog from 'posthog-js'
import { useSnapshot } from 'valtio/index'
import { Spinner } from '@/primitives/Spinner'
import { useConfig } from '@/api/useConfig'
import { FeatureFlags } from '@/features/analytics/enums'
import { NoAccessView } from './NoAccessView'
import { HStack, VStack } from '@/styled-system/jsx'
import { ControlsButton } from './ControlsButton'
import { RowWrapper } from './RowWrapper'
import { VStack } from '@/styled-system/jsx'
import { Checkbox } from '@/primitives/Checkbox'
import { useTranscriptionLanguage } from '@/features/settings'
@@ -35,9 +33,8 @@ export const ScreenRecordingSidePanel = () => {
const { data } = useConfig()
const recordingMaxDuration = useHumanizeRecordingMaxDuration()
const [isLoading, setIsLoading] = useState(false)
const recordingSnap = useSnapshot(recordingStore)
const { t } = useTranslation('rooms', { keyPrefix: 'screenRecording' })
const keyPrefix = 'screenRecording'
const { t } = useTranslation('rooms', { keyPrefix })
const [isErrorDialogOpen, setIsErrorDialogOpen] = useState('')
@@ -63,31 +60,9 @@ export const ScreenRecordingSidePanel = () => {
onError: () => setIsErrorDialogOpen('stop'),
})
const statuses = useMemo(() => {
return {
isAnotherModeStarted:
recordingSnap.status == RecordingStatus.TRANSCRIPT_STARTED,
isStarting:
recordingSnap.status == RecordingStatus.SCREEN_RECORDING_STARTING,
isStarted:
recordingSnap.status == RecordingStatus.SCREEN_RECORDING_STARTED,
isStopping:
recordingSnap.status == RecordingStatus.SCREEN_RECORDING_STOPPING,
}
}, [recordingSnap])
const statuses = useRecordingStatuses(RecordingMode.ScreenRecording)
const room = useRoomContext()
const isRoomConnected = room.state == ConnectionState.Connected
useEffect(() => {
const handleRecordingStatusChanged = () => {
setIsLoading(false)
}
room.on(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
return () => {
room.off(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
}
}, [room])
const handleScreenRecording = async () => {
if (!roomId) {
@@ -95,11 +70,10 @@ export const ScreenRecordingSidePanel = () => {
return
}
try {
setIsLoading(true)
if (room.isRecording) {
if (statuses.isStarted || statuses.isStarting) {
setIncludeTranscript(false)
await stopRecordingRoom({ id: roomId })
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STOPPING
await notifyParticipants({
type: NotificationType.ScreenRecordingStopped,
})
@@ -120,7 +94,7 @@ export const ScreenRecordingSidePanel = () => {
mode: RecordingMode.ScreenRecording,
options: recordingOptions,
})
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STARTING
await notifyParticipants({
type: NotificationType.ScreenRecordingStarted,
})
@@ -128,14 +102,13 @@ export const ScreenRecordingSidePanel = () => {
}
} catch (error) {
console.error('Failed to handle recording:', error)
setIsLoading(false)
}
}
if (hasFeatureWithoutAdminRights) {
return (
<NoAccessView
i18nKeyPrefix="screenRecording"
i18nKeyPrefix={keyPrefix}
i18nKey="notAdminOrOwner"
helpArticle={data?.support?.help_article_recording}
imagePath="/assets/intro-slider/4.png"
@@ -208,51 +181,19 @@ export const ScreenRecordingSidePanel = () => {
size="sm"
isSelected={includeTranscript}
onChange={setIncludeTranscript}
isDisabled={
statuses.isStarting || statuses.isStarted || isPendingToStart
}
isDisabled={statuses.isActive || isPendingToStart}
>
<Text variant="sm">{t('details.transcription')}</Text>
</Checkbox>
</div>
</VStack>
<div
className={css({
marginBottom: '80px',
width: '100%',
})}
>
{statuses.isStopping || isPendingToStop ? (
<HStack width={'100%'} height={'46px'} justify="center">
<Spinner size={30} />
<Text variant="body">{t('button.saving')}</Text>
</HStack>
) : (
<>
{statuses.isStarted || statuses.isStarting || room.isRecording ? (
<Button
variant="tertiary"
fullWidth
onPress={() => handleScreenRecording()}
isDisabled={statuses.isStopping || isPendingToStop || isLoading}
data-attr="stop-transcript"
>
{t('button.stop')}
</Button>
) : (
<Button
variant="tertiary"
fullWidth
onPress={() => handleScreenRecording()}
isDisabled={isPendingToStart || !isRoomConnected || isLoading}
data-attr="start-transcript"
>
{t('button.start')}
</Button>
)}
</>
)}
</div>
<ControlsButton
i18nKeyPrefix={keyPrefix}
handle={handleScreenRecording}
statuses={statuses}
isPendingToStart={isPendingToStart}
isPendingToStop={isPendingToStop}
/>
<Dialog
isOpen={!!isErrorDialogOpen}
role="alertdialog"

View File

@@ -10,11 +10,10 @@ import {
useStopRecording,
useHasFeatureWithoutAdminRights,
useHumanizeRecordingMaxDuration,
useRecordingStatuses,
} from '../index'
import { useEffect, useMemo, useState } from 'react'
import { ConnectionState, RoomEvent } from 'livekit-client'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RecordingStatus } from '@store/recording'
import { FeatureFlags } from '@/features/analytics/enums'
import {
NotificationType,
@@ -22,8 +21,6 @@ import {
notifyRecordingSaveInProgress,
} from '@/features/notifications'
import posthog from 'posthog-js'
import { useSnapshot } from 'valtio/index'
import { Spinner } from '@/primitives/Spinner'
import { useConfig } from '@/api/useConfig'
import { VStack } from '@/styled-system/jsx'
import { Checkbox } from '@/primitives/Checkbox.tsx'
@@ -34,20 +31,19 @@ import {
useTranscriptionLanguage,
} from '@/features/settings'
import { NoAccessView } from './NoAccessView'
import { ControlsButton } from './ControlsButton'
import { RowWrapper } from './RowWrapper'
export const TranscriptSidePanel = () => {
const { data } = useConfig()
const recordingMaxDuration = useHumanizeRecordingMaxDuration()
const [isLoading, setIsLoading] = useState(false)
const { t } = useTranslation('rooms', { keyPrefix: 'transcript' })
const keyPrefix = 'transcript'
const { t } = useTranslation('rooms', { keyPrefix })
const [isErrorDialogOpen, setIsErrorDialogOpen] = useState('')
const [includeScreenRecording, setIncludeScreenRecording] = useState(false)
const recordingSnap = useSnapshot(recordingStore)
const { notifyParticipants } = useNotifyParticipants()
const { selectedLanguageKey, selectedLanguageLabel, isLanguageSetToAuto } =
useTranscriptionLanguage()
@@ -76,28 +72,9 @@ export const TranscriptSidePanel = () => {
onError: () => setIsErrorDialogOpen('stop'),
})
const statuses = useMemo(() => {
return {
isAnotherModeStarted:
recordingSnap.status == RecordingStatus.SCREEN_RECORDING_STARTED,
isStarting: recordingSnap.status == RecordingStatus.TRANSCRIPT_STARTING,
isStarted: recordingSnap.status == RecordingStatus.TRANSCRIPT_STARTED,
isStopping: recordingSnap.status == RecordingStatus.TRANSCRIPT_STOPPING,
}
}, [recordingSnap])
const statuses = useRecordingStatuses(RecordingMode.Transcript)
const room = useRoomContext()
const isRoomConnected = room.state == ConnectionState.Connected
useEffect(() => {
const handleRecordingStatusChanged = () => {
setIsLoading(false)
}
room.on(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
return () => {
room.off(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
}
}, [room])
const handleTranscript = async () => {
if (!roomId) {
@@ -105,11 +82,10 @@ export const TranscriptSidePanel = () => {
return
}
try {
setIsLoading(true)
if (room.isRecording) {
if (statuses.isStarted || statuses.isStarting) {
await stopRecordingRoom({ id: roomId })
setIncludeScreenRecording(false)
recordingStore.status = RecordingStatus.TRANSCRIPT_STOPPING
await notifyParticipants({
type: NotificationType.TranscriptionStopped,
})
@@ -126,8 +102,10 @@ export const TranscriptSidePanel = () => {
...(!isLanguageSetToAuto && {
language: selectedLanguageKey,
}),
...(includeScreenRecording && {
transcribe: true,
original_mode: RecordingMode.Transcript,
}),
...(includeScreenRecording && { transcribe: true }),
}
await startRecordingRoom({
@@ -135,7 +113,7 @@ export const TranscriptSidePanel = () => {
mode: recordingMode,
options: recordingOptions,
})
recordingStore.status = RecordingStatus.TRANSCRIPT_STARTING
await notifyParticipants({
type: NotificationType.TranscriptionStarted,
})
@@ -143,14 +121,13 @@ export const TranscriptSidePanel = () => {
}
} catch (error) {
console.error('Failed to handle transcript:', error)
setIsLoading(false)
}
}
if (hasFeatureWithoutAdminRights) {
return (
<NoAccessView
i18nKeyPrefix="transcript"
i18nKeyPrefix={keyPrefix}
i18nKey="notAdminOrOwner"
helpArticle={data?.support?.help_article_transcript}
imagePath="/assets/intro-slider/3.png"
@@ -161,7 +138,7 @@ export const TranscriptSidePanel = () => {
if (!hasTranscriptAccess) {
return (
<NoAccessView
i18nKeyPrefix="transcript"
i18nKeyPrefix={keyPrefix}
i18nKey="premium"
helpArticle={data?.support?.help_article_transcript}
imagePath="/assets/intro-slider/3.png"
@@ -251,7 +228,6 @@ export const TranscriptSidePanel = () => {
</Text>
</RowWrapper>
<div className={css({ height: '15px' })} />
<div
className={css({
width: '100%',
@@ -262,51 +238,19 @@ export const TranscriptSidePanel = () => {
size="sm"
isSelected={includeScreenRecording}
onChange={setIncludeScreenRecording}
isDisabled={
statuses.isStarting || statuses.isStarted || isPendingToStart
}
isDisabled={statuses.isActive || isPendingToStart}
>
<Text variant="sm">{t('details.recording')}</Text>
</Checkbox>
</div>
</VStack>
<div
className={css({
marginBottom: '80px',
width: '100%',
})}
>
{statuses.isStopping || isPendingToStop ? (
<HStack width={'100%'} height={'46px'} justify="center">
<Spinner size={30} />
<Text variant="body">{t('button.saving')}</Text>
</HStack>
) : (
<>
{statuses.isStarted || statuses.isStarting || room.isRecording ? (
<Button
variant="tertiary"
fullWidth
onPress={() => handleTranscript()}
isDisabled={statuses.isStopping || isPendingToStop || isLoading}
data-attr="stop-transcript"
>
{t('button.stop')}
</Button>
) : (
<Button
variant="tertiary"
fullWidth
onPress={() => handleTranscript()}
isDisabled={isPendingToStart || !isRoomConnected || isLoading}
data-attr="start-transcript"
>
{t('button.start')}
</Button>
)}
</>
)}
</div>
<ControlsButton
i18nKeyPrefix={keyPrefix}
handle={handleTranscript}
statuses={statuses}
isPendingToStart={isPendingToStart}
isPendingToStop={isPendingToStop}
/>
<Dialog
isOpen={!!isErrorDialogOpen}
role="alertdialog"

View File

@@ -1,22 +0,0 @@
import { useSnapshot } from 'valtio'
import { RecordingStatus, recordingStore } from '@/stores/recording'
import { RecordingMode } from '@/features/recording'
export const useIsRecordingActive = (mode: RecordingMode) => {
const recordingSnap = useSnapshot(recordingStore)
switch (mode) {
case RecordingMode.Transcript:
return [
RecordingStatus.TRANSCRIPT_STARTED,
RecordingStatus.TRANSCRIPT_STARTING,
RecordingStatus.TRANSCRIPT_STOPPING,
].includes(recordingSnap.status)
case RecordingMode.ScreenRecording:
return [
RecordingStatus.SCREEN_RECORDING_STARTED,
RecordingStatus.SCREEN_RECORDING_STARTING,
RecordingStatus.SCREEN_RECORDING_STOPPING,
].includes(recordingSnap.status)
}
}

View File

@@ -0,0 +1,36 @@
import { RecordingMode } from '@/features/recording'
import { useRoomMetadata } from './useRoomMetadata'
import { useMemo } from 'react'
export interface RecordingStatuses {
isStarting: boolean
isStarted: boolean
isSaving: boolean
isActive: boolean
}
export const useRecordingStatuses = (
mode: RecordingMode
): RecordingStatuses => {
const metadata = useRoomMetadata()
return useMemo(() => {
if (metadata && metadata?.recording_mode === mode) {
return {
isStarting: metadata.recording_status === 'starting',
isStarted: metadata.recording_status === 'started',
isSaving: metadata.recording_status === 'saving',
isActive: ['starting', 'started', 'saving'].includes(
metadata.recording_status
),
}
}
return {
isStarting: false,
isStarted: false,
isSaving: false,
isActive: false,
}
}, [mode, metadata])
}

View File

@@ -0,0 +1,18 @@
import { useRoomInfo } from '@livekit/components-react'
import { useMemo } from 'react'
export const useRoomMetadata = () => {
const { metadata } = useRoomInfo()
return useMemo(() => {
if (metadata) {
try {
return JSON.parse(metadata)
} catch (error) {
console.error('Failed to parse room metadata:', error)
return undefined
}
} else {
return undefined
}
}, [metadata])
}

View File

@@ -1,9 +1,9 @@
// hooks
export { useIsRecordingModeEnabled } from './hooks/useIsRecordingModeEnabled'
export { useHasRecordingAccess } from './hooks/useHasRecordingAccess'
export { useIsRecordingActive } from './hooks/useIsRecordingActive'
export { useHasFeatureWithoutAdminRights } from './hooks/useHasFeatureWithoutAdminRights'
export { useHumanizeRecordingMaxDuration } from './hooks/useHumanizeRecordingMaxDuration'
export { useRecordingStatuses } from './hooks/useRecordingStatuses'
// api
export { useStartRecording } from './api/startRecording'

View File

@@ -323,7 +323,8 @@
"button": {
"start": "Meeting transkribieren starten",
"stop": "Meeting transkribieren beenden",
"saving": "Speichern…"
"saving": "Speichern…",
"starting": "Wird gestartet…"
},
"linkMore": "Mehr erfahren",
"notAdminOrOwner": {
@@ -368,7 +369,8 @@
"button": {
"start": "Meeting-Aufzeichnung starten",
"stop": "Meeting-Aufzeichnung beenden",
"saving": "Wird gespeichert…"
"saving": "Wird gespeichert…",
"starting": "Wird gestartet…"
},
"notAdminOrOwner": {
"heading": "Zugriff eingeschränkt",
@@ -507,7 +509,7 @@
"starting": "Transkription wird gestartet",
"stopping": "Transkription wird gestoppt"
},
"screenRecording": {
"screen_recording": {
"started": "Aufzeichnung läuft",
"starting": "Aufzeichnung wird gestartet",
"stopping": "Aufzeichnung wird gestoppt"

View File

@@ -323,7 +323,8 @@
"button": {
"start": "Start transcribing the meeting",
"stop": "Stop transcribing the meeting",
"saving": "Saving…"
"saving": "Saving…",
"starting": "Starting…"
},
"linkMore": "Learn more",
"notAdminOrOwner": {
@@ -368,7 +369,8 @@
"button": {
"start": "Start recording the meeting",
"stop": "Stop recording the meeting",
"saving": "Saving…"
"saving": "Saving…",
"starting": "Starting…"
},
"notAdminOrOwner": {
"heading": "Restricted Access",
@@ -507,7 +509,7 @@
"starting": "Transcription starting",
"stopping": "Transcription stopping"
},
"screenRecording": {
"screen_recording": {
"started": "Recording in progress",
"starting": "Starting recording",
"stopping": "Stopping recording"

View File

@@ -323,7 +323,8 @@
"button": {
"start": "Commencer à transcrire la réunion",
"stop": "Arrêter de transcrire la réunion",
"saving": "Sauvegarde…"
"saving": "Sauvegarde…",
"starting": "Démarrage…"
},
"linkMore": "En savoir plus",
"notAdminOrOwner": {
@@ -368,7 +369,8 @@
"button": {
"start": "Commencer à enregistrer la réunion",
"stop": "Arrêter d'enregistrer la réunion",
"saving": "Sauvegarde…"
"saving": "Sauvegarde…",
"starting": "Démarrage…"
},
"notAdminOrOwner": {
"heading": "Accès restreint",
@@ -507,7 +509,7 @@
"starting": "Démarrage de la transcription",
"stopping": "Arrêt de la transcription"
},
"screenRecording": {
"screen_recording": {
"started": "Enregistrement en cours",
"starting": "Démarrage de l'enregistrement",
"stopping": "Arrêt de l'enregistrement"

View File

@@ -323,7 +323,8 @@
"button": {
"start": "Begin met het transcriberen van de vergadering",
"stop": "Stop met het transcriberen van de vergadering",
"saving": "Opslaan…"
"saving": "Opslaan…",
"starting": "Wordt gestart…"
},
"linkMore": "Meer informatie",
"notAdminOrOwner": {
@@ -368,7 +369,8 @@
"button": {
"start": "Start met opnemen van de vergadering",
"stop": "Stop met opnemen van de vergadering",
"saving": "Bezig met opslaan…"
"saving": "Bezig met opslaan…",
"starting": "Wordt gestart…"
},
"notAdminOrOwner": {
"heading": "Toegang beperkt",
@@ -507,7 +509,7 @@
"starting": "Transcriptie begint",
"stopping": "Transcriptie stopt"
},
"screenRecording": {
"screen_recording": {
"started": "Opname bezig",
"starting": "Opname starten",
"stopping": "Opname stoppen"

View File

@@ -6,23 +6,10 @@ export enum RecordingLanguage {
AUTOMATIC = 'auto',
}
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
language: RecordingLanguage
}
export const recordingStore = proxy<State>({
status: RecordingStatus.STOPPED,
language: RecordingLanguage.FRENCH,
})