diff --git a/src/frontend/src/features/pip/components/PictureInPictureConference.tsx b/src/frontend/src/features/pip/components/PictureInPictureConference.tsx new file mode 100644 index 00000000..7a528176 --- /dev/null +++ b/src/frontend/src/features/pip/components/PictureInPictureConference.tsx @@ -0,0 +1,8 @@ +import { PictureInPicturePortal } from '@/features/pip/components/PictureInPicturePortal' +import { PipView } from '@/features/pip/components/PipView' + +export const PictureInPictureConference = () => ( + + + +) diff --git a/src/frontend/src/features/pip/components/PipControlBar.tsx b/src/frontend/src/features/pip/components/PipControlBar.tsx new file mode 100644 index 00000000..4bf8a973 --- /dev/null +++ b/src/frontend/src/features/pip/components/PipControlBar.tsx @@ -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 ( + + + + + + {showScreenShare && } + + + + + + ) +} + +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', + }, +}) diff --git a/src/frontend/src/features/pip/components/PipFloatingReactions.tsx b/src/frontend/src/features/pip/components/PipFloatingReactions.tsx new file mode 100644 index 00000000..e612d6b0 --- /dev/null +++ b/src/frontend/src/features/pip/components/PipFloatingReactions.tsx @@ -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) => ( + + ))} + + ) +} + +const PipFloatingReaction = ({ reaction }: { reaction: Reaction }) => { + const containerRef = useRef(null) + const speed = useMemo(() => Math.random() * 1.5 + 0.5, []) + const scale = useMemo(() => Math.max(Math.random() + 0.5, 1), []) + return ( +
+ +
+ ) +} diff --git a/src/frontend/src/features/pip/components/PipView.tsx b/src/frontend/src/features/pip/components/PipView.tsx new file mode 100644 index 00000000..da463acc --- /dev/null +++ b/src/frontend/src/features/pip/components/PipView.tsx @@ -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 ( + + + + + + ) +} diff --git a/src/frontend/src/features/rooms/components/Conference.tsx b/src/frontend/src/features/rooms/components/Conference.tsx index fe7d9090..81a7cc15 100644 --- a/src/frontend/src/features/rooms/components/Conference.tsx +++ b/src/frontend/src/features/rooms/components/Conference.tsx @@ -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 })} /> - - - + diff --git a/src/frontend/src/locales/de/rooms.json b/src/frontend/src/locales/de/rooms.json index 4389e5ef..07eadd20 100644 --- a/src/frontend/src/locales/de/rooms.json +++ b/src/frontend/src/locales/de/rooms.json @@ -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", diff --git a/src/frontend/src/locales/en/rooms.json b/src/frontend/src/locales/en/rooms.json index c7486c9d..1c724203 100644 --- a/src/frontend/src/locales/en/rooms.json +++ b/src/frontend/src/locales/en/rooms.json @@ -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", diff --git a/src/frontend/src/locales/fr/rooms.json b/src/frontend/src/locales/fr/rooms.json index f3864224..0f03f877 100644 --- a/src/frontend/src/locales/fr/rooms.json +++ b/src/frontend/src/locales/fr/rooms.json @@ -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", diff --git a/src/frontend/src/locales/nl/rooms.json b/src/frontend/src/locales/nl/rooms.json index 19d113b7..b6734794 100644 --- a/src/frontend/src/locales/nl/rooms.json +++ b/src/frontend/src/locales/nl/rooms.json @@ -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",