1
Fork 0

Various changes

This commit is contained in:
Tobias Berger 2022-01-27 14:24:20 +00:00 committed by GitHub
parent 3662e40305
commit 3eb1c87f05
4 changed files with 132 additions and 71 deletions

View file

@ -14,6 +14,7 @@ const AuthorComponent: FunctionComponent<AuthorComponentProps> = ({
className={ className={
"message-author" + (authorId !== authorNickname ? " nickname" : "") "message-author" + (authorId !== authorNickname ? " nickname" : "")
} }
title={authorId}
> >
{authorNickname} {authorNickname}
</span> </span>

View file

@ -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<string | undefined>(undefined);
return (
<>
<label htmlFor="ws-url">WebSocket URL:</label>
<input
id="ws-url"
type="text"
value={socketUrl}
placeholder="wss://..."
onChange={(e) => setSocketUrl(e.target.value)}
/>
<label htmlFor="desired-name-input">Name:</label>
<input
id="desired-name-input"
type="text"
placeholder="..."
onChange={(e) => setDesiredName(e.target.value)}
/>
<button
onClick={() =>
tryConnect({
url: socketUrl,
desiredName: desiredName === "" ? undefined : desiredName,
})
}
>
Connect
</button>
</>
);
}

View file

@ -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 (
<section id="user-area">
<h2>Users</h2>
<ol>
{connectedUsers.map(({ id }) => (
<li
className={
"user" +
(currentlyTyping.includes(id) ? " typing" : "") +
(getName(id) !== id ? " nickname" : "")
}
key={id}
title={id}
>
{getName(id)}
</li>
))}
</ol>
</section>
);
}

View file

@ -1,9 +1,10 @@
import { useCallback, useMemo, useRef, useState } from "react"; import { useCallback, useMemo, useRef, useState } from "react";
import useWebSocket, { import useWebSocket, {
ReadyState,
ReadyState as WebSocketReadyState, ReadyState as WebSocketReadyState,
} from "react-use-websocket"; } from "react-use-websocket";
import Connect, { ConnectionOptions } from "src/components/Connect";
import MessageComponent from "src/components/Message"; import MessageComponent from "src/components/Message";
import UserBar from "src/components/UserBar";
import type { import type {
AckMessage, AckMessage,
ConnectedUser, ConnectedUser,
@ -26,23 +27,28 @@ let shouldResendTyping = true;
export default function Index(): JSX.Element { export default function Index(): JSX.Element {
const [messageHistory, setMessageHistory] = useState<TextMessage[]>([]); const [messageHistory, setMessageHistory] = useState<TextMessage[]>([]);
const [currentlyTyping, setCurrentlyTyping] = useState<string[]>([]); const [currentlyTyping, setCurrentlyTyping] = useState<string[]>([]);
const [socketUrl, setSocketUrl] = useState("wss.tobot.tk:8085/"); const [authorId, setAuthorId] = useState("wss.tobot.tk:8085/");
const [authorId, setAuthorId] = useState("");
const [connectedUsers, setConnectedUsers] = useState<ConnectedUser[]>([]); const [connectedUsers, setConnectedUsers] = useState<ConnectedUser[]>([]);
const desiredNameInput = useRef<HTMLInputElement>(null); const [desiredName, setDesiredName] = useState<string | undefined>(undefined);
const [socketUrl, setSocketUrl] = useState("");
const messageInput = useRef<HTMLInputElement>(null); const messageInput = useRef<HTMLInputElement>(null);
const messageContainer = useRef<HTMLOListElement>(null); const messageContainer = useRef<HTMLOListElement>(null);
const getName = useCallback( const getName = useCallback(
(id: string) => { (id: string) => {
if (id === authorId) { if (id === authorId) {
return "You"; return (
(connectedUsers.find((user) => user.id === id)?.desiredName ?? id) +
" (you)"
);
} else { } else {
return connectedUsers.find((user) => user.id === id)?.desiredName ?? id; return connectedUsers.find((user) => user.id === id)?.desiredName ?? id;
} }
}, },
[authorId, connectedUsers] [authorId, connectedUsers]
); );
function onMessage(event: MessageEvent<string>): void { function onMessage(event: MessageEvent<string>): void {
const message = JSON.parse(event.data); const message = JSON.parse(event.data);
@ -53,6 +59,14 @@ export default function Index(): JSX.Element {
if (isIdResponseMessage(message)) { if (isIdResponseMessage(message)) {
setAuthorId(message.authorId); setAuthorId(message.authorId);
if (desiredName) {
webSocket.sendJsonMessage({
type: MessageType.DESIRED_NAME,
date: Date.now(),
desiredName: desiredName,
} as DesiredNameMessage);
}
} else if (isTextMessage(message)) { } else if (isTextMessage(message)) {
setMessageHistory([...messageHistory, message]); setMessageHistory([...messageHistory, message]);
} else if (isCurrentlyTypingMessage(message)) { } else if (isCurrentlyTypingMessage(message)) {
@ -65,24 +79,16 @@ export default function Index(): JSX.Element {
} }
let keepAliveInterval: NodeJS.Timeout; let keepAliveInterval: NodeJS.Timeout;
const websocket = useWebSocket("wss://" + socketUrl, { const webSocket = useWebSocket("wss://" + socketUrl, {
onMessage, onMessage,
onOpen() { onOpen() {
keepAliveInterval = setInterval(() => { keepAliveInterval = setInterval(() => {
websocket.sendJsonMessage({ webSocket.sendJsonMessage({
type: MessageType.ACK, type: MessageType.ACK,
date: Date.now(), date: Date.now(),
} as AckMessage); } as AckMessage);
}, 1000); }, 1000);
keepAliveIntervals.push(keepAliveInterval); keepAliveIntervals.push(keepAliveInterval);
if (desiredNameInput.current?.value.length) {
websocket.sendJsonMessage({
type: MessageType.DESIRED_NAME,
date: Date.now(),
desiredName: desiredNameInput.current.value,
} as DesiredNameMessage);
}
}, },
onClose() { onClose() {
clearInterval(keepAliveInterval); clearInterval(keepAliveInterval);
@ -90,8 +96,25 @@ export default function Index(): JSX.Element {
(interval) => interval !== keepAliveInterval (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(() => { const trySendMessage = useCallback(() => {
if (!messageInput.current) { if (!messageInput.current) {
return; return;
@ -103,37 +126,20 @@ export default function Index(): JSX.Element {
messageInput.current.value = ""; messageInput.current.value = "";
websocket.sendJsonMessage({ webSocket.sendJsonMessage({
author: authorId, author: authorId,
type: MessageType.TEXT, type: MessageType.TEXT,
date: Date.now(), date: Date.now(),
content: messageText, content: messageText,
} as TextMessage); } as TextMessage);
}, [authorId, websocket]); }, [authorId, webSocket]);
const trySetSocketUrl = useCallback( function handleTyping() {
(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() {
if ( if (
websocket.readyState === ReadyState.OPEN && webSocket.readyState === WebSocketReadyState.OPEN &&
(shouldResendTyping || !currentlyTyping.includes(authorId)) (shouldResendTyping || !currentlyTyping.includes(authorId))
) { ) {
websocket.sendJsonMessage({ webSocket.sendJsonMessage({
type: MessageType.TYPING, type: MessageType.TYPING,
date: Date.now(), date: Date.now(),
} as TypingMessage); } as TypingMessage);
@ -193,25 +199,11 @@ export default function Index(): JSX.Element {
Your ID: <span style={{ fontWeight: "bold" }}>{authorId}</span> Your ID: <span style={{ fontWeight: "bold" }}>{authorId}</span>
</> </>
)}{" "} )}{" "}
<Connect tryConnect={tryConnect} />
<span>Ready state: </span> <span>Ready state: </span>
<span style={{ fontWeight: "bold" }}> <span style={{ fontWeight: "bold" }}>
{ReadyState[websocket.readyState]} ({websocket.readyState}) {WebSocketReadyState[webSocket.readyState]} ({webSocket.readyState})
</span> </span>
<label htmlFor="ws-url">WebSocket URL:</label>
<input
id="ws-url"
type="text"
value={socketUrl}
placeholder="wss://..."
onChange={(e) => trySetSocketUrl(e.target.value)}
/>
<label htmlFor="desired-name-input">Name:</label>
<input
ref={desiredNameInput}
id="desired-name-input"
type="text"
placeholder="..."
/>
</header> </header>
<div id="container"> <div id="container">
<section id="message-area"> <section id="message-area">
@ -232,7 +224,7 @@ export default function Index(): JSX.Element {
type="text" type="text"
placeholder="Type here..." placeholder="Type here..."
id="message-input" id="message-input"
onInput={handleInput} onInput={handleTyping}
onKeyPress={(keyEvent) => { onKeyPress={(keyEvent) => {
if (keyEvent.key === "Enter") { if (keyEvent.key === "Enter") {
trySendMessage(); trySendMessage();
@ -244,23 +236,11 @@ export default function Index(): JSX.Element {
</span> </span>
</div> </div>
</section> </section>
<section id="user-area"> <UserBar
<h2>Users</h2> connectedUsers={connectedUsers}
<ol> currentlyTyping={currentlyTyping}
{connectedUsers.map(({ id }) => ( getName={getName}
<li />
className={
"user" +
(currentlyTyping.includes(id) ? " typing" : "") +
(getName(id) !== id ? " nickname" : "")
}
key={id}
>
{getName(id)}
</li>
))}
</ol>
</section>
</div> </div>
</main> </main>
); );