diff --git a/src/components/Author.tsx b/src/components/Author.tsx index 7bbaf6c..6e5e0d9 100644 --- a/src/components/Author.tsx +++ b/src/components/Author.tsx @@ -14,6 +14,7 @@ const AuthorComponent: FunctionComponent = ({ className={ "message-author" + (authorId !== authorNickname ? " nickname" : "") } + title={authorId} > {authorNickname} diff --git a/src/components/Connect.tsx b/src/components/Connect.tsx new file mode 100644 index 0000000..edd5c87 --- /dev/null +++ b/src/components/Connect.tsx @@ -0,0 +1,46 @@ +import { useState } from "react"; +export type ConnectionOptions = { + desiredName?: string; + url: string; +}; + +export type ConnectComponentProps = { + tryConnect(opts: ConnectionOptions): void; +}; + +export default function Connect({ + tryConnect, +}: ConnectComponentProps): JSX.Element { + const [socketUrl, setSocketUrl] = useState("wss.tobot.tk:8085/"); + const [desiredName, setDesiredName] = useState(undefined); + + return ( + <> + + setSocketUrl(e.target.value)} + /> + + setDesiredName(e.target.value)} + /> + + + ); +} diff --git a/src/components/UserBar.tsx b/src/components/UserBar.tsx new file mode 100644 index 0000000..df1524b --- /dev/null +++ b/src/components/UserBar.tsx @@ -0,0 +1,34 @@ +import { ConnectedUser } from "src/lib/ServerMessage"; + +export type UserBarProps = { + connectedUsers: ConnectedUser[]; + currentlyTyping: string[]; + getName(id: string): string; +}; + +export default function UserBar({ + connectedUsers, + currentlyTyping, + getName, +}: UserBarProps): JSX.Element { + return ( +
+

Users

+
    + {connectedUsers.map(({ id }) => ( +
  1. + {getName(id)} +
  2. + ))} +
+
+ ); +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 355cce9..f7447f5 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,9 +1,10 @@ import { useCallback, useMemo, useRef, useState } from "react"; import useWebSocket, { - ReadyState, ReadyState as WebSocketReadyState, } from "react-use-websocket"; +import Connect, { ConnectionOptions } from "src/components/Connect"; import MessageComponent from "src/components/Message"; +import UserBar from "src/components/UserBar"; import type { AckMessage, ConnectedUser, @@ -26,23 +27,28 @@ let shouldResendTyping = true; export default function Index(): JSX.Element { const [messageHistory, setMessageHistory] = useState([]); const [currentlyTyping, setCurrentlyTyping] = useState([]); - const [socketUrl, setSocketUrl] = useState("wss.tobot.tk:8085/"); - const [authorId, setAuthorId] = useState(""); + const [authorId, setAuthorId] = useState("wss.tobot.tk:8085/"); const [connectedUsers, setConnectedUsers] = useState([]); - const desiredNameInput = useRef(null); + const [desiredName, setDesiredName] = useState(undefined); + const [socketUrl, setSocketUrl] = useState(""); + const messageInput = useRef(null); const messageContainer = useRef(null); const getName = useCallback( (id: string) => { if (id === authorId) { - return "You"; + return ( + (connectedUsers.find((user) => user.id === id)?.desiredName ?? id) + + " (you)" + ); } else { return connectedUsers.find((user) => user.id === id)?.desiredName ?? id; } }, [authorId, connectedUsers] ); + function onMessage(event: MessageEvent): void { const message = JSON.parse(event.data); @@ -53,6 +59,14 @@ export default function Index(): JSX.Element { if (isIdResponseMessage(message)) { setAuthorId(message.authorId); + + if (desiredName) { + webSocket.sendJsonMessage({ + type: MessageType.DESIRED_NAME, + date: Date.now(), + desiredName: desiredName, + } as DesiredNameMessage); + } } else if (isTextMessage(message)) { setMessageHistory([...messageHistory, message]); } else if (isCurrentlyTypingMessage(message)) { @@ -65,24 +79,16 @@ export default function Index(): JSX.Element { } let keepAliveInterval: NodeJS.Timeout; - const websocket = useWebSocket("wss://" + socketUrl, { + const webSocket = useWebSocket("wss://" + socketUrl, { onMessage, onOpen() { keepAliveInterval = setInterval(() => { - websocket.sendJsonMessage({ + webSocket.sendJsonMessage({ type: MessageType.ACK, date: Date.now(), } as AckMessage); }, 1000); keepAliveIntervals.push(keepAliveInterval); - - if (desiredNameInput.current?.value.length) { - websocket.sendJsonMessage({ - type: MessageType.DESIRED_NAME, - date: Date.now(), - desiredName: desiredNameInput.current.value, - } as DesiredNameMessage); - } }, onClose() { clearInterval(keepAliveInterval); @@ -90,8 +96,25 @@ export default function Index(): JSX.Element { (interval) => interval !== keepAliveInterval ); }, + onError(e) { + console.error(2, e); + }, }); + const tryConnect = useCallback( + (opts: ConnectionOptions) => { + webSocket.getWebSocket()?.close(); + console.debug(opts); + setConnectedUsers([]); + setMessageHistory([]); + setAuthorId(""); + + setDesiredName(opts.desiredName); + setSocketUrl(opts.url); + }, + [webSocket] + ); + const trySendMessage = useCallback(() => { if (!messageInput.current) { return; @@ -103,37 +126,20 @@ export default function Index(): JSX.Element { messageInput.current.value = ""; - websocket.sendJsonMessage({ + webSocket.sendJsonMessage({ author: authorId, type: MessageType.TEXT, date: Date.now(), content: messageText, } as TextMessage); - }, [authorId, websocket]); + }, [authorId, webSocket]); - const trySetSocketUrl = useCallback( - (url: string) => { - try { - console.debug(new URL("wss://" + url), url); - setSocketUrl(url); - } catch (e) { - console.debug("Invalid URL"); - // Invalid URL, don't do anything - } finally { - if (websocket.readyState === WebSocketReadyState.OPEN) { - websocket.getWebSocket()?.close(); - } - } - }, - [websocket] - ); - - function handleInput() { + function handleTyping() { if ( - websocket.readyState === ReadyState.OPEN && + webSocket.readyState === WebSocketReadyState.OPEN && (shouldResendTyping || !currentlyTyping.includes(authorId)) ) { - websocket.sendJsonMessage({ + webSocket.sendJsonMessage({ type: MessageType.TYPING, date: Date.now(), } as TypingMessage); @@ -193,25 +199,11 @@ export default function Index(): JSX.Element { Your ID: {authorId} )}{" "} + Ready state: - {ReadyState[websocket.readyState]} ({websocket.readyState}) + {WebSocketReadyState[webSocket.readyState]} ({webSocket.readyState}) - - trySetSocketUrl(e.target.value)} - /> - -
@@ -232,7 +224,7 @@ export default function Index(): JSX.Element { type="text" placeholder="Type here..." id="message-input" - onInput={handleInput} + onInput={handleTyping} onKeyPress={(keyEvent) => { if (keyEvent.key === "Enter") { trySendMessage(); @@ -244,23 +236,11 @@ export default function Index(): JSX.Element {
-
-

Users

-
    - {connectedUsers.map(({ id }) => ( -
  1. - {getName(id)} -
  2. - ))} -
-
+ );