From c6157f62b28e7cbe968b0f422f659481cb2678e3 Mon Sep 17 00:00:00 2001 From: Lanskikh Date: Fri, 31 Oct 2025 00:17:00 +0500 Subject: [PATCH 1/3] Update environment variables for production URLs, refactor DraggableContainer to wrap children in a div when disabled, and enhance SessionUsersPanel layout with responsive adjustments for camera display. Improve UserCamera component's class handling and clean up SessionPage layout for better structure. --- client/.env | 8 +- client/src/components/DraggableContainer.tsx | 2 +- client/src/components/SessionUsersPanel.tsx | 88 ++++++++++---------- client/src/components/ui/UserCamera.tsx | 14 +++- client/src/pages/SessionPage.tsx | 9 +- 5 files changed, 61 insertions(+), 60 deletions(-) diff --git a/client/.env b/client/.env index 0f22f67..5e374be 100644 --- a/client/.env +++ b/client/.env @@ -1,4 +1,4 @@ -VITE_API_URL=http://localhost:3000 -VITE_WEBRTC_URL=http://localhost:3001 -# VITE_API_URL=https://stream.graff.estate/api -# VITE_WEBRTC_URL=https://stream.graff.estate \ No newline at end of file +# VITE_API_URL=http://localhost:3000 +# VITE_WEBRTC_URL=http://localhost:3001 +VITE_API_URL=https://stream.graff.estate/api +VITE_WEBRTC_URL=https://stream.graff.estate \ No newline at end of file diff --git a/client/src/components/DraggableContainer.tsx b/client/src/components/DraggableContainer.tsx index f50da2d..6f93a6d 100644 --- a/client/src/components/DraggableContainer.tsx +++ b/client/src/components/DraggableContainer.tsx @@ -496,7 +496,7 @@ export default function DraggableContainer({ // Если компонент отключен, просто рендерим children без стилей и логики if (!enabled) { - return <>{children}; + return
{children}
; } return ( diff --git a/client/src/components/SessionUsersPanel.tsx b/client/src/components/SessionUsersPanel.tsx index e8db0ca..a4e042c 100644 --- a/client/src/components/SessionUsersPanel.tsx +++ b/client/src/components/SessionUsersPanel.tsx @@ -66,7 +66,7 @@ function SessionUsersPanel({ // Вычисляем количество камер для grid const activeCamerasCount = - (localStream ? 8 : 0) + + (localStream ? 1 : 0) + participants.filter( (p) => p.stream != null && p.stream.getTracks().length > 0 ).length; @@ -76,19 +76,28 @@ function SessionUsersPanel({ if (count <= 2) return 1; if (count <= 4) return 2; if (count <= 9) return 3; - if (count <= 16) return 4; - return 5; + return 4; }; const gridColumns = getGridColumns(activeCamerasCount); // Вычисляем количество рядов для правильного расчета высоты - const gridRows = Math.ceil(activeCamerasCount / gridColumns); + // const gridRows = Math.ceil(activeCamerasCount / gridColumns); - // Рендерим камеры - const camerasContent = ( - <> - {/* Локальная камера пользователя - показываем только если есть разрешение */} + return ( + = 640 ? "bottom-right" : "top-right"} + padding="1.111vw" + className={clsx( + "z-[999]", + mode === "full" + ? "flex 2xl:gap-[0.556vw] gap-2" + : `2xl:p-[5vw] w-full max- h-dvh grid grid-cols-${gridColumns} 2xl:gap-[0.556vw] gap-2` + )} + > {localStream && ( console.log("Toggle control")} onSpeakingChange={handleSpeakingChange} + className={activeCamerasCount <= 2 ? "m-auto" : " w-full"} /> )} @@ -115,6 +125,7 @@ function SessionUsersPanel({ ) .map((participant) => ( - + ); - - // Для режима 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} -
-
- ); + // return ( + //
+ //
+ // {camerasContent} + //
+ //
+ // ); } export default SessionUsersPanel; diff --git a/client/src/components/ui/UserCamera.tsx b/client/src/components/ui/UserCamera.tsx index 1f80021..8d98da6 100644 --- a/client/src/components/ui/UserCamera.tsx +++ b/client/src/components/ui/UserCamera.tsx @@ -75,9 +75,13 @@ export default function UserCamera({ // Отправляем изменения состояния для локального пользователя // Используем ref для отслеживания предыдущего состояния, чтобы избежать лишних вызовов const prevLocalSpeakingRef = useRef(localSpeaking); - + useEffect(() => { - if (isLocal && onSpeakingChange && prevLocalSpeakingRef.current !== localSpeaking) { + if ( + isLocal && + onSpeakingChange && + prevLocalSpeakingRef.current !== localSpeaking + ) { prevLocalSpeakingRef.current = localSpeaking; onSpeakingChange(localSpeaking); } @@ -276,7 +280,7 @@ export default function UserCamera({ className={clsx( mode === "full" ? "aspect-square h-fit group 2xl:rounded-[1.667vw] rounded-2xl relative flex-shrink-0 pointer-events-auto 2xl:hover:w-[10.833vw] 2xl:w-[6.944vw] sm:w-[15.625vw] w-[27.778vw] overflow-hidden" - : "aspect-video 2xl:rounded-[2.222vw] rounded-[32px] overflow-hidden group relative flex-shrink-0 pointer-events-auto w-full h-full object-contain", + : "aspect-video 2xl:rounded-[2.222vw] rounded-[32px] overflow-hidden group relative pointer-events-auto max-w-full h-full object-cover", isLocal && "order-last", isVideoOff ? "bg-green-500" : "bg-yellow-500/10", className @@ -290,7 +294,9 @@ export default function UserCamera({ window.innerWidth >= 1536 ? "0.069vw" : "1px" } rgba(255, 255, 255, 0.3)`, transition: - "box-shadow 0.1s ease-out, width 0.3s, background-color 0.3s", + mode === "full" + ? "box-shadow 0.1s ease-out, width 0.3s, background-color 0.3s" + : undefined, }} onClick={handleVideoClick} > diff --git a/client/src/pages/SessionPage.tsx b/client/src/pages/SessionPage.tsx index 32a4361..c340e42 100644 --- a/client/src/pages/SessionPage.tsx +++ b/client/src/pages/SessionPage.tsx @@ -80,7 +80,6 @@ function SessionPage() { const session = sessionData?.session; function handleChatOpen() { - console.log("handleChatOpen"); setPopup(); } @@ -156,9 +155,9 @@ function SessionPage() { return (
{/* Pixel Streaming - показывается только когда сессия активна */} @@ -166,7 +165,7 @@ function SessionPage() { session.mode === "stream" && session.server?.localIp && session.playerPort && ( -
+
Date: Fri, 31 Oct 2025 13:04:22 +0500 Subject: [PATCH 2/3] Enhance SessionUsersPanel with responsive screen size tracking and dynamic grid column calculations for camera display. Update ControlsPopover to generate session-specific share links. Clean up SessionPage layout by removing unnecessary background styling. --- client/src/components/SessionUsersPanel.tsx | 92 +++++++++++++++++--- client/src/components/ui/ControlsPopover.tsx | 4 +- client/src/pages/SessionPage.tsx | 2 +- 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/client/src/components/SessionUsersPanel.tsx b/client/src/components/SessionUsersPanel.tsx index a4e042c..d8bc6cc 100644 --- a/client/src/components/SessionUsersPanel.tsx +++ b/client/src/components/SessionUsersPanel.tsx @@ -33,11 +33,59 @@ function SessionUsersPanel({ const lastSentSpeakingRef = useRef(false); const speakingStateTimeoutRef = useRef(null); + // State для отслеживания размеров и ориентации экрана + const [windowDimensions, setWindowDimensions] = useState({ + width: window.innerWidth, + height: window.innerHeight, + }); + // Callback для получения изменений состояния speaking от UserCamera const handleSpeakingChange = (isSpeaking: boolean) => { setLocalSpeaking(isSpeaking); }; + // useEffect для отслеживания изменения ориентации и размеров экрана + useEffect(() => { + const handleResize = () => { + setWindowDimensions({ + width: window.innerWidth, + height: window.innerHeight, + }); + }; + + const handleOrientationChange = () => { + // Небольшая задержка для корректного получения новых размеров после поворота + setTimeout(() => { + setWindowDimensions({ + width: window.innerWidth, + height: window.innerHeight, + }); + }, 100); + }; + + // Слушаем событие resize (срабатывает при изменении размера окна) + window.addEventListener("resize", handleResize); + + // Слушаем событие orientationchange (срабатывает при повороте устройства) + window.addEventListener("orientationchange", handleOrientationChange); + + // Также слушаем изменения в screen.orientation API (современный способ) + if (screen.orientation) { + screen.orientation.addEventListener("change", handleOrientationChange); + } + + return () => { + window.removeEventListener("resize", handleResize); + window.removeEventListener("orientationchange", handleOrientationChange); + if (screen.orientation) { + screen.orientation.removeEventListener( + "change", + handleOrientationChange + ); + } + }; + }, []); + // useEffect для throttle и отправки состояния speaking через socket useEffect(() => { // Отправляем только если состояние действительно изменилось @@ -70,32 +118,48 @@ function SessionUsersPanel({ 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 => { + const getDesktopGridColumns = (count: number): number => { if (count <= 2) return 1; if (count <= 4) return 2; if (count <= 9) return 3; return 4; }; - const gridColumns = getGridColumns(activeCamerasCount); + const getMobilePortraitGridColumns = (count: number): number => { + if (count <= 3) return 1; + if (count <= 8) return 2; + return 3; + }; - // Вычисляем количество рядов для правильного расчета высоты - // const gridRows = Math.ceil(activeCamerasCount / gridColumns); + const getMobileLandscapeGridColumns = (count: number): number => { + if (count <= 3) return count; + if (count <= 6) return 3; + return 4; + }; + + const gridColumns = + windowDimensions.width >= 1440 + ? getDesktopGridColumns(activeCamerasCount) + : windowDimensions.height / windowDimensions.width < 1 + ? getMobileLandscapeGridColumns(activeCamerasCount) + : getMobilePortraitGridColumns(activeCamerasCount); return ( = 640 ? "bottom-right" : "top-right"} + initialCorner={ + windowDimensions.width >= 640 ? "bottom-right" : "top-right" + } padding="1.111vw" className={clsx( - "z-[999]", + "z-[999] 2xl:gap-[0.556vw] gap-2", mode === "full" - ? "flex 2xl:gap-[0.556vw] gap-2" - : `2xl:p-[5vw] w-full max- h-dvh grid grid-cols-${gridColumns} 2xl:gap-[0.556vw] gap-2` + ? "flex" + : `2xl:p-[5vw] p-4 w-full 2xl:h-dvh max-2xl:portrait:h-[calc(100dvh-17.778vw)] max-2xl:landscape:h-[calc(100dvh-8.75vw)] grid grid-cols-${gridColumns}` )} > {localStream && ( @@ -112,7 +176,10 @@ function SessionUsersPanel({ onVideoOff={toggleVideo} onCanControl={() => console.log("Toggle control")} onSpeakingChange={handleSpeakingChange} - className={activeCamerasCount <= 2 ? "m-auto" : " w-full"} + className={clsx( + mode === "mini" && + (activeCamerasCount <= 2 ? "2xl:m-auto" : "w-full") + )} /> )} @@ -125,7 +192,10 @@ function SessionUsersPanel({ ) .map((participant) => ( ); + setPopup( + + ); } function handleClickOpenSettingsModal() { diff --git a/client/src/pages/SessionPage.tsx b/client/src/pages/SessionPage.tsx index c340e42..22f8e4c 100644 --- a/client/src/pages/SessionPage.tsx +++ b/client/src/pages/SessionPage.tsx @@ -165,7 +165,7 @@ function SessionPage() { session.mode === "stream" && session.server?.localIp && session.playerPort && ( -
+
Date: Fri, 31 Oct 2025 14:05:42 +0500 Subject: [PATCH 3/3] Refactor SessionUsersPanel to display multiple local cameras based on a defined count, enhance grid column calculations for better responsiveness, and update UserCamera component styling. Adjust ControlsPopover z-index for improved visibility and clean up SessionPage layout for consistency. --- client/src/components/SessionUsersPanel.tsx | 60 ++++++++++++-------- client/src/components/ui/ControlsPopover.tsx | 2 +- client/src/components/ui/UserCamera.tsx | 2 +- client/src/pages/SessionPage.tsx | 7 +-- 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/client/src/components/SessionUsersPanel.tsx b/client/src/components/SessionUsersPanel.tsx index d8bc6cc..fb556b2 100644 --- a/client/src/components/SessionUsersPanel.tsx +++ b/client/src/components/SessionUsersPanel.tsx @@ -5,6 +5,8 @@ import { useWebRTC } from "../hooks/useWebRTC"; import clsx from "clsx"; import { useEffect, useRef, useState } from "react"; +const LOCAL_CAMERAS_COUNT = 10; + interface SessionUsersPanelProps { roomId: string; autoJoin?: boolean; @@ -114,7 +116,7 @@ function SessionUsersPanel({ // Вычисляем количество камер для grid const activeCamerasCount = - (localStream ? 1 : 0) + + (localStream ? LOCAL_CAMERAS_COUNT : 0) + participants.filter( (p) => p.stream != null && p.stream.getTracks().length > 0 ).length; @@ -129,7 +131,7 @@ function SessionUsersPanel({ const getMobilePortraitGridColumns = (count: number): number => { if (count <= 3) return 1; - if (count <= 8) return 2; + if (count <= 12) return 2; return 3; }; @@ -159,29 +161,35 @@ function SessionUsersPanel({ "z-[999] 2xl:gap-[0.556vw] gap-2", mode === "full" ? "flex" - : `2xl:p-[5vw] p-4 w-full 2xl:h-dvh max-2xl:portrait:h-[calc(100dvh-17.778vw)] max-2xl:landscape:h-[calc(100dvh-8.75vw)] grid grid-cols-${gridColumns}` + : `2xl:p-[5vw] p-4 w-full 2xl:h-dvh max-2xl:portrait:max-h-[calc(100dvh-17.778vw)] max-2xl:landscape:max-h-[calc(100dvh-8.75vw)] grid grid-cols-${gridColumns}` )} > - {localStream && ( - console.log("Toggle control")} - onSpeakingChange={handleSpeakingChange} - className={clsx( - mode === "mini" && - (activeCamerasCount <= 2 ? "2xl:m-auto" : "w-full") - )} - /> - )} + {localStream && + Array.from({ length: LOCAL_CAMERAS_COUNT }).map((_, index) => ( + console.log("Toggle control")} + onSpeakingChange={handleSpeakingChange} + className={clsx( + mode === "mini" && + (activeCamerasCount <= 2 + ? "m-auto" + : activeCamerasCount > 12 + ? "!aspect-square w-full" + : "w-full") + )} + /> + ))} {/* Камеры удаленных участников - показываем только если есть поток с активными треками */} {participants @@ -194,7 +202,11 @@ function SessionUsersPanel({ 12 + ? "!aspect-square w-full" + : "w-full") )} key={participant.id} mode={mode} diff --git a/client/src/components/ui/ControlsPopover.tsx b/client/src/components/ui/ControlsPopover.tsx index c5c4bf7..df68eca 100644 --- a/client/src/components/ui/ControlsPopover.tsx +++ b/client/src/components/ui/ControlsPopover.tsx @@ -56,7 +56,7 @@ function ControlsPopover({ session }: ControlsPopoverProps) { } return ( -
+
); } - const [mode, setMode] = useState<"full" | "mini">("full"); + const [mode, setMode] = useState<"full" | "mini">("mini"); function toggleMode() { setMode(mode === "full" ? "mini" : "full"); @@ -155,8 +155,7 @@ function SessionPage() { return (
@@ -200,7 +199,7 @@ function SessionPage() {
)} - +
{mode === "mini" ? : }