upd
This commit is contained in:
@@ -0,0 +1,468 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import "./StreamPage2.css";
|
||||
import ky from "ky";
|
||||
import { PixelStreamingWrapper } from "../components/PixelStreamingWrapper";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { FormEvent, useEffect, useRef, useState } from "react";
|
||||
import { Transition } from "react-transition-group";
|
||||
import Button from "../components/ui/Button";
|
||||
import CloseIcon from "../components/icons/CloseIcon";
|
||||
import HandOffIcon from "../components/icons/HandOffIcon";
|
||||
import MicroOffIcon from "../components/icons/MicroOffIcon";
|
||||
import PersonsIcon from "../components/icons/PersonsIcon";
|
||||
import MicroOnIcon from "../components/icons/MicroOnIcon";
|
||||
import MoreIcon from "../components/icons/MoreIcon";
|
||||
import FullscreenIcon from "../components/icons/FullscreenIcon";
|
||||
import WindowIcon from "../components/icons/WindowIcon";
|
||||
import ChatIcon from "../components/icons/ChatIcon";
|
||||
import ShareIcon from "../components/icons/ShareIcon";
|
||||
import GearIcon from "../components/icons/GearIcon";
|
||||
import { useFullscreen } from "ahooks";
|
||||
import AttachIcon from "../components/icons/AttachIcon";
|
||||
import DetachIcon from "../components/icons/DetachIcon";
|
||||
import Draggable from "react-draggable";
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
function StreamPage2() {
|
||||
const params = useParams();
|
||||
const [wsUrl, setWsUrl] = useState<string>();
|
||||
const [username, setUsername] = useState<string>("");
|
||||
const [step, setStep] = useState<number>(1);
|
||||
const usernameRef = useRef<HTMLInputElement>(null!);
|
||||
const [users, setUsers] = useState<User[]>([
|
||||
{
|
||||
id: "1",
|
||||
username: "User 1",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
username: "User 2",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
username: "User 3",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
username: "User 3",
|
||||
},
|
||||
{
|
||||
id: "1",
|
||||
username: "User 1",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
username: "User 2",
|
||||
},
|
||||
]);
|
||||
const [isShowUsers, setIsShowUsers] = useState<boolean>(false);
|
||||
const [isShowChat, setIsShowChat] = useState<boolean>(false);
|
||||
const [isMicEnabled, setIsMicEnabled] = useState<boolean>(false);
|
||||
const [isVideoInitialized, setIsVideoInitialized] = useState<boolean>(false);
|
||||
const ref = useRef(null);
|
||||
const [isFullscreen, { toggleFullscreen }] = useFullscreen(ref);
|
||||
const [isUsersDetached, setIsUsersDetached] = useState<boolean>(false);
|
||||
const [isChatDetached, setIsChatDetached] = useState<boolean>(false);
|
||||
|
||||
async function getWsUrl() {
|
||||
const activeSession: any = await ky
|
||||
.get(`${import.meta.env.VITE_COORD_URL}/active_sessions/${params.id}`)
|
||||
.json();
|
||||
|
||||
setWsUrl(
|
||||
`wss://${activeSession.location}.sess.stream.graff.tech/${activeSession.server}/${activeSession.cirrusPort}/`
|
||||
);
|
||||
}
|
||||
|
||||
function handleSetUsername(e: FormEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!username) {
|
||||
usernameRef.current.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
setStep(2);
|
||||
}
|
||||
|
||||
function setUsernameGuest() {
|
||||
setUsername("Гость");
|
||||
setStep(2);
|
||||
}
|
||||
|
||||
function allowMic() {
|
||||
setStep(3);
|
||||
}
|
||||
|
||||
function disallowMic() {
|
||||
setStep(3);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!username) return;
|
||||
|
||||
setUsername(() => username.trim());
|
||||
}, [username]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isChatDetached || isUsersDetached) {
|
||||
document.body.classList.add("overflow-hidden");
|
||||
} else {
|
||||
document.body.classList.remove("overflow-hidden");
|
||||
}
|
||||
}, [isUsersDetached, isChatDetached]);
|
||||
|
||||
useEffect(() => {
|
||||
getWsUrl();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={ref} className="">
|
||||
<div className="h-screen flex flex-col">
|
||||
<div className="h-12 bg-white px-6 py-3 flex items-center justify-between">
|
||||
<div id="left" className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-6 w-6 flex items-center justify-center bg-[#E6ECF2] font-semibold text-[10px] rounded-full">
|
||||
{username && username[0].toUpperCase()}
|
||||
</div>
|
||||
<p className="text-xs">{username}</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" icon={<HandOffIcon />} onlyIcon />
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={isMicEnabled ? <MicroOnIcon /> : <MicroOffIcon />}
|
||||
onlyIcon
|
||||
onClick={() => setIsMicEnabled((prev) => !prev)}
|
||||
/>
|
||||
</div>
|
||||
<div id="divider" className="w-px h-4 bg-[#DAE0E5]"></div>
|
||||
<div className="flex gap-6">
|
||||
{users.map(
|
||||
(user, index) =>
|
||||
index < 3 && (
|
||||
<div key={user.id} className="flex items-center gap-2">
|
||||
<div className="h-6 w-6 flex items-center justify-center bg-[#E6ECF2] font-semibold text-[10px] rounded-full">
|
||||
{user.username && user.username[0].toUpperCase()}
|
||||
</div>
|
||||
<p className="text-xs">{user.username}</p>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{users.length > 3 && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={<PersonsIcon />}
|
||||
onlyIcon
|
||||
onClick={() => setIsShowUsers((prev) => !prev)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="right" className="flex items-center gap-4">
|
||||
<div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={<ChatIcon />}
|
||||
onlyIcon
|
||||
onClick={() => setIsShowChat((prev) => !prev)}
|
||||
/>
|
||||
</div>
|
||||
<div id="divider" className="w-px h-4 bg-[#DAE0E5]"></div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={isFullscreen ? <WindowIcon /> : <FullscreenIcon />}
|
||||
onlyIcon
|
||||
onClick={toggleFullscreen}
|
||||
/>
|
||||
<Button variant="secondary" icon={<ShareIcon />} onlyIcon />
|
||||
<Button variant="secondary" icon={<GearIcon />} onlyIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex">
|
||||
<div className="w-full">
|
||||
{wsUrl && (
|
||||
<PixelStreamingWrapper
|
||||
initialSettings={{
|
||||
AutoPlayVideo: true,
|
||||
AutoConnect: true,
|
||||
ss: wsUrl,
|
||||
StartVideoMuted: true,
|
||||
HoveringMouse: true,
|
||||
WaitForStreamer: true,
|
||||
}}
|
||||
onVideoInitialized={() => setIsVideoInitialized(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col bg-white max-h-[calc(100vh-48px)]">
|
||||
<Draggable
|
||||
position={!isUsersDetached ? { x: 0, y: 0 } : undefined}
|
||||
disabled={!isUsersDetached}
|
||||
>
|
||||
<div
|
||||
id="modal-users"
|
||||
className={`max-h-[calc(100vh-48px)] ${
|
||||
isUsersDetached ? "absolute right-0 p-4" : ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`${
|
||||
isUsersDetached ? "rounded-lg" : "h-full"
|
||||
} bg-white flex flex-col ${
|
||||
isShowUsers ? "w-[296px]" : "w-0 hidden"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-between p-2 pl-4 ${
|
||||
isUsersDetached ? "border-b" : ""
|
||||
}`}
|
||||
>
|
||||
<p className="font-semibold">Участники</p>
|
||||
<div className="flex">
|
||||
<Button
|
||||
variant="tertiary"
|
||||
icon={isUsersDetached ? <AttachIcon /> : <DetachIcon />}
|
||||
onlyIcon
|
||||
onClick={() => setIsUsersDetached((prev) => !prev)}
|
||||
/>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
icon={<CloseIcon />}
|
||||
onlyIcon
|
||||
onClick={() => setIsShowUsers(false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 p-4">
|
||||
{users.map((user) => (
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 bg-[#E6ECF2] rounded-full flex items-center justify-center">
|
||||
<p className="font-medium text-[10px] select-none">
|
||||
{user.username[0].toUpperCase()}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-sm">{user.username}</p>
|
||||
</div>
|
||||
<div className="">
|
||||
<Button
|
||||
variant="tertiary"
|
||||
icon={<MoreIcon />}
|
||||
onlyIcon
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Draggable>
|
||||
|
||||
<Draggable
|
||||
position={!isChatDetached ? { x: 0, y: 0 } : undefined}
|
||||
disabled={!isChatDetached}
|
||||
>
|
||||
<div
|
||||
id="modal-chat"
|
||||
className={`max-h-[calc(100vh-48px)] ${
|
||||
isChatDetached
|
||||
? "absolute right-[346px] p-4 h-fit"
|
||||
: "h-full"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`${
|
||||
isChatDetached ? "rounded-lg" : "h-full"
|
||||
} bg-white flex flex-col ${
|
||||
isShowChat ? "w-[296px]" : "w-0 hidden"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-between p-2 pl-4 ${
|
||||
isChatDetached ? "border-b" : ""
|
||||
}`}
|
||||
>
|
||||
<p className="font-semibold">Чат</p>
|
||||
<div className="flex">
|
||||
<Button
|
||||
variant="tertiary"
|
||||
icon={isChatDetached ? <AttachIcon /> : <DetachIcon />}
|
||||
onlyIcon
|
||||
onClick={() => setIsChatDetached((prev) => !prev)}
|
||||
/>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
icon={<CloseIcon />}
|
||||
onlyIcon
|
||||
onClick={() => setIsShowChat(false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 p-4">//</div>
|
||||
</div>
|
||||
</div>
|
||||
</Draggable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
in={step !== 3 || !isVideoInitialized}
|
||||
timeout={300}
|
||||
mountOnEnter
|
||||
unmountOnExit
|
||||
>
|
||||
{(state) => (
|
||||
<div
|
||||
className={`absolute top-0 left-0 w-full h-full flex items-center justify-center bg-[#14161F] transition-all ${state} ${
|
||||
step !== 3 || !isVideoInitialized
|
||||
? "bg-opacity-50 backdrop-blur-2xl"
|
||||
: "bg-opacity-0 backdrop-blur-none"
|
||||
} `}
|
||||
>
|
||||
<Transition
|
||||
in={step === 1}
|
||||
timeout={300}
|
||||
mountOnEnter
|
||||
unmountOnExit
|
||||
>
|
||||
{(state) => (
|
||||
<form
|
||||
className={`absolute bg-white rounded-lg p-12 w-[396px] transition-opacity ${state}`}
|
||||
onSubmit={handleSetUsername}
|
||||
>
|
||||
<div className="mb-6">
|
||||
<p className="text-2xl font-semibold">Здравствуйте!</p>
|
||||
</div>
|
||||
<div className="mb-6 flex flex-col gap-2">
|
||||
<p className="font-semibold">Представьтесь, пожалуйста</p>
|
||||
<p className="text-[#77828C] text-sm">
|
||||
Так мы будем знать, как к вам обратиться
|
||||
</p>
|
||||
</div>
|
||||
<div className="mb-10">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[#77828C] text-xs">Имя</label>
|
||||
<input
|
||||
ref={usernameRef}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
className="rounded-lg px-2 py-2.5 bg-white border border-[#DAE0E5] hover:border-[#49A1F5] focus:border-[#49A1F5] outline-none transition-colors text-sm"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
fullWidth
|
||||
large
|
||||
onClick={setUsernameGuest}
|
||||
>
|
||||
Не указывать
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
large
|
||||
onClick={void handleSetUsername}
|
||||
>
|
||||
Продолжить
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</Transition>
|
||||
|
||||
<Transition
|
||||
in={step === 2}
|
||||
timeout={300}
|
||||
mountOnEnter
|
||||
unmountOnExit
|
||||
>
|
||||
{(state) => (
|
||||
<div
|
||||
className={`absolute bg-white rounded-lg p-12 w-[396px] transition-opacity ${state}`}
|
||||
>
|
||||
<div className="mb-6">
|
||||
<p className="text-2xl font-semibold">
|
||||
Хотите принять участие в обсуждении?
|
||||
</p>
|
||||
</div>
|
||||
<div className="mb-6 flex gap-6">
|
||||
<div className="border border-[#DAE0E5] text-[#49A1F5] p-2 rounded-full">
|
||||
<MicroOnIcon />
|
||||
</div>
|
||||
<p className="text-sm">Разрешите использование микрофона</p>
|
||||
</div>
|
||||
<div className="mb-10">
|
||||
<p className="text-[#77828C] text-xs">
|
||||
Выключить микрофон можно в любой момент
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
fullWidth
|
||||
large
|
||||
onClick={disallowMic}
|
||||
>
|
||||
Запретить
|
||||
</Button>
|
||||
<Button fullWidth large onClick={allowMic}>
|
||||
Разрешить
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
|
||||
<Transition
|
||||
in={step === 3 && !isVideoInitialized}
|
||||
timeout={300}
|
||||
mountOnEnter
|
||||
unmountOnExit
|
||||
>
|
||||
{(state) => (
|
||||
<div
|
||||
className={`absolute bg-white rounded-lg p-12 w-[396px] transition-opacity ${state}`}
|
||||
>
|
||||
<div className="mb-6">
|
||||
<p className="text-2xl font-semibold">
|
||||
Пожалуйста, подождите
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<img
|
||||
src="/icons/LoaderPrimary.png"
|
||||
alt=""
|
||||
className="w-8 h-8 animate-spin"
|
||||
/>
|
||||
<p className="font-semibold text-[#49A1F5]">Подключение</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default StreamPage2;
|
||||
Reference in New Issue
Block a user