✨(frontend) introde the Screen Recording side panel
Offer one new tool to user, the screen recording side panel.
This commit is contained in:
committed by
aleb_the_flash
parent
a22d052f46
commit
468d09dc3b
BIN
src/frontend/src/assets/intro-slider/4_record.png
Normal file
BIN
src/frontend/src/assets/intro-slider/4_record.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 704 KiB |
@@ -0,0 +1,163 @@
|
|||||||
|
import { A, Button, Div, Text } from '@/primitives'
|
||||||
|
|
||||||
|
import fourthSlide from '@/assets/intro-slider/4_record.png'
|
||||||
|
import { css } from '@/styled-system/css'
|
||||||
|
import { useRoomId } from '@/features/rooms/livekit/hooks/useRoomId'
|
||||||
|
import { useRoomContext } from '@livekit/components-react'
|
||||||
|
import {
|
||||||
|
RecordingMode,
|
||||||
|
useStartRecording,
|
||||||
|
} from '@/features/rooms/api/startRecording'
|
||||||
|
import { useStopRecording } from '@/features/rooms/api/stopRecording'
|
||||||
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
|
import { RoomEvent } from 'livekit-client'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { NotificationPayload } from '@/features/notifications/NotificationPayload'
|
||||||
|
import { NotificationType } from '@/features/notifications/NotificationType'
|
||||||
|
import { useSnapshot } from 'valtio/index'
|
||||||
|
import { RecordingStatus, recordingStore } from '@/stores/recording'
|
||||||
|
import { CRISP_HELP_ARTICLE_RECORDING } from '@/utils/constants'
|
||||||
|
|
||||||
|
export const ScreenRecording = () => {
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const { t } = useTranslation('rooms', { keyPrefix: 'screenRecording' })
|
||||||
|
|
||||||
|
const roomId = useRoomId()
|
||||||
|
|
||||||
|
const { mutateAsync: startRecordingRoom } = useStartRecording()
|
||||||
|
const { mutateAsync: stopRecordingRoom } = useStopRecording()
|
||||||
|
|
||||||
|
const recordingSnap = useSnapshot(recordingStore)
|
||||||
|
|
||||||
|
const room = useRoomContext()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleRecordingStatusChanged = () => {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
room.on(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
|
||||||
|
return () => {
|
||||||
|
room.off(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanged)
|
||||||
|
}
|
||||||
|
}, [room])
|
||||||
|
|
||||||
|
const notifyParticipant = async (status: NotificationType) => {
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
const payload: NotificationPayload = {
|
||||||
|
type: status,
|
||||||
|
}
|
||||||
|
const data = encoder.encode(JSON.stringify(payload))
|
||||||
|
await room.localParticipant.publishData(data, {
|
||||||
|
reliable: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTranscript = async () => {
|
||||||
|
if (!roomId) {
|
||||||
|
console.warn('No room ID found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
|
if (room.isRecording) {
|
||||||
|
await stopRecordingRoom({ id: roomId })
|
||||||
|
await notifyParticipant(NotificationType.ScreenRecordingStopped)
|
||||||
|
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STOPPING
|
||||||
|
} else {
|
||||||
|
await startRecordingRoom({
|
||||||
|
id: roomId,
|
||||||
|
mode: RecordingMode.ScreenRecording,
|
||||||
|
})
|
||||||
|
await notifyParticipant(NotificationType.ScreenRecordingStarted)
|
||||||
|
recordingStore.status = RecordingStatus.SCREEN_RECORDING_STARTING
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to handle transcript:', error)
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDisabled = useMemo(
|
||||||
|
() =>
|
||||||
|
isLoading ||
|
||||||
|
recordingSnap.status == RecordingStatus.SCREEN_RECORDING_STARTING ||
|
||||||
|
recordingSnap.status == RecordingStatus.SCREEN_RECORDING_STOPPING,
|
||||||
|
[isLoading, recordingSnap]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Div
|
||||||
|
display="flex"
|
||||||
|
overflowY="scroll"
|
||||||
|
padding="0 1.5rem"
|
||||||
|
flexGrow={1}
|
||||||
|
flexDirection="column"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={fourthSlide}
|
||||||
|
alt={''}
|
||||||
|
className={css({
|
||||||
|
minHeight: '309px',
|
||||||
|
marginBottom: '1rem',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{room.isRecording ? (
|
||||||
|
<>
|
||||||
|
<Text>{t('stop.heading')}</Text>
|
||||||
|
<Text
|
||||||
|
variant="note"
|
||||||
|
wrap={'pretty'}
|
||||||
|
centered
|
||||||
|
className={css({
|
||||||
|
textStyle: 'sm',
|
||||||
|
marginBottom: '2.5rem',
|
||||||
|
marginTop: '0.25rem',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{t('stop.body')}
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
onPress={() => handleTranscript()}
|
||||||
|
data-attr="stop-transcript"
|
||||||
|
size="sm"
|
||||||
|
variant="tertiary"
|
||||||
|
>
|
||||||
|
{t('stop.button')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Text>{t('start.heading')}</Text>
|
||||||
|
<Text
|
||||||
|
variant="note"
|
||||||
|
wrap={'pretty'}
|
||||||
|
centered
|
||||||
|
className={css({
|
||||||
|
textStyle: 'sm',
|
||||||
|
maxWidth: '90%',
|
||||||
|
marginBottom: '2.5rem',
|
||||||
|
marginTop: '0.25rem',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{t('start.body')} <br />{' '}
|
||||||
|
<A href={CRISP_HELP_ARTICLE_RECORDING} target="_blank">
|
||||||
|
{t('start.linkMore')}
|
||||||
|
</A>
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
onPress={() => handleTranscript()}
|
||||||
|
data-attr="start-transcript"
|
||||||
|
size="sm"
|
||||||
|
variant="tertiary"
|
||||||
|
>
|
||||||
|
{t('start.button')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -5,9 +5,10 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { CRISP_HELP_ARTICLE_MORE_TOOLS } from '@/utils/constants'
|
import { CRISP_HELP_ARTICLE_MORE_TOOLS } from '@/utils/constants'
|
||||||
import { ReactNode } from 'react'
|
import { ReactNode } from 'react'
|
||||||
import { Transcript } from './Transcript'
|
import { Transcript } from './Transcript'
|
||||||
import { RiFileTextFill } from '@remixicon/react'
|
|
||||||
import { useSidePanel } from '../hooks/useSidePanel'
|
|
||||||
import { useIsRecordingModeEnabled } from '../hooks/useIsRecordingModeEnabled'
|
import { useIsRecordingModeEnabled } from '../hooks/useIsRecordingModeEnabled'
|
||||||
|
import { RiFileTextFill, RiLiveFill } from '@remixicon/react'
|
||||||
|
import { SubPanelId, useSidePanel } from '../hooks/useSidePanel'
|
||||||
|
import { ScreenRecording } from './ScreenRecording'
|
||||||
import { RecordingMode } from '@/features/rooms/api/startRecording'
|
import { RecordingMode } from '@/features/rooms/api/startRecording'
|
||||||
|
|
||||||
export interface ToolsButtonProps {
|
export interface ToolsButtonProps {
|
||||||
@@ -69,14 +70,24 @@ const ToolButton = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Tools = () => {
|
export const Tools = () => {
|
||||||
const { openTranscript, isTranscriptOpen } = useSidePanel()
|
const { openTranscript, openScreenRecording, activeSubPanelId } =
|
||||||
|
useSidePanel()
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'moreTools' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'moreTools' })
|
||||||
const isTranscriptEnabled = useIsRecordingModeEnabled(
|
const isTranscriptEnabled = useIsRecordingModeEnabled(
|
||||||
RecordingMode.Transcript
|
RecordingMode.Transcript
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isTranscriptOpen && isTranscriptEnabled) {
|
const isScreenRecordingEnabled = useIsRecordingModeEnabled(
|
||||||
return <Transcript />
|
RecordingMode.ScreenRecording
|
||||||
|
)
|
||||||
|
|
||||||
|
switch (activeSubPanelId) {
|
||||||
|
case SubPanelId.TRANSCRIPT:
|
||||||
|
return <Transcript />
|
||||||
|
case SubPanelId.SCREEN_RECORDING:
|
||||||
|
return <ScreenRecording />
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -111,6 +122,14 @@ export const Tools = () => {
|
|||||||
onPress={() => openTranscript()}
|
onPress={() => openTranscript()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{isScreenRecordingEnabled && (
|
||||||
|
<ToolButton
|
||||||
|
icon={<RiLiveFill size={24} color="white" />}
|
||||||
|
title={t('tools.screenRecording.title')}
|
||||||
|
description={t('tools.screenRecording.body')}
|
||||||
|
onPress={() => openScreenRecording()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Div>
|
</Div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export enum PanelId {
|
|||||||
|
|
||||||
export enum SubPanelId {
|
export enum SubPanelId {
|
||||||
TRANSCRIPT = 'transcript',
|
TRANSCRIPT = 'transcript',
|
||||||
|
SCREEN_RECORDING = 'screenRecording',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSidePanel = () => {
|
export const useSidePanel = () => {
|
||||||
@@ -24,6 +25,7 @@ export const useSidePanel = () => {
|
|||||||
const isToolsOpen = activePanelId == PanelId.TOOLS
|
const isToolsOpen = activePanelId == PanelId.TOOLS
|
||||||
const isAdminOpen = activePanelId == PanelId.ADMIN
|
const isAdminOpen = activePanelId == PanelId.ADMIN
|
||||||
const isTranscriptOpen = activeSubPanelId == SubPanelId.TRANSCRIPT
|
const isTranscriptOpen = activeSubPanelId == SubPanelId.TRANSCRIPT
|
||||||
|
const isScreenRecordingOpen = activeSubPanelId == SubPanelId.SCREEN_RECORDING
|
||||||
const isSidePanelOpen = !!activePanelId
|
const isSidePanelOpen = !!activePanelId
|
||||||
const isSubPanelOpen = !!activeSubPanelId
|
const isSubPanelOpen = !!activeSubPanelId
|
||||||
|
|
||||||
@@ -57,6 +59,11 @@ export const useSidePanel = () => {
|
|||||||
layoutStore.activePanelId = PanelId.TOOLS
|
layoutStore.activePanelId = PanelId.TOOLS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openScreenRecording = () => {
|
||||||
|
layoutStore.activeSubPanelId = SubPanelId.SCREEN_RECORDING
|
||||||
|
layoutStore.activePanelId = PanelId.TOOLS
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activePanelId,
|
activePanelId,
|
||||||
activeSubPanelId,
|
activeSubPanelId,
|
||||||
@@ -66,6 +73,7 @@ export const useSidePanel = () => {
|
|||||||
toggleTools,
|
toggleTools,
|
||||||
toggleAdmin,
|
toggleAdmin,
|
||||||
openTranscript,
|
openTranscript,
|
||||||
|
openScreenRecording,
|
||||||
isSubPanelOpen,
|
isSubPanelOpen,
|
||||||
isChatOpen,
|
isChatOpen,
|
||||||
isParticipantsOpen,
|
isParticipantsOpen,
|
||||||
@@ -74,5 +82,6 @@ export const useSidePanel = () => {
|
|||||||
isToolsOpen,
|
isToolsOpen,
|
||||||
isAdminOpen,
|
isAdminOpen,
|
||||||
isTranscriptOpen,
|
isTranscriptOpen,
|
||||||
|
isScreenRecordingOpen,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,7 @@
|
|||||||
"effects": "Effects",
|
"effects": "Effects",
|
||||||
"chat": "Messages in the chat",
|
"chat": "Messages in the chat",
|
||||||
"transcript": "Transcription",
|
"transcript": "Transcription",
|
||||||
|
"screenRecording": "Recording",
|
||||||
"admin": "Admin settings",
|
"admin": "Admin settings",
|
||||||
"tools": "More tools"
|
"tools": "More tools"
|
||||||
},
|
},
|
||||||
@@ -175,7 +176,8 @@
|
|||||||
"participants": "participants",
|
"participants": "participants",
|
||||||
"effects": "effects",
|
"effects": "effects",
|
||||||
"chat": "messages",
|
"chat": "messages",
|
||||||
"transcript": "Transcription",
|
"transcript": "transcription",
|
||||||
|
"screenRecording": "recording",
|
||||||
"admin": "admin settings",
|
"admin": "admin settings",
|
||||||
"tools": "more tools"
|
"tools": "more tools"
|
||||||
},
|
},
|
||||||
@@ -190,7 +192,11 @@
|
|||||||
"tools": {
|
"tools": {
|
||||||
"transcript": {
|
"transcript": {
|
||||||
"title": "Transcription",
|
"title": "Transcription",
|
||||||
"body": "Transcribe your meeting for later"
|
"body": "Keep a written record of your meeting."
|
||||||
|
},
|
||||||
|
"screenRecording": {
|
||||||
|
"title": "Recording",
|
||||||
|
"body": "Record your meeting to watch it again whenever you like."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -212,6 +218,19 @@
|
|||||||
"button": "Sign up"
|
"button": "Sign up"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"screenRecording": {
|
||||||
|
"start": {
|
||||||
|
"heading": "Record this call",
|
||||||
|
"body": "Record this call to watch it later and receive the video recording by email.",
|
||||||
|
"button": "Start recording",
|
||||||
|
"linkMore": "Learn more"
|
||||||
|
},
|
||||||
|
"stop": {
|
||||||
|
"heading": "Recording in progress…",
|
||||||
|
"body": "You will receive the result by email once the recording is complete.",
|
||||||
|
"button": "Stop recording"
|
||||||
|
}
|
||||||
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"description": "These organizer settings allow you to maintain control of your meeting. Only organizers can access these controls.",
|
"description": "These organizer settings allow you to maintain control of your meeting. Only organizers can access these controls.",
|
||||||
"access": {
|
"access": {
|
||||||
|
|||||||
@@ -168,6 +168,7 @@
|
|||||||
"effects": "Effets",
|
"effects": "Effets",
|
||||||
"chat": "Messages dans l'appel",
|
"chat": "Messages dans l'appel",
|
||||||
"transcript": "Transcription",
|
"transcript": "Transcription",
|
||||||
|
"screenRecording": "Enregistrement",
|
||||||
"admin": "Commandes de l'organisateur",
|
"admin": "Commandes de l'organisateur",
|
||||||
"tools": "Plus d'outils"
|
"tools": "Plus d'outils"
|
||||||
},
|
},
|
||||||
@@ -176,6 +177,7 @@
|
|||||||
"effects": "les effets",
|
"effects": "les effets",
|
||||||
"chat": "les messages",
|
"chat": "les messages",
|
||||||
"transcript": "transcription",
|
"transcript": "transcription",
|
||||||
|
"screenRecording": "enregistrement",
|
||||||
"admin": "commandes de l'organisateur",
|
"admin": "commandes de l'organisateur",
|
||||||
"tools": "plus d'outils"
|
"tools": "plus d'outils"
|
||||||
},
|
},
|
||||||
@@ -190,7 +192,11 @@
|
|||||||
"tools": {
|
"tools": {
|
||||||
"transcript": {
|
"transcript": {
|
||||||
"title": "Transcription",
|
"title": "Transcription",
|
||||||
"body": "Transcrivez votre réunion pour plus tard"
|
"body": "Conservez une trace écrite de votre réunion."
|
||||||
|
},
|
||||||
|
"screenRecording": {
|
||||||
|
"title": "Enregistrement",
|
||||||
|
"body": "Enregistrez votre réunion pour la revoir quand vous le souhaitez."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -212,6 +218,19 @@
|
|||||||
"button": "Inscrivez-vous"
|
"button": "Inscrivez-vous"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"screenRecording": {
|
||||||
|
"start": {
|
||||||
|
"heading": "Enregistrer cet appel",
|
||||||
|
"body": "Enregistrez cet appel pour plus tard et recevez l'enregistrement vidéo par mail.",
|
||||||
|
"button": "Démarrer l'enregistrement",
|
||||||
|
"linkMore": "En savoir plus"
|
||||||
|
},
|
||||||
|
"stop": {
|
||||||
|
"heading": "Enregistrement en cours …",
|
||||||
|
"body": "Vous recevrez le resultat par email une fois l'enregistrement terminé.",
|
||||||
|
"button": "Arrêter l'enregistrement"
|
||||||
|
}
|
||||||
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"description": "Ces paramètres organisateur vous permettent de garder le contrôle de votre réunion. Seuls les organisateurs peuvent accéder à ces commandes.",
|
"description": "Ces paramètres organisateur vous permettent de garder le contrôle de votre réunion. Seuls les organisateurs peuvent accéder à ces commandes.",
|
||||||
"access": {
|
"access": {
|
||||||
|
|||||||
@@ -168,6 +168,7 @@
|
|||||||
"effects": "Effecten",
|
"effects": "Effecten",
|
||||||
"chat": "Berichten in de chat",
|
"chat": "Berichten in de chat",
|
||||||
"transcript": "Transcriptie",
|
"transcript": "Transcriptie",
|
||||||
|
"screenRecording": "Schermopname",
|
||||||
"admin": "Beheerdersbediening",
|
"admin": "Beheerdersbediening",
|
||||||
"tools": "Meer tools"
|
"tools": "Meer tools"
|
||||||
},
|
},
|
||||||
@@ -175,7 +176,8 @@
|
|||||||
"participants": "deelnemers",
|
"participants": "deelnemers",
|
||||||
"effects": "effecten",
|
"effects": "effecten",
|
||||||
"chat": "berichten",
|
"chat": "berichten",
|
||||||
"transcript": "transcriptie",
|
"screenRecording": "transcriptie",
|
||||||
|
"transcript": "schermopname",
|
||||||
"admin": "beheerdersbediening",
|
"admin": "beheerdersbediening",
|
||||||
"tools": "meer tools"
|
"tools": "meer tools"
|
||||||
},
|
},
|
||||||
@@ -190,7 +192,11 @@
|
|||||||
"tools": {
|
"tools": {
|
||||||
"transcript": {
|
"transcript": {
|
||||||
"title": "Transcriptie",
|
"title": "Transcriptie",
|
||||||
"body": "Transcribeer je vergadering voor later"
|
"body": "Bewaar een schriftelijk verslag van je vergadering."
|
||||||
|
},
|
||||||
|
"screenRecording": {
|
||||||
|
"title": "Opname",
|
||||||
|
"body": "Neem je vergadering op om die later opnieuw te bekijken."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -212,6 +218,19 @@
|
|||||||
"button": "Aanmelden"
|
"button": "Aanmelden"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"screenRecording": {
|
||||||
|
"start": {
|
||||||
|
"heading": "Dit gesprek opnemen",
|
||||||
|
"body": "Neem dit gesprek op om het later terug te kijken. Je ontvangt de video-opname per e-mail.",
|
||||||
|
"button": "Opname starten",
|
||||||
|
"linkMore": "Meer informatie"
|
||||||
|
},
|
||||||
|
"stop": {
|
||||||
|
"heading": "Opname bezig …",
|
||||||
|
"body": "Je ontvangt het resultaat per e-mail zodra de opname is voltooid.",
|
||||||
|
"button": "Opname stoppen"
|
||||||
|
}
|
||||||
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"description": "Deze organisatorinstellingen geven u controle over uw vergadering. Alleen organisatoren hebben toegang tot deze bedieningselementen.",
|
"description": "Deze organisatorinstellingen geven u controle over uw vergadering. Alleen organisatoren hebben toegang tot deze bedieningselementen.",
|
||||||
"access": {
|
"access": {
|
||||||
|
|||||||
@@ -9,3 +9,6 @@ export const CRISP_HELP_ARTICLE_MORE_TOOLS =
|
|||||||
|
|
||||||
export const CRISP_HELP_ARTICLE_TRANSCRIPT =
|
export const CRISP_HELP_ARTICLE_TRANSCRIPT =
|
||||||
'https://lasuite.crisp.help/fr/article/visio-transcript-1sjq43x' as const
|
'https://lasuite.crisp.help/fr/article/visio-transcript-1sjq43x' as const
|
||||||
|
|
||||||
|
export const CRISP_HELP_ARTICLE_RECORDING =
|
||||||
|
'https://lasuite.crisp.help/fr/article/visio-enregistrement-wgc8o0' as const
|
||||||
|
|||||||
Reference in New Issue
Block a user