From 5342b3b30306ce33aec0d517d956a60095855863 Mon Sep 17 00:00:00 2001 From: inmake Date: Wed, 4 Oct 2023 17:28:00 +0500 Subject: [PATCH] upd --- src/StreamPage.tsx | 72 +++++++++++-- src/components/Chat.css | 19 ++++ src/components/Chat.tsx | 162 ++++++++++++++++++++++++++++++ src/components/icons/ChatIcon.tsx | 19 ++++ src/stores/useChatStore.ts | 17 ++++ 5 files changed, 283 insertions(+), 6 deletions(-) create mode 100644 src/components/Chat.css create mode 100644 src/components/Chat.tsx create mode 100644 src/components/icons/ChatIcon.tsx create mode 100644 src/stores/useChatStore.ts diff --git a/src/StreamPage.tsx b/src/StreamPage.tsx index acde2f1..66ea19f 100644 --- a/src/StreamPage.tsx +++ b/src/StreamPage.tsx @@ -3,7 +3,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { useParams } from "react-router-dom"; import { FullScreen, useFullScreenHandle } from "react-full-screen"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import ky from "ky"; import { Socket, io } from "socket.io-client"; import userAgentParser from "ua-parser-js"; @@ -29,6 +29,8 @@ import AlertIcon from "./components/icons/AlertIcon"; import useSocketStore from "./stores/useSocketStore"; import { LiveKitRoom, RoomAudioRenderer } from "@livekit/components-react"; import ToggleMic from "./components/ToggleMic"; +import Chat from "./components/Chat"; +import ChatIcon from "./components/icons/ChatIcon"; function StreamPage() { const { t } = useTranslation(); @@ -53,6 +55,10 @@ function StreamPage() { state.users, state.setUsers, ]); + const [isShowChat, setIsShowChat] = useState(false); + const [lastActivity, setLastActivity] = useState(new Date()); + const lastActivityRef = useRef(); + lastActivityRef.current = lastActivity; async function getToken() { if (!socket) return; @@ -138,6 +144,34 @@ function StreamPage() { // }); // } + const handleAction = () => { + setLastActivity(new Date()); + }; + + const updateLastActivity = async () => { + console.log("lastActivity: ", lastActivityRef.current); + + try { + const result = await ky + .put(`${import.meta.env.VITE_COORD_URL}/active_sessions/${params.id}`, { + json: { + lastActivityAt: lastActivityRef.current, + }, + }) + .json(); + + console.log(result); + } catch (error) { + if (error instanceof Error) { + console.log(error.message); + } + } + + setTimeout(() => { + updateLastActivity(); + }, 2000); + }; + useEffect(() => { connect(); @@ -162,11 +196,6 @@ function StreamPage() { setUsers(sockets); }); - socket.on("leave", (socketId, sockets) => { - console.log("User disconnected: ", socketId); - setUsers(sockets); - }); - socket.on("kick", () => { window.close(); }); @@ -179,8 +208,21 @@ function StreamPage() { ); }); + document.addEventListener("mousemove", handleAction); + document.addEventListener("touchstart", handleAction); + + updateLastActivity(); + return () => { + socket.off("connect"); + socket.off("join"); + socket.off("update"); + socket.off("kick"); + socket.off("leave"); socket.close(); + + document.removeEventListener("mousemove", handleAction); + document.removeEventListener("touchstart", handleAction); }; }, []); @@ -307,6 +349,16 @@ function StreamPage() { + + + + {(state) => ( +
+ setIsShowChat(false)} /> +
+ )} +
+ void; +} + +function Chat({ className, handleClose }: ChatProps) { + const socket = useSocketStore((state) => state.socket); + const [message, setMessage] = useState(""); + const [messages, setMessages] = useChatStore((state) => [ + state.messages, + state.setMessages, + ]); + const [isBottom, setIsBottom] = useState(true); + const [isNewMessage, setIsNewMessage] = useState(false); + const inputRef = useRef(null); + const messagesEndRef = useRef(null); + + function sendMessage(e: FormEvent) { + e.preventDefault(); + + if (!socket || !message) return; + + socket.emit("message", socket.id, message); + + setMessage(""); + inputRef.current?.focus(); + messagesEndRef.current?.scrollIntoView(); + } + + const handleScroll = (e: UIEvent) => { + if ( + e.currentTarget.scrollHeight - Math.round(e.currentTarget.scrollTop) === + e.currentTarget.clientHeight + ) { + setIsBottom(true); + setIsNewMessage(false); + } else { + setIsBottom(false); + } + }; + + useEffect(() => { + function handleKeyUp(e: KeyboardEvent) { + if (e.key === "Escape") { + handleClose(); + } + } + + document.addEventListener("keyup", handleKeyUp); + + return () => { + document.removeEventListener("keyup", handleKeyUp); + }; + }, []); + + useEffect(() => { + if (!socket) return; + + console.log(messages); + + console.log("CHAT Socket Init: ", socket.id); + + socket.on("message", (socketId, message) => { + setMessages([ + ...useChatStore.getState().messages, + { id: socketId, message }, + ]); + }); + + return () => { + socket.off("message"); + }; + }, [socket]); + + useEffect(() => { + if (isBottom) { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + } else { + setIsNewMessage(true); + } + }, [messages]); + + return ( +
+
+

Chat

+ +
+
+ {messages.map((data, index) => ( +

+ {data.id}: {data.message} +

+ ))} + +
+
+ +
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) + } + className={[ + "absolute mr-8 right-0 bottom-[136px] bg-neutral-950 p-2 rounded-full shadow transition-opacity", + isBottom + ? "opacity-0 pointer-events-none cursor-auto" + : "opacity-100 pointer-events-auto cursor-pointer", + ].join(" ")} + > + + +
+
+ +
+ setMessage(e.target.value)} + className="px-3 py-2 outline-none rounded" + /> + +
+
+ ); +} + +export default Chat; diff --git a/src/components/icons/ChatIcon.tsx b/src/components/icons/ChatIcon.tsx new file mode 100644 index 0000000..5201671 --- /dev/null +++ b/src/components/icons/ChatIcon.tsx @@ -0,0 +1,19 @@ +interface IconProps { + className?: string; +} + +function ChatIcon({ className }: IconProps) { + return ( + + + + + ); +} + +export default ChatIcon; diff --git a/src/stores/useChatStore.ts b/src/stores/useChatStore.ts new file mode 100644 index 0000000..e67ddbc --- /dev/null +++ b/src/stores/useChatStore.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { create } from "zustand"; +import { devtools } from "zustand/middleware"; + +interface ChatState { + messages: any[]; + setMessages: (messages: any[]) => void; +} + +const useChatStore = create()( + devtools((set) => ({ + messages: [], + setMessages: (messages) => set({ messages }), + })) +); + +export default useChatStore;