From 56feab2ea11d3da6032e02b7845235ef1e01c9f0 Mon Sep 17 00:00:00 2001 From: Zhongheng Liu Date: Sat, 20 Jan 2024 12:06:34 +0200 Subject: [PATCH] format and use more aggressive tabstop widths --- src/App.tsx | 9 - src/Chat/Chat.tsx | 241 +++++++++++----------- src/Login/Login.css | 4 +- src/Login/Login.tsx | 278 ++++++++++++++------------ src/MessageDisplay/MessageDisplay.tsx | 111 +++++----- src/type/messageTypes.ts | 57 +++--- src/type/userTypes.ts | 10 +- 7 files changed, 366 insertions(+), 344 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 47d2f05..e5d3328 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -90,15 +90,6 @@ const App = ({ } }); }; - // if (!username) { - // var newName = prompt(home.userNamePrompt) as string; - // while (!validateName(newName)) { - // console.log(newName); - - // prompt("Username invalid! Please enter again.") as string; - // } - // setUsername(newName); - // } if (!login) { return <>; } else diff --git a/src/Chat/Chat.tsx b/src/Chat/Chat.tsx index 00e0d0f..7aff06f 100644 --- a/src/Chat/Chat.tsx +++ b/src/Chat/Chat.tsx @@ -1,9 +1,9 @@ import React, { - ReactElement, - useContext, - useEffect, - useRef, - useState, + ReactElement, + useContext, + useEffect, + useRef, + useState, } from "react"; import { MessageDisplay } from "../MessageDisplay/MessageDisplay"; import { Client } from "@stomp/stompjs"; @@ -13,117 +13,128 @@ import strings from "../Intl/strings.json"; import { LangContext } from "../context"; import { connectionAddress, endpoints } from "../consts"; const Chat = ({ user }: { user: string }): React.ReactElement => { - const lang = useContext(LangContext); - const chatPage = strings[lang].chat; - const [messages, setMessages] = useState([]); - let stompClientRef = useRef( - new Client({ - brokerURL: connectionAddress, - }) - ); - // TODO solve issue with non-static markup - stompClientRef.current.onConnect = (frame) => { - stompClientRef.current.subscribe(endpoints.subscription, (message) => { - console.log(`Collected new message: ${message.body}`); - const messageBody = JSON.parse(message.body) as Message; - console.log(messageBody); - setMessages((message) => { - return message.concat([ - , - ]); - }); - }); - stompClientRef.current.publish({ - body: JSON.stringify({ - type: MessageType.HELLO, - fromUserId: user, - toUserId: "everyone", - content: `${user} has joined the server!`, - timeMillis: Date.now(), - }), - destination: endpoints.destination, - }); - }; + const lang = useContext(LangContext); + const chatPage = strings[lang].chat; + const [messages, setMessages] = useState([]); + let stompClientRef = useRef( + new Client({ + brokerURL: connectionAddress, + }) + ); + // TODO solve issue with non-static markup + stompClientRef.current.onConnect = (frame) => { + stompClientRef.current.subscribe( + endpoints.subscription, + (message) => { + console.log( + `Collected new message: ${message.body}` + ); + const messageBody = JSON.parse( + message.body + ) as Message; + console.log(messageBody); + setMessages((message) => { + return message.concat([ + , + ]); + }); + } + ); + stompClientRef.current.publish({ + body: JSON.stringify({ + type: MessageType.HELLO, + fromUserId: user, + toUserId: "everyone", + content: `${user} has joined the server!`, + timeMillis: Date.now(), + }), + destination: endpoints.destination, + }); + }; + // Generic error handlers + stompClientRef.current.onWebSocketError = (error) => { + console.error("Error with websocket", error); + }; - // Generic error handlers - stompClientRef.current.onWebSocketError = (error) => { - console.error("Error with websocket", error); - }; + stompClientRef.current.onStompError = (frame) => { + console.error( + "Broker reported error: " + frame.headers["message"] + ); + console.error("Additional details: " + frame.body); + }; - stompClientRef.current.onStompError = (frame) => { - console.error("Broker reported error: " + frame.headers["message"]); - console.error("Additional details: " + frame.body); - }; - - // Button press event handler. - const sendData = () => { - const entryElement: HTMLInputElement = document.getElementById( - "data-entry" - ) as HTMLInputElement; - if (entryElement.value === "") { - return; - } - const messageData: Message = { - type: MessageType.MESSAGE, - fromUserId: user, - toUserId: "everyone", - content: entryElement.value, - timeMillis: Date.now(), - }; - console.log( - `STOMP connection status: ${stompClientRef.current.connected}` - ); - stompClientRef.current.publish({ - body: JSON.stringify(messageData), - destination: endpoints.destination, - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - }); - entryElement.value = ""; - }; - useEffect(() => { - // Stomp client is disconnected after each re-render - // This should be actively avoided - stompClientRef.current.activate(); - return () => { - stompClientRef.current.deactivate(); - }; - }, []); - // https://www.w3schools.com/jsref/obj_keyboardevent.asp - document.addEventListener("keydown", (ev: KeyboardEvent) => { - if (ev.key === "Enter") { - sendData(); - } - }); - useEffect(() => { - try { - const elem = document.querySelector(".chat-inner-wrapper"); - if (elem) { - elem.scrollTop = elem.scrollHeight; - } else { - } - } catch (err) { - console.log("error encountered"); - } - return () => {}; - }, [messages]); - return ( -
- - Logged in as {user} - -
{messages}
- - - - -
- ); + // Button press event handler. + const sendData = () => { + const entryElement: HTMLInputElement = document.getElementById( + "data-entry" + ) as HTMLInputElement; + if (entryElement.value === "") { + return; + } + const messageData: Message = { + type: MessageType.MESSAGE, + fromUserId: user, + toUserId: "everyone", + content: entryElement.value, + timeMillis: Date.now(), + }; + console.log( + `STOMP connection status: ${stompClientRef.current.connected}` + ); + stompClientRef.current.publish({ + body: JSON.stringify(messageData), + destination: endpoints.destination, + headers: { + "Content-Type": + "application/json; charset=utf-8", + }, + }); + entryElement.value = ""; + }; + useEffect(() => { + // Stomp client is disconnected after each re-render + // This should be actively avoided + stompClientRef.current.activate(); + return () => { + stompClientRef.current.deactivate(); + }; + }, []); + // https://www.w3schools.com/jsref/obj_keyboardevent.asp + document.addEventListener("keydown", (ev: KeyboardEvent) => { + if (ev.key === "Enter") { + sendData(); + } + }); + useEffect(() => { + try { + const elem = document.querySelector( + ".chat-inner-wrapper" + ); + if (elem) { + elem.scrollTop = elem.scrollHeight; + } else { + } + } catch (err) { + console.log("error encountered"); + } + return () => {}; + }, [messages]); + return ( +
+ + Logged in as {user} + +
{messages}
+ + + + +
+ ); }; export default Chat; diff --git a/src/Login/Login.css b/src/Login/Login.css index 957484e..21bceff 100644 --- a/src/Login/Login.css +++ b/src/Login/Login.css @@ -1,3 +1,3 @@ .uname-error-text { - color: red; -} \ No newline at end of file + color: red; +} diff --git a/src/Login/Login.tsx b/src/Login/Login.tsx index 6de932c..7f5ff79 100644 --- a/src/Login/Login.tsx +++ b/src/Login/Login.tsx @@ -4,138 +4,156 @@ import { LoginType } from "../context"; import { User } from "../type/userTypes"; import "./Login.css"; const encrypt = (rawPasswordString: string) => { - // TODO Encryption method stub - return rawPasswordString; + // TODO Encryption method stub + return rawPasswordString; }; export const Login = ({ - setLogin, + setLogin, }: { - setLogin: (newLogin: LoginType | undefined) => void; + setLogin: (newLogin: LoginType | undefined) => void; }): React.ReactElement => { - const [valid, setValid] = useState(true); - const [validText, setValidText] = useState(); - const registrationHandler = () => { - const uname = (document.getElementById("username") as HTMLInputElement) - .value; - const passwd = encrypt( - (document.getElementById("passwd") as HTMLInputElement).value - ); - fetch(`http://${domain}:${port}${endpoints.user}`, { - method: "POST", - mode: "cors", - headers: contentTypes.json, - body: JSON.stringify({ - userName: uname, - dateJoined: Date.now(), - passwordHash: passwd, - }), - }).then((response) => { - if (response.status === 400) { - // 400 Bad request - console.log("Username is taken or invalid!"); - setValid(false); - setValidText("Username is taken or invalid!"); - } else if (response.status === 200) { - // 200 OK - const futureDate = new Date(); - futureDate.setHours(futureDate.getHours() + 2); - setLogin({ - username: uname, - lastSeen: Date.now(), - validUntil: futureDate.getUTCMilliseconds(), - }); - document.title = `IRC User ${uname}`; - } - }); - }; - // login button press handler - const loginHandler = () => { - const uname = (document.getElementById("username") as HTMLInputElement) - .value; - const passwd = encrypt( - (document.getElementById("passwd") as HTMLInputElement).value - ); - // async invocation of Fetch API - fetch(`http://${domain}:${port}${endpoints.user}?name=${uname}`, { - method: "GET", - mode: "cors", - }) - .then((res) => { - if (res.status === 404) { - console.log("404 not found encountered"); - throw new Error("Username does not exist"); - } else if (res.status === 200) { - console.log("200 OK"); - } - return res.json(); - }) - .then((userObject) => { - if (!userObject) { - return; - } - const user = userObject as User; - const validLogin = passwd === user.passwordHash; - if (!validLogin) { - // login invalid - throw new Error("Password incorrect!"); - } else { - // login valid - const validUntilDate: Date = new Date(); - validUntilDate.setHours(validUntilDate.getHours() + 2); - setLogin({ - username: user.userName, - lastSeen: user.lastSeen, - validUntil: validUntilDate.getUTCMilliseconds(), - }); - document.title = `IRC User ${uname}`; - } - }) - .catch((reason: Error) => { - setValid(false); - setValidText(reason.message); - }); - }; - return ( -
-
- Login window -

- {valid && valid !== undefined ? "" : validText} -

- -
- -
- -
- -
- - - -
-
- ); + const [valid, setValid] = useState(true); + const [validText, setValidText] = useState(); + const registrationHandler = () => { + const uname = ( + document.getElementById("username") as HTMLInputElement + ).value; + const passwd = encrypt( + (document.getElementById("passwd") as HTMLInputElement) + .value + ); + fetch(`http://${domain}:${port}${endpoints.user}`, { + method: "POST", + mode: "cors", + headers: contentTypes.json, + body: JSON.stringify({ + userName: uname, + dateJoined: Date.now(), + passwordHash: passwd, + }), + }).then((response) => { + if (response.status === 400) { + // 400 Bad request + console.log("Username is taken or invalid!"); + setValid(false); + setValidText("Username is taken or invalid!"); + } else if (response.status === 200) { + // 200 OK + const futureDate = new Date(); + futureDate.setHours(futureDate.getHours() + 2); + setLogin({ + username: uname, + lastSeen: Date.now(), + validUntil: futureDate.getUTCMilliseconds(), + }); + document.title = `IRC User ${uname}`; + } + }); + }; + // login button press handler + const loginHandler = () => { + const uname = ( + document.getElementById("username") as HTMLInputElement + ).value; + const passwd = encrypt( + (document.getElementById("passwd") as HTMLInputElement) + .value + ); + // async invocation of Fetch API + fetch( + `http://${domain}:${port}${endpoints.user}?name=${uname}`, + { + method: "GET", + mode: "cors", + } + ) + .then((res) => { + if (res.status === 404) { + console.log( + "404 not found encountered" + ); + throw new Error( + "Username does not exist" + ); + } else if (res.status === 200) { + console.log("200 OK"); + } + return res.json(); + }) + .then((userObject) => { + if (!userObject) { + return; + } + const user = userObject as User; + const validLogin = passwd === user.passwordHash; + if (!validLogin) { + // login invalid + throw new Error("Password incorrect!"); + } else { + // login valid + const validUntilDate: Date = new Date(); + validUntilDate.setHours( + validUntilDate.getHours() + 2 + ); + setLogin({ + username: user.userName, + lastSeen: user.lastSeen, + validUntil: validUntilDate.getUTCMilliseconds(), + }); + document.title = `IRC User ${uname}`; + } + }) + .catch((reason: Error) => { + setValid(false); + setValidText(reason.message); + }); + }; + return ( +
+
+ Login window +

+ {valid && valid !== undefined + ? "" + : validText} +

+ +
+ +
+ +
+ +
+ + + +
+
+ ); }; diff --git a/src/MessageDisplay/MessageDisplay.tsx b/src/MessageDisplay/MessageDisplay.tsx index 19853ca..92e9e79 100644 --- a/src/MessageDisplay/MessageDisplay.tsx +++ b/src/MessageDisplay/MessageDisplay.tsx @@ -4,57 +4,64 @@ import { LangContext } from "../context"; import strings from "../Intl/strings.json"; import "./MessageDisplay.css"; export const MessageDisplay = ({ - type, - fromUserId, - toUserId, - content, - timeMillis, + type, + fromUserId, + toUserId, + content, + timeMillis, }: Message): React.ReactElement => { - const dateTime: Date = new Date(timeMillis); - const lang = useContext(LangContext); - const msgPage = strings[lang].chat; - /* FIXED funny error - * DESCRIPTION - * The line below was - * return (

[{dateTime.toLocaleString(Intl.DateTimeFormat().resolvedOptions().timeZone)}]...

) - * The line incorrectly generated a value of "UTC" as the parameter to toLocaleString() - * While "UTC" is an accepted string value, in EEST, aka. "Europe/Athens" timezone string is not an acceptable parameter. - * This caused the return statement to fail, and the message fails to render, despite it being correctly committed to the db. - * Funny clown moment 🤡 - */ - const timeString = `${ - dateTime.getHours() > 12 - ? dateTime.getHours() - 12 - : dateTime.getHours() - }:${dateTime.getMinutes()} ${dateTime.getHours() > 12 ? "PM" : "AM"}`; - switch (type) { - case MessageType.HELLO as MessageType: - return ( -

- [{timeString}]{" "} - {msgPage.joinMessage.replace("$userName", fromUserId)} -

- ); - case MessageType.MESSAGE as MessageType: - return ( -

- [{timeString}]{" "} - {msgPage.serverMessage - .replace("$userName", fromUserId) - .replace("$content", content)} -

- ); - case MessageType.DATA as MessageType: - return <>; - case MessageType.CHNAME as MessageType: - return <>; - default: - console.error("Illegal MessageType reported!"); - return ( -

- [{timeString}] **THIS MESSAGE CANNOT BE CORRECTLY SHOWN - BECAUSE THE CLIENT ENCOUNTERED AN ERROR** -

- ); - } + const dateTime: Date = new Date(timeMillis); + const lang = useContext(LangContext); + const msgPage = strings[lang].chat; + /* FIXED funny error + * DESCRIPTION + * The line below was + * return (

[{dateTime.toLocaleString(Intl.DateTimeFormat().resolvedOptions().timeZone)}]...

) + * The line incorrectly generated a value of "UTC" as the parameter to toLocaleString() + * While "UTC" is an accepted string value, in EEST, aka. "Europe/Athens" timezone string is not an acceptable parameter. + * This caused the return statement to fail, and the message fails to render, despite it being correctly committed to the db. + * Funny clown moment 🤡 + */ + const timeString = `${ + dateTime.getHours() > 12 + ? dateTime.getHours() - 12 + : dateTime.getHours() + }:${dateTime.getMinutes()} ${dateTime.getHours() > 12 ? "PM" : "AM"}`; + switch (type) { + case MessageType.HELLO as MessageType: + return ( +

+ [{timeString}]{" "} + {msgPage.joinMessage.replace( + "$userName", + fromUserId + )} +

+ ); + case MessageType.MESSAGE as MessageType: + return ( +

+ [{timeString}]{" "} + {msgPage.serverMessage + .replace( + "$userName", + fromUserId + ) + .replace("$content", content)} +

+ ); + case MessageType.DATA as MessageType: + return <>; + case MessageType.CHNAME as MessageType: + return <>; + default: + console.error("Illegal MessageType reported!"); + return ( +

+ [{timeString}] **THIS MESSAGE CANNOT BE + CORRECTLY SHOWN BECAUSE THE CLIENT + ENCOUNTERED AN ERROR** +

+ ); + } }; diff --git a/src/type/messageTypes.ts b/src/type/messageTypes.ts index 3243d25..f83594a 100644 --- a/src/type/messageTypes.ts +++ b/src/type/messageTypes.ts @@ -1,51 +1,46 @@ export const enum MessageType { - MESSAGE = "MESSAGE", - CHNAME = "CHNAME", - HELLO = "HELLO", - DATA = "DATA", + MESSAGE = "MESSAGE", + CHNAME = "CHNAME", + HELLO = "HELLO", + DATA = "DATA", } export enum SystemMessageCode { - REQ, - RES, - ERR, + REQ, + RES, + ERR, } export type HistoryFetchResult = { - count: number; - items: Array; + count: number; + items: Array; }; export type ErrorResult = { - text: string; + text: string; }; export type TimestampSendRequest = { - ts: number; + ts: number; }; export type SystemMessage = { - code: SystemMessageCode; - data: HistoryFetchResult | ErrorResult | TimestampSendRequest; + code: SystemMessageCode; + data: HistoryFetchResult | ErrorResult | TimestampSendRequest; }; export type ChatMessage = { - fromUserId: string; - toUserId: string; - content: string; - timeMillis: number; + fromUserId: string; + toUserId: string; + content: string; + timeMillis: number; }; export type HelloMessage = { - fromUserId: string; - timeMillis: number; + fromUserId: string; + timeMillis: number; }; export type DataMessage = {}; export type Message = { - type: MessageType; - // data: SystemMessage | ChatMessage | HelloMessage - fromUserId: string; - toUserId: string; - content: string; - timeMillis: number; + type: MessageType; + // data: SystemMessage | ChatMessage | HelloMessage + fromUserId: string; + toUserId: string; + content: string; + timeMillis: number; }; -export const acceptedLangs = [ - "en_US", - "zh_TW", - "el_GR", - "ar_SA" -] as const; +export const acceptedLangs = ["en_US", "zh_TW", "el_GR", "ar_SA"] as const; export type LangType = (typeof acceptedLangs)[number]; diff --git a/src/type/userTypes.ts b/src/type/userTypes.ts index 7d03592..b888745 100644 --- a/src/type/userTypes.ts +++ b/src/type/userTypes.ts @@ -1,7 +1,7 @@ export type User = { - id: number; - userName: string; - dateJoined: number; - lastSeen: number; - passwordHash: string; + id: number; + userName: string; + dateJoined: number; + lastSeen: number; + passwordHash: string; };