format and use more aggressive tabstop widths

This commit is contained in:
Zhongheng Liu 2024-01-20 12:06:34 +02:00
commit 56feab2ea1
No known key found for this signature in database
7 changed files with 366 additions and 344 deletions

View file

@ -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) { if (!login) {
return <></>; return <></>;
} else } else

View file

@ -1,9 +1,9 @@
import React, { import React, {
ReactElement, ReactElement,
useContext, useContext,
useEffect, useEffect,
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import { MessageDisplay } from "../MessageDisplay/MessageDisplay"; import { MessageDisplay } from "../MessageDisplay/MessageDisplay";
import { Client } from "@stomp/stompjs"; import { Client } from "@stomp/stompjs";
@ -13,117 +13,128 @@ import strings from "../Intl/strings.json";
import { LangContext } from "../context"; import { LangContext } from "../context";
import { connectionAddress, endpoints } from "../consts"; import { connectionAddress, endpoints } from "../consts";
const Chat = ({ user }: { user: string }): React.ReactElement => { const Chat = ({ user }: { user: string }): React.ReactElement => {
const lang = useContext(LangContext); const lang = useContext(LangContext);
const chatPage = strings[lang].chat; const chatPage = strings[lang].chat;
const [messages, setMessages] = useState<ReactElement[]>([]); const [messages, setMessages] = useState<ReactElement[]>([]);
let stompClientRef = useRef( let stompClientRef = useRef(
new Client({ new Client({
brokerURL: connectionAddress, brokerURL: connectionAddress,
}) })
); );
// TODO solve issue with non-static markup // TODO solve issue with non-static markup
stompClientRef.current.onConnect = (frame) => { stompClientRef.current.onConnect = (frame) => {
stompClientRef.current.subscribe(endpoints.subscription, (message) => { stompClientRef.current.subscribe(
console.log(`Collected new message: ${message.body}`); endpoints.subscription,
const messageBody = JSON.parse(message.body) as Message; (message) => {
console.log(messageBody); console.log(
setMessages((message) => { `Collected new message: ${message.body}`
return message.concat([ );
<MessageDisplay const messageBody = JSON.parse(
key={`${messageBody.type}@${messageBody.timeMillis}`} message.body
{...messageBody} ) as Message;
/>, console.log(messageBody);
]); setMessages((message) => {
}); return message.concat([
}); <MessageDisplay
stompClientRef.current.publish({ key={`${messageBody.type}@${messageBody.timeMillis}`}
body: JSON.stringify({ {...messageBody}
type: MessageType.HELLO, />,
fromUserId: user, ]);
toUserId: "everyone", });
content: `${user} has joined the server!`, }
timeMillis: Date.now(), );
}), stompClientRef.current.publish({
destination: endpoints.destination, 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.onStompError = (frame) => {
stompClientRef.current.onWebSocketError = (error) => { console.error(
console.error("Error with websocket", error); "Broker reported error: " + frame.headers["message"]
}; );
console.error("Additional details: " + frame.body);
};
stompClientRef.current.onStompError = (frame) => { // Button press event handler.
console.error("Broker reported error: " + frame.headers["message"]); const sendData = () => {
console.error("Additional details: " + frame.body); const entryElement: HTMLInputElement = document.getElementById(
}; "data-entry"
) as HTMLInputElement;
// Button press event handler. if (entryElement.value === "") {
const sendData = () => { return;
const entryElement: HTMLInputElement = document.getElementById( }
"data-entry" const messageData: Message = {
) as HTMLInputElement; type: MessageType.MESSAGE,
if (entryElement.value === "") { fromUserId: user,
return; toUserId: "everyone",
} content: entryElement.value,
const messageData: Message = { timeMillis: Date.now(),
type: MessageType.MESSAGE, };
fromUserId: user, console.log(
toUserId: "everyone", `STOMP connection status: ${stompClientRef.current.connected}`
content: entryElement.value, );
timeMillis: Date.now(), stompClientRef.current.publish({
}; body: JSON.stringify(messageData),
console.log( destination: endpoints.destination,
`STOMP connection status: ${stompClientRef.current.connected}` headers: {
); "Content-Type":
stompClientRef.current.publish({ "application/json; charset=utf-8",
body: JSON.stringify(messageData), },
destination: endpoints.destination, });
headers: { entryElement.value = "";
"Content-Type": "application/json; charset=utf-8", };
}, useEffect(() => {
}); // Stomp client is disconnected after each re-render
entryElement.value = ""; // This should be actively avoided
}; stompClientRef.current.activate();
useEffect(() => { return () => {
// Stomp client is disconnected after each re-render stompClientRef.current.deactivate();
// This should be actively avoided };
stompClientRef.current.activate(); }, []);
return () => { // https://www.w3schools.com/jsref/obj_keyboardevent.asp
stompClientRef.current.deactivate(); document.addEventListener("keydown", (ev: KeyboardEvent) => {
}; if (ev.key === "Enter") {
}, []); sendData();
// https://www.w3schools.com/jsref/obj_keyboardevent.asp }
document.addEventListener("keydown", (ev: KeyboardEvent) => { });
if (ev.key === "Enter") { useEffect(() => {
sendData(); try {
} const elem = document.querySelector(
}); ".chat-inner-wrapper"
useEffect(() => { );
try { if (elem) {
const elem = document.querySelector(".chat-inner-wrapper"); elem.scrollTop = elem.scrollHeight;
if (elem) { } else {
elem.scrollTop = elem.scrollHeight; }
} else { } catch (err) {
} console.log("error encountered");
} catch (err) { }
console.log("error encountered"); return () => {};
} }, [messages]);
return () => {}; return (
}, [messages]); <fieldset className="chat">
return ( <legend>
<fieldset className="chat"> Logged in as <b>{user}</b>
<legend> </legend>
Logged in as <b>{user}</b> <div className="chat-inner-wrapper">{messages}</div>
</legend> <span className="entry-box">
<div className="chat-inner-wrapper">{messages}</div> <input id="data-entry"></input>
<span className="entry-box"> <button onClick={() => sendData()}>
<input id="data-entry"></input> {chatPage.sendButtonPrompt}
<button onClick={() => sendData()}> </button>
{chatPage.sendButtonPrompt} </span>
</button> </fieldset>
</span> );
</fieldset>
);
}; };
export default Chat; export default Chat;

View file

@ -1,3 +1,3 @@
.uname-error-text { .uname-error-text {
color: red; color: red;
} }

View file

@ -4,138 +4,156 @@ import { LoginType } from "../context";
import { User } from "../type/userTypes"; import { User } from "../type/userTypes";
import "./Login.css"; import "./Login.css";
const encrypt = (rawPasswordString: string) => { const encrypt = (rawPasswordString: string) => {
// TODO Encryption method stub // TODO Encryption method stub
return rawPasswordString; return rawPasswordString;
}; };
export const Login = ({ export const Login = ({
setLogin, setLogin,
}: { }: {
setLogin: (newLogin: LoginType | undefined) => void; setLogin: (newLogin: LoginType | undefined) => void;
}): React.ReactElement => { }): React.ReactElement => {
const [valid, setValid] = useState<boolean | undefined>(true); const [valid, setValid] = useState<boolean | undefined>(true);
const [validText, setValidText] = useState<string | undefined>(); const [validText, setValidText] = useState<string | undefined>();
const registrationHandler = () => { const registrationHandler = () => {
const uname = (document.getElementById("username") as HTMLInputElement) const uname = (
.value; document.getElementById("username") as HTMLInputElement
const passwd = encrypt( ).value;
(document.getElementById("passwd") as HTMLInputElement).value const passwd = encrypt(
); (document.getElementById("passwd") as HTMLInputElement)
fetch(`http://${domain}:${port}${endpoints.user}`, { .value
method: "POST", );
mode: "cors", fetch(`http://${domain}:${port}${endpoints.user}`, {
headers: contentTypes.json, method: "POST",
body: JSON.stringify({ mode: "cors",
userName: uname, headers: contentTypes.json,
dateJoined: Date.now(), body: JSON.stringify({
passwordHash: passwd, userName: uname,
}), dateJoined: Date.now(),
}).then((response) => { passwordHash: passwd,
if (response.status === 400) { }),
// 400 Bad request }).then((response) => {
console.log("Username is taken or invalid!"); if (response.status === 400) {
setValid(false); // 400 Bad request
setValidText("Username is taken or invalid!"); console.log("Username is taken or invalid!");
} else if (response.status === 200) { setValid(false);
// 200 OK setValidText("Username is taken or invalid!");
const futureDate = new Date(); } else if (response.status === 200) {
futureDate.setHours(futureDate.getHours() + 2); // 200 OK
setLogin({ const futureDate = new Date();
username: uname, futureDate.setHours(futureDate.getHours() + 2);
lastSeen: Date.now(), setLogin({
validUntil: futureDate.getUTCMilliseconds(), username: uname,
}); lastSeen: Date.now(),
document.title = `IRC User ${uname}`; validUntil: futureDate.getUTCMilliseconds(),
} });
}); document.title = `IRC User ${uname}`;
}; }
// login button press handler });
const loginHandler = () => { };
const uname = (document.getElementById("username") as HTMLInputElement) // login button press handler
.value; const loginHandler = () => {
const passwd = encrypt( const uname = (
(document.getElementById("passwd") as HTMLInputElement).value document.getElementById("username") as HTMLInputElement
); ).value;
// async invocation of Fetch API const passwd = encrypt(
fetch(`http://${domain}:${port}${endpoints.user}?name=${uname}`, { (document.getElementById("passwd") as HTMLInputElement)
method: "GET", .value
mode: "cors", );
}) // async invocation of Fetch API
.then((res) => { fetch(
if (res.status === 404) { `http://${domain}:${port}${endpoints.user}?name=${uname}`,
console.log("404 not found encountered"); {
throw new Error("Username does not exist"); method: "GET",
} else if (res.status === 200) { mode: "cors",
console.log("200 OK"); }
} )
return res.json(); .then((res) => {
}) if (res.status === 404) {
.then((userObject) => { console.log(
if (!userObject) { "404 not found encountered"
return; );
} throw new Error(
const user = userObject as User; "Username does not exist"
const validLogin = passwd === user.passwordHash; );
if (!validLogin) { } else if (res.status === 200) {
// login invalid console.log("200 OK");
throw new Error("Password incorrect!"); }
} else { return res.json();
// login valid })
const validUntilDate: Date = new Date(); .then((userObject) => {
validUntilDate.setHours(validUntilDate.getHours() + 2); if (!userObject) {
setLogin({ return;
username: user.userName, }
lastSeen: user.lastSeen, const user = userObject as User;
validUntil: validUntilDate.getUTCMilliseconds(), const validLogin = passwd === user.passwordHash;
}); if (!validLogin) {
document.title = `IRC User ${uname}`; // login invalid
} throw new Error("Password incorrect!");
}) } else {
.catch((reason: Error) => { // login valid
setValid(false); const validUntilDate: Date = new Date();
setValidText(reason.message); validUntilDate.setHours(
}); validUntilDate.getHours() + 2
}; );
return ( setLogin({
<div className="login"> username: user.userName,
<fieldset> lastSeen: user.lastSeen,
<legend>Login window</legend> validUntil: validUntilDate.getUTCMilliseconds(),
<p className="uname-error-text"> });
{valid && valid !== undefined ? "" : validText} document.title = `IRC User ${uname}`;
</p> }
<label htmlFor="username">Username: </label> })
<br /> .catch((reason: Error) => {
<input id="username" type="text"></input> setValid(false);
<br /> setValidText(reason.message);
<label htmlFor="passwd">Password: </label> });
<br /> };
<input id="passwd" type="password"></input> return (
<br /> <div className="login">
<button <fieldset>
type="submit" <legend>Login window</legend>
onClick={() => { <p className="uname-error-text">
loginHandler(); {valid && valid !== undefined
}} ? ""
> : validText}
Login </p>
</button> <label htmlFor="username">Username: </label>
<button <br />
type="submit" <input id="username" type="text"></input>
onClick={() => { <br />
registrationHandler(); <label htmlFor="passwd">Password: </label>
}} <br />
> <input id="passwd" type="password"></input>
Register <br />
</button> <button
<button disabled={!valid}
type="submit" type="submit"
onClick={() => { onClick={() => {
setLogin(undefined); loginHandler();
setValid(false); }}
}} >
> Login
Logout </button>
</button> <button
</fieldset> disabled={!valid}
</div> type="submit"
); onClick={() => {
registrationHandler();
}}
>
Register
</button>
<button
disabled={valid}
type="submit"
onClick={() => {
setLogin(undefined);
setValid(false);
}}
>
Logout
</button>
</fieldset>
</div>
);
}; };

View file

@ -4,57 +4,64 @@ import { LangContext } from "../context";
import strings from "../Intl/strings.json"; import strings from "../Intl/strings.json";
import "./MessageDisplay.css"; import "./MessageDisplay.css";
export const MessageDisplay = ({ export const MessageDisplay = ({
type, type,
fromUserId, fromUserId,
toUserId, toUserId,
content, content,
timeMillis, timeMillis,
}: Message): React.ReactElement<Message> => { }: Message): React.ReactElement<Message> => {
const dateTime: Date = new Date(timeMillis); const dateTime: Date = new Date(timeMillis);
const lang = useContext(LangContext); const lang = useContext(LangContext);
const msgPage = strings[lang].chat; const msgPage = strings[lang].chat;
/* FIXED funny error /* FIXED funny error
* DESCRIPTION * DESCRIPTION
* The line below was * The line below was
* return (<p>[{dateTime.toLocaleString(Intl.DateTimeFormat().resolvedOptions().timeZone)}]...</p>) * return (<p>[{dateTime.toLocaleString(Intl.DateTimeFormat().resolvedOptions().timeZone)}]...</p>)
* The line incorrectly generated a value of "UTC" as the parameter to toLocaleString() * 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. * 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. * This caused the return statement to fail, and the message fails to render, despite it being correctly committed to the db.
* Funny clown moment 🤡 * Funny clown moment 🤡
*/ */
const timeString = `${ const timeString = `${
dateTime.getHours() > 12 dateTime.getHours() > 12
? dateTime.getHours() - 12 ? dateTime.getHours() - 12
: dateTime.getHours() : dateTime.getHours()
}:${dateTime.getMinutes()} ${dateTime.getHours() > 12 ? "PM" : "AM"}`; }:${dateTime.getMinutes()} ${dateTime.getHours() > 12 ? "PM" : "AM"}`;
switch (type) { switch (type) {
case MessageType.HELLO as MessageType: case MessageType.HELLO as MessageType:
return ( return (
<p className="msg"> <p className="msg">
[{timeString}]{" "} [{timeString}]{" "}
{msgPage.joinMessage.replace("$userName", fromUserId)} {msgPage.joinMessage.replace(
</p> "$userName",
); fromUserId
case MessageType.MESSAGE as MessageType: )}
return ( </p>
<p className="msg"> );
[{timeString}]{" "} case MessageType.MESSAGE as MessageType:
{msgPage.serverMessage return (
.replace("$userName", fromUserId) <p className="msg">
.replace("$content", content)} [{timeString}]{" "}
</p> {msgPage.serverMessage
); .replace(
case MessageType.DATA as MessageType: "$userName",
return <></>; fromUserId
case MessageType.CHNAME as MessageType: )
return <></>; .replace("$content", content)}
default: </p>
console.error("Illegal MessageType reported!"); );
return ( case MessageType.DATA as MessageType:
<p className="msg-err"> return <></>;
[{timeString}] **THIS MESSAGE CANNOT BE CORRECTLY SHOWN case MessageType.CHNAME as MessageType:
BECAUSE THE CLIENT ENCOUNTERED AN ERROR** return <></>;
</p> default:
); console.error("Illegal MessageType reported!");
} return (
<p className="msg-err">
[{timeString}] **THIS MESSAGE CANNOT BE
CORRECTLY SHOWN BECAUSE THE CLIENT
ENCOUNTERED AN ERROR**
</p>
);
}
}; };

View file

@ -1,51 +1,46 @@
export const enum MessageType { export const enum MessageType {
MESSAGE = "MESSAGE", MESSAGE = "MESSAGE",
CHNAME = "CHNAME", CHNAME = "CHNAME",
HELLO = "HELLO", HELLO = "HELLO",
DATA = "DATA", DATA = "DATA",
} }
export enum SystemMessageCode { export enum SystemMessageCode {
REQ, REQ,
RES, RES,
ERR, ERR,
} }
export type HistoryFetchResult = { export type HistoryFetchResult = {
count: number; count: number;
items: Array<ChatMessage>; items: Array<ChatMessage>;
}; };
export type ErrorResult = { export type ErrorResult = {
text: string; text: string;
}; };
export type TimestampSendRequest = { export type TimestampSendRequest = {
ts: number; ts: number;
}; };
export type SystemMessage = { export type SystemMessage = {
code: SystemMessageCode; code: SystemMessageCode;
data: HistoryFetchResult | ErrorResult | TimestampSendRequest; data: HistoryFetchResult | ErrorResult | TimestampSendRequest;
}; };
export type ChatMessage = { export type ChatMessage = {
fromUserId: string; fromUserId: string;
toUserId: string; toUserId: string;
content: string; content: string;
timeMillis: number; timeMillis: number;
}; };
export type HelloMessage = { export type HelloMessage = {
fromUserId: string; fromUserId: string;
timeMillis: number; timeMillis: number;
}; };
export type DataMessage = {}; export type DataMessage = {};
export type Message = { export type Message = {
type: MessageType; type: MessageType;
// data: SystemMessage | ChatMessage | HelloMessage // data: SystemMessage | ChatMessage | HelloMessage
fromUserId: string; fromUserId: string;
toUserId: string; toUserId: string;
content: string; content: string;
timeMillis: number; timeMillis: number;
}; };
export const acceptedLangs = [ export const acceptedLangs = ["en_US", "zh_TW", "el_GR", "ar_SA"] as const;
"en_US",
"zh_TW",
"el_GR",
"ar_SA"
] as const;
export type LangType = (typeof acceptedLangs)[number]; export type LangType = (typeof acceptedLangs)[number];

View file

@ -1,7 +1,7 @@
export type User = { export type User = {
id: number; id: number;
userName: string; userName: string;
dateJoined: number; dateJoined: number;
lastSeen: number; lastSeen: number;
passwordHash: string; passwordHash: string;
}; };