diff --git a/client/src/components/PopupContainer.tsx b/client/src/components/PopupContainer.tsx index f40404a..52c1a98 100644 --- a/client/src/components/PopupContainer.tsx +++ b/client/src/components/PopupContainer.tsx @@ -25,6 +25,7 @@ function PopupContainer() { transition={{ bounce: 0, ease: "easeInOut" }} drag={isMobile ? "y" : false} dragConstraints={{ top: 0, bottom: 0 }} + dragElastic={{ top: 0, bottom: 0 }} onDragEnd={handleDragEnd} > {popup} diff --git a/client/src/components/ui/ControlButton.tsx b/client/src/components/ui/ControlButton.tsx index 8b26d4b..597b658 100644 --- a/client/src/components/ui/ControlButton.tsx +++ b/client/src/components/ui/ControlButton.tsx @@ -1,4 +1,5 @@ import clsx from "clsx"; +import Warning from "../indicators/Warning"; interface ControlButtonProps extends React.ButtonHTMLAttributes { @@ -10,18 +11,20 @@ function ControlButton({ size, icon, className, + disabled, onClick, ...props }: ControlButtonProps) { return ( ); } diff --git a/client/src/components/ui/FloatingActionButton.tsx b/client/src/components/ui/FloatingActionButton.tsx index 41f1d15..3da1897 100644 --- a/client/src/components/ui/FloatingActionButton.tsx +++ b/client/src/components/ui/FloatingActionButton.tsx @@ -1,4 +1,5 @@ import clsx from "clsx"; +import Warning from "../indicators/Warning"; interface FloatingActionButtonProps extends React.ButtonHTMLAttributes { @@ -12,6 +13,7 @@ function FloatingActionButton({ variant = "default", className, onClick, + disabled, ref, ...props }: FloatingActionButtonProps) { @@ -19,16 +21,22 @@ function FloatingActionButton({ ); } diff --git a/client/src/components/ui/UserCamera.tsx b/client/src/components/ui/UserCamera.tsx index f107f0f..f62344f 100644 --- a/client/src/components/ui/UserCamera.tsx +++ b/client/src/components/ui/UserCamera.tsx @@ -11,6 +11,7 @@ import clsx from "clsx"; import VolumeIcon from "../icons/VolumeIcon"; import VolumeOffIcon from "../icons/VolumeOffIcon"; import { useVoiceActivity } from "../../hooks/useVoiceActivity"; +import MicrophoneOffFilledIcon from "../icons/MicrophoneOffFilledIcon"; interface UserCameraControlsProps { isMuted: boolean; @@ -267,7 +268,7 @@ export default function UserCamera({ return (
e.stopPropagation()} > : } + icon={ + isMuted ? : + } size={"small"} disabled={false} onClick={onMute} @@ -447,7 +450,7 @@ function UserCameraControls({ ) } size={"small"} - disabled={isControlDisabled} + disabled={false} onClick={onCanControl} />
diff --git a/client/src/components/ui/UserDevicesControls.tsx b/client/src/components/ui/UserDevicesControls.tsx index 59044ec..79dd022 100644 --- a/client/src/components/ui/UserDevicesControls.tsx +++ b/client/src/components/ui/UserDevicesControls.tsx @@ -1,5 +1,4 @@ import MicrophoneFilledIcon from "../icons/MicrophoneFilledIcon"; -import MicrophoneOffIcon from "../icons/MicrophoneOffIcon"; import ControlButton from "./ControlButton"; import VideoFilledIcon from "../icons/VideoFilledIcon"; import VideoOffFilledIcon from "../icons/VideoOffFilledIcon"; @@ -7,6 +6,7 @@ import HandRaisedFilledIcon from "../icons/HandRaisedFilledIcon"; import CogFilledIcon from "../icons/CogFilledIcon"; import useModalStore from "../../store/modalStore"; import SettingsModal from "../modals/SettingsModal"; +import MicrophoneOffFilledIcon from "../icons/MicrophoneOffFilledIcon"; export interface UserDevicesControlsProps { toggleAudio: () => void; @@ -34,14 +34,26 @@ export default function UserDevicesControls({ e.stopPropagation()} size="large" - icon={isAudioMuted ? : } + icon={ + isAudioMuted || !hasLocalStream ? ( + + ) : ( + + ) + } disabled={!hasLocalStream} onClick={toggleAudio} /> e.stopPropagation()} size="large" - icon={isVideoMuted ? : } + icon={ + isVideoMuted || !hasLocalStream ? ( + + ) : ( + + ) + } disabled={!hasLocalStream} onClick={toggleVideo} /> diff --git a/client/src/pages/SessionPage.tsx b/client/src/pages/SessionPage.tsx index 77ac448..a535bb7 100644 --- a/client/src/pages/SessionPage.tsx +++ b/client/src/pages/SessionPage.tsx @@ -23,6 +23,9 @@ import WarningIcon from "../components/icons/WarningIcon"; import Button from "../components/ui/Button"; import LoaderIcon from "../components/icons/LoaderIcon"; import SessionUsersPanel from "../components/SessionUsersPanel"; +import { useWebRTC } from "../hooks/useWebRTC"; +import MicrophoneOffFilledIcon from "../components/icons/MicrophoneOffFilledIcon"; +import VideoFilledIcon from "../components/icons/VideoFilledIcon"; function SessionPage() { const { setPopup } = usePopupStore(); @@ -98,6 +101,9 @@ function SessionPage() { // } // }, [session?.status, navigate]); + const { localStream, toggleAudio, isAudioMuted, toggleVideo, isVideoMuted } = + useWebRTC(session?.id, true); + if (isLoading) { return (
@@ -139,7 +145,7 @@ function SessionPage() { } return ( -
+
{/* Pixel Streaming - показывается только когда сессия активна */} {session.status === "started" && session.mode === "stream" && @@ -205,14 +211,30 @@ function SessionPage() {
- +
- + {!localStream || isAudioMuted ? ( + + ) : ( + + )}
- +
- + {!localStream || isVideoMuted ? ( + + ) : ( + + )}
apps.id), - userId: uuid("user_id") - .references(() => users.id), // для авторизованных пользователей (nullable - если пользователь не авторизован) + userId: uuid("user_id").references(() => users.id), // для авторизованных пользователей (nullable - если пользователь не авторизован) guestId: uuid("guest_id"), // UUID v4 генерируется на клиенте для неавторизованных пользователей (nullable - если пользователь авторизован) startAt: timestamp("start_at", { withTimezone: true }).defaultNow().notNull(), endAt: timestamp("end_at", { withTimezone: true }), // Default 30 minutes from start_at