diff options
author | Zhongheng Liu <z.liu@outlook.com.gr> | 2024-01-14 14:05:50 +0200 |
---|---|---|
committer | Zhongheng Liu <z.liu@outlook.com.gr> | 2024-01-14 14:05:50 +0200 |
commit | 847ecd9a69efe9e0395f78f3b2e1729d181ec109 (patch) | |
tree | 2841fcc4b037ce393be58e7d40dc9cee612bf1a4 | |
parent | 728bf3385633e3a9848890bb2a4b8526ccc11638 (diff) | |
download | epq-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.tsx | 44 | ||||
-rw-r--r-- | src/Chat/Chat.css | 2 | ||||
-rw-r--r-- | src/Chat/Chat.tsx | 118 | ||||
-rw-r--r-- | src/Chat/ChatWrapper.tsx | 120 | ||||
-rw-r--r-- | src/Chat/server.ts | 44 |
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 |