This commit is contained in:
2025-10-28 16:59:39 +05:00
7 changed files with 70 additions and 17 deletions
+1
View File
@@ -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}
+11 -3
View File
@@ -1,4 +1,5 @@
import clsx from "clsx";
import Warning from "../indicators/Warning";
interface ControlButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
@@ -10,18 +11,20 @@ function ControlButton({
size,
icon,
className,
disabled,
onClick,
...props
}: ControlButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled}
{...props}
className={clsx(
"backdrop-blur-[10px] rounded-full transition-colors cursor-pointer disabled:!cursor-default outline-none disabled:bg-[#FF4517] disabled:hover:bg-[#FF4517]/85",
"backdrop-blur-[10px] rounded-full transition-colors cursor-pointer outline-none disabled:!cursor-default",
size === "large"
? "2xl:p-[0.833vw] p-3 enabled:bg-[#FFFFFF]/15 enabled:hover:bg-[#FFFFFF]/25"
: "2xl:p-[0.417vw] p-[6px] enabled:bg-[#141414]/15 enabled:hover:bg-[#141414]/25",
? "2xl:p-[0.833vw] p-3 bg-[#FFFFFF]/15 hover:bg-[#FFFFFF]/25"
: "2xl:p-[0.417vw] p-[6px] bg-[#141414]/15 hover:bg-[#141414]/25",
className
)}
>
@@ -35,6 +38,11 @@ function ControlButton({
>
{icon}
</div>
{disabled && size === "large" && (
<div className="absolute 2xl:-top-[0.139vw] 2xl:-right-[0.139vw] -top-0.5 -right-0.5">
<Warning type="critical" className="text-white" />
</div>
)}
</button>
);
}
@@ -1,4 +1,5 @@
import clsx from "clsx";
import Warning from "../indicators/Warning";
interface FloatingActionButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
@@ -12,6 +13,7 @@ function FloatingActionButton({
variant = "default",
className,
onClick,
disabled,
ref,
...props
}: FloatingActionButtonProps) {
@@ -19,16 +21,22 @@ function FloatingActionButton({
<button
onClick={onClick}
ref={ref}
disabled={disabled}
className={clsx(
"2xl:p-[0.833vw] p-3 rounded-full transition-all cursor-pointer disabled:!cursor-default outline-none backdrop-blur-[10px]",
variant === "default" &&
"2xl:border-[0.069vw] border border-[#FFFFFF]/15 bg-[#000000]/15 hover:bg-[#000000]/25 hover:border-[#FFFFFF]/25 active:bg-[#7B60F3] active:border-[#FFFFFF]/25",
"2xl:border-[0.069vw] border border-[#FFFFFF]/15 bg-[#000000]/15 hover:bg-[#000000]/25 hover:border-[#FFFFFF]/25 enabled:active:bg-[#7B60F3] enabled:active:border-[#FFFFFF]/25",
variant === "critical" && "bg-[#FF4517] hover:bg-[#FF4517]/85",
className
)}
{...props}
>
{children}
{disabled && (
<div className="absolute 2xl:-top-[0.139vw] 2xl:-right-[0.139vw] -top-0.5 -right-0.5">
<Warning type="critical" className="text-white" />
</div>
)}
</button>
);
}
+6 -3
View File
@@ -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 (
<div
className={clsx(
"aspect-square h-fit group 2xl:rounded-[1.667vw] rounded-2xl relative flex-shrink-0 pointer-events-auto hover:w-[10.833vw] w-[6.944vw] overflow-hidden",
"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",
isLocal && "order-last",
isVideoOff ? "bg-green-500" : "bg-yellow-500/10"
)}
@@ -427,7 +428,9 @@ function UserCameraControls({
onMouseDown={(e) => e.stopPropagation()}
>
<ControlButton
icon={isMuted ? <MicrophoneOffIcon /> : <MicrophoneFilledIcon />}
icon={
isMuted ? <MicrophoneOffFilledIcon /> : <MicrophoneFilledIcon />
}
size={"small"}
disabled={false}
onClick={onMute}
@@ -447,7 +450,7 @@ function UserCameraControls({
)
}
size={"small"}
disabled={isControlDisabled}
disabled={false}
onClick={onCanControl}
/>
</div>
@@ -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({
<ControlButton
onMouseDown={(e) => e.stopPropagation()}
size="large"
icon={isAudioMuted ? <MicrophoneOffIcon /> : <MicrophoneFilledIcon />}
icon={
isAudioMuted || !hasLocalStream ? (
<MicrophoneOffFilledIcon />
) : (
<MicrophoneFilledIcon />
)
}
disabled={!hasLocalStream}
onClick={toggleAudio}
/>
<ControlButton
onMouseDown={(e) => e.stopPropagation()}
size="large"
icon={isVideoMuted ? <VideoOffFilledIcon /> : <VideoFilledIcon />}
icon={
isVideoMuted || !hasLocalStream ? (
<VideoOffFilledIcon />
) : (
<VideoFilledIcon />
)
}
disabled={!hasLocalStream}
onClick={toggleVideo}
/>
+27 -5
View File
@@ -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 (
<div className="flex justify-center items-center min-h-screen bg-gray-50">
@@ -139,7 +145,7 @@ function SessionPage() {
}
return (
<div className="flex overflow-hidden relative order-3 w-screen h-screen bg-black justify-center_items-center">
<div className="flex overflow-hidden relative order-3 w-screen h-dvh bg-black justify-center_items-center touch-none">
{/* Pixel Streaming - показывается только когда сессия активна */}
{session.status === "started" &&
session.mode === "stream" &&
@@ -205,14 +211,30 @@ function SessionPage() {
<ShareFilledIcon />
</div>
</FloatingActionButton>
<FloatingActionButton className="2xl:hidden">
<FloatingActionButton
className="2xl:hidden"
disabled={!localStream}
onClick={toggleAudio}
>
<div className="text-white size-4">
<MicrophoneFilledIcon />
{!localStream || isAudioMuted ? (
<MicrophoneOffFilledIcon />
) : (
<MicrophoneFilledIcon />
)}
</div>
</FloatingActionButton>
<FloatingActionButton className="2xl:hidden">
<FloatingActionButton
className="2xl:hidden"
disabled={!localStream}
onClick={toggleVideo}
>
<div className="text-white size-4">
<VideoOffFilledIcon />
{!localStream || isVideoMuted ? (
<VideoOffFilledIcon />
) : (
<VideoFilledIcon />
)}
</div>
</FloatingActionButton>
<FloatingActionButton
+1 -2
View File
@@ -21,8 +21,7 @@ export const serverSessions = pgTable("server_sessions", {
appId: uuid("app_id")
.notNull()
.references(() => 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