(frontend) introduce a side panel for effects

It simply renders the video track if enabled. It's a basis
for building the 'blur your screen' feature.

More in the upcoming commits.
This commit is contained in:
lebaudantoine
2024-09-18 16:49:36 +02:00
committed by aleb_the_flash
parent 00fa7beebd
commit 756be17cc7
8 changed files with 106 additions and 10 deletions

View File

@@ -0,0 +1,68 @@
import { useEffect, useRef } from 'react'
import { useLocalParticipant } from '@livekit/components-react'
import { LocalVideoTrack } from 'livekit-client'
import { Div, P } from '@/primitives'
import { useTranslation } from 'react-i18next'
export const Effects = () => {
const { t } = useTranslation('rooms', { keyPrefix: 'effects' })
const { isCameraEnabled, cameraTrack } = useLocalParticipant()
const videoRef = useRef<HTMLVideoElement>(null)
const localCameraTrack = cameraTrack?.track as LocalVideoTrack
useEffect(() => {
const videoElement = videoRef.current
const attachVideoTrack = async () => {
if (!videoElement) return
localCameraTrack?.attach(videoElement)
}
attachVideoTrack()
return () => {
if (!videoElement) return
localCameraTrack.detach(videoElement)
}
}, [localCameraTrack, isCameraEnabled])
return (
<Div padding="0 1.5rem">
{localCameraTrack && isCameraEnabled ? (
<video
ref={videoRef}
width="100%"
muted
style={{
transform: 'rotateY(180deg)',
minHeight: '173px',
borderRadius: '4px',
}}
/>
) : (
<div
style={{
width: '100%',
height: '174px',
display: 'flex',
backgroundColor: 'black',
justifyContent: 'center',
flexDirection: 'column',
}}
>
<P
style={{
color: 'white',
textAlign: 'center',
textWrap: 'balance',
marginBottom: 0,
}}
>
{t('activateCamera')}
</P>
</div>
)}
</Div>
)
}

View File

@@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next'
import { ParticipantsList } from './controls/Participants/ParticipantsList'
import { useWidgetInteraction } from '../hooks/useWidgetInteraction'
import { ReactNode } from 'react'
import { Effects } from './Effects'
type StyledSidePanelProps = {
title: string
@@ -65,7 +66,7 @@ export const SidePanel = () => {
const layoutSnap = useSnapshot(layoutStore)
const sidePanel = layoutSnap.sidePanel
const { isParticipantsOpen } = useWidgetInteraction()
const { isParticipantsOpen, isEffectsOpen } = useWidgetInteraction()
const { t } = useTranslation('rooms', { keyPrefix: 'sidePanel' })
if (!sidePanel) {
@@ -81,6 +82,7 @@ export const SidePanel = () => {
})}
>
{isParticipantsOpen && <ParticipantsList />}
{isEffectsOpen && <Effects />}
</StyledSidePanel>
)
}

View File

@@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next'
import { Dispatch, SetStateAction } from 'react'
import { DialogState } from './OptionsButton'
import { Separator } from '@/primitives/Separator'
import { useWidgetInteraction } from '../../../hooks/useWidgetInteraction'
// @todo try refactoring it to use MenuList component
export const OptionsMenuItems = ({
@@ -18,7 +19,7 @@ export const OptionsMenuItems = ({
onOpenDialog: Dispatch<SetStateAction<DialogState>>
}) => {
const { t } = useTranslation('rooms', { keyPrefix: 'options.items' })
const { toggleEffects } = useWidgetInteraction()
return (
<RACMenu
style={{
@@ -28,7 +29,7 @@ export const OptionsMenuItems = ({
>
<Section>
<MenuItem
onAction={() => console.log('open dialog')}
onAction={() => toggleEffects()}
className={menuItemRecipe({ icon: true })}
>
<RiAccountBoxLine size={20} />

View File

@@ -9,6 +9,7 @@ export const useWidgetInteraction = () => {
const sidePanel = layoutSnap.sidePanel
const isParticipantsOpen = sidePanel == 'participants'
const isEffectsOpen = sidePanel == 'effects'
const toggleParticipants = () => {
if (dispatch && state?.showChat) {
@@ -26,11 +27,20 @@ export const useWidgetInteraction = () => {
}
}
const toggleEffects = () => {
if (dispatch && state?.showChat) {
dispatch({ msg: 'toggle_chat' })
}
layoutStore.sidePanel = isEffectsOpen ? null : 'effects'
}
return {
toggleParticipants,
toggleChat,
toggleEffects,
isChatOpen: state?.showChat,
unreadMessages: state?.unreadMessages,
isParticipantsOpen,
isEffectsOpen,
}
}

View File

@@ -73,12 +73,17 @@
"effects": ""
}
},
"effects": {
"activateCamera": ""
},
"sidePanel": {
"heading": {
"participants": ""
"participants": "",
"effects": ""
},
"content": {
"participants": ""
"participants": "",
"effects": ""
},
"closeButton": ""
},

View File

@@ -71,12 +71,17 @@
"effects": "Apply effects"
}
},
"effects": {
"activateCamera": "Camera is disabled"
},
"sidePanel": {
"heading": {
"participants": "Participants"
"participants": "Participants",
"effects": "Effects"
},
"content": {
"participants": "participants"
"participants": "participants",
"effects": "effects"
},
"closeButton": "Hide {{content}}"
},

View File

@@ -71,12 +71,17 @@
"effects": "Appliquer des effets"
}
},
"effects": {
"activateCamera": "Votre camera est désactivée"
},
"sidePanel": {
"heading": {
"participants": "Participants"
"participants": "Participants",
"effects": "Effets"
},
"content": {
"participants": "les participants"
"participants": "les participants",
"effects": "les effets"
},
"closeButton": "Masquer {{content}}"
},

View File

@@ -2,7 +2,7 @@ import { proxy } from 'valtio'
type State = {
showHeader: boolean
sidePanel: 'participants' | null
sidePanel: 'participants' | 'effects' | null
}
export const layoutStore = proxy<State>({