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) {
return <></>;
} else

View file

@ -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<ReactElement[]>([]);
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([
<MessageDisplay
key={`${messageBody.type}@${messageBody.timeMillis}`}
{...messageBody}
/>,
]);
});
});
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<ReactElement[]>([]);
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([
<MessageDisplay
key={`${messageBody.type}@${messageBody.timeMillis}`}
{...messageBody}
/>,
]);
});
}
);
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 (
<fieldset className="chat">
<legend>
Logged in as <b>{user}</b>
</legend>
<div className="chat-inner-wrapper">{messages}</div>
<span className="entry-box">
<input id="data-entry"></input>
<button onClick={() => sendData()}>
{chatPage.sendButtonPrompt}
</button>
</span>
</fieldset>
);
// 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 (
<fieldset className="chat">
<legend>
Logged in as <b>{user}</b>
</legend>
<div className="chat-inner-wrapper">{messages}</div>
<span className="entry-box">
<input id="data-entry"></input>
<button onClick={() => sendData()}>
{chatPage.sendButtonPrompt}
</button>
</span>
</fieldset>
);
};
export default Chat;

View file

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

View file

@ -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<boolean | undefined>(true);
const [validText, setValidText] = useState<string | undefined>();
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 (
<div className="login">
<fieldset>
<legend>Login window</legend>
<p className="uname-error-text">
{valid && valid !== undefined ? "" : validText}
</p>
<label htmlFor="username">Username: </label>
<br />
<input id="username" type="text"></input>
<br />
<label htmlFor="passwd">Password: </label>
<br />
<input id="passwd" type="password"></input>
<br />
<button
type="submit"
onClick={() => {
loginHandler();
}}
>
Login
</button>
<button
type="submit"
onClick={() => {
registrationHandler();
}}
>
Register
</button>
<button
type="submit"
onClick={() => {
setLogin(undefined);
setValid(false);
}}
>
Logout
</button>
</fieldset>
</div>
);
const [valid, setValid] = useState<boolean | undefined>(true);
const [validText, setValidText] = useState<string | undefined>();
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 (
<div className="login">
<fieldset>
<legend>Login window</legend>
<p className="uname-error-text">
{valid && valid !== undefined
? ""
: validText}
</p>
<label htmlFor="username">Username: </label>
<br />
<input id="username" type="text"></input>
<br />
<label htmlFor="passwd">Password: </label>
<br />
<input id="passwd" type="password"></input>
<br />
<button
disabled={!valid}
type="submit"
onClick={() => {
loginHandler();
}}
>
Login
</button>
<button
disabled={!valid}
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 "./MessageDisplay.css";
export const MessageDisplay = ({
type,
fromUserId,
toUserId,
content,
timeMillis,
type,
fromUserId,
toUserId,
content,
timeMillis,
}: Message): React.ReactElement<Message> => {
const dateTime: Date = new Date(timeMillis);
const lang = useContext(LangContext);
const msgPage = strings[lang].chat;
/* FIXED funny error
* DESCRIPTION
* The line below was
* return (<p>[{dateTime.toLocaleString(Intl.DateTimeFormat().resolvedOptions().timeZone)}]...</p>)
* 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 (
<p className="msg">
[{timeString}]{" "}
{msgPage.joinMessage.replace("$userName", fromUserId)}
</p>
);
case MessageType.MESSAGE as MessageType:
return (
<p className="msg">
[{timeString}]{" "}
{msgPage.serverMessage
.replace("$userName", fromUserId)
.replace("$content", content)}
</p>
);
case MessageType.DATA as MessageType:
return <></>;
case MessageType.CHNAME as MessageType:
return <></>;
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>
);
}
const dateTime: Date = new Date(timeMillis);
const lang = useContext(LangContext);
const msgPage = strings[lang].chat;
/* FIXED funny error
* DESCRIPTION
* The line below was
* return (<p>[{dateTime.toLocaleString(Intl.DateTimeFormat().resolvedOptions().timeZone)}]...</p>)
* 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 (
<p className="msg">
[{timeString}]{" "}
{msgPage.joinMessage.replace(
"$userName",
fromUserId
)}
</p>
);
case MessageType.MESSAGE as MessageType:
return (
<p className="msg">
[{timeString}]{" "}
{msgPage.serverMessage
.replace(
"$userName",
fromUserId
)
.replace("$content", content)}
</p>
);
case MessageType.DATA as MessageType:
return <></>;
case MessageType.CHNAME as MessageType:
return <></>;
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 {
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<ChatMessage>;
count: number;
items: Array<ChatMessage>;
};
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];

View file

@ -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;
};