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.

This commit is contained in:
2025-10-31 13:04:22 +05:00
parent c6157f62b2
commit 39107bb1ba
3 changed files with 85 additions and 13 deletions
+81 -11
View File
@@ -33,11 +33,59 @@ function SessionUsersPanel({
const lastSentSpeakingRef = useRef<boolean>(false);
const speakingStateTimeoutRef = useRef<NodeJS.Timeout | null>(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 (
<DraggableContainer
enableSnapping={mode === "full"}
enabled={mode === "full"}
autoAlign={true}
initialCorner={innerWidth >= 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) => (
<UserCamera
className={activeCamerasCount <= 2 ? "m-auto" : " w-full"}
className={clsx(
mode === "mini" &&
(activeCamerasCount <= 2 ? "2xl:m-auto" : "w-full")
)}
key={participant.id}
mode={mode}
name={participant.id}
+3 -1
View File
@@ -45,7 +45,9 @@ function ControlsPopover({ session }: ControlsPopoverProps) {
function handleClickOpenSharePopup() {
setIsOpened(false);
setPopup(<SharePopup link="https://estate.stream/ahdy12jdco1" />);
setPopup(
<SharePopup link={`${window.location.origin}/sessions/${session?.id}`} />
);
}
function handleClickOpenSettingsModal() {
+1 -1
View File
@@ -165,7 +165,7 @@ function SessionPage() {
session.mode === "stream" &&
session.server?.localIp &&
session.playerPort && (
<div className="absolute w-full h-full aspect-video bg-green-950">
<div className="absolute w-full h-full aspect-video">
<PixelStreamingWrapper
initialSettings={{
ss: `ws://${session.server.localIp}:${session.playerPort}`,