diff options
Diffstat (limited to 'trongleposting-client/src')
| -rw-r--r-- | trongleposting-client/src/App.css | 78 | ||||
| -rw-r--r-- | trongleposting-client/src/App.js | 126 | ||||
| -rw-r--r-- | trongleposting-client/src/index.js | 10 | ||||
| -rw-r--r-- | trongleposting-client/src/utils/generate_gruvbox.js | 14 |
4 files changed, 228 insertions, 0 deletions
diff --git a/trongleposting-client/src/App.css b/trongleposting-client/src/App.css new file mode 100644 index 0000000..8e7ba34 --- /dev/null +++ b/trongleposting-client/src/App.css @@ -0,0 +1,78 @@ +body { + font-family: Consolas, monaco, monospace; + color: #fbf1c7; + margin: 0; + background-color: #3c3836; +} + +.container { + max-width: 900px; + width: 80%; + + border: 1px solid #b16286; + border-radius: 8px; + margin: 0; + padding: 24px; + + position: absolute; + top: 50%; + left: 50%; + -moz-transform: translateX(-50%) translateY(-50%); + -webkit-transform: translateX(-50%) translateY(-50%); + transform: translateX(-50%) translateY(-50%); + background-color: #282828; + + box-shadow: rgb( 0, 0, 0, 0.6) 6px 45px 45px -12px; +} + +.chat { + border-bottom: 1px solid #d65d0e; + height: 200px; + overflow-y: scroll; + padding-bottom: 12px; +} + +* { + scrollbar-width: thin; + scrollbar-color: #d5c4a1 rgba(0,0,0,0); +} + +*::-webkit-scrollbar { + width: 4px; +} + +*::-webkit-scrollbar-track { + background: rgba(0,0,0,0); +} + +*::-webkit-scrollbar-thumb { + background-color: #d5c4a1; + border-radius: 12px; +} + +.input { + font-size: 16px; + font-size: max(16px, 1em); + font-family: inherit; + padding: 0.25em 0.5em; + background-color: rgba(0,0,0,0); + border: none; + border-bottom: 1px solid #83a598; + color: #d5c4a1; + margin-top: 12px; + margin-bottom: 12px; + display: block; +} + +.input:focus { + outline: none; +} + +.button { + padding: 12px; + cursor: pointer; + border: 1px solid #8ec07c; + color: #8ec07c; + border-radius: 8px; + display: inline-block; +}
\ No newline at end of file diff --git a/trongleposting-client/src/App.js b/trongleposting-client/src/App.js new file mode 100644 index 0000000..37643c5 --- /dev/null +++ b/trongleposting-client/src/App.js @@ -0,0 +1,126 @@ +import { useRef, useState, useEffect } from "react"; +import { generateGruvboxFromString } from "./utils/generate_gruvbox"; +import "./App.css"; + +function App() { + const [socket, setSocket] = useState(null); + const [posts, setPosts] = useState([]); + const postsRef = useRef([]); + const [content, setContent] = useState(""); + const [username, setUsername] = useState(""); + const [error, setError] = useState(""); + const [color, setColor] = useState(""); + + const scrollToBottomOfChat = () => { + const objDiv = document.getElementById("chat"); + objDiv.scrollTop = objDiv.scrollHeight; + }; + + useEffect(() => { + const protocol = document.location.protocol === "https:" ? "wss:" : "ws:"; + let socket; + + fetch("/posts") + .then((r) => r.json()) + .then((msgs) => { + postsRef.current = msgs; + setPosts(postsRef.current); + scrollToBottomOfChat(); + }) + .then(() => { + socket = new WebSocket(`${protocol}//${document.location.host}/`); + socket.addEventListener("open", () => { + socket.send("ping"); + }); + socket.addEventListener("message", (msg) => { + const { data } = msg; + if (data === "pong") return; + + const chat = JSON.parse(msg.data); + if (chat.author) { + postsRef.current = [...postsRef.current, chat]; + setPosts(postsRef.current); + scrollToBottomOfChat(); + } + }); + + setSocket(socket); + }); + + return () => { + socket.close(); + }; + }, []); + + useEffect(() => { + setColor(generateGruvboxFromString(username)); + }, [username]); + + const addPost = () => { + setError(""); + if (socket) { + fetch("/posts", { + method: "POST", + headers: { Content: "application/json" }, + body: JSON.stringify({ author: username, message: content }), + }).then(() => setContent("")); + } + }; + + return ( + <div className="container" style={{ border: `1px solid ${color}` }}> + <div style={{ textAlign: "center" }}> + <h2>TronglePosting in ELisp</h2> + </div> + <div id="chat" className="chat"> + <p>Welcome!</p> + {posts.map((post) => ( + <div + key={post.id} + style={{ + lineBreak: "normal", + display: "flex", + alignItems: "center", + justifyContent: "space-between", + }} + > + <div> + <span style={{ color: generateGruvboxFromString(post.author) }}> + {post.author}:{" "} + </span> + <span>{post.message}</span> + </div> + <div style={{ fontSize: "x-small" }}> + [{new Date(post.date).toLocaleString()}] + </div> + </div> + ))} + </div> + <div> + <input + placeholder={"Username"} + className="input" + style={{ color }} + onChange={(e) => { + setUsername(e.target.value); + }} + value={username} + ></input> + <textarea + placeholder={"Message"} + className="input" + onChange={(e) => setContent(e.target.value)} + value={content} + rows={1} + cols={50} + ></textarea> + <div className="button" onClick={addPost}> + Post + </div> + {error ? <p style={{ color: "red" }}>{error}</p> : null} + </div> + </div> + ); +} + +export default App; diff --git a/trongleposting-client/src/index.js b/trongleposting-client/src/index.js new file mode 100644 index 0000000..7173ce5 --- /dev/null +++ b/trongleposting-client/src/index.js @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +ReactDOM.render( + <React.StrictMode> + <App /> + </React.StrictMode>, + document.getElementById('root') +);
\ No newline at end of file diff --git a/trongleposting-client/src/utils/generate_gruvbox.js b/trongleposting-client/src/utils/generate_gruvbox.js new file mode 100644 index 0000000..7ff109b --- /dev/null +++ b/trongleposting-client/src/utils/generate_gruvbox.js @@ -0,0 +1,14 @@ +const gruvboxColors = [ + "#b8bb26", + "#fabd2f", + "#83a598", + "#d3869b", + "#8ec07c", + "#458588", + "#cc241d", + "#d65d0e", + "#bdae93", +]; + +export const generateGruvboxFromString = (string) => + gruvboxColors[Array.from(string).map((x) => x.charCodeAt(0)).reduce((a, x) => a+x, 0) % gruvboxColors.length];
\ No newline at end of file |
