✨(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:
committed by
aleb_the_flash
parent
16badde82d
commit
da3dfedcbc
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,132 +1,69 @@
|
|||||||
import { css } from '@/styled-system/css'
|
import { css } from '@/styled-system/css'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSnapshot } from 'valtio'
|
|
||||||
import { useRoomContext } from '@livekit/components-react'
|
|
||||||
import { Spinner } from '@/primitives/Spinner'
|
import { Spinner } from '@/primitives/Spinner'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { Text } from '@/primitives'
|
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 { RiRecordCircleLine } from '@remixicon/react'
|
||||||
import {
|
import {
|
||||||
RecordingMode,
|
RecordingMode,
|
||||||
useHasRecordingAccess,
|
useHasRecordingAccess,
|
||||||
useIsRecordingActive,
|
useRecordingStatuses,
|
||||||
} from '@/features/recording'
|
} from '@/features/recording'
|
||||||
import { FeatureFlags } from '@/features/analytics/enums'
|
import { FeatureFlags } from '@/features/analytics/enums'
|
||||||
import { Button as RACButton } from 'react-aria-components'
|
import { Button as RACButton } from 'react-aria-components'
|
||||||
import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
|
import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
|
||||||
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
|
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
|
||||||
import { LimitReachedAlertDialog } from './LimitReachedAlertDialog'
|
import { LimitReachedAlertDialog } from './LimitReachedAlertDialog'
|
||||||
|
import { useRoomMetadata } from '../hooks/useRoomMetadata'
|
||||||
|
|
||||||
export const RecordingStateToast = () => {
|
export const RecordingStateToast = () => {
|
||||||
const { t } = useTranslation('rooms', {
|
const { t } = useTranslation('rooms', {
|
||||||
keyPrefix: 'recordingStateToast',
|
keyPrefix: 'recordingStateToast',
|
||||||
})
|
})
|
||||||
const room = useRoomContext()
|
|
||||||
const isAdminOrOwner = useIsAdminOrOwner()
|
const isAdminOrOwner = useIsAdminOrOwner()
|
||||||
|
|
||||||
const { openTranscript, openScreenRecording } = useSidePanel()
|
const { openTranscript, openScreenRecording } = useSidePanel()
|
||||||
const [isAlertOpen, setIsAlertOpen] = useState(false)
|
const [isAlertOpen, setIsAlertOpen] = useState(false)
|
||||||
|
|
||||||
const recordingSnap = useSnapshot(recordingStore)
|
|
||||||
|
|
||||||
const hasTranscriptAccess = useHasRecordingAccess(
|
const hasTranscriptAccess = useHasRecordingAccess(
|
||||||
RecordingMode.Transcript,
|
RecordingMode.Transcript,
|
||||||
FeatureFlags.Transcript
|
FeatureFlags.Transcript
|
||||||
)
|
)
|
||||||
|
|
||||||
const isTranscriptActive = useIsRecordingActive(RecordingMode.Transcript)
|
|
||||||
|
|
||||||
const hasScreenRecordingAccess = useHasRecordingAccess(
|
const hasScreenRecordingAccess = useHasRecordingAccess(
|
||||||
RecordingMode.ScreenRecording,
|
RecordingMode.ScreenRecording,
|
||||||
FeatureFlags.ScreenRecording
|
FeatureFlags.ScreenRecording
|
||||||
)
|
)
|
||||||
|
|
||||||
const isScreenRecordingActive = useIsRecordingActive(
|
const {
|
||||||
RecordingMode.ScreenRecording
|
isStarted: isScreenRecordingStarted,
|
||||||
)
|
isStarting: isScreenRecordingStarting,
|
||||||
|
isActive: isScreenRecordingActive,
|
||||||
|
} = useRecordingStatuses(RecordingMode.ScreenRecording)
|
||||||
|
|
||||||
useEffect(() => {
|
const {
|
||||||
if (room.isRecording && recordingSnap.status == RecordingStatus.STOPPED) {
|
isStarted: isTranscriptStarted,
|
||||||
recordingStore.status = RecordingStatus.ANY_STARTED
|
isStarting: isTranscriptStarting,
|
||||||
}
|
isActive: isTranscriptActive,
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
} = useRecordingStatuses(RecordingMode.Transcript)
|
||||||
}, [room.isRecording])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const isStarted = isScreenRecordingStarted || isTranscriptStarted
|
||||||
const handleDataReceived = (payload: Uint8Array) => {
|
const isStarting = isTranscriptStarting || isScreenRecordingStarting
|
||||||
const notification = decodeNotificationDataReceived(payload)
|
|
||||||
|
|
||||||
if (!notification) return
|
const metadata = useRoomMetadata()
|
||||||
|
|
||||||
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 key = useMemo(() => {
|
const key = useMemo(() => {
|
||||||
switch (recordingSnap.status) {
|
if (!metadata?.recording_status || !metadata?.recording_mode) {
|
||||||
case RecordingStatus.TRANSCRIPT_STARTED:
|
return undefined
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}, [recordingSnap])
|
|
||||||
|
if (!isStarting && !isStarted) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${metadata.recording_mode}.${metadata.recording_status}`
|
||||||
|
}, [metadata, isStarted, isStarting])
|
||||||
|
|
||||||
if (!key)
|
if (!key)
|
||||||
return isAdminOrOwner ? (
|
return isAdminOrOwner ? (
|
||||||
@@ -137,8 +74,6 @@ export const RecordingStateToast = () => {
|
|||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
const isStarted = key?.includes('started')
|
|
||||||
|
|
||||||
const hasScreenRecordingAccessAndActive =
|
const hasScreenRecordingAccessAndActive =
|
||||||
isScreenRecordingActive && hasScreenRecordingAccess
|
isScreenRecordingActive && hasScreenRecordingAccess
|
||||||
const hasTranscriptAccessAndActive = isTranscriptActive && hasTranscriptAccess
|
const hasTranscriptAccessAndActive = isTranscriptActive && hasTranscriptAccess
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ import {
|
|||||||
useStartRecording,
|
useStartRecording,
|
||||||
useStopRecording,
|
useStopRecording,
|
||||||
useHumanizeRecordingMaxDuration,
|
useHumanizeRecordingMaxDuration,
|
||||||
|
useRecordingStatuses,
|
||||||
} from '@/features/recording'
|
} from '@/features/recording'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { ConnectionState, RoomEvent } from 'livekit-client'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { RecordingStatus, recordingStore } from '@/stores/recording'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NotificationType,
|
NotificationType,
|
||||||
@@ -21,13 +20,12 @@ import {
|
|||||||
useNotifyParticipants,
|
useNotifyParticipants,
|
||||||
} from '@/features/notifications'
|
} from '@/features/notifications'
|
||||||
import posthog from 'posthog-js'
|
import posthog from 'posthog-js'
|
||||||
import { useSnapshot } from 'valtio/index'
|
|
||||||
import { Spinner } from '@/primitives/Spinner'
|
|
||||||
import { useConfig } from '@/api/useConfig'
|
import { useConfig } from '@/api/useConfig'
|
||||||
import { FeatureFlags } from '@/features/analytics/enums'
|
import { FeatureFlags } from '@/features/analytics/enums'
|
||||||
import { NoAccessView } from './NoAccessView'
|
import { NoAccessView } from './NoAccessView'
|
||||||
import { HStack, VStack } from '@/styled-system/jsx'
|
import { ControlsButton } from './ControlsButton'
|
||||||
import { RowWrapper } from './RowWrapper'
|
import { RowWrapper } from './RowWrapper'
|
||||||
|
import { VStack } from '@/styled-system/jsx'
|
||||||
import { Checkbox } from '@/primitives/Checkbox'
|
import { Checkbox } from '@/primitives/Checkbox'
|
||||||
import { useTranscriptionLanguage } from '@/features/settings'
|
import { useTranscriptionLanguage } from '@/features/settings'
|
||||||
|
|
||||||
@@ -35,9 +33,8 @@ export const ScreenRecordingSidePanel = () => {
|
|||||||
const { data } = useConfig()
|
const { data } = useConfig()
|
||||||
const recordingMaxDuration = useHumanizeRecordingMaxDuration()
|
const recordingMaxDuration = useHumanizeRecordingMaxDuration()
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const keyPrefix = 'screenRecording'
|
||||||
const recordingSnap = useSnapshot(recordingStore)
|
const { t } = useTranslation('rooms', { keyPrefix })
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'screenRecording' })
|
|
||||||
|
|
||||||
const [isErrorDialogOpen, setIsErrorDialogOpen] = useState('')
|
const [isErrorDialogOpen, setIsErrorDialogOpen] = useState('')
|
||||||
|
|
||||||
@@ -63,31 +60,9 @@ export const ScreenRecordingSidePanel = () => {
|
|||||||
onError: () => setIsErrorDialogOpen('stop'),
|
onError: () => setIsErrorDialogOpen('stop'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const statuses = useMemo(() => {
|
const statuses = useRecordingStatuses(RecordingMode.ScreenRecording)
|
||||||
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 room = useRoomContext()
|
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 () => {
|
const handleScreenRecording = async () => {
|
||||||
if (!roomId) {
|
if (!roomId) {
|
||||||
@@ -95,11 +70,10 @@ export const ScreenRecordingSidePanel = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setIsLoading(true)
|
if (statuses.isStarted || statuses.isStarting) {
|
||||||
if (room.isRecording) {
|
|
||||||
setIncludeTranscript(false)
|
setIncludeTranscript(false)
|
||||||
await stopRecordingRoom({ id: roomId })
|
await stopRecordingRoom({ id: roomId })
|
||||||
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STOPPING
|
|
||||||
await notifyParticipants({
|
await notifyParticipants({
|
||||||
type: NotificationType.ScreenRecordingStopped,
|
type: NotificationType.ScreenRecordingStopped,
|
||||||
})
|
})
|
||||||
@@ -120,7 +94,7 @@ export const ScreenRecordingSidePanel = () => {
|
|||||||
mode: RecordingMode.ScreenRecording,
|
mode: RecordingMode.ScreenRecording,
|
||||||
options: recordingOptions,
|
options: recordingOptions,
|
||||||
})
|
})
|
||||||
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STARTING
|
|
||||||
await notifyParticipants({
|
await notifyParticipants({
|
||||||
type: NotificationType.ScreenRecordingStarted,
|
type: NotificationType.ScreenRecordingStarted,
|
||||||
})
|
})
|
||||||
@@ -128,14 +102,13 @@ export const ScreenRecordingSidePanel = () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to handle recording:', error)
|
console.error('Failed to handle recording:', error)
|
||||||
setIsLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasFeatureWithoutAdminRights) {
|
if (hasFeatureWithoutAdminRights) {
|
||||||
return (
|
return (
|
||||||
<NoAccessView
|
<NoAccessView
|
||||||
i18nKeyPrefix="screenRecording"
|
i18nKeyPrefix={keyPrefix}
|
||||||
i18nKey="notAdminOrOwner"
|
i18nKey="notAdminOrOwner"
|
||||||
helpArticle={data?.support?.help_article_recording}
|
helpArticle={data?.support?.help_article_recording}
|
||||||
imagePath="/assets/intro-slider/4.png"
|
imagePath="/assets/intro-slider/4.png"
|
||||||
@@ -208,51 +181,19 @@ export const ScreenRecordingSidePanel = () => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
isSelected={includeTranscript}
|
isSelected={includeTranscript}
|
||||||
onChange={setIncludeTranscript}
|
onChange={setIncludeTranscript}
|
||||||
isDisabled={
|
isDisabled={statuses.isActive || isPendingToStart}
|
||||||
statuses.isStarting || statuses.isStarted || isPendingToStart
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Text variant="sm">{t('details.transcription')}</Text>
|
<Text variant="sm">{t('details.transcription')}</Text>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
</VStack>
|
</VStack>
|
||||||
<div
|
<ControlsButton
|
||||||
className={css({
|
i18nKeyPrefix={keyPrefix}
|
||||||
marginBottom: '80px',
|
handle={handleScreenRecording}
|
||||||
width: '100%',
|
statuses={statuses}
|
||||||
})}
|
isPendingToStart={isPendingToStart}
|
||||||
>
|
isPendingToStop={isPendingToStop}
|
||||||
{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>
|
|
||||||
<Dialog
|
<Dialog
|
||||||
isOpen={!!isErrorDialogOpen}
|
isOpen={!!isErrorDialogOpen}
|
||||||
role="alertdialog"
|
role="alertdialog"
|
||||||
|
|||||||
@@ -10,11 +10,10 @@ import {
|
|||||||
useStopRecording,
|
useStopRecording,
|
||||||
useHasFeatureWithoutAdminRights,
|
useHasFeatureWithoutAdminRights,
|
||||||
useHumanizeRecordingMaxDuration,
|
useHumanizeRecordingMaxDuration,
|
||||||
|
useRecordingStatuses,
|
||||||
} from '../index'
|
} from '../index'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { ConnectionState, RoomEvent } from 'livekit-client'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { RecordingStatus } from '@store/recording'
|
|
||||||
import { FeatureFlags } from '@/features/analytics/enums'
|
import { FeatureFlags } from '@/features/analytics/enums'
|
||||||
import {
|
import {
|
||||||
NotificationType,
|
NotificationType,
|
||||||
@@ -22,8 +21,6 @@ import {
|
|||||||
notifyRecordingSaveInProgress,
|
notifyRecordingSaveInProgress,
|
||||||
} from '@/features/notifications'
|
} from '@/features/notifications'
|
||||||
import posthog from 'posthog-js'
|
import posthog from 'posthog-js'
|
||||||
import { useSnapshot } from 'valtio/index'
|
|
||||||
import { Spinner } from '@/primitives/Spinner'
|
|
||||||
import { useConfig } from '@/api/useConfig'
|
import { useConfig } from '@/api/useConfig'
|
||||||
import { VStack } from '@/styled-system/jsx'
|
import { VStack } from '@/styled-system/jsx'
|
||||||
import { Checkbox } from '@/primitives/Checkbox.tsx'
|
import { Checkbox } from '@/primitives/Checkbox.tsx'
|
||||||
@@ -34,20 +31,19 @@ import {
|
|||||||
useTranscriptionLanguage,
|
useTranscriptionLanguage,
|
||||||
} from '@/features/settings'
|
} from '@/features/settings'
|
||||||
import { NoAccessView } from './NoAccessView'
|
import { NoAccessView } from './NoAccessView'
|
||||||
|
import { ControlsButton } from './ControlsButton'
|
||||||
import { RowWrapper } from './RowWrapper'
|
import { RowWrapper } from './RowWrapper'
|
||||||
|
|
||||||
export const TranscriptSidePanel = () => {
|
export const TranscriptSidePanel = () => {
|
||||||
const { data } = useConfig()
|
const { data } = useConfig()
|
||||||
const recordingMaxDuration = useHumanizeRecordingMaxDuration()
|
const recordingMaxDuration = useHumanizeRecordingMaxDuration()
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const keyPrefix = 'transcript'
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'transcript' })
|
const { t } = useTranslation('rooms', { keyPrefix })
|
||||||
|
|
||||||
const [isErrorDialogOpen, setIsErrorDialogOpen] = useState('')
|
const [isErrorDialogOpen, setIsErrorDialogOpen] = useState('')
|
||||||
const [includeScreenRecording, setIncludeScreenRecording] = useState(false)
|
const [includeScreenRecording, setIncludeScreenRecording] = useState(false)
|
||||||
|
|
||||||
const recordingSnap = useSnapshot(recordingStore)
|
|
||||||
|
|
||||||
const { notifyParticipants } = useNotifyParticipants()
|
const { notifyParticipants } = useNotifyParticipants()
|
||||||
const { selectedLanguageKey, selectedLanguageLabel, isLanguageSetToAuto } =
|
const { selectedLanguageKey, selectedLanguageLabel, isLanguageSetToAuto } =
|
||||||
useTranscriptionLanguage()
|
useTranscriptionLanguage()
|
||||||
@@ -76,28 +72,9 @@ export const TranscriptSidePanel = () => {
|
|||||||
onError: () => setIsErrorDialogOpen('stop'),
|
onError: () => setIsErrorDialogOpen('stop'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const statuses = useMemo(() => {
|
const statuses = useRecordingStatuses(RecordingMode.Transcript)
|
||||||
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 room = useRoomContext()
|
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 () => {
|
const handleTranscript = async () => {
|
||||||
if (!roomId) {
|
if (!roomId) {
|
||||||
@@ -105,11 +82,10 @@ export const TranscriptSidePanel = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setIsLoading(true)
|
if (statuses.isStarted || statuses.isStarting) {
|
||||||
if (room.isRecording) {
|
|
||||||
await stopRecordingRoom({ id: roomId })
|
await stopRecordingRoom({ id: roomId })
|
||||||
setIncludeScreenRecording(false)
|
setIncludeScreenRecording(false)
|
||||||
recordingStore.status = RecordingStatus.TRANSCRIPT_STOPPING
|
|
||||||
await notifyParticipants({
|
await notifyParticipants({
|
||||||
type: NotificationType.TranscriptionStopped,
|
type: NotificationType.TranscriptionStopped,
|
||||||
})
|
})
|
||||||
@@ -126,8 +102,10 @@ export const TranscriptSidePanel = () => {
|
|||||||
...(!isLanguageSetToAuto && {
|
...(!isLanguageSetToAuto && {
|
||||||
language: selectedLanguageKey,
|
language: selectedLanguageKey,
|
||||||
}),
|
}),
|
||||||
|
...(includeScreenRecording && {
|
||||||
|
transcribe: true,
|
||||||
|
original_mode: RecordingMode.Transcript,
|
||||||
}),
|
}),
|
||||||
...(includeScreenRecording && { transcribe: true }),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await startRecordingRoom({
|
await startRecordingRoom({
|
||||||
@@ -135,7 +113,7 @@ export const TranscriptSidePanel = () => {
|
|||||||
mode: recordingMode,
|
mode: recordingMode,
|
||||||
options: recordingOptions,
|
options: recordingOptions,
|
||||||
})
|
})
|
||||||
recordingStore.status = RecordingStatus.TRANSCRIPT_STARTING
|
|
||||||
await notifyParticipants({
|
await notifyParticipants({
|
||||||
type: NotificationType.TranscriptionStarted,
|
type: NotificationType.TranscriptionStarted,
|
||||||
})
|
})
|
||||||
@@ -143,14 +121,13 @@ export const TranscriptSidePanel = () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to handle transcript:', error)
|
console.error('Failed to handle transcript:', error)
|
||||||
setIsLoading(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasFeatureWithoutAdminRights) {
|
if (hasFeatureWithoutAdminRights) {
|
||||||
return (
|
return (
|
||||||
<NoAccessView
|
<NoAccessView
|
||||||
i18nKeyPrefix="transcript"
|
i18nKeyPrefix={keyPrefix}
|
||||||
i18nKey="notAdminOrOwner"
|
i18nKey="notAdminOrOwner"
|
||||||
helpArticle={data?.support?.help_article_transcript}
|
helpArticle={data?.support?.help_article_transcript}
|
||||||
imagePath="/assets/intro-slider/3.png"
|
imagePath="/assets/intro-slider/3.png"
|
||||||
@@ -161,7 +138,7 @@ export const TranscriptSidePanel = () => {
|
|||||||
if (!hasTranscriptAccess) {
|
if (!hasTranscriptAccess) {
|
||||||
return (
|
return (
|
||||||
<NoAccessView
|
<NoAccessView
|
||||||
i18nKeyPrefix="transcript"
|
i18nKeyPrefix={keyPrefix}
|
||||||
i18nKey="premium"
|
i18nKey="premium"
|
||||||
helpArticle={data?.support?.help_article_transcript}
|
helpArticle={data?.support?.help_article_transcript}
|
||||||
imagePath="/assets/intro-slider/3.png"
|
imagePath="/assets/intro-slider/3.png"
|
||||||
@@ -251,7 +228,6 @@ export const TranscriptSidePanel = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
</RowWrapper>
|
</RowWrapper>
|
||||||
<div className={css({ height: '15px' })} />
|
<div className={css({ height: '15px' })} />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={css({
|
className={css({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -262,51 +238,19 @@ export const TranscriptSidePanel = () => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
isSelected={includeScreenRecording}
|
isSelected={includeScreenRecording}
|
||||||
onChange={setIncludeScreenRecording}
|
onChange={setIncludeScreenRecording}
|
||||||
isDisabled={
|
isDisabled={statuses.isActive || isPendingToStart}
|
||||||
statuses.isStarting || statuses.isStarted || isPendingToStart
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Text variant="sm">{t('details.recording')}</Text>
|
<Text variant="sm">{t('details.recording')}</Text>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
</VStack>
|
</VStack>
|
||||||
<div
|
<ControlsButton
|
||||||
className={css({
|
i18nKeyPrefix={keyPrefix}
|
||||||
marginBottom: '80px',
|
handle={handleTranscript}
|
||||||
width: '100%',
|
statuses={statuses}
|
||||||
})}
|
isPendingToStart={isPendingToStart}
|
||||||
>
|
isPendingToStop={isPendingToStop}
|
||||||
{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>
|
|
||||||
<Dialog
|
<Dialog
|
||||||
isOpen={!!isErrorDialogOpen}
|
isOpen={!!isErrorDialogOpen}
|
||||||
role="alertdialog"
|
role="alertdialog"
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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])
|
||||||
|
}
|
||||||
18
src/frontend/src/features/recording/hooks/useRoomMetadata.ts
Normal file
18
src/frontend/src/features/recording/hooks/useRoomMetadata.ts
Normal 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])
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
// hooks
|
// hooks
|
||||||
export { useIsRecordingModeEnabled } from './hooks/useIsRecordingModeEnabled'
|
export { useIsRecordingModeEnabled } from './hooks/useIsRecordingModeEnabled'
|
||||||
export { useHasRecordingAccess } from './hooks/useHasRecordingAccess'
|
export { useHasRecordingAccess } from './hooks/useHasRecordingAccess'
|
||||||
export { useIsRecordingActive } from './hooks/useIsRecordingActive'
|
|
||||||
export { useHasFeatureWithoutAdminRights } from './hooks/useHasFeatureWithoutAdminRights'
|
export { useHasFeatureWithoutAdminRights } from './hooks/useHasFeatureWithoutAdminRights'
|
||||||
export { useHumanizeRecordingMaxDuration } from './hooks/useHumanizeRecordingMaxDuration'
|
export { useHumanizeRecordingMaxDuration } from './hooks/useHumanizeRecordingMaxDuration'
|
||||||
|
export { useRecordingStatuses } from './hooks/useRecordingStatuses'
|
||||||
|
|
||||||
// api
|
// api
|
||||||
export { useStartRecording } from './api/startRecording'
|
export { useStartRecording } from './api/startRecording'
|
||||||
|
|||||||
@@ -323,7 +323,8 @@
|
|||||||
"button": {
|
"button": {
|
||||||
"start": "Meeting transkribieren starten",
|
"start": "Meeting transkribieren starten",
|
||||||
"stop": "Meeting transkribieren beenden",
|
"stop": "Meeting transkribieren beenden",
|
||||||
"saving": "Speichern…"
|
"saving": "Speichern…",
|
||||||
|
"starting": "Wird gestartet…"
|
||||||
},
|
},
|
||||||
"linkMore": "Mehr erfahren",
|
"linkMore": "Mehr erfahren",
|
||||||
"notAdminOrOwner": {
|
"notAdminOrOwner": {
|
||||||
@@ -368,7 +369,8 @@
|
|||||||
"button": {
|
"button": {
|
||||||
"start": "Meeting-Aufzeichnung starten",
|
"start": "Meeting-Aufzeichnung starten",
|
||||||
"stop": "Meeting-Aufzeichnung beenden",
|
"stop": "Meeting-Aufzeichnung beenden",
|
||||||
"saving": "Wird gespeichert…"
|
"saving": "Wird gespeichert…",
|
||||||
|
"starting": "Wird gestartet…"
|
||||||
},
|
},
|
||||||
"notAdminOrOwner": {
|
"notAdminOrOwner": {
|
||||||
"heading": "Zugriff eingeschränkt",
|
"heading": "Zugriff eingeschränkt",
|
||||||
@@ -507,7 +509,7 @@
|
|||||||
"starting": "Transkription wird gestartet",
|
"starting": "Transkription wird gestartet",
|
||||||
"stopping": "Transkription wird gestoppt"
|
"stopping": "Transkription wird gestoppt"
|
||||||
},
|
},
|
||||||
"screenRecording": {
|
"screen_recording": {
|
||||||
"started": "Aufzeichnung läuft",
|
"started": "Aufzeichnung läuft",
|
||||||
"starting": "Aufzeichnung wird gestartet",
|
"starting": "Aufzeichnung wird gestartet",
|
||||||
"stopping": "Aufzeichnung wird gestoppt"
|
"stopping": "Aufzeichnung wird gestoppt"
|
||||||
|
|||||||
@@ -323,7 +323,8 @@
|
|||||||
"button": {
|
"button": {
|
||||||
"start": "Start transcribing the meeting",
|
"start": "Start transcribing the meeting",
|
||||||
"stop": "Stop transcribing the meeting",
|
"stop": "Stop transcribing the meeting",
|
||||||
"saving": "Saving…"
|
"saving": "Saving…",
|
||||||
|
"starting": "Starting…"
|
||||||
},
|
},
|
||||||
"linkMore": "Learn more",
|
"linkMore": "Learn more",
|
||||||
"notAdminOrOwner": {
|
"notAdminOrOwner": {
|
||||||
@@ -368,7 +369,8 @@
|
|||||||
"button": {
|
"button": {
|
||||||
"start": "Start recording the meeting",
|
"start": "Start recording the meeting",
|
||||||
"stop": "Stop recording the meeting",
|
"stop": "Stop recording the meeting",
|
||||||
"saving": "Saving…"
|
"saving": "Saving…",
|
||||||
|
"starting": "Starting…"
|
||||||
},
|
},
|
||||||
"notAdminOrOwner": {
|
"notAdminOrOwner": {
|
||||||
"heading": "Restricted Access",
|
"heading": "Restricted Access",
|
||||||
@@ -507,7 +509,7 @@
|
|||||||
"starting": "Transcription starting",
|
"starting": "Transcription starting",
|
||||||
"stopping": "Transcription stopping"
|
"stopping": "Transcription stopping"
|
||||||
},
|
},
|
||||||
"screenRecording": {
|
"screen_recording": {
|
||||||
"started": "Recording in progress",
|
"started": "Recording in progress",
|
||||||
"starting": "Starting recording",
|
"starting": "Starting recording",
|
||||||
"stopping": "Stopping recording"
|
"stopping": "Stopping recording"
|
||||||
|
|||||||
@@ -323,7 +323,8 @@
|
|||||||
"button": {
|
"button": {
|
||||||
"start": "Commencer à transcrire la réunion",
|
"start": "Commencer à transcrire la réunion",
|
||||||
"stop": "Arrêter de transcrire la réunion",
|
"stop": "Arrêter de transcrire la réunion",
|
||||||
"saving": "Sauvegarde…"
|
"saving": "Sauvegarde…",
|
||||||
|
"starting": "Démarrage…"
|
||||||
},
|
},
|
||||||
"linkMore": "En savoir plus",
|
"linkMore": "En savoir plus",
|
||||||
"notAdminOrOwner": {
|
"notAdminOrOwner": {
|
||||||
@@ -368,7 +369,8 @@
|
|||||||
"button": {
|
"button": {
|
||||||
"start": "Commencer à enregistrer la réunion",
|
"start": "Commencer à enregistrer la réunion",
|
||||||
"stop": "Arrêter d'enregistrer la réunion",
|
"stop": "Arrêter d'enregistrer la réunion",
|
||||||
"saving": "Sauvegarde…"
|
"saving": "Sauvegarde…",
|
||||||
|
"starting": "Démarrage…"
|
||||||
},
|
},
|
||||||
"notAdminOrOwner": {
|
"notAdminOrOwner": {
|
||||||
"heading": "Accès restreint",
|
"heading": "Accès restreint",
|
||||||
@@ -507,7 +509,7 @@
|
|||||||
"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": {
|
"screen_recording": {
|
||||||
"started": "Enregistrement en cours",
|
"started": "Enregistrement en cours",
|
||||||
"starting": "Démarrage de l'enregistrement",
|
"starting": "Démarrage de l'enregistrement",
|
||||||
"stopping": "Arrêt de l'enregistrement"
|
"stopping": "Arrêt de l'enregistrement"
|
||||||
|
|||||||
@@ -323,7 +323,8 @@
|
|||||||
"button": {
|
"button": {
|
||||||
"start": "Begin met het transcriberen van de vergadering",
|
"start": "Begin met het transcriberen van de vergadering",
|
||||||
"stop": "Stop met het transcriberen van de vergadering",
|
"stop": "Stop met het transcriberen van de vergadering",
|
||||||
"saving": "Opslaan…"
|
"saving": "Opslaan…",
|
||||||
|
"starting": "Wordt gestart…"
|
||||||
},
|
},
|
||||||
"linkMore": "Meer informatie",
|
"linkMore": "Meer informatie",
|
||||||
"notAdminOrOwner": {
|
"notAdminOrOwner": {
|
||||||
@@ -368,7 +369,8 @@
|
|||||||
"button": {
|
"button": {
|
||||||
"start": "Start met opnemen van de vergadering",
|
"start": "Start met opnemen van de vergadering",
|
||||||
"stop": "Stop met opnemen van de vergadering",
|
"stop": "Stop met opnemen van de vergadering",
|
||||||
"saving": "Bezig met opslaan…"
|
"saving": "Bezig met opslaan…",
|
||||||
|
"starting": "Wordt gestart…"
|
||||||
},
|
},
|
||||||
"notAdminOrOwner": {
|
"notAdminOrOwner": {
|
||||||
"heading": "Toegang beperkt",
|
"heading": "Toegang beperkt",
|
||||||
@@ -507,7 +509,7 @@
|
|||||||
"starting": "Transcriptie begint",
|
"starting": "Transcriptie begint",
|
||||||
"stopping": "Transcriptie stopt"
|
"stopping": "Transcriptie stopt"
|
||||||
},
|
},
|
||||||
"screenRecording": {
|
"screen_recording": {
|
||||||
"started": "Opname bezig",
|
"started": "Opname bezig",
|
||||||
"starting": "Opname starten",
|
"starting": "Opname starten",
|
||||||
"stopping": "Opname stoppen"
|
"stopping": "Opname stoppen"
|
||||||
|
|||||||
@@ -6,23 +6,10 @@ export enum RecordingLanguage {
|
|||||||
AUTOMATIC = 'auto',
|
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 = {
|
type State = {
|
||||||
status: RecordingStatus
|
|
||||||
language: RecordingLanguage
|
language: RecordingLanguage
|
||||||
}
|
}
|
||||||
|
|
||||||
export const recordingStore = proxy<State>({
|
export const recordingStore = proxy<State>({
|
||||||
status: RecordingStatus.STOPPED,
|
|
||||||
language: RecordingLanguage.FRENCH,
|
language: RecordingLanguage.FRENCH,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user