From 86ca74d79ce26f0b083c7e5156cdfecc14f2bb30 Mon Sep 17 00:00:00 2001 From: Lanskikh Date: Thu, 30 Oct 2025 18:01:09 +0500 Subject: [PATCH] Enhance SessionUsersPanel and UserCamera components to support 'full' and 'mini' modes. Implement responsive grid layout for camera display based on active participants, and adjust UserDevicesControls visibility accordingly. Refactor SessionPage to manage mode state and toggle between layouts. --- client/src/components/SessionUsersPanel.tsx | 86 ++++++++++++++++--- client/src/components/ui/UserCamera.tsx | 15 ++-- .../src/components/ui/UserDevicesControls.tsx | 11 ++- client/src/pages/SessionPage.tsx | 24 +++++- 4 files changed, 114 insertions(+), 22 deletions(-) diff --git a/client/src/components/SessionUsersPanel.tsx b/client/src/components/SessionUsersPanel.tsx index 3790fb7..47313a1 100644 --- a/client/src/components/SessionUsersPanel.tsx +++ b/client/src/components/SessionUsersPanel.tsx @@ -2,16 +2,18 @@ import UserCamera from "./ui/UserCamera"; import UserDevicesControls from "./ui/UserDevicesControls"; import DraggableContainer from "./DraggableContainer"; import { useWebRTC } from "../hooks/useWebRTC"; -import { useCallback } from "react"; +import clsx from "clsx"; interface SessionUsersPanelProps { roomId: string; autoJoin?: boolean; + mode?: "full" | "mini"; } function SessionUsersPanel({ roomId, autoJoin = false, + mode = "full", }: SessionUsersPanelProps) { const { localStream, @@ -26,21 +28,38 @@ function SessionUsersPanel({ const hasLocalStream = localStream !== null; // Callback для отправки состояния speaking - const handleSpeakingChange = useCallback((isSpeaking: boolean) => { + const handleSpeakingChange = (isSpeaking: boolean) => { updateSpeakingState?.(isSpeaking); - }, [updateSpeakingState]); + }; - return ( - = 640 ? "bottom-right" : "top-right"} - padding="1.111vw" - className="flex gap-4 z-[999]" - > + // Вычисляем количество камер для grid + const activeCamerasCount = + (localStream ? 8 : 0) + + participants.filter( + (p) => p.stream != null && p.stream.getTracks().length > 0 + ).length; + // Определяем количество колонок в зависимости от количества камер + // 1-2 камеры: 1 колонка (друг под другом), 3-4: 2 колонки, 5-9: 3 колонки, 10-16: 4 колонки, 17-25: 5 колонок + const getGridColumns = (count: number): number => { + if (count <= 2) return 1; + if (count <= 4) return 2; + if (count <= 9) return 3; + if (count <= 16) return 4; + return 5; + }; + + const gridColumns = getGridColumns(activeCamerasCount); + + // Вычисляем количество рядов для правильного расчета высоты + const gridRows = Math.ceil(activeCamerasCount / gridColumns); + + // Рендерим камеры + const camerasContent = ( + <> {/* Локальная камера пользователя - показываем только если есть разрешение */} {localStream && ( ( console.log(`Mute user ${participant.id}`)} @@ -82,13 +102,53 @@ function SessionUsersPanel({ ))} - + + ); + + // Для режима full используем DraggableContainer + if (mode === "full") { + return ( + = 640 ? "bottom-right" : "top-right"} + padding="1.111vw" + className="z-[999] flex gap-4" + > + {camerasContent} + + ); + } + + // Для режима mini используем flex-обертку для центрирования и внутри grid + return ( +
+
+ {camerasContent} +
+
); } diff --git a/client/src/components/ui/UserCamera.tsx b/client/src/components/ui/UserCamera.tsx index 1672c1f..264b118 100644 --- a/client/src/components/ui/UserCamera.tsx +++ b/client/src/components/ui/UserCamera.tsx @@ -37,6 +37,8 @@ interface UserCameraProps { isSpeaking?: boolean; // Для удаленных участников - получаем по Socket.IO onSpeakingChange?: (isSpeaking: boolean) => void; // Для локального - отправляем изменения hasLocalMediaPermission?: boolean; // Есть ли у локального пользователя разрешение на медиа + mode: "full" | "mini"; + className?: string; } export default function UserCamera({ @@ -53,6 +55,8 @@ export default function UserCamera({ isSpeaking: remoteSpeaking, onSpeakingChange, hasLocalMediaPermission = false, + mode = "full", + className, }: UserCameraProps) { const ref = useRef(null); // Для удаленных участников: если у локального пользователя есть разрешение на медиа - unmute, иначе mute (для autoplay) @@ -243,9 +247,7 @@ export default function UserCamera({ const newMutedState = !isAudioMuted; setIsAudioMuted(newMutedState); console.log( - `[UserCamera] ${name} audio ${ - newMutedState ? "muted" : "unmuted" - }` + `[UserCamera] ${name} audio ${newMutedState ? "muted" : "unmuted"}` ); } }; @@ -268,9 +270,12 @@ export default function UserCamera({ return (
void; @@ -14,6 +15,7 @@ export interface UserDevicesControlsProps { isAudioMuted: boolean; isVideoMuted: boolean; hasLocalStream?: boolean; + mode?: "full" | "mini"; } export default function UserDevicesControls({ @@ -22,6 +24,7 @@ export default function UserDevicesControls({ isAudioMuted, isVideoMuted, hasLocalStream = true, + mode = "full", }: UserDevicesControlsProps) { const { setModal } = useModalStore(); @@ -30,7 +33,13 @@ export default function UserDevicesControls({ } return ( -
+