Skip to content

Commit 0095292

Browse files
committed
wip refactor the frontend to rely on backend state
1 parent 132498d commit 0095292

12 files changed

Lines changed: 252 additions & 281 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { css } from '@/styled-system/css'
2+
import { HStack } from '@/styled-system/jsx'
3+
import { Spinner } from '@/primitives/Spinner'
4+
import { Button, Text } from '@/primitives'
5+
import { useTranslation } from 'react-i18next'
6+
import { RecordingStatuses } from '../hooks/useRecordingStatuses'
7+
import { ReactNode, useEffect, useRef, useState } from 'react'
8+
import { useRoomContext } from '@livekit/components-react'
9+
import { ConnectionState } from 'livekit-client'
10+
11+
const Layout = ({ children }: { children: ReactNode }) => (
12+
<div
13+
className={css({
14+
marginBottom: '80px',
15+
width: '100%',
16+
})}
17+
>
18+
{children}
19+
</div>
20+
)
21+
22+
interface ControlsButtonProps {
23+
i18nKeyPrefix: string
24+
statuses: RecordingStatuses
25+
handle: () => void
26+
isPendingToStart: boolean
27+
isPendingToStop: boolean
28+
}
29+
30+
const MIN_SPINNER_DISPLAY_TIME = 2000
31+
32+
export const ControlsButton = ({
33+
i18nKeyPrefix,
34+
statuses,
35+
handle,
36+
isPendingToStart,
37+
isPendingToStop,
38+
}: ControlsButtonProps) => {
39+
const { t } = useTranslation('rooms', { keyPrefix: i18nKeyPrefix })
40+
41+
const room = useRoomContext()
42+
const isRoomConnected = room.state == ConnectionState.Connected
43+
44+
const [showSaving, setShowSaving] = useState(false)
45+
const timeoutRef = useRef<NodeJS.Timeout>()
46+
47+
const isSaving = statuses.isSaving || isPendingToStop
48+
const isDisabled = !isRoomConnected
49+
50+
useEffect(() => {
51+
if (isSaving) {
52+
clearTimeout(timeoutRef.current)
53+
setShowSaving(true)
54+
} else if (showSaving) {
55+
timeoutRef.current = setTimeout(() => {
56+
setShowSaving(false)
57+
}, MIN_SPINNER_DISPLAY_TIME)
58+
}
59+
60+
return () => clearTimeout(timeoutRef.current)
61+
}, [isSaving, showSaving])
62+
63+
// Saving state
64+
if (showSaving) {
65+
return (
66+
<Layout>
67+
<HStack width="100%" height="46px" justify="center">
68+
<Spinner size={30} />
69+
<Text variant="body">{t('button.saving')}</Text>
70+
</HStack>
71+
</Layout>
72+
)
73+
}
74+
75+
// Starting state
76+
if (statuses.isStarting || isPendingToStart) {
77+
return (
78+
<Layout>
79+
<HStack width="100%" height="46px" justify="center">
80+
<Spinner size={30} />
81+
{t('button.starting')}
82+
</HStack>
83+
</Layout>
84+
)
85+
}
86+
87+
// Active state (Stop button)
88+
if (statuses.isStarted) {
89+
return (
90+
<Layout>
91+
<Button
92+
variant="tertiary"
93+
fullWidth
94+
onPress={handle}
95+
isDisabled={isDisabled}
96+
>
97+
{t('button.stop')}
98+
</Button>
99+
</Layout>
100+
)
101+
}
102+
103+
// Inactive state (Start button)
104+
return (
105+
<Layout>
106+
<Button
107+
variant="tertiary"
108+
fullWidth
109+
onPress={handle}
110+
isDisabled={isDisabled}
111+
>
112+
{t('button.start')}
113+
</Button>
114+
</Layout>
115+
)
116+
}

src/frontend/src/features/recording/components/RecordingStateToast.tsx

Lines changed: 25 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,69 @@
11
import { css } from '@/styled-system/css'
22
import { useTranslation } from 'react-i18next'
3-
import { useSnapshot } from 'valtio'
4-
import { useRoomContext } from '@livekit/components-react'
53
import { Spinner } from '@/primitives/Spinner'
6-
import { useEffect, useMemo, useState } from 'react'
4+
import { useMemo, useState } from 'react'
75
import { Text } from '@/primitives'
8-
import { RoomEvent } from 'livekit-client'
9-
import { decodeNotificationDataReceived } from '@/features/notifications/utils'
10-
import { NotificationType } from '@/features/notifications/NotificationType'
11-
import { RecordingStatus, recordingStore } from '@/stores/recording'
126
import { RiRecordCircleLine } from '@remixicon/react'
137
import {
148
RecordingMode,
159
useHasRecordingAccess,
16-
useIsRecordingActive,
10+
useRecordingStatuses,
1711
} from '@/features/recording'
1812
import { FeatureFlags } from '@/features/analytics/enums'
1913
import { Button as RACButton } from 'react-aria-components'
2014
import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
2115
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
2216
import { LimitReachedAlertDialog } from './LimitReachedAlertDialog'
17+
import { useRoomMetadata } from '../hooks/useRoomMetadata'
2318

2419
export const RecordingStateToast = () => {
2520
const { t } = useTranslation('rooms', {
2621
keyPrefix: 'recordingStateToast',
2722
})
28-
const room = useRoomContext()
23+
2924
const isAdminOrOwner = useIsAdminOrOwner()
3025

3126
const { openTranscript, openScreenRecording } = useSidePanel()
3227
const [isAlertOpen, setIsAlertOpen] = useState(false)
3328

34-
const recordingSnap = useSnapshot(recordingStore)
35-
3629
const hasTranscriptAccess = useHasRecordingAccess(
3730
RecordingMode.Transcript,
3831
FeatureFlags.Transcript
3932
)
4033

41-
const isTranscriptActive = useIsRecordingActive(RecordingMode.Transcript)
42-
4334
const hasScreenRecordingAccess = useHasRecordingAccess(
4435
RecordingMode.ScreenRecording,
4536
FeatureFlags.ScreenRecording
4637
)
4738

48-
const isScreenRecordingActive = useIsRecordingActive(
49-
RecordingMode.ScreenRecording
50-
)
39+
const {
40+
isStarted: isScreenRecordingStarted,
41+
isStarting: isScreenRecordingStarting,
42+
isActive: isScreenRecordingActive,
43+
} = useRecordingStatuses(RecordingMode.ScreenRecording)
5144

52-
useEffect(() => {
53-
if (room.isRecording && recordingSnap.status == RecordingStatus.STOPPED) {
54-
recordingStore.status = RecordingStatus.ANY_STARTED
55-
}
56-
// eslint-disable-next-line react-hooks/exhaustive-deps
57-
}, [room.isRecording])
58-
59-
useEffect(() => {
60-
const handleDataReceived = (payload: Uint8Array) => {
61-
const notification = decodeNotificationDataReceived(payload)
62-
63-
if (!notification) return
64-
65-
switch (notification.type) {
66-
case NotificationType.TranscriptionStarted:
67-
recordingStore.status = RecordingStatus.TRANSCRIPT_STARTING
68-
break
69-
case NotificationType.TranscriptionStopped:
70-
recordingStore.status = RecordingStatus.TRANSCRIPT_STOPPING
71-
break
72-
case NotificationType.TranscriptionLimitReached:
73-
if (isAdminOrOwner) setIsAlertOpen(true)
74-
recordingStore.status = RecordingStatus.TRANSCRIPT_STOPPING
75-
break
76-
case NotificationType.ScreenRecordingStarted:
77-
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STARTING
78-
break
79-
case NotificationType.ScreenRecordingStopped:
80-
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STOPPING
81-
break
82-
case NotificationType.ScreenRecordingLimitReached:
83-
if (isAdminOrOwner) setIsAlertOpen(true)
84-
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STOPPING
85-
break
86-
default:
87-
return
88-
}
89-
}
45+
const {
46+
isStarted: isTranscriptStarted,
47+
isStarting: isTranscriptStarting,
48+
isActive: isTranscriptActive,
49+
} = useRecordingStatuses(RecordingMode.Transcript)
9050

91-
const handleRecordingStatusChanged = (status: boolean) => {
92-
if (!status) {
93-
recordingStore.status = RecordingStatus.STOPPED
94-
} else if (recordingSnap.status == RecordingStatus.TRANSCRIPT_STARTING) {
95-
recordingStore.status = RecordingStatus.TRANSCRIPT_STARTED
96-
} else if (
97-
recordingSnap.status == RecordingStatus.SCREEN_RECORDING_STARTING
98-
) {
99-
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STARTED
100-
} else {
101-
recordingStore.status = RecordingStatus.ANY_STARTED
102-
}
103-
}
51+
const isStarted = isScreenRecordingStarted || isTranscriptStarted
52+
const isStarting = isTranscriptStarting || isScreenRecordingStarting
10453

105-
room.on(RoomEvent.DataReceived, handleDataReceived)
106-
room.on(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
54+
const metadata = useRoomMetadata()
10755

108-
return () => {
109-
room.off(RoomEvent.DataReceived, handleDataReceived)
110-
room.off(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
56+
const key = useMemo(() => {
57+
if (!metadata?.recording_status || !metadata?.recording_mode) {
58+
return undefined
11159
}
112-
}, [room, recordingSnap, setIsAlertOpen, isAdminOrOwner])
11360

114-
const key = useMemo(() => {
115-
switch (recordingSnap.status) {
116-
case RecordingStatus.TRANSCRIPT_STARTED:
117-
return 'transcript.started'
118-
case RecordingStatus.TRANSCRIPT_STARTING:
119-
return 'transcript.starting'
120-
case RecordingStatus.SCREEN_RECORDING_STARTED:
121-
return 'screenRecording.started'
122-
case RecordingStatus.SCREEN_RECORDING_STARTING:
123-
return 'screenRecording.starting'
124-
case RecordingStatus.ANY_STARTED:
125-
return 'any.started'
126-
default:
127-
return
61+
if (!isStarting && !isStarted) {
62+
return undefined
12863
}
129-
}, [recordingSnap])
64+
65+
return `${metadata.recording_mode}.${metadata.recording_status}`
66+
}, [metadata, isStarted, isStarting])
13067

13168
if (!key)
13269
return isAdminOrOwner ? (
@@ -137,8 +74,6 @@ export const RecordingStateToast = () => {
13774
/>
13875
) : null
13976

140-
const isStarted = key?.includes('started')
141-
14277
const hasScreenRecordingAccessAndActive =
14378
isScreenRecordingActive && hasScreenRecordingAccess
14479
const hasTranscriptAccessAndActive = isTranscriptActive && hasTranscriptAccess

0 commit comments

Comments
 (0)