This commit is contained in:
2024-09-27 17:51:01 +05:00
parent 60ee65dab5
commit a55c1e8b10
7 changed files with 186 additions and 1411 deletions
+21 -13
View File
@@ -9,6 +9,7 @@ import useStreamUserStore from "../stores/useStreamUserStore";
import { FormEvent, useEffect, useRef, useState } from "react"; import { FormEvent, useEffect, useRef, useState } from "react";
import useSocketStore from "../stores/useSocketStore"; import useSocketStore from "../stores/useSocketStore";
import useChatStore from "../stores/useChatStore"; import useChatStore from "../stores/useChatStore";
import { isMobile } from "react-device-detect";
interface Props { interface Props {
onClose: () => void; onClose: () => void;
@@ -26,6 +27,8 @@ function Chat2({ onClose }: Props) {
function sendMessage(e: FormEvent) { function sendMessage(e: FormEvent) {
e.preventDefault(); e.preventDefault();
if (!messageText || messageText === " ") return;
socket?.emit("message", messageText); socket?.emit("message", messageText);
setMessageText(""); setMessageText("");
@@ -42,22 +45,24 @@ function Chat2({ onClose }: Props) {
return ( return (
<div <div
className={`chat relative h-[calc(100vh-48px)] flex flex-col w-[296px] bg-white`} className={`chat lg:relative absolute right-0 lg:h-[calc(100dvh-48px)] h-dvh flex flex-col w-[296px] bg-white border-t border-[#DAE0E5]`}
> >
<div className="flex items-center justify-between p-2 pl-4 border-b border-[#DAE0E5]"> <div className="p-4 pb-2">
<p className="text-sm font-semibold"> <div className="flex items-center justify-between border-b border-[#DAE0E5] pb-4">
<Trans i18nKey={"chat"}>Чат</Trans> <p className="text-sm font-semibold">
</p> <Trans i18nKey={"chat"}>Чат демонстрации</Trans>
<Button </p>
variant="tertiary" <Button
icon={<CloseIcon />} variant="tertiary"
onlyIcon icon={<CloseIcon />}
onClick={onClose} onlyIcon
/> onClick={onClose}
/>
</div>
</div> </div>
<div <div
ref={messagesRef} ref={messagesRef}
className="flex-1 overflow-y-scroll flex flex-col gap-1 p-3 border-b border-[#DAE0E5]" className="flex-1 overflow-y-scroll flex flex-col gap-1 px-4 pb-2 pt-0 border-b border-[#DAE0E5]"
> >
{messages.map((message, index) => ( {messages.map((message, index) => (
<div <div
@@ -85,12 +90,15 @@ function Chat2({ onClose }: Props) {
<div className="p-3 pl-4"> <div className="p-3 pl-4">
<form onSubmit={sendMessage} className="flex items-center gap-3"> <form onSubmit={sendMessage} className="flex items-center gap-3">
<input <input
autoFocus={isMobile ? false : true}
ref={messageTextRef} ref={messageTextRef}
type="text" type="text"
placeholder={t("writeAMessage")} placeholder={t("writeAMessage")}
className="w-full text-sm bg-transparent outline-none" className="w-full text-sm bg-transparent outline-none"
value={messageText} value={messageText}
onChange={(e) => setMessageText(e.target.value)} onChange={(e) =>
setMessageText(e.target.value.replace(/\s+/g, " "))
}
/> />
<button type="submit" className="text-[#49A1F5] outline-none"> <button type="submit" className="text-[#49A1F5] outline-none">
<SendChatIcon /> <SendChatIcon />
+41
View File
@@ -0,0 +1,41 @@
import CloseIcon from "./icons/CloseIcon";
import HandOffIcon from "./icons/HandOffIcon";
// import MicroOnIcon from "./icons/MicroOnIcon";
interface Props {
handleToggleControl: () => void;
handleKick: () => void;
}
function Dropdown({ handleToggleControl, handleKick }: Props) {
return (
<div className="absolute py-2 mt-4 space-y-1 bg-white rounded-lg shadow w-60">
{/* <button className="flex items-center gap-2 px-4 py-1 text-sm">
<span className="text-[#77828C]">
<MicroOnIcon />
</span>
<span>Отключить микрофон</span>
</button> */}
<button
className="flex items-center gap-2 px-4 py-1 text-sm"
onClick={handleToggleControl}
>
<span className="text-[#77828C]">
<HandOffIcon />
</span>
<span>Передать управление</span>
</button>
<button
className="flex items-center gap-2 px-4 py-1 text-sm"
onClick={handleKick}
>
<span className="text-[#EB5757]">
<CloseIcon />
</span>
<span>Исключить</span>
</button>
</div>
);
}
export default Dropdown;
+69 -65
View File
@@ -1,82 +1,86 @@
import { useRef, useState } from "react"; import { isMobile } from "react-device-detect";
import CloseIcon from "./icons/CloseIcon"; import DesktopIcon from "./icons/DesktopIcon";
import HandOnIcon from "./icons/HandOnIcon"; import HandOnIcon from "./icons/HandOnIcon";
import Button from "./ui/Button"; import MobileIcon from "./icons/MobileIcon";
import MoreIcon from "./icons/MoreIcon"; import MoreIcon from "./icons/MoreIcon";
import { useOnClickOutside } from "usehooks-ts";
import Tooltip from "./Tooltip"; import Tooltip from "./Tooltip";
import Button from "./ui/Button";
import IUser from "../types/IUser"; import IUser from "../types/IUser";
import CloseIcon from "./icons/CloseIcon";
import { useState } from "react";
import { useClickAway } from "@uidotdev/usehooks";
interface UserProps { interface Props {
me: IUser;
user: IUser; user: IUser;
onTransferControl: (userId: string) => void; handleTransferControl: () => void;
onKickUser: (userId: string) => void; handleKick: () => void;
className?: string;
tooltip?: boolean;
} }
function User({ function User({ me, user, handleTransferControl, handleKick }: Props) {
user, const [showMore, setShowMore] = useState(false);
onTransferControl,
onKickUser,
className,
tooltip = false,
}: UserProps) {
const [isShowMore, setIsShowMore] = useState(false);
const moreRef = useRef(null);
function handleClickOutside() { const ref = useClickAway<HTMLDivElement>(() => {
setIsShowMore(false); setShowMore(false);
} });
useOnClickOutside(moreRef, handleClickOutside);
function handleClickTransferControl() {
onTransferControl(user.id);
setIsShowMore(false);
}
function handleClickKickUser() {
onKickUser(user.id);
setIsShowMore(false);
}
return ( return (
<div ref={moreRef} className="relative"> <div key={user.id} className="flex items-center gap-2">
<Button <div className="relative w-6 h-6 bg-[#E6ECF2] rounded-full flex items-center justify-center">
variant="tertiary" <p className="text-xs font-semibold">{user.name[0]?.toUpperCase()}</p>
icon={<MoreIcon />} {user?.isControlAllowed && (
onlyIcon <div className="absolute bottom-0 right-0 bg-[#49A1F5] w-2 h-2 rounded-full border border-white"></div>
onClick={() => setIsShowMore((prev) => !prev)} )}
className="group relative" </div>
> <p className="text-xs">{user.name}</p>
{tooltip && <Tooltip text="Действия" />} <div className="text-[#CCCCCC]">
</Button> {isMobile ? <MobileIcon /> : <DesktopIcon />}
</div>
{isShowMore && ( {me.isAdmin && (
<div <div ref={ref} className="relative z-10">
className={`absolute top-[calc(100%+18px)] bg-white rounded-lg py-2 z-10 shadow ${className}`} <Button
> variant="tertiary"
<button icon={<MoreIcon />}
className="flex items-center gap-2 px-4 py-1 hover:bg-[#E6ECF2] transition-colors w-full" onlyIcon
onClick={handleClickTransferControl} onClick={() => setShowMore(true)}
> />
<Tooltip text={"Действия"} />
{showMore && (
<div className="absolute py-2 mt-4 space-y-1 bg-white rounded-lg shadow w-60">
{/* <button className="flex items-center gap-2 px-4 py-1 text-sm hover:bg-[#E6ECF2] transition-colors">
<span className="text-[#77828C]"> <span className="text-[#77828C]">
<HandOnIcon /> <MicroOnIcon />
</span> </span>
<span className="text-sm whitespace-nowrap"> <span>Отключить микрофон</span>
Передать управление </button> */}
</span> <button
</button> className="flex items-center w-full gap-2 px-4 py-1 text-sm hover:bg-[#E6ECF2] transition-colors"
<button onClick={() => {
className="flex items-center gap-2 px-4 py-1 hover:bg-[#E6ECF2] transition-colors w-full" handleTransferControl();
onClick={handleClickKickUser} setShowMore(false);
> }}
<span className="text-[#EB5757]"> >
<CloseIcon /> <span className="text-[#77828C]">
</span> <HandOnIcon />
<span className="text-sm">Исключить</span> </span>
</button> <span>Передать управление</span>
</button>
<button
className="flex items-center w-full gap-2 px-4 py-1 text-sm hover:bg-[#E6ECF2] transition-colors"
onClick={() => {
handleKick();
setShowMore(false);
}}
>
<span className="text-[#EB5757]">
<CloseIcon />
</span>
<span>Исключить</span>
</button>
</div>
)}
</div> </div>
)} )}
</div> </div>
+2 -3
View File
@@ -7,8 +7,7 @@ import App from "./App";
// import ErrorBoundary from "./ErrorBoundary"; // import ErrorBoundary from "./ErrorBoundary";
import HistoryPage from "./HistoryPage"; import HistoryPage from "./HistoryPage";
import ScheduledPage from "./ScheduledPage"; import ScheduledPage from "./ScheduledPage";
// import StreamPage2 from "./pages/StreamPage2"; import StreamPage from "./pages/StreamPage";
import StreamPage3 from "./pages/StreamPage3";
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
@@ -18,7 +17,7 @@ const router = createBrowserRouter([
}, },
{ {
path: "/stream/:id", path: "/stream/:id",
element: <StreamPage3 />, element: <StreamPage />,
}, },
{ {
path: "/history", path: "/history",
@@ -43,13 +43,14 @@ import DesktopIcon from "../components/icons/DesktopIcon";
import MobileIcon from "../components/icons/MobileIcon"; import MobileIcon from "../components/icons/MobileIcon";
import ChatIcon from "../components/icons/ChatIcon"; import ChatIcon from "../components/icons/ChatIcon";
import useChatStore from "../stores/useChatStore"; import useChatStore from "../stores/useChatStore";
import User from "../components/User";
// import ChatIcon from "../components/icons/ChatIcon"; // import ChatIcon from "../components/icons/ChatIcon";
// import MoreIcon from "../components/icons/MoreIcon"; // import MoreIcon from "../components/icons/MoreIcon";
const userId = uuidv4(); const userId = uuidv4();
function StreamPage3() { function StreamPage() {
const params = useParams(); const params = useParams();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const [WSUrl, setWSUrl] = useState<string>(""); const [WSUrl, setWSUrl] = useState<string>("");
@@ -218,6 +219,17 @@ function StreamPage3() {
toast.info(`Вы получили разрешение на управление`); toast.info(`Вы получили разрешение на управление`);
}); });
socket.on("kick", (userId) => {
if (useStreamUserStore.getState().me?.id !== userId) return;
window.close();
socket.disconnect();
setPeerInstance(undefined);
setWSUrl("");
setUsers([]);
setRemoteStreams([]);
});
socket.on("message", ({ userId, text }) => { socket.on("message", ({ userId, text }) => {
setMessages([...useChatStore.getState().messages, { userId, text }]); setMessages([...useChatStore.getState().messages, { userId, text }]);
}); });
@@ -289,10 +301,13 @@ function StreamPage3() {
} }
function requestControl(userId: string) { function requestControl(userId: string) {
console.log("requestControl func", userId);
socket?.emit("request-control", userId); socket?.emit("request-control", userId);
} }
function kick(userId: string) {
socket?.emit("kick", userId);
}
async function getActiveSession() { async function getActiveSession() {
const activeSession: any = await api const activeSession: any = await api
.get(`activeSessions/${params.id}`) .get(`activeSessions/${params.id}`)
@@ -443,50 +458,22 @@ function StreamPage3() {
</div> </div>
<div className="h-4 w-px bg-[#DAE0E5] lg:block hidden"></div> <div className="h-4 w-px bg-[#DAE0E5] lg:block hidden"></div>
<div className="hidden gap-6 lg:flex"> <div className="hidden gap-6 lg:flex">
{users.map((user) => { {me &&
if (user.id !== userId) { users.map((user) => {
return ( if (user.id !== userId) {
<div key={user.id} className="flex items-center gap-2"> return (
<div className="relative w-6 h-6 bg-[#E6ECF2] rounded-full flex items-center justify-center"> <User
<p className="text-xs font-semibold"> me={me}
{name[0]?.toUpperCase()} user={user}
</p> handleTransferControl={() => transferControl(user.id)}
{user?.isControlAllowed && ( handleKick={() => kick(user.id)}
<div className="absolute bottom-0 right-0 bg-[#49A1F5] w-2 h-2 rounded-full border border-white"></div> />
)} );
</div> }
<p className="text-xs">{user.name}</p> })}
<div className="text-[#CCCCCC]">
{isMobile ? <MobileIcon /> : <DesktopIcon />}
</div>
{me?.isAdmin && me?.isControlAllowed && (
<div className="relative">
{/* <Button
variant="secondary"
icon={<MoreIcon />}
onlyIcon
/> */}
{/* <div className="absolute"> */}
<div className="relative group">
<Button
variant="secondary"
icon={<HandOnIcon />}
onlyIcon
onClick={() => transferControl(user.id)}
/>
<Tooltip text={"Передать управление"} />
</div>
{/* </div> */}
</div>
)}
</div>
);
}
})}
</div> </div>
</div> </div>
<div className="flex gap-2 max-lg:flex-col lg:ml-auto"> <div className="flex items-center gap-2 lg:gap-4 max-lg:flex-col lg:ml-auto">
<div className="relative group"> <div className="relative group">
<Button <Button
variant="secondary" variant="secondary"
@@ -496,30 +483,33 @@ function StreamPage3() {
/> />
<Tooltip text={isShowChat ? "Скрыть чат" : "Показать чат"} /> <Tooltip text={isShowChat ? "Скрыть чат" : "Показать чат"} />
</div> </div>
<div className="relative group"> <div className="w-px h-4 bg-[#DAE0E5] max-lg:hidden"></div>
<Button <div className="flex gap-2 max-lg:flex-col lg:ml-auto">
variant="secondary"
icon={<ShareIcon />}
onlyIcon
onClick={() => setModal(<InviteModal />)}
/>
<Tooltip text={"Поделиться"} />
</div>
{!isIOS && (
<div className="relative group"> <div className="relative group">
<Button <Button
variant="secondary" variant="secondary"
icon={isFullscreen ? <WindowIcon /> : <FullscreenIcon />} icon={<ShareIcon />}
onlyIcon onlyIcon
onClick={toggleFullscreen} onClick={() => setModal(<InviteModal />)}
/>
<Tooltip
text={
isFullscreen ? "Оконный режим" : "Полноэкранный режим"
}
/> />
<Tooltip text={"Поделиться"} />
</div> </div>
)} {!isIOS && (
<div className="relative group">
<Button
variant="secondary"
icon={isFullscreen ? <WindowIcon /> : <FullscreenIcon />}
onlyIcon
onClick={toggleFullscreen}
/>
<Tooltip
text={
isFullscreen ? "Оконный режим" : "Полноэкранный режим"
}
/>
</div>
)}
</div>
</div> </div>
</div> </div>
<div className="relative flex flex-1"> <div className="relative flex flex-1">
@@ -607,4 +597,4 @@ function StreamPage3() {
); );
} }
export default StreamPage3; export default StreamPage;
-33
View File
@@ -1,33 +0,0 @@
.entering {
opacity: 1;
}
.entered {
opacity: 1;
}
.exiting {
opacity: 0;
}
.exited {
opacity: 0;
}
:root {
--toastify-toast-offset: 52px;
}
.Toastify__toast-body {
padding: 0;
margin: 0 4px 0 0;
align-items: normal;
font-family: "Inter", sans-serif;
font-size: 14px;
color: #111c26;
}
.Toastify__toast-icon {
width: auto;
margin-inline-end: 8px;
}
File diff suppressed because it is too large Load Diff