diff --git a/client/public/img/UserCameraAvatar.png b/client/public/img/UserCameraAvatar.png new file mode 100644 index 0000000..9a2dabc Binary files /dev/null and b/client/public/img/UserCameraAvatar.png differ diff --git a/client/src/components/SessionUsersPanel.tsx b/client/src/components/SessionUsersPanel.tsx index ec2ec9e..10fa2e4 100644 --- a/client/src/components/SessionUsersPanel.tsx +++ b/client/src/components/SessionUsersPanel.tsx @@ -4,6 +4,9 @@ import DraggableContainer from "./DraggableContainer"; import { useWebRTC } from "../hooks/useWebRTC"; import clsx from "clsx"; import { useEffect, useRef, useState } from "react"; +import type { Session } from "../types/Session"; +import { getGuestId } from "../lib/guestId"; +import { useMe } from "../hooks/useAuth"; const LOCAL_CAMERAS_COUNT = 1; @@ -11,12 +14,14 @@ interface SessionUsersPanelProps { roomId: string; autoJoin?: boolean; mode?: "full" | "mini"; + session?: Session; } function SessionUsersPanel({ roomId, autoJoin = false, mode = "full", + session, }: SessionUsersPanelProps) { const { localStream, @@ -29,6 +34,22 @@ function SessionUsersPanel({ } = useWebRTC(roomId, autoJoin); const hasLocalStream = localStream !== null; + const { data: user } = useMe(); + + // Определяем, является ли локальный пользователь организатором сессии + const isLocalUserOrganizer = session + ? !!(session.userId && user?.id === session.userId) || + !!(session.guestId && getGuestId() === session.guestId) + : false; + + // Функция для определения, является ли конкретный участник организатором + const isParticipantOrganizer = (participantId: string) => { + if (!session) return false; + return ( + (session.userId && participantId === session.userId) || + (session.guestId && participantId === session.guestId) + ); + }; // Логируем изменения hasLocalStream для отладки useEffect(() => { @@ -180,8 +201,8 @@ function SessionUsersPanel({ name="Вы" isMuted={isLocalAudioMuted} isVideoOff={isLocalVideoMuted} - isControlDisabled={false} - isAdmin={true} + hasControl={false} + isAdmin={isLocalUserOrganizer} isLocal={true} mediaStream={localStream} onMute={toggleAudio} @@ -214,8 +235,8 @@ function SessionUsersPanel({ isMuted={participant.isMuted || false} isVideoOff={participant.isVideoOff || false} isSpeaking={participant.isSpeaking} - isControlDisabled={true} - isAdmin={false} + hasControl={false} + isAdmin={isParticipantOrganizer(participant.id) || undefined} mediaStream={participant.stream} hasLocalMediaPermission={hasLocalStream} onMute={() => console.log(`Mute user ${participant.id}`)} diff --git a/client/src/components/popups/ParticipantsPopup.tsx b/client/src/components/popups/ParticipantsPopup.tsx index cc862c7..4297afe 100644 --- a/client/src/components/popups/ParticipantsPopup.tsx +++ b/client/src/components/popups/ParticipantsPopup.tsx @@ -1,6 +1,5 @@ import PopupWrapper from "../PopupWrapper"; import ActionsPopover from "../ui/ActionsPopover"; -import MicrophoneFilledIcon from "../icons/MicrophoneFilledIcon"; import VideoOffFilledIcon from "../icons/VideoOffFilledIcon"; import HandRaisedFilledIcon from "../icons/HandRaisedFilledIcon"; import XMarkFilledIcon from "../icons/XMarkFilledIcon"; @@ -160,19 +159,32 @@ function ParticipantItem({ // Логируем каждый рендер console.log( - `[ParticipantItem RENDER] ${isLocal ? "Local" : "Remote"} ${ - participant.id.slice(0, 8) - } - isMuted=${isMuted}, isVideoOff=${isVideoOff}, hasStream=${!!participant.stream}` + `[ParticipantItem RENDER] ${ + isLocal ? "Local" : "Remote" + } ${participant.id.slice( + 0, + 8 + )} - isMuted=${isMuted}, isVideoOff=${isVideoOff}, hasStream=${!!participant.stream}` ); // Логируем состояние участника для отладки useEffect(() => { console.log( - `[ParticipantItem] ${isLocal ? "Local" : "Remote"} ${ - participant.id.slice(0, 8) - } - isMuted: ${isMuted}, isVideoOff: ${isVideoOff}, participant.isMuted: ${participant.isMuted}, participant.isVideoOff: ${participant.isVideoOff}` + `[ParticipantItem] ${isLocal ? "Local" : "Remote"} ${participant.id.slice( + 0, + 8 + )} - isMuted: ${isMuted}, isVideoOff: ${isVideoOff}, participant.isMuted: ${ + participant.isMuted + }, participant.isVideoOff: ${participant.isVideoOff}` ); - }, [isMuted, isVideoOff, isLocal, participant.id, participant.isMuted, participant.isVideoOff]); + }, [ + isMuted, + isVideoOff, + isLocal, + participant.id, + participant.isMuted, + participant.isVideoOff, + ]); // Определяем, является ли этот конкретный участник организатором сессии const isThisParticipantOrganizer = @@ -220,7 +232,7 @@ function ParticipantItem({ , + icon: , label: "Выключить микрофон", onClick: () => { console.log("Mute participant:", participant.id); diff --git a/client/src/components/ui/UserCamera.tsx b/client/src/components/ui/UserCamera.tsx index a44e736..571efe0 100644 --- a/client/src/components/ui/UserCamera.tsx +++ b/client/src/components/ui/UserCamera.tsx @@ -1,11 +1,8 @@ import { useEffect, useRef, useState } from "react"; -import HandRaisedOffFilledIcon from "../icons/HandRaisedOffFilledIcon"; import HandRaisedFilledIcon from "../icons/HandRaisedFilledIcon"; import MicrophoneFilledIcon from "../icons/MicrophoneFilledIcon"; import VideoOffFilledIcon from "../icons/VideoOffFilledIcon"; -import MicrophoneOffIcon from "../icons/MicrophoneOffIcon"; import VideoFilledIcon from "../icons/VideoFilledIcon"; -import ControlButton from "./ControlButton"; import Admin from "../indicators/Admin"; import clsx from "clsx"; import VolumeIcon from "../icons/VolumeIcon"; @@ -16,17 +13,13 @@ import MicrophoneOffFilledIcon from "../icons/MicrophoneOffFilledIcon"; interface UserCameraControlsProps { isMuted: boolean; isVideoOff: boolean; - isControlDisabled: boolean; - isAdmin: boolean; - onMute: () => void; - onVideoOff: () => void; - onCanControl: () => void; + hasControl: boolean; } interface UserCameraProps { isMuted: boolean; isVideoOff: boolean; - isControlDisabled: boolean; + hasControl?: boolean; onMute: () => void; onVideoOff: () => void; onCanControl: () => void; @@ -44,10 +37,10 @@ interface UserCameraProps { export default function UserCamera({ isMuted, isVideoOff, - isControlDisabled, - onMute, - onVideoOff, - onCanControl, + hasControl = false, + // onMute, + // onVideoOff, + // onCanControl, isAdmin = false, name = "Гость", mediaStream = null, @@ -299,11 +292,11 @@ export default function UserCamera({ return (
- {isAdmin && mode === "mini" && ( - - )} - - {mode === "mini" && ( -
- {name} -
- )} - - {/* Заглушка когда нет видео */} - {(!mediaStream || isVideoOff) && ( - //
- //
- //
- // - //
- // Нет видео - //
- //
-
-
- ВД -
-
- )} -