From 847ecd9a69efe9e0395f78f3b2e1729d181ec109 Mon Sep 17 00:00:00 2001 From: Zhongheng Liu Date: Sun, 14 Jan 2024 14:05:50 +0200 Subject: [PATCH] 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 --- src/App.tsx | 44 ++++++++------ src/Chat/Chat.css | 2 +- src/Chat/Chat.tsx | 118 -------------------------------------- src/Chat/ChatWrapper.tsx | 120 +++++++++++++++++++++++++++++++++++++++ src/Chat/server.ts | 44 ++++++++++++++ 5 files changed, 193 insertions(+), 135 deletions(-) delete mode 100644 src/Chat/Chat.tsx create mode 100644 src/Chat/ChatWrapper.tsx create mode 100644 src/Chat/server.ts 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() - if (!username) { - const newName = prompt("Username:") as string - setUsername(newName) - } - return ( -
-

Local Area Network Chat Application

-
This web application was built for the purposes of an EPQ project.
- - { } -
- ) -} -export default App; \ No newline at end of file + const [username, setUsername] = useState(); + const [messages, setMessages] = useState([]); + if (!username) { + const newName = prompt("Username:") as string; + setUsername(newName); + } + return ( +
+

Local Area Network Chat Application

+
+				This web application was built for the purposes of an EPQ
+				project.
+			
+ {messages.map((message) => { + return ; + })} + { + + } +
+ ); +}; +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 = - 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 ( -
-
-
-
-
- -
- ) -} -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([]); + 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([ + , + ]); + }); + 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 ( +
+
+ {messages} +
+ + + + +
+ ); +}; +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