mirror of
https://github.com/suitenumerique/meet.git
synced 2026-07-26 11:58:53 +00:00
✨(frontend) add a basic control bar to the PiP window
The PiP control bar shares state with the main window's control bar — most importantly, the reaction toolbar and the mic/camera toggles, which are fully stateful. Sharing state works naturally here because the PiP content is rendered through a portal into the same React tree, so the controls in both windows read from and write to the same stores. Co-authored-by: Cyril <c.gromoff@gmail.com>
This commit is contained in:
committed by
aleb_the_flash
parent
faa86b8293
commit
4456137948
@@ -0,0 +1,8 @@
|
||||
import { PictureInPicturePortal } from '@/features/pip/components/PictureInPicturePortal'
|
||||
import { PipView } from '@/features/pip/components/PipView'
|
||||
|
||||
export const PictureInPictureConference = () => (
|
||||
<PictureInPicturePortal>
|
||||
<PipView />
|
||||
</PictureInPicturePortal>
|
||||
)
|
||||
@@ -0,0 +1,64 @@
|
||||
import { styled } from '@/styled-system/jsx'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AudioDevicesControl } from '@/features/rooms/livekit/components/controls/Device/AudioDevicesControl'
|
||||
import { VideoDeviceControl } from '@/features/rooms/livekit/components/controls/Device/VideoDeviceControl'
|
||||
import { ScreenShareToggle } from '@/features/rooms/livekit/components/controls/ScreenShareToggle'
|
||||
import { LeaveButton } from '@/features/rooms/livekit/components/controls/LeaveButton'
|
||||
import { HandToggle } from '@/features/rooms/livekit/components/controls/HandToggle'
|
||||
import { StartMediaButton } from '@/features/rooms/livekit/components/controls/StartMediaButton'
|
||||
import { ReactionsToggle } from '@/features/reactions/components/ReactionsToggle'
|
||||
|
||||
export const PipControlBar = ({
|
||||
showScreenShare,
|
||||
}: {
|
||||
showScreenShare: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation('rooms', {
|
||||
keyPrefix: 'pictureInPicture',
|
||||
})
|
||||
|
||||
return (
|
||||
<PipControls
|
||||
id="pip-control-bar"
|
||||
role="toolbar"
|
||||
aria-label={t('controlBar')}
|
||||
>
|
||||
<PipControlsCenter>
|
||||
<AudioDevicesControl hideMenu />
|
||||
<VideoDeviceControl hideMenu />
|
||||
<ReactionsToggle />
|
||||
{showScreenShare && <ScreenShareToggle />}
|
||||
<HandToggle />
|
||||
<StartMediaButton />
|
||||
<LeaveButton />
|
||||
</PipControlsCenter>
|
||||
</PipControls>
|
||||
)
|
||||
}
|
||||
|
||||
const PipControls = styled('div', {
|
||||
base: {
|
||||
flex: '0 0 auto',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
padding: '1.125rem',
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
})
|
||||
|
||||
const PipControlsCenter = styled('div', {
|
||||
base: {
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '0.4rem',
|
||||
flex: '1 1 auto',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { reactionsStore } from '@/stores/reactions'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { FloatingReaction } from '@/features/reactions/components/ReactionPortals'
|
||||
import { Reaction } from '@/features/reactions/types'
|
||||
import { css } from '@/styled-system/css'
|
||||
|
||||
export const PipFloatingReactions = () => {
|
||||
const { reactions } = useSnapshot(reactionsStore)
|
||||
|
||||
return (
|
||||
<>
|
||||
{reactions.map((reaction) => (
|
||||
<PipFloatingReaction key={reaction.id} reaction={reaction} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const PipFloatingReaction = ({ reaction }: { reaction: Reaction }) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const speed = useMemo(() => Math.random() * 1.5 + 0.5, [])
|
||||
const scale = useMemo(() => Math.max(Math.random() + 0.5, 1), [])
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
pointerEvents: 'none',
|
||||
overflow: 'hidden',
|
||||
})}
|
||||
>
|
||||
<FloatingReaction
|
||||
emoji={reaction.emoji}
|
||||
speed={speed}
|
||||
scale={scale}
|
||||
name={reaction.participantName}
|
||||
isLocal={reaction.isLocal}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { PipControlBar } from './PipControlBar'
|
||||
import { PipFloatingReactions } from './PipFloatingReactions'
|
||||
import { ReactionsToolbar } from '@/features/reactions/components/toolbar/ReactionsToolbar'
|
||||
import { styled } from '@/styled-system/jsx'
|
||||
|
||||
const Container = styled('div', {
|
||||
base: {
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'grid',
|
||||
gridTemplateRows: 'minmax(0, 1fr) auto auto',
|
||||
backgroundColor: 'primaryDark.50',
|
||||
// Disable LiveKit's own border-radius on tiles so our containers
|
||||
// (GridCell, Thumbnail, StageFrame) own the clipping exclusively.
|
||||
'--lk-border-radius': '4px',
|
||||
'& .lk-participant-tile': {
|
||||
height: '100%',
|
||||
},
|
||||
'& .lk-participant-media': {
|
||||
height: '100%',
|
||||
},
|
||||
'& .lk-participant-media-video': {
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
},
|
||||
'& .lk-grid-layout': {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const PipView = () => {
|
||||
return (
|
||||
<Container>
|
||||
<ReactionsToolbar adjustedCentering={false} />
|
||||
<PipControlBar showScreenShare={false} />
|
||||
<PipFloatingReactions />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -32,8 +32,7 @@ import { isFireFox } from '@/utils/livekit'
|
||||
import { useIsMobile } from '@/utils/useIsMobile'
|
||||
import { navigateTo } from '@/navigation/navigateTo'
|
||||
import { connectionObserverStore } from '@/stores/connectionObserver'
|
||||
import { PictureInPicturePortal } from '@/features/pip/components/PictureInPicturePortal'
|
||||
import { Spinner } from '@/primitives/Spinner'
|
||||
import { PictureInPictureConference } from '@/features/pip/components/PictureInPictureConference'
|
||||
|
||||
export const Conference = ({
|
||||
roomId,
|
||||
@@ -293,9 +292,7 @@ export const Conference = ({
|
||||
{...mediaDeviceError}
|
||||
onClose={() => setMediaDeviceError({ error: null, kind: null })}
|
||||
/>
|
||||
<PictureInPicturePortal>
|
||||
<Spinner />
|
||||
</PictureInPicturePortal>
|
||||
<PictureInPictureConference />
|
||||
</LiveKitRoom>
|
||||
</Screen>
|
||||
</QueryAware>
|
||||
|
||||
@@ -236,7 +236,8 @@
|
||||
"title": "Ihr Videoanruf befindet sich in einem anderen Fenster.",
|
||||
"description": "Im Bild-im-Bild-Modus bleiben Sie mit dem Anruf verbunden, während Sie andere Aufgaben erledigen.",
|
||||
"bringBack": "Anruf hierher zurückholen"
|
||||
}
|
||||
},
|
||||
"controlBar": "Besprechungssteuerung"
|
||||
},
|
||||
"options": {
|
||||
"buttonLabel": "Weitere Optionen",
|
||||
|
||||
@@ -236,7 +236,8 @@
|
||||
"title": "Your video call is in another window.",
|
||||
"description": "Picture-in-Picture mode allows you to stay connected to the call while performing other tasks.",
|
||||
"bringBack": "Bring the call back here"
|
||||
}
|
||||
},
|
||||
"controlBar": "Meeting controls"
|
||||
},
|
||||
"options": {
|
||||
"buttonLabel": "More Options",
|
||||
|
||||
@@ -236,7 +236,8 @@
|
||||
"title": "Votre appel vidéo est dans une autre fenêtre.",
|
||||
"description": "Le mode image dans l'image vous permet de rester connecté à l'appel tout en effectuant d'autres tâches.",
|
||||
"bringBack": "Ramener l'appel ici"
|
||||
}
|
||||
},
|
||||
"controlBar": "Commandes de la réunion"
|
||||
},
|
||||
"options": {
|
||||
"buttonLabel": "Plus d'options",
|
||||
|
||||
@@ -236,7 +236,8 @@
|
||||
"title": "Uw videogesprek bevindt zich in een ander venster.",
|
||||
"description": "Met de beeld-in-beeld-modus kunt u verbonden blijven met het gesprek terwijl u andere taken uitvoert.",
|
||||
"bringBack": "Gesprek hier terughalen"
|
||||
}
|
||||
},
|
||||
"controlBar": "Vergaderbesturing"
|
||||
},
|
||||
"options": {
|
||||
"buttonLabel": "Meer opties",
|
||||
|
||||
Reference in New Issue
Block a user