aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZhongheng Liu <z.liu@outlook.com.gr>2024-01-14 14:05:50 +0200
committerZhongheng Liu <z.liu@outlook.com.gr>2024-01-14 14:05:50 +0200
commit847ecd9a69efe9e0395f78f3b2e1729d181ec109 (patch)
tree2841fcc4b037ce393be58e7d40dc9cee612bf1a4
parent728bf3385633e3a9848890bb2a4b8526ccc11638 (diff)
downloadepq-web-847ecd9a69efe9e0395f78f3b2e1729d181ec109.tar.gz
epq-web-847ecd9a69efe9e0395f78f3b2e1729d181ec109.tar.bz2
epq-web-847ecd9a69efe9e0395f78f3b2e1729d181ec109.zip
Solved issue previously ignored by statically rendering components through
unrelated bug Enter keypress event fires the value presence checker incorrectly This commit allows further functionality to be expanded to the application such as proper registration support and complex user types
-rw-r--r--src/App.tsx44
-rw-r--r--src/Chat/Chat.css2
-rw-r--r--src/Chat/Chat.tsx118
-rw-r--r--src/Chat/ChatWrapper.tsx120
-rw-r--r--src/Chat/server.ts44
5 files changed, 193 insertions, 135 deletions
diff --git a/src/App.tsx b/src/App.tsx
index 0932852..9542d36 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,19 +1,31 @@
import React, { useState } from "react";
-import ChatWrapper from "./Chat/Chat";
+import ChatWrapper from "./Chat/ChatWrapper";
import "./App.css";
+import { Message } from "./Chat/types";
+import { MessageContainer } from "./Chat/MessageContainer";
const App = (): React.ReactElement => {
- const [username, setUsername] = useState<string>()
- if (!username) {
- const newName = prompt("Username:") as string
- setUsername(newName)
- }
- return (
- <div className="App">
- <h1>Local Area Network Chat Application</h1>
- <pre>This web application was built for the purposes of an EPQ project.</pre>
-
- {<ChatWrapper user={username as string} brokerURL=""/> }
- </div>
- )
-}
-export default App; \ No newline at end of file
+ const [username, setUsername] = useState<string>();
+ const [messages, setMessages] = useState<Message[]>([]);
+ if (!username) {
+ const newName = prompt("Username:") as string;
+ setUsername(newName);
+ }
+ return (
+ <div className="App">
+ <h1>Local Area Network Chat Application</h1>
+ <pre>
+ This web application was built for the purposes of an EPQ
+ project.
+ </pre>
+ {messages.map((message) => {
+ return <MessageContainer {...message} />;
+ })}
+ {
+ <ChatWrapper
+ user={username as string}
+ />
+ }
+ </div>
+ );
+};
+export default App;
diff --git a/src/Chat/Chat.css b/src/Chat/Chat.css
index bee333e..c5b74e3 100644
--- a/src/Chat/Chat.css
+++ b/src/Chat/Chat.css
@@ -3,7 +3,7 @@
overflow-y: auto;
overflow-wrap: normal;
display: flex;
- flex-direction: column-reverse;
+ flex-direction: column;
}
.entry-box {
diff --git a/src/Chat/Chat.tsx b/src/Chat/Chat.tsx
deleted file mode 100644
index c2e92ba..0000000
--- a/src/Chat/Chat.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import React, { useEffect, useState } from "react";
-import { MessageContainer } from "./MessageContainer";
-import { Client, Stomp, StompHeaders } from "@stomp/stompjs";
-import { Message, MessageType } from "./types";
-import { renderToStaticMarkup } from 'react-dom/server';
-import './Chat.css';
-// The last bit of magic sauce to make this work
-// EXPLANATION
-//
-const domain = window.location.hostname
-const port = "8080"
-const connectionAddress = `ws://${domain}:${port}/ws`
-const endpoints = {
- destination: "/app/chat",
- subscription: "/sub/chat",
- history: "/api/v1/chat/history/"
-}
-const ChatWrapper = (
- {
- user,
- brokerURL,
- }:
- {
- user: string,
- brokerURL: string,
- }
-): React.ReactElement => {
- const stompClient = new Client({
- brokerURL: connectionAddress
- })
- // TODO solve issue with non-static markup
- stompClient.onConnect = (frame) => {
- stompClient.subscribe(endpoints.subscription, (message) => {
- console.log(`Collected new message: ${message.body}`);
- const messageBody = JSON.parse(message.body) as Message
- // if (messageBody.type !== MessageType.MESSAGE) {return;}
- const messageElement = <MessageContainer {...messageBody} />
- console.log(messageElement);
-
- // Temporary solution
- // The solution lacks interactibility - because it's static markup
- const container = document.getElementById("chat-inner") as HTMLDivElement
-
- // Truly horrible and disgusting
- container.innerHTML += renderToStaticMarkup(messageElement)
- });
- stompClient.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
- stompClient.onWebSocketError = (error) => {
- console.error('Error with websocket', error);
- };
-
- stompClient.onStompError = (frame) => {
- console.error('Broker reported error: ' + frame.headers['message']);
- console.error('Additional details: ' + frame.body);
- };
-
- // Button press event handler.
- const sendData = () => {
- console.log("WebSockets handler invoked.")
- // There must be a react-native and non-document-getElementById way to do this
- // TODO Explore
- const entryElement: HTMLInputElement = document.getElementById("data-entry") as HTMLInputElement
- if (!entryElement.value) {alert("Message cannot be empty!"); return;}
- const messageData: Message =
- {
- type: MessageType.MESSAGE,
- fromUserId: user,
- toUserId: "everyone",
- content: entryElement.value,
- timeMillis: Date.now()
- }
- console.log(`STOMP connection status: ${stompClient.connected}`);
- stompClient.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
- stompClient.activate()
- return () => {
- stompClient.deactivate()
- }
- }, [stompClient])
- // https://www.w3schools.com/jsref/obj_keyboardevent.asp
- document.addEventListener("keypress", (ev: KeyboardEvent) => {
- if (ev.key == "Enter") {
- sendData();
- }
- })
- return (
- <div className="chat">
- <div className="chat-inner-wrapper">
- <div id="chat-inner">
- </div>
- </div>
- <span className="entry-box"><input id="data-entry"></input><button onClick={() => sendData()}>Send</button></span>
- </div>
- )
-}
-export default ChatWrapper; \ No newline at end of file
diff --git a/src/Chat/ChatWrapper.tsx b/src/Chat/ChatWrapper.tsx
new file mode 100644
index 0000000..66e0301
--- /dev/null
+++ b/src/Chat/ChatWrapper.tsx
@@ -0,0 +1,120 @@
+import React, { ReactElement, useEffect, useRef, useState } from "react";
+import { MessageContainer } from "./MessageContainer";
+import { Client, Stomp, StompHeaders } from "@stomp/stompjs";
+import { Message, MessageType } from "./types";
+import { renderToStaticMarkup } from "react-dom/server";
+import "./Chat.css";
+// The last bit of magic sauce to make this work
+// EXPLANATION
+//
+const domain = window.location.hostname;
+const port = "8080";
+const connectionAddress = `ws://${domain}:${port}/ws`;
+const endpoints = {
+ destination: "/app/chat",
+ subscription: "/sub/chat",
+ history: "/api/v1/msg/",
+};
+const ChatWrapper = ({ user }: { user: string }): React.ReactElement => {
+ 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([
+ <MessageContainer
+ key={`${messageBody.type}@${messageBody.timeMillis}`}
+ {...messageBody}
+ />,
+ ]);
+ });
+ console.log(messages);
+ });
+ 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);
+ };
+
+ stompClientRef.current.onStompError = (frame) => {
+ console.error("Broker reported error: " + frame.headers["message"]);
+ console.error("Additional details: " + frame.body);
+ };
+
+ // Button press event handler.
+ const sendData = () => {
+ console.log("WebSockets handler invoked.");
+ // There must be a react-native and non-document-getElementById way to do this
+ // TODO Explore
+ const entryElement: HTMLInputElement = document.getElementById(
+ "data-entry"
+ ) as HTMLInputElement;
+ if (!entryElement.value) {
+ alert("Message cannot be empty!");
+ 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();
+ };
+ }, [stompClientRef]);
+ // https://www.w3schools.com/jsref/obj_keyboardevent.asp
+ document.addEventListener("keypress", (ev: KeyboardEvent) => {
+ if (ev.key == "Enter") {
+ sendData();
+ }
+ });
+ return (
+ <div className="chat">
+ <div className="chat-inner-wrapper">
+ {messages}
+ </div>
+ <span className="entry-box">
+ <input id="data-entry"></input>
+ <button onClick={() => sendData()}>Send</button>
+ </span>
+ </div>
+ );
+};
+export default ChatWrapper;
diff --git a/src/Chat/server.ts b/src/Chat/server.ts
new file mode 100644
index 0000000..d155270
--- /dev/null
+++ b/src/Chat/server.ts
@@ -0,0 +1,44 @@
+import { Client } from "@stomp/stompjs";
+import { Message, MessageType } from "./types";
+const domain = window.location.hostname
+const port = "8080"
+const connectionAddress = `ws://${domain}:${port}/ws`
+const endpoints = {
+ destination: "/app/chat",
+ subscription: "/sub/chat",
+ history: "/api/v1/msg/"
+}
+export const createStompConnection = (user: string, subUpdateHandler: (message: Message) => void) => {
+ const stompClient = new Client({
+ brokerURL: connectionAddress
+ })
+ stompClient.onConnect = (frame) => {
+ stompClient.subscribe(endpoints.subscription, (message) => {
+ console.log(`Collected new message: ${message.body}`);
+ const messageBody = JSON.parse(message.body) as Message
+ console.log(messageBody);
+ subUpdateHandler(messageBody);
+ });
+ stompClient.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
+ stompClient.onWebSocketError = (error) => {
+ console.error('Error with websocket', error);
+ };
+
+ stompClient.onStompError = (frame) => {
+ console.error('Broker reported error: ' + frame.headers['message']);
+ console.error('Additional details: ' + frame.body);
+ };
+ return stompClient;
+} \ No newline at end of file