Files
stream.graff.tech-new/client/src/components/SessionUsersPanel.tsx
T

167 lines
5.8 KiB
TypeScript

import { useMemo } from "react";
import UserCamera from "./ui/UserCamera";
import UserDevicesControls from "./ui/UserDevicesControls";
import DraggableContainer from "./DraggableContainer";
import { useWebRTC } from "../hooks/useWebRTC";
import clsx from "clsx";
interface SessionUsersPanelProps {
roomId: string;
autoJoin?: boolean;
mode?: "full" | "mini";
}
function SessionUsersPanel({
roomId,
autoJoin = false,
mode = "full",
}: SessionUsersPanelProps) {
const {
localStream,
participants,
isAudioMuted: isLocalAudioMuted,
isVideoMuted: isLocalVideoMuted,
toggleAudio,
toggleVideo,
updateSpeakingState,
} = useWebRTC(roomId, autoJoin);
const hasLocalStream = localStream !== null;
// Callback для отправки состояния speaking
const handleSpeakingChange = (isSpeaking: boolean) => {
updateSpeakingState?.(isSpeaking);
};
// Вычисляем количество камер для grid - мемоизируем для избежания лишних вычислений
const activeCamerasCount = useMemo(
() =>
(localStream ? 1 : 0) +
participants.filter(
(p) => p.stream != null && p.stream.getTracks().length > 0
).length,
[localStream, participants]
);
// Определяем количество колонок в зависимости от количества камер
// 1-2 камеры: 1 колонка (друг под другом), 3-4: 2 колонки, 5-9: 3 колонки, 10-16: 4 колонки, 17-25: 5 колонок
const gridColumns = useMemo(() => {
if (activeCamerasCount <= 2) return 1;
if (activeCamerasCount <= 4) return 2;
if (activeCamerasCount <= 9) return 3;
if (activeCamerasCount <= 16) return 4;
return 5;
}, [activeCamerasCount]);
// Вычисляем количество рядов для правильного расчета высоты
const gridRows = useMemo(
() => Math.ceil(activeCamerasCount / gridColumns),
[activeCamerasCount, gridColumns]
);
// Фильтруем участников с активными потоками - мемоизируем для избежания лишних фильтраций
const activeParticipants = useMemo(
() =>
participants.filter(
(participant) =>
participant.stream != null &&
participant.stream.getTracks().length > 0
),
[participants]
);
// Рендерим камеры
const camerasContent = (
<>
{/* Локальная камера пользователя - показываем только если есть разрешение */}
{localStream && (
<UserCamera
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}
/>
)}
{/* Камеры удаленных участников - показываем только если есть поток с активными треками */}
{activeParticipants.map((participant) => (
<UserCamera
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}`)
}
/>
))}
<UserDevicesControls
mode={mode}
toggleAudio={toggleAudio}
toggleVideo={toggleVideo}
isAudioMuted={isLocalAudioMuted}
isVideoMuted={isLocalVideoMuted}
hasLocalStream={hasLocalStream}
/>
</>
);
// Для режима full используем DraggableContainer
if (mode === "full") {
return (
<DraggableContainer
enableSnapping={true}
enabled={true}
autoAlign={true}
initialCorner={innerWidth >= 640 ? "bottom-right" : "top-right"}
padding="1.111vw"
className="z-[999] flex gap-4"
>
{camerasContent}
</DraggableContainer>
);
}
// Для режима mini используем flex-обертку для центрирования и внутри grid
return (
<div className="flex justify-center items-center w-full h-full z-[99]a">
<div
className={clsx(
"grid 2xl:gap-[0.556vw] gap-2",
gridColumns === 1 && "grid-cols-1 2xl:w-[45vw] w-[calc(50vw-1rem)]",
gridColumns === 2 && "grid-cols-2 2xl:w-[90vw] w-[calc(100vw-2rem)]",
gridColumns === 3 && "grid-cols-3 2xl:w-[90vw] w-[calc(100vw-2rem)]",
gridColumns === 4 && "grid-cols-4 2xl:w-[90vw] w-[calc(100vw-2rem)]",
gridColumns === 5 && "grid-cols-5 2xl:w-[90vw] w-[calc(100vw-2rem)]",
gridRows === 1 && "auto-rows-[calc((86vh-1.111vw)/1)]",
gridRows === 2 && "auto-rows-[calc((86vh-1.667vw)/2)]",
gridRows === 3 && "auto-rows-[calc((86vh-2.222vw)/3)]",
gridRows === 4 && "auto-rows-[calc((86vh-2.778vw)/4)]",
gridRows === 5 && "auto-rows-[calc((86vh-3.333vw)/5)]"
)}
>
{camerasContent}
</div>
</div>
);
}
export default SessionUsersPanel;