Enhance DraggableContainer to set initial position based on bounding rectangle and update SessionUsersPanel to limit local cameras to one. Refactor UserCamera styling for consistency and improve PopoverWrapper positioning logic. Clean up unnecessary code in SessionUsersPanel for better readability.

This commit is contained in:
2025-11-01 13:27:42 +05:00
parent 871fb10cc4
commit 0964d650a4
4 changed files with 82 additions and 92 deletions
+10 -1
View File
@@ -446,6 +446,15 @@ export default function DraggableContainer({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDragging]);
useEffect(() => {
if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
setPosition({
top: rect.top,
left: rect.left,
});
}, []);
// Устанавливаем cursor стили на элемент-хэндл
useEffect(() => {
if (!draggable || !dragHandleRef?.current) return;
@@ -496,7 +505,7 @@ export default function DraggableContainer({
// Если компонент отключен, просто рендерим children без стилей и логики
if (!enabled) {
return <>{children}</>;
return <div className={className}>{children}</div>;
}
return (
+68 -83
View File
@@ -5,7 +5,7 @@ import { useWebRTC } from "../hooks/useWebRTC";
import clsx from "clsx";
import { useEffect, useRef, useState } from "react";
const LOCAL_CAMERAS_COUNT = 10;
const LOCAL_CAMERAS_COUNT = 1;
interface SessionUsersPanelProps {
roomId: string;
@@ -157,91 +157,76 @@ function SessionUsersPanel({
windowDimensions.width >= 640 ? "bottom-right" : "top-right"
}
padding="1.111vw"
// className={clsx(
// "z-[100] 2xl:gap-[0.556vw] gap-2",
// mode === "mini"
// ? "flex"
// : `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} relative bg-black`
// )}
className={clsx(
"z-[100] 2xl:gap-[0.556vw] gap-2",
mode === "mini"
? "flex"
: `2xl:p-[2.778vw_5vw_5vw] p-[12px_12px_72px] w-full 2xl:h-dvh grid grid-cols-${gridColumns} relative 2xl:bg-black auto-rows-fr`
)}
>
<div
className={clsx(
"z-[100] 2xl:gap-[0.556vw] gap-2",
mode === "mini"
? "flex"
: `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} relative bg-black`
)}
>
{localStream &&
Array.from({ length: LOCAL_CAMERAS_COUNT }).map((_, index) => (
<UserCamera
key={index}
mode={mode}
name="Вы"
isMuted={isLocalAudioMuted}
isVideoOff={isLocalVideoMuted}
isControlDisabled={false}
isAdmin={true}
isLocal={true}
mediaStream={localStream}
onMute={toggleAudio}
onVideoOff={toggleVideo}
onCanControl={() => console.log("Toggle control")}
onSpeakingChange={handleSpeakingChange}
className={clsx(
mode === "full" && activeCamerasCount <= 2
? "m-auto"
: activeCamerasCount > 12
? "!aspect-square w-full"
: "w-full"
)}
/>
))}
{mode === "full" && <div className="fixed bg-black inset-0 2xl:hidden" />}
{localStream &&
Array.from({ length: LOCAL_CAMERAS_COUNT }).map((_, index) => (
<UserCamera
key={index}
mode={mode}
name="Вы"
isMuted={isLocalAudioMuted}
isVideoOff={isLocalVideoMuted}
isControlDisabled={false}
isAdmin={true}
isLocal={true}
mediaStream={localStream}
onMute={toggleAudio}
onVideoOff={toggleVideo}
onCanControl={() => console.log("Toggle control")}
onSpeakingChange={handleSpeakingChange}
className={clsx(
mode === "full" &&
(activeCamerasCount <= 2 ? "m-auto" : "min-w-full min-h-full")
)}
/>
))}
{/* Камеры удаленных участников - показываем только если есть поток с активными треками */}
{participants
.filter(
(participant) =>
participant.stream != null &&
participant.stream.getTracks().length > 0
)
.map((participant) => (
<UserCamera
className={clsx(
mode === "full" &&
(activeCamerasCount <= 2
? "m-auto"
: activeCamerasCount > 12
? "!aspect-square w-full"
: "w-full")
)}
key={participant.id}
mode={mode}
name={participant.id}
isMuted={participant.isMuted || false}
isVideoOff={participant.isVideoOff || false}
isSpeaking={participant.isSpeaking}
isControlDisabled={true}
isAdmin={true}
mediaStream={participant.stream}
hasLocalMediaPermission={hasLocalStream}
onMute={() => console.log(`Mute user ${participant.id}`)}
onVideoOff={() => console.log(`Video off user ${participant.id}`)}
onCanControl={() =>
console.log(`Can control user ${participant.id}`)
}
/>
))}
{/* Камеры удаленных участников - показываем только если есть поток с активными треками */}
{participants
.filter(
(participant) =>
participant.stream != null &&
participant.stream.getTracks().length > 0
)
.map((participant) => (
<UserCamera
className={clsx(
mode === "full" &&
(activeCamerasCount <= 2 ? "m-auto" : "min-w-full min-h-full")
)}
key={participant.id}
mode={mode}
name={participant.name}
isMuted={participant.isMuted || false}
isVideoOff={participant.isVideoOff || false}
isSpeaking={participant.isSpeaking}
isControlDisabled={true}
isAdmin={true}
mediaStream={participant.stream}
hasLocalMediaPermission={hasLocalStream}
onMute={() => console.log(`Mute user ${participant.id}`)}
onVideoOff={() => console.log(`Video off user ${participant.id}`)}
onCanControl={() =>
console.log(`Can control user ${participant.id}`)
}
/>
))}
<UserDevicesControls
mode={mode}
toggleAudio={toggleAudio}
toggleVideo={toggleVideo}
isAudioMuted={isLocalAudioMuted}
isVideoMuted={isLocalVideoMuted}
hasLocalStream={hasLocalStream}
/>
</div>
<UserDevicesControls
mode={mode}
toggleAudio={toggleAudio}
toggleVideo={toggleVideo}
isAudioMuted={isLocalAudioMuted}
isVideoMuted={isLocalVideoMuted}
hasLocalStream={hasLocalStream}
/>
</DraggableContainer>
);
}
+2 -3
View File
@@ -28,6 +28,7 @@ function PopoverWrapper({
(position === "side" || position === "vertical")
) {
const buttonRect = parentElRef.current.getBoundingClientRect();
console.log(buttonRect);
const spaceBelow = window.innerHeight - buttonRect.bottom;
const spaceAbove = buttonRect.top;
setOpenUpwards(spaceBelow < menuHeight && spaceAbove > menuHeight);
@@ -99,9 +100,7 @@ function PopoverWrapper({
}}
style={getPositionStyles()}
className={clsx(
"fixed z-[99999] shadow-[0_4px_40px_0_rgba(15,16,17,0.1)] overflow-hidden bg-white 2xl:rounded-[1.111vw] rounded-2xl",
position === "side" && "-translate-x-full",
position === "vertical" && "-translate-x-full",
"fixed z-[99999] shadow-[0_4px_40px_0_rgba(15,16,17,0.1)] overflow-hidden bg-white 2xl:rounded-[1.111vw] rounded-2xl -translate-x-full",
className
)}
>
+2 -5
View File
@@ -280,7 +280,7 @@ export default function UserCamera({
className={clsx(
mode === "mini"
? "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 pointer-events-auto max-w-full h-full self-center object-cover",
: "aspect-video 2xl:rounded-[1.667vw] rounded-2xl overflow-hidden group relative pointer-events-auto max-w-full 2xl:h-full object-cover",
isLocal && "order-last",
isVideoOff ? "bg-green-500" : "bg-yellow-500/10",
className
@@ -302,10 +302,7 @@ export default function UserCamera({
>
{isLocal && <Admin className="absolute top-0 right-0" />}
<div
key="name"
className="absolute whitespace-nowrap transition-opacity duration-300 group-hover:opacity-100 opacity-0 text-white button-s top-[0.556vw] left-1/2 translate-x-[-50%] px-[0.556vw] py-[0.278vw] rounded-full bg-[#14141440] backdrop-blur-[4px]"
>
<div className="absolute whitespace-nowrap transition-opacity duration-300 group-hover:opacity-100 opacity-0 text-white button-s top-[0.556vw] left-1/2 translate-x-[-50%] px-[0.556vw] py-[0.278vw] rounded-full bg-[#14141440] backdrop-blur-[4px]">
{name}
</div>