Add UserCameraAvatar image and enhance SessionUsersPanel with session management features
- Introduced UserCameraAvatar.png for user camera display. - Updated SessionUsersPanel to accept a session prop, allowing for identification of local user as session organizer. - Implemented logic to determine if participants are organizers based on session data. - Adjusted admin control logic in UserCamera and UserCameraControls for better role management. - Improved logging and conditional rendering in ParticipantItem and UserCamera components for enhanced debugging and user experience.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 670 KiB |
@@ -4,6 +4,9 @@ import DraggableContainer from "./DraggableContainer";
|
|||||||
import { useWebRTC } from "../hooks/useWebRTC";
|
import { useWebRTC } from "../hooks/useWebRTC";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useEffect, useRef, useState } from "react";
|
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;
|
const LOCAL_CAMERAS_COUNT = 1;
|
||||||
|
|
||||||
@@ -11,12 +14,14 @@ interface SessionUsersPanelProps {
|
|||||||
roomId: string;
|
roomId: string;
|
||||||
autoJoin?: boolean;
|
autoJoin?: boolean;
|
||||||
mode?: "full" | "mini";
|
mode?: "full" | "mini";
|
||||||
|
session?: Session;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SessionUsersPanel({
|
function SessionUsersPanel({
|
||||||
roomId,
|
roomId,
|
||||||
autoJoin = false,
|
autoJoin = false,
|
||||||
mode = "full",
|
mode = "full",
|
||||||
|
session,
|
||||||
}: SessionUsersPanelProps) {
|
}: SessionUsersPanelProps) {
|
||||||
const {
|
const {
|
||||||
localStream,
|
localStream,
|
||||||
@@ -29,6 +34,22 @@ function SessionUsersPanel({
|
|||||||
} = useWebRTC(roomId, autoJoin);
|
} = useWebRTC(roomId, autoJoin);
|
||||||
|
|
||||||
const hasLocalStream = localStream !== null;
|
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 для отладки
|
// Логируем изменения hasLocalStream для отладки
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -180,8 +201,8 @@ function SessionUsersPanel({
|
|||||||
name="Вы"
|
name="Вы"
|
||||||
isMuted={isLocalAudioMuted}
|
isMuted={isLocalAudioMuted}
|
||||||
isVideoOff={isLocalVideoMuted}
|
isVideoOff={isLocalVideoMuted}
|
||||||
isControlDisabled={false}
|
hasControl={false}
|
||||||
isAdmin={true}
|
isAdmin={isLocalUserOrganizer}
|
||||||
isLocal={true}
|
isLocal={true}
|
||||||
mediaStream={localStream}
|
mediaStream={localStream}
|
||||||
onMute={toggleAudio}
|
onMute={toggleAudio}
|
||||||
@@ -214,8 +235,8 @@ function SessionUsersPanel({
|
|||||||
isMuted={participant.isMuted || false}
|
isMuted={participant.isMuted || false}
|
||||||
isVideoOff={participant.isVideoOff || false}
|
isVideoOff={participant.isVideoOff || false}
|
||||||
isSpeaking={participant.isSpeaking}
|
isSpeaking={participant.isSpeaking}
|
||||||
isControlDisabled={true}
|
hasControl={false}
|
||||||
isAdmin={false}
|
isAdmin={isParticipantOrganizer(participant.id) || undefined}
|
||||||
mediaStream={participant.stream}
|
mediaStream={participant.stream}
|
||||||
hasLocalMediaPermission={hasLocalStream}
|
hasLocalMediaPermission={hasLocalStream}
|
||||||
onMute={() => console.log(`Mute user ${participant.id}`)}
|
onMute={() => console.log(`Mute user ${participant.id}`)}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import PopupWrapper from "../PopupWrapper";
|
import PopupWrapper from "../PopupWrapper";
|
||||||
import ActionsPopover from "../ui/ActionsPopover";
|
import ActionsPopover from "../ui/ActionsPopover";
|
||||||
import MicrophoneFilledIcon from "../icons/MicrophoneFilledIcon";
|
|
||||||
import VideoOffFilledIcon from "../icons/VideoOffFilledIcon";
|
import VideoOffFilledIcon from "../icons/VideoOffFilledIcon";
|
||||||
import HandRaisedFilledIcon from "../icons/HandRaisedFilledIcon";
|
import HandRaisedFilledIcon from "../icons/HandRaisedFilledIcon";
|
||||||
import XMarkFilledIcon from "../icons/XMarkFilledIcon";
|
import XMarkFilledIcon from "../icons/XMarkFilledIcon";
|
||||||
@@ -160,19 +159,32 @@ function ParticipantItem({
|
|||||||
|
|
||||||
// Логируем каждый рендер
|
// Логируем каждый рендер
|
||||||
console.log(
|
console.log(
|
||||||
`[ParticipantItem RENDER] ${isLocal ? "Local" : "Remote"} ${
|
`[ParticipantItem RENDER] ${
|
||||||
participant.id.slice(0, 8)
|
isLocal ? "Local" : "Remote"
|
||||||
} - isMuted=${isMuted}, isVideoOff=${isVideoOff}, hasStream=${!!participant.stream}`
|
} ${participant.id.slice(
|
||||||
|
0,
|
||||||
|
8
|
||||||
|
)} - isMuted=${isMuted}, isVideoOff=${isVideoOff}, hasStream=${!!participant.stream}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Логируем состояние участника для отладки
|
// Логируем состояние участника для отладки
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(
|
console.log(
|
||||||
`[ParticipantItem] ${isLocal ? "Local" : "Remote"} ${
|
`[ParticipantItem] ${isLocal ? "Local" : "Remote"} ${participant.id.slice(
|
||||||
participant.id.slice(0, 8)
|
0,
|
||||||
} - isMuted: ${isMuted}, isVideoOff: ${isVideoOff}, participant.isMuted: ${participant.isMuted}, participant.isVideoOff: ${participant.isVideoOff}`
|
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 =
|
const isThisParticipantOrganizer =
|
||||||
@@ -220,7 +232,7 @@ function ParticipantItem({
|
|||||||
<ActionsPopover
|
<ActionsPopover
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
icon: <MicrophoneFilledIcon />,
|
icon: <MicrophoneOffFilledIcon />,
|
||||||
label: "Выключить микрофон",
|
label: "Выключить микрофон",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
console.log("Mute participant:", participant.id);
|
console.log("Mute participant:", participant.id);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import HandRaisedOffFilledIcon from "../icons/HandRaisedOffFilledIcon";
|
|
||||||
import HandRaisedFilledIcon from "../icons/HandRaisedFilledIcon";
|
import HandRaisedFilledIcon from "../icons/HandRaisedFilledIcon";
|
||||||
import MicrophoneFilledIcon from "../icons/MicrophoneFilledIcon";
|
import MicrophoneFilledIcon from "../icons/MicrophoneFilledIcon";
|
||||||
import VideoOffFilledIcon from "../icons/VideoOffFilledIcon";
|
import VideoOffFilledIcon from "../icons/VideoOffFilledIcon";
|
||||||
import MicrophoneOffIcon from "../icons/MicrophoneOffIcon";
|
|
||||||
import VideoFilledIcon from "../icons/VideoFilledIcon";
|
import VideoFilledIcon from "../icons/VideoFilledIcon";
|
||||||
import ControlButton from "./ControlButton";
|
|
||||||
import Admin from "../indicators/Admin";
|
import Admin from "../indicators/Admin";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import VolumeIcon from "../icons/VolumeIcon";
|
import VolumeIcon from "../icons/VolumeIcon";
|
||||||
@@ -16,17 +13,13 @@ import MicrophoneOffFilledIcon from "../icons/MicrophoneOffFilledIcon";
|
|||||||
interface UserCameraControlsProps {
|
interface UserCameraControlsProps {
|
||||||
isMuted: boolean;
|
isMuted: boolean;
|
||||||
isVideoOff: boolean;
|
isVideoOff: boolean;
|
||||||
isControlDisabled: boolean;
|
hasControl: boolean;
|
||||||
isAdmin: boolean;
|
|
||||||
onMute: () => void;
|
|
||||||
onVideoOff: () => void;
|
|
||||||
onCanControl: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserCameraProps {
|
interface UserCameraProps {
|
||||||
isMuted: boolean;
|
isMuted: boolean;
|
||||||
isVideoOff: boolean;
|
isVideoOff: boolean;
|
||||||
isControlDisabled: boolean;
|
hasControl?: boolean;
|
||||||
onMute: () => void;
|
onMute: () => void;
|
||||||
onVideoOff: () => void;
|
onVideoOff: () => void;
|
||||||
onCanControl: () => void;
|
onCanControl: () => void;
|
||||||
@@ -44,10 +37,10 @@ interface UserCameraProps {
|
|||||||
export default function UserCamera({
|
export default function UserCamera({
|
||||||
isMuted,
|
isMuted,
|
||||||
isVideoOff,
|
isVideoOff,
|
||||||
isControlDisabled,
|
hasControl = false,
|
||||||
onMute,
|
// onMute,
|
||||||
onVideoOff,
|
// onVideoOff,
|
||||||
onCanControl,
|
// onCanControl,
|
||||||
isAdmin = false,
|
isAdmin = false,
|
||||||
name = "Гость",
|
name = "Гость",
|
||||||
mediaStream = null,
|
mediaStream = null,
|
||||||
@@ -299,11 +292,11 @@ export default function UserCamera({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
"select-none group relative pointer-events-auto",
|
||||||
mode === "mini"
|
mode === "mini"
|
||||||
? "aspect-square h-fit group relative flex-shrink-0 pointer-events-auto 2xl:hover:w-[10.833vw] 2xl:w-[6.944vw] sm:w-[15.625vw] w-[27.778vw]"
|
? "aspect-square h-fit flex-shrink-0 2xl:hover:w-[10.833vw] 2xl:w-[6.944vw] sm:w-[15.625vw] w-[27.778vw]"
|
||||||
: "aspect-video group relative pointer-events-auto max-w-full 2xl:h-full object-cover",
|
: "aspect-video max-w-full h-full",
|
||||||
isLocal && "order-last",
|
isLocal && "order-last",
|
||||||
// isVideoOff ? "bg-green-500" : "bg-yellow-500/10",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
@@ -321,39 +314,12 @@ export default function UserCamera({
|
|||||||
}}
|
}}
|
||||||
onClick={handleVideoClick}
|
onClick={handleVideoClick}
|
||||||
>
|
>
|
||||||
{isAdmin && mode === "mini" && (
|
|
||||||
<Admin className="absolute top-0 right-0 z-10" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{mode === "mini" && (
|
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Заглушка когда нет видео */}
|
|
||||||
{(!mediaStream || isVideoOff) && (
|
|
||||||
// <div className="flex absolute inset-0 justify-center items-center bg-gradient-to-br from-gray-700 to-gray-900">
|
|
||||||
// <div className="flex flex-col gap-2 items-center text-white/60">
|
|
||||||
// <div className="2xl:size-[2.778vw] size-10">
|
|
||||||
// <VideoOffFilledIcon />
|
|
||||||
// </div>
|
|
||||||
// <span className="text-xs">Нет видео</span>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
<div className="2xl:rounded-[1.667vw] rounded-2xl absolute inset-0 bg-white/15 flex items-start justify-center">
|
|
||||||
<div className="rounded-full 2xl:w-1/3 aspect-square bg-[#7B60F3] mt-12 flex items-center justify-center text-6xl font-medium text-white">
|
|
||||||
ВД
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<video
|
<video
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"object-cover size-full 2xl:rounded-[1.667vw] rounded-2xl",
|
"object-cover size-full 2xl:rounded-[1.667vw] rounded-2xl",
|
||||||
isLocal && "scale-x-[-1]",
|
isLocal && "scale-x-[-1]"
|
||||||
(!mediaStream || isVideoOff) && "hidden"
|
// (!mediaStream || isVideoOff) && ""
|
||||||
)}
|
)}
|
||||||
autoPlay
|
autoPlay
|
||||||
muted={isLocal ? true : isAudioMuted}
|
muted={isLocal ? true : isAudioMuted}
|
||||||
@@ -418,6 +384,20 @@ export default function UserCamera({
|
|||||||
handleVideoClick();
|
handleVideoClick();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{isAdmin && mode === "mini" && (
|
||||||
|
<Admin className="absolute top-0 right-0 z-10" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Заглушка когда нет видео */}
|
||||||
|
{(!mediaStream || isVideoOff) && (
|
||||||
|
<div className="select-none pointer-events-none absolute inset-0 bg-[url(/img/UserCameraAvatar.png)] bg-cover bg-center 2xl:rounded-[1.667vw] rounded-2xl" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{mode === "mini" && (
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Кнопка управления звуком для удаленных участников */}
|
{/* Кнопка управления звуком для удаленных участников */}
|
||||||
{!isLocal && mediaStream && !isVideoOff && (
|
{!isLocal && mediaStream && !isVideoOff && (
|
||||||
@@ -440,27 +420,18 @@ export default function UserCamera({
|
|||||||
{mode === "full" && (
|
{mode === "full" && (
|
||||||
<div className="2xl:px-[1.111vw] 2xl:py-[0.556vw] px-4 py-2 bg-[#141414]/25 backdrop-blur-[10px] 2xl:rounded-[1.111vw] rounded-2xl absolute 2xl:bottom-[1.111vw] bottom-4 left-1/2 -translate-x-1/2 z-10 flex 2xl:gap-[0.556vw] gap-2 items-center">
|
<div className="2xl:px-[1.111vw] 2xl:py-[0.556vw] px-4 py-2 bg-[#141414]/25 backdrop-blur-[10px] 2xl:rounded-[1.111vw] rounded-2xl absolute 2xl:bottom-[1.111vw] bottom-4 left-1/2 -translate-x-1/2 z-10 flex 2xl:gap-[0.556vw] gap-2 items-center">
|
||||||
<p className="font-medium text-white button-m">{name}</p>
|
<p className="font-medium text-white button-m">{name}</p>
|
||||||
{isMuted && (
|
|
||||||
<div className="2xl:size-[1.111vw] size-4 text-white/50">
|
|
||||||
<MicrophoneOffFilledIcon />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<Admin className="2xl:size-[1.111vw] size-4 absolute 2xl:-top-[0.278vw] 2xl:-right-[0.278vw] -right-1 -top-1" />
|
<Admin className="2xl:size-[1.111vw] size-4 absolute 2xl:-top-[0.278vw] 2xl:-right-[0.278vw] -right-1 -top-1" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Элементы управления только для удаленных участников */}
|
{/* Индикаторы состояния только для удаленных участников */}
|
||||||
{!isLocal && mode === "mini" && (
|
{!isLocal && mode === "mini" && (
|
||||||
<UserCameraControls
|
<UserCameraControls
|
||||||
isMuted={isMuted}
|
isMuted={isMuted}
|
||||||
isVideoOff={isVideoOff}
|
isVideoOff={isVideoOff}
|
||||||
isControlDisabled={isControlDisabled}
|
hasControl={hasControl}
|
||||||
isAdmin={isAdmin || false}
|
|
||||||
onMute={onMute}
|
|
||||||
onVideoOff={onVideoOff}
|
|
||||||
onCanControl={onCanControl}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -470,58 +441,33 @@ export default function UserCamera({
|
|||||||
function UserCameraControls({
|
function UserCameraControls({
|
||||||
isMuted,
|
isMuted,
|
||||||
isVideoOff,
|
isVideoOff,
|
||||||
isControlDisabled,
|
hasControl,
|
||||||
isAdmin,
|
|
||||||
onMute,
|
|
||||||
onVideoOff,
|
|
||||||
onCanControl,
|
|
||||||
}: UserCameraControlsProps) {
|
}: UserCameraControlsProps) {
|
||||||
return (
|
return (
|
||||||
<div className="absolute transition-[bottom] duration-300 2xl:bottom-[0.278vw] 2xl:group-hover:bottom-[0.556vw] group-hover:bottom-2 bottom-1 left-1/2 -translate-x-1/2">
|
<div
|
||||||
{/* Индикатор muted - показывается всегда */}
|
className="select-none absolute transition-opacity 2xl:bottom-[0.278vw] bottom-1 left-1/2 -translate-x-1/2 flex 2xl:gap-[0.278vw] gap-1 2xl:mb-[0.278vw] mb-1 group-hover:opacity-100 opacity-0"
|
||||||
<div
|
// onMouseDown={(e) => e.stopPropagation()}
|
||||||
className={clsx(
|
>
|
||||||
"2xl:size-[1.667vw] size-6 bg-[#14141426] backdrop-blur-[4px] transition-opacity duration-300 rounded-full flex items-center justify-center z-10a absolute left-1/2 -translate-x-1/2 2xl:bottom-0 [0.278vw] group-hover:opacity-0",
|
{/* Индикатор микрофона */}
|
||||||
isMuted ? "opacity-100" : "opacity-0"
|
<div className="2xl:size-[1.667vw] size-6 bg-[#14141426] backdrop-blur-[4px] rounded-full flex items-center justify-center">
|
||||||
)}
|
<div className="2xl:size-[0.972vw] size-3.5 text-white">
|
||||||
>
|
{isMuted ? <MicrophoneOffFilledIcon /> : <MicrophoneFilledIcon />}
|
||||||
<div className="size-[0.972vw] text-white">
|
|
||||||
<MicrophoneOffIcon />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Кнопки управления - только для администраторов */}
|
{/* Индикатор видео */}
|
||||||
{isAdmin && (
|
<div className="2xl:size-[1.667vw] size-6 bg-[#14141426] backdrop-blur-[4px] rounded-full flex items-center justify-center">
|
||||||
<div
|
<div className="2xl:size-[0.972vw] size-3.5 text-white">
|
||||||
className="flex gap-[0.278vw] mb-[0.278vw] group-hover:opacity-100 opacity-0 transition-opacity duration-300"
|
{isVideoOff ? <VideoOffFilledIcon /> : <VideoFilledIcon />}
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
</div>
|
||||||
>
|
</div>
|
||||||
<ControlButton
|
|
||||||
icon={
|
{/* Индикатор управления - показывается только если hasControl = true */}
|
||||||
isMuted ? <MicrophoneOffFilledIcon /> : <MicrophoneFilledIcon />
|
{hasControl && (
|
||||||
}
|
<div className="2xl:size-[1.667vw] size-6 bg-[#14141426] backdrop-blur-[4px] rounded-full flex items-center justify-center">
|
||||||
size={"small"}
|
<div className="2xl:size-[0.972vw] size-3.5 text-white">
|
||||||
disabled={false}
|
<HandRaisedFilledIcon />
|
||||||
onClick={onMute}
|
</div>
|
||||||
/>
|
|
||||||
<ControlButton
|
|
||||||
icon={isVideoOff ? <VideoOffFilledIcon /> : <VideoFilledIcon />}
|
|
||||||
size={"small"}
|
|
||||||
disabled={false}
|
|
||||||
onClick={onVideoOff}
|
|
||||||
/>
|
|
||||||
<ControlButton
|
|
||||||
icon={
|
|
||||||
isControlDisabled ? (
|
|
||||||
<HandRaisedOffFilledIcon />
|
|
||||||
) : (
|
|
||||||
<HandRaisedFilledIcon />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
size={"small"}
|
|
||||||
disabled={false}
|
|
||||||
onClick={onCanControl}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -298,7 +298,12 @@ function SessionPage() {
|
|||||||
</ActionsSidebarWrapper>
|
</ActionsSidebarWrapper>
|
||||||
|
|
||||||
{/* WebRTC видеочат - работает всегда, пока пользователь на странице */}
|
{/* WebRTC видеочат - работает всегда, пока пользователь на странице */}
|
||||||
<SessionUsersPanel roomId={session.id} autoJoin={true} mode={mode} />
|
<SessionUsersPanel
|
||||||
|
roomId={session.id}
|
||||||
|
autoJoin={true}
|
||||||
|
mode={mode}
|
||||||
|
session={session}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user