From 9e873641474057cfe65cb22e8245529703b7e1c3 Mon Sep 17 00:00:00 2001 From: metacryst Date: Tue, 25 Nov 2025 23:38:59 -0600 Subject: [PATCH] Well, sending messages took longer than it should have --- db/db.json | 54 ++++++- server/db/db.js | 24 ++- server/db/model/{ => Forum}/Posts.js | 2 +- server/db/model/Members.js | 4 +- server/db/model/Messages/Conversations.js | 62 ++++++++ server/db/model/Messages/Messages.js | 57 +++++++ server/index.js | 5 - server/ws/handlers/ForumHandler.js | 2 +- server/ws/handlers/MessagesHandler.js | 40 +++++ server/ws/ws.js | 23 ++- ui/_/code/quill.js | 3 + ui/_/code/shared.css | 5 +- ui/site/apps/Forum/Forum.js | 54 ++----- .../Forum/{MessagesPanel.js => ForumPanel.js} | 71 +++++---- ui/site/apps/Jobs/Jobs.js | 6 +- ui/site/apps/Jobs/JobsGrid.js | 2 +- ui/site/apps/Market/MarketSidebar.js | 5 + ui/site/apps/Messages/Messages.js | 143 ++++++++---------- ui/site/apps/Messages/MessagesPanel.js | 56 +++++++ ui/site/apps/Messages/MessagesSidebar.js | 73 +++++++++ ui/site/components/LoadingCircle.js | 25 +++ ui/site/components/ProfileMenu.js | 10 +- ui/site/index.js | 3 + ui/site/util.js | 9 ++ ui/site/ws/Socket.js | 1 - 25 files changed, 550 insertions(+), 189 deletions(-) rename server/db/model/{ => Forum}/Posts.js (96%) create mode 100644 server/db/model/Messages/Conversations.js create mode 100644 server/db/model/Messages/Messages.js create mode 100644 server/ws/handlers/MessagesHandler.js rename ui/site/apps/Forum/{MessagesPanel.js => ForumPanel.js} (55%) create mode 100644 ui/site/apps/Messages/MessagesPanel.js create mode 100644 ui/site/apps/Messages/MessagesSidebar.js create mode 100644 ui/site/components/LoadingCircle.js create mode 100644 ui/site/util.js diff --git a/db/db.json b/db/db.json index 3578e3a..42935be 100644 --- a/db/db.json +++ b/db/db.json @@ -4,6 +4,7 @@ "fullName": "Founder" }, "MEMBER-1": { + "id": 1, "email": "samrussell99@pm.me", "firstName": "Sam", "lastName": "Russell", @@ -18,6 +19,22 @@ "tokenUsed": "810387b6-851e-4883-b9a3-c59703dc0fc9", "joined": "11.24.2025-12:54:360784am" }, + "MEMBER-2": { + "id": 2, + "email": "nate@sun.museum", + "firstName": "Nate", + "lastName": "De Luna", + "password": "$argon2id$v=19$m=65536,t=3,p=4$laoMb54lVHlXD+VsWe2lNg$YSNqIk/MC32jUc+Hj48LBTSul3+WXQAyfYasfH6L0k4", + "address": { + "address1": "2014 E 9th St", + "address2": "Unit A", + "zip": "78702", + "state": "Texas", + "city": "Austin" + }, + "tokenUsed": "1d57b6e0-e6c3-4ab6-808f-72b64b8303ee", + "joined": "11.25.2025-6:34:040998pm" + }, "TOKEN-810387b6-851e-4883-b9a3-c59703dc0fc9": { "index": 1, "url": "https://hyperia.so/signup?token=", @@ -64,7 +81,7 @@ "index": 8, "url": "https://hyperia.so/signup?token=", "uuid": "1d57b6e0-e6c3-4ab6-808f-72b64b8303ee", - "used": false + "used": true }, "TOKEN-67264f06-c4b1-4334-8ba8-0e5e045021df": { "index": 9, @@ -322,6 +339,41 @@ "text": "This is the first message ever.", "time": "11.24.2025-12:54:360784am", "sentBy": "MEMBER-1" + }, + "POST-HY-2": { + "text": "asdf", + "sentBy": "MEMBER-1", + "time": "11.25.2025-5:36:510165pm" + }, + "POST-HY-3": { + "text": "Hey guys!", + "sentBy": "MEMBER-2", + "time": "11.25.2025-6:35:420148pm" + }, + "POST-HY-4": { + "text": "(I am not real)", + "sentBy": "MEMBER-2", + "time": "11.25.2025-6:43:280160pm" + }, + "POST-HY-5": { + "text": "asd", + "sentBy": "MEMBER-2", + "time": "11.25.2025-9:02:490950pm" + }, + "CONVERSATION-1": { + "id": 1, + "between": [ + "MEMBER-1", + "MEMBER-2" + ], + "lastUpdated": "11.25.2025-6:43:280160pm" + }, + "DM-1": { + "id": 1, + "conversation": "CONVERSATION-1", + "from": "MEMBER-1", + "text": "What's up?", + "time": "11.25.2025-6:43:280160pm" } }, "edges": {} diff --git a/server/db/db.js b/server/db/db.js index bce6529..b3de5ed 100644 --- a/server/db/db.js +++ b/server/db/db.js @@ -5,19 +5,25 @@ import QuillDB from "../_/quilldb.js" import Titles from "./model/Titles.js" import Members from './model/Members.js' import Tokens from './model/Tokens.js' -import Posts from "./model/Posts.js" +import Posts from "./model/Forum/Posts.js" +import Conversations from "./model/Messages/Conversations.js" +import Messages from "./model/Messages/Messages.js" export default class Database { titles = new Titles() members = new Members() tokens = new Tokens() posts = new Posts() + conversations = new Conversations() + messages = new Messages() fromID = { "HY": this.titles, "MEMBER": this.members, "TOKEN": this.tokens, - "POST": this.posts + "POST": this.posts, + "CONVERSATION": this.conversations, + "DM": this.messages } constructor() { @@ -51,7 +57,11 @@ export default class Database { } } - this.saveData() + + setInterval(() => { + console.log("saving db") + global.db.saveData() + }, 5000) } async saveData() { @@ -67,13 +77,19 @@ export default class Database { this.titles.entries, this.members.entries, this.tokens.entries, - this.posts.entries + this.posts.entries, + this.conversations.entries, + this.messages.entries + ] let ids = [ Object.entries(this.titles.ids), Object.entries(this.members.ids), Object.entries(this.tokens.ids), Object.entries(this.posts.ids), + Object.entries(this.conversations.ids), + Object.entries(this.messages.ids), + ] for(let i=0; i {return s.startsWith("$argon2")} save(member) { - let id = `MEMBER-${this.entries.length+1}` + let id = `MEMBER-${member.id}` let result = this.schema.safeParse(member) if(result.success) { try { @@ -46,6 +47,7 @@ export default class Members extends OrderedObject { const hash = await argon2.hash(newMember.password); newMember.password = hash newMember.joined = global.currentTime() + newMember.id = this.entries.length+1 this.save(newMember) } diff --git a/server/db/model/Messages/Conversations.js b/server/db/model/Messages/Conversations.js new file mode 100644 index 0000000..ed5cd38 --- /dev/null +++ b/server/db/model/Messages/Conversations.js @@ -0,0 +1,62 @@ +import OrderedObject from "../OrderedObject.js" +const { z } = require("zod") + +export default class Conversations extends OrderedObject { + + schema = z.object({ + id: z.number(), + between: z.array(z.string()), + lastUpdated: z.string() + }).strict() + + save(convo) { + let id = `CONVERSATION-${convo.id}` + let result = this.schema.safeParse(convo) + if(result.success) { + try { + super.add(id, convo) + } catch(e) { + console.error(e) + throw e + } + } else { + console.error(result.error) + throw new global.ServerError(400, "Invalid Conversation Data!"); + } + } + + get(convoID) { + console.log("convo getting, ", convoID) + return this.entries[this.ids[convoID]] + } + + getByMember(userID) { + let convos = [] + + function populateMemberProfilesFromIDs(ids) { + let result = [] + for(let i=0; i { - console.log("saving db") - global.db.saveData() - }, 5000) - const server = http.createServer(app); global.Socket = new Socket(server); const PORT = 3003; diff --git a/server/ws/handlers/ForumHandler.js b/server/ws/handlers/ForumHandler.js index b35f273..676c958 100644 --- a/server/ws/handlers/ForumHandler.js +++ b/server/ws/handlers/ForumHandler.js @@ -17,7 +17,7 @@ export default class ForumHandler { static handleSend(msg, ws) { try { global.db.posts.add(msg.text, msg.forum, ws.userEmail) - global.Socket.broadcast({event: "new-message", app: "FORUM", forum: msg.forum, msg: this.handleGet({forum: msg.forum, number: 100})}) + global.Socket.broadcast({event: "new-post", app: "FORUM", forum: msg.forum, msg: this.handleGet({forum: msg.forum, number: 100})}) return {success: true} } catch(e) { console.error(e) diff --git a/server/ws/handlers/MessagesHandler.js b/server/ws/handlers/MessagesHandler.js new file mode 100644 index 0000000..ad06408 --- /dev/null +++ b/server/ws/handlers/MessagesHandler.js @@ -0,0 +1,40 @@ +const { z } = require("zod") + +const sendSchema = z.object({ + conversation: z.string(), + text: z.string(), +}) +.strict() + +export default class MessagesHandler { + + static handleSend(msg, ws) { + let user = global.db.members.getByEmail(ws.userEmail) + let convo = global.db.conversations.get(msg.conversation) + if(convo.between.includes(`MEMBER-${user.id}`)) { + global.db.messages.add(msg.conversation, msg.text, `MEMBER-${user.id}`) + global.Socket.broadcast({event: "new-message", app: "MESSAGES", msg: {conversationID: convo.id, messages: global.db.messages.getByConversation(`CONVERSATION-${msg.conversation}`)}}) + + } else { + throw new Error("Can't add to a conversation user is not a part of!") + } + return {success: true} + } + + static handleGet(ws) { + let user = global.db.members.getByEmail(ws.userEmail) + let data = global.db.conversations.getByMember(`MEMBER-${user.id}`) + return data + } + + static handle(operation, msg, ws) { + switch(operation) { + case "GET": + return this.handleGet(ws) + case "SEND": + if(!sendSchema.safeParse(msg).success) throw new Error("Incorrectly formatted Forum ws message!") + return this.handleSend(msg, ws) + } + + } +} \ No newline at end of file diff --git a/server/ws/ws.js b/server/ws/ws.js index 91db7b5..df35594 100644 --- a/server/ws/ws.js +++ b/server/ws/ws.js @@ -2,6 +2,7 @@ const { WebSocket, WebSocketServer } = require('ws'); const { z } = require("zod") const jwt = require('jsonwebtoken'); import ForumHandler from "./handlers/ForumHandler.js" +import MessagesHandler from "./handlers/MessagesHandler.js" export default class Socket { wss; @@ -12,8 +13,18 @@ export default class Socket { msg: z.union([ z.object({}).passthrough(), // allows any object z.array(z.any()) // allows any array - ]) - }).strict() + ]).optional() + }) + .superRefine((data, ctx) => { + if (data.operation !== "GET" && data.msg === undefined) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["msg"], + message: "msg is required when operation is not GET" + }) + } + }) + .strict() constructor(server) { this.wss = new WebSocketServer({ server }); @@ -54,7 +65,7 @@ export default class Socket { try { const text = msg.toString(); const req = JSON.parse(text); - if(!this.messageSchema.safeParse(req).success) throw new Error("Socket.handleMessage: Incorrectly formatted incoming ws message!") + if(!this.messageSchema.safeParse(req).success) throw new Error("Socket.handleMessage: Incoming ws message has incorrect format!") let responseData; switch (req.app) { @@ -62,6 +73,10 @@ export default class Socket { responseData = ForumHandler.handle(req.operation, req.msg, ws) break; + case "MESSAGES": + responseData = MessagesHandler.handle(req.operation, req.msg, ws) + break; + default: console.error("unknown ws message") } @@ -71,7 +86,7 @@ export default class Socket { } response.msg = responseData - if(!this.messageSchema.safeParse(response).success) throw new Error("Socket.handleMessage: Incorrectly formatted outgoing ws message!") + if(!this.messageSchema.safeParse(response).success) throw new Error("Socket.handleMessage: Outgoing ws message has incorrect format!") ws.send(JSON.stringify(response)) } catch (e) { diff --git a/ui/_/code/quill.js b/ui/_/code/quill.js index be4f0c7..fcebb29 100644 --- a/ui/_/code/quill.js +++ b/ui/_/code/quill.js @@ -1,6 +1,7 @@ /* Sam Russell Captured Sun + 11.25.25.1 - Added minHeight and minWidth to be counted as numerical styles 11.25.25 - Added onChange for check boxes, added setQuery / onQueryChanged for easy filtering 11.24.25 - Fixing onClick because it was reversed, adding event to onHover params 11.23.25 - Added onSubmit() event for form submission, added marginHorizontal() and marginVertical() @@ -292,6 +293,8 @@ function extendHTMLElementWithStyleSetters() { case "height": case "maxWidth": case "maxHeight": + case "minWidth": + case "minHeight": case "left": case "top": diff --git a/ui/_/code/shared.css b/ui/_/code/shared.css index d80986b..4c1c4ca 100644 --- a/ui/_/code/shared.css +++ b/ui/_/code/shared.css @@ -4,13 +4,11 @@ --tan: #FFDFB4; --gold: #F2B36F; - --purple: #251D44; + --divider: #bb7c36; --green: #0857265c; --red: #BC1C02; --brown: #812A18; --darkbrown: #3f0808; - --orange: #FE9201; - --periwinkle: #655BAF; --accent2: var(--green); } @@ -19,7 +17,6 @@ :root { --main: var(--brown); --accent: var(--gold); - --dividerColor: var(--gold); --accent2: var(--gold); } } diff --git a/ui/site/apps/Forum/Forum.js b/ui/site/apps/Forum/Forum.js index 8fef3e7..8556c79 100644 --- a/ui/site/apps/Forum/Forum.js +++ b/ui/site/apps/Forum/Forum.js @@ -1,4 +1,4 @@ -import './MessagesPanel.js' +import './ForumPanel.js' css(` forum- { @@ -40,18 +40,18 @@ class Forum extends Shadow { .marginLeft(0.4, em) }) .height(100, vh) - .paddingLeft(2, em) - .paddingRight(2, em) - .paddingTop(2, em) + .paddingLeft(1, em) + .paddingRight(1, em) .gap(0, em) + .marginTop(13, vh) VStack(() => { - MessagesPanel() + ForumPanel() - input("Message Hyperia", "93%") + input("Message Hyperia", "98%") .paddingVertical(1, em) - .paddingHorizontal(2, em) + .paddingLeft(2, pct) .color("var(--accent)") .background("var(--darkbrown)") .marginBottom(6, em) @@ -64,49 +64,15 @@ class Forum extends Shadow { } }) }) - .gap(1, em) + .gap(0.5, em) .width(100, pct) + .height(100, vh) .alignHorizontal("center") .alignVertical("end") }) .width(100, "%") .height(87, vh) - .x(0).y(13, vh) - - HStack(() => { - input("Search...", "45vw") - .attr({ - "type": "text" - }) - .fontSize(1.1, em) - .paddingLeft(1.3, em) - .background("transparent") - .border("0.5px solid #bb7c36") - .outline("none") - .color("var(--accent)") - .borderRadius(10, px) - - button("Search") - .marginLeft(2, em) - .borderRadius(10, px) - .background("transparent") - .border("0.5px solid #bb7c36") - .color("var(--accent)") - .fontFamily("Bona Nova") - .onHover(function (hovering) { - if(hovering) { - this.style.background = "var(--green)" - - } else { - this.style.background = "transparent" - - } - }) - - }) - .x(55, vw).y(4, vh) - .position("absolute") - .transform("translateX(-50%)") + .x(0).y(0, vh) }) .width(100, pct) .height(100, pct) diff --git a/ui/site/apps/Forum/MessagesPanel.js b/ui/site/apps/Forum/ForumPanel.js similarity index 55% rename from ui/site/apps/Forum/MessagesPanel.js rename to ui/site/apps/Forum/ForumPanel.js index b13b1ee..d2a6fa4 100644 --- a/ui/site/apps/Forum/MessagesPanel.js +++ b/ui/site/apps/Forum/ForumPanel.js @@ -1,4 +1,6 @@ -class MessagesPanel extends Shadow { +import "../../components/LoadingCircle.js" + +class ForumPanel extends Shadow { forums = [ "HY" ] @@ -7,15 +9,31 @@ class MessagesPanel extends Shadow { render() { VStack(() => { if(this.messages.length > 0) { + + let previousDate = null + for(let i=0; i { HStack(() => { p(message.sentBy) .fontWeight("bold") .marginBottom(0.3, em) - p(this.formatTime(message.time)) + p(util.formatTime(message.time)) .opacity(0.2) .marginLeft(1, em) }) @@ -23,38 +41,23 @@ class MessagesPanel extends Shadow { }) } } else { - div() - .borderRadius(100, pct) - .width(2, em).height(2, em) - .x(45, pct).y(50, pct) - .center() - .backgroundColor("var(--accent") - .transition("transform 1.75s ease-in-out") - .onAppear(function () { - let growing = true; - - setInterval(() => { - if (growing) { - this.style.transform = "scale(1.5)"; - } else { - this.style.transform = "scale(0.7)"; - } - growing = !growing; - }, 750); - }); + LoadingCircle() } }) .gap(1, em) .position("relative") .overflow("scroll") - .height(95, pct) - .width(93, pct) - .paddingTop(2, em) + .height(100, pct) + .width(96, pct) + .paddingTop(5, em) .paddingBottom(2, em) - .paddingHorizontal(2, em) + .paddingLeft(4, pct) .backgroundColor("var(--darkbrown)") .onAppear(async () => { console.log("appear") + requestAnimationFrame(() => { + this.scrollTop = this.scrollHeight + }); let res = await Socket.send({app: "FORUM", operation: "GET", msg: {forum: "HY", number: 100}}) if(!res) console.error("failed to get messages") if(res.msg.length > 0 && this.messages.length === 0) { @@ -62,24 +65,26 @@ class MessagesPanel extends Shadow { this.messages = res.msg this.rerender() } - window.addEventListener("new-message", (e) => { + window.addEventListener("new-post", (e) => { this.messages = e.detail if(e.detail.length !== this.messages || e.detail.last.time !== this.messages.last.time || e.detail.first.time !== this.messages.first.time) { this.rerender() } }) }) - .scrollTop = this.scrollHeight } - formatTime(str) { - // Extract the time part with am/pm - const match = str.match(/-(\d+:\d+):\d+.*(am|pm)/i); + parseDate(str) { + // Format: MM.DD.YYYY-HH:MM:SSxxxxxx(am|pm) + const match = str.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})-(\d{1,2}):(\d{2}).*(am|pm)$/i); if (!match) return null; - const [_, hourMin, ampm] = match; - return hourMin + ampm.toLowerCase(); + const [, mm, dd, yyyy, hh, min, ampm] = match; + const date = `${mm}/${dd}/${yyyy}`; + const time = `${hh}:${min}${ampm.toLowerCase()}`; + + return { date, time }; } } -register(MessagesPanel) \ No newline at end of file +register(ForumPanel) \ No newline at end of file diff --git a/ui/site/apps/Jobs/Jobs.js b/ui/site/apps/Jobs/Jobs.js index 12bd8de..5782c5b 100644 --- a/ui/site/apps/Jobs/Jobs.js +++ b/ui/site/apps/Jobs/Jobs.js @@ -54,7 +54,7 @@ class Jobs extends Shadow { .fontSize(1.1, em) .paddingLeft(1.3, em) .background("transparent") - .border("1px solid var(--accent2)") + .border("0.3px solid var(--accent2)") .outline("none") .color("var(--accent)") .borderRadius(10, px) @@ -63,7 +63,7 @@ class Jobs extends Shadow { .marginLeft(2, em) .borderRadius(10, px) .background("transparent") - .border("1px solid var(--accent2)") + .border("0.3px solid var(--accent2)") .color("var(--accent)") .fontFamily("Bona Nova") .onHover(function (hovering) { @@ -81,7 +81,7 @@ class Jobs extends Shadow { .marginLeft(1, em) .borderRadius(10, px) .background("transparent") - .border("1px solid var(--accent2)") + .border("0.3px solid var(--accent2)") .color("var(--accent)") .fontFamily("Bona Nova") .onHover(function (hovering) { diff --git a/ui/site/apps/Jobs/JobsGrid.js b/ui/site/apps/Jobs/JobsGrid.js index 415fa3c..2af5d4f 100644 --- a/ui/site/apps/Jobs/JobsGrid.js +++ b/ui/site/apps/Jobs/JobsGrid.js @@ -37,8 +37,8 @@ class JobsGrid extends Shadow { p(this.boldUntilFirstSpace(this.jobs[i].salary)) }) .padding(1, em) - .border("1px solid var(--accent2)") .borderRadius(5, "px") + .background("var(--darkbrown)") } }) .display("grid") diff --git a/ui/site/apps/Market/MarketSidebar.js b/ui/site/apps/Market/MarketSidebar.js index 16d1bac..9324618 100644 --- a/ui/site/apps/Market/MarketSidebar.js +++ b/ui/site/apps/Market/MarketSidebar.js @@ -12,6 +12,9 @@ class MarketSidebar extends Shadow { render() { VStack(() => { + + p("Make") + HStack(() => { input() .attr({ @@ -40,6 +43,8 @@ class MarketSidebar extends Shadow { .marginLeft(0.5, em) }) + p("Condition") + HStack(() => { input() .attr({ diff --git a/ui/site/apps/Messages/Messages.js b/ui/site/apps/Messages/Messages.js index 6c6c3b9..5b52c2b 100644 --- a/ui/site/apps/Messages/Messages.js +++ b/ui/site/apps/Messages/Messages.js @@ -1,3 +1,6 @@ +import "./MessagesSidebar.js" +import "./MessagesPanel.js" + css(` messages- { font-family: 'Bona'; @@ -23,62 +26,76 @@ css(` `) class Messages extends Shadow { - friends = [] conversations = [] + selectedConvoID = null + onConversationSelect(i) { + console.log("convo selected: ", i) + this.selectedConvoID = i + this.$("messagessidebar-").rerender() + this.$("messagespanel-").rerender() + } + + getConvoFromID(id) { + for(let i=0; i { HStack(() => { - VStack(() => { - h3("Friends") - .marginTop(0) - .marginBottom(1, em) - .marginLeft(0.4, em) - - if (this.friends.length > 1) { - for(let i = 0; i < this.friends.length; i++) { - p(this.friends[i].name) - } - } else { - p("No Friends!") - } - }) - .height(100, vh) - .paddingLeft(2, em) - .paddingRight(2, em) - .paddingTop(2, em) - .gap(0, em) - .borderRight("1px solid var(--accent2)") + MessagesSidebar(this.conversations, this.selectedConvoID, this.onConversationSelect) VStack(() => { - h3("Conversations") - .marginTop(0) - .marginBottom(1, em) - .marginLeft(0.4, em) - - if (this.conversations.length > 1) { - for(let i = 0; i < this.conversations.length; i++) { - p(this.conversations[i].name) - } + if(this.getConvoFromID(this.selectedConvoID)) { + MessagesPanel(this.getConvoFromID(this.selectedConvoID).messages) } else { - p("No Conversations!") + MessagesPanel() } + + input("Send Message", "93%") + .paddingVertical(1, em) + .paddingHorizontal(2, em) + .color("var(--accent)") + .background("var(--darkbrown)") + .marginBottom(6, em) + .border("none") + .fontSize(1, em) + .onKeyDown((e) => { + if (e.key === "Enter") { + window.Socket.send({app: "MESSAGES", operation: "SEND", msg: { conversation: `CONVERSATION-${this.selectedConvoID}`, text: e.target.value }}) + e.target.value = "" + } + }) + }) + .gap(1, em) + .width(100, pct) + .alignHorizontal("center") + .alignVertical("end") + }) + .onAppear(async () => { + let res = await Socket.send({app: "MESSAGES", operation: "GET"}) + if(!res) console.error("failed to get messages") + + if(res.msg.length > 0 && this.conversations.length === 0) { + this.conversations = res.msg + this.selectedConvoID = this.conversations[0].id + this.rerender() + } + + window.addEventListener("new-message", (e) => { + let convoID = e.detail.conversationID + let messages = e.detail.messages + let convo = this.getConvoFromID(convoID) + convo.messages = messages + this.rerender() }) - .height(100, vh) - .paddingLeft(2, em) - .paddingRight(2, em) - .paddingTop(2, em) - .gap(0, em) - .borderRight("1px solid var(--accent2)") }) .width(100, "%") + .height(87, vh) .x(0).y(13, vh) - .borderTop("1px solid var(--accent2)") - - p("0 Items") - .position("absolute") - .x(50, vw).y(50, vh) - .transform("translate(-50%, -50%)") HStack(() => { input("Search messages...", "45vw") @@ -88,7 +105,7 @@ class Messages extends Shadow { .fontSize(1.1, em) .paddingLeft(1.3, em) .background("transparent") - .border("1px solid var(--accent2)") + .border("0.5px solid var(--divider)") .outline("none") .color("var(--accent)") .borderRadius(10, px) @@ -97,7 +114,7 @@ class Messages extends Shadow { .marginLeft(2, em) .borderRadius(10, px) .background("transparent") - .border("1px solid var(--accent2)") + .border("0.5px solid var(--divider)") .color("var(--accent)") .fontFamily("Bona Nova") .onHover(function (hovering) { @@ -115,7 +132,7 @@ class Messages extends Shadow { .marginLeft(1, em) .borderRadius(10, px) .background("transparent") - .border("1px solid var(--accent2)") + .border("0.5px solid var(--divider)") .color("var(--accent)") .fontFamily("Bona Nova") .onHover(function (hovering) { @@ -139,40 +156,6 @@ class Messages extends Shadow { .width(100, "%") .height(100, "%") } - - SidebarName(name) { - let firstLetter = name[0] - - HStack(() => { - div(firstLetter) - .display("flex") - .justifyContent("center") - .alignItems("center") - .width(1.5, em) - .height(1.5, em) - .border("1px solid var(--accent2)") - .borderRadius(100, "%") - p(name) - .marginLeft(1, em) - }) - .alignItems("center") - .padding(5, px) - .borderRadius(0.5, em) - .cursor("default") - .onHover(function (hovering) { - if(hovering) { - this.style.background = "var(--green)" - - } else { - this.style.background = "transparent" - - } - }) - } - - connectedCallback() { - // Optional additional logic - } } register(Messages) \ No newline at end of file diff --git a/ui/site/apps/Messages/MessagesPanel.js b/ui/site/apps/Messages/MessagesPanel.js new file mode 100644 index 0000000..b608212 --- /dev/null +++ b/ui/site/apps/Messages/MessagesPanel.js @@ -0,0 +1,56 @@ +import "../../components/LoadingCircle.js" + +class MessagesPanel extends Shadow { + messages + + constructor(messages) { + super() + this.messages = messages + } + + render() { + VStack(() => { + if(this.messages) { + for(let i=0; i { + HStack(() => { + p(message.from.firstName + " " + message.from.lastName) + .fontWeight("bold") + .marginBottom(0.3, em) + + p(util.formatTime(message.time)) + .opacity(0.2) + .marginLeft(1, em) + }) + p(message.text) + }) + .paddingVertical(0.5, em) + .marginLeft(fromMe ? 70 : 0, pct) + .paddingRight(fromMe ? 10 : 0, pct) + .marginRight(fromMe ? 0 : 70, pct) + .paddingLeft(fromMe ? 5 : 10, pct) + .background(fromMe ? "var(--brown)" : "var(--green)") + } + } else { + LoadingCircle() + } + }) + .onAppear(async () => { + requestAnimationFrame(() => { + this.scrollTop = this.scrollHeight + }); + }) + .gap(1, em) + .position("relative") + .overflow("scroll") + .height(95, pct) + .width(100, pct) + .paddingTop(2, em) + .paddingBottom(2, em) + .backgroundColor("var(--darkbrown)") + } +} + +register(MessagesPanel) \ No newline at end of file diff --git a/ui/site/apps/Messages/MessagesSidebar.js b/ui/site/apps/Messages/MessagesSidebar.js new file mode 100644 index 0000000..453a91d --- /dev/null +++ b/ui/site/apps/Messages/MessagesSidebar.js @@ -0,0 +1,73 @@ +class MessagesSidebar extends Shadow { + conversations = [] + selectedConvoID + onSelect + + constructor(conversations, selectedConvoID, onSelect) { + super() + this.conversations = conversations + this.selectedConvoID = selectedConvoID + this.onSelect = onSelect + } + + render() { + VStack(() => { + this.conversations.forEach((convo, i) => { + + VStack(() => { + HStack(() => { + + p(this.makeConvoTitle(convo.between)) + .textAlign("left") + .marginLeft(0.5, inches) + .paddingTop(0.2, inches) + .width(100, pct) + .marginTop(0) + .fontSize(1, em) + .fontWeight("bold") + + p(util.formatTime(convo.messages.last.time)) + .paddingTop(0.2, inches) + .fontSize(0.8, em) + .marginRight(0.1, inches) + .color("var(--divider") + }) + .justifyContent("space-between") + .marginBottom(0) + + p(convo.messages.last.text) + .fontSize(0.8, em) + .textAlign("left") + .marginLeft(0.5, inches) + .marginBottom(2, em) + .color("var(--divider)") + }) + .background(convo.id === this.selectedConvoID ? "var(--darkbrown)" : "") + .onClick(() => { + this.onSelect(i) + }) + }) + }) + .minWidth(15, vw) + .height(100, vh) + .gap(0, em) + } + + makeConvoTitle(members) { + let membersString = "" + for(let i=0; i 2) { + membersString += member.firstName + } else { + membersString += member.firstName + " " + member.lastName + } + } + return membersString + } +} + +register(MessagesSidebar) \ No newline at end of file diff --git a/ui/site/components/LoadingCircle.js b/ui/site/components/LoadingCircle.js new file mode 100644 index 0000000..f71a86c --- /dev/null +++ b/ui/site/components/LoadingCircle.js @@ -0,0 +1,25 @@ +class LoadingCircle extends Shadow { + render() { + div() + .borderRadius(100, pct) + .width(2, em).height(2, em) + .x(45, pct).y(50, pct) + .center() + .backgroundColor("var(--accent") + .transition("transform 1.75s ease-in-out") + .onAppear(function () { + let growing = true; + + setInterval(() => { + if (growing) { + this.style.transform = "scale(1.5)"; + } else { + this.style.transform = "scale(0.7)"; + } + growing = !growing; + }, 750); + }); + } +} + +register(LoadingCircle) \ No newline at end of file diff --git a/ui/site/components/ProfileMenu.js b/ui/site/components/ProfileMenu.js index 142bf0d..5a20d6b 100644 --- a/ui/site/components/ProfileMenu.js +++ b/ui/site/components/ProfileMenu.js @@ -1,5 +1,4 @@ class ProfileMenu extends Shadow { - profile; render() { ZStack(() => { @@ -9,7 +8,7 @@ class ProfileMenu extends Shadow { p("Email: ") .fontWeight("bold") - p(this.profile?.email) + p(window.profile?.email) }) .gap(1, em) @@ -17,7 +16,7 @@ class ProfileMenu extends Shadow { p("Name: ") .fontWeight("bold") - p(this.profile?.name) + p(window.profile?.name) }) .gap(1, em) @@ -38,8 +37,8 @@ class ProfileMenu extends Shadow { .center() .display("none") .onAppear(async () => { - if(!this.profile) { - this.profile = await this.fetchProfile() + if(!window.profile) { + window.profile = await this.fetchProfile() this.rerender() } }) @@ -60,7 +59,6 @@ class ProfileMenu extends Shadow { const profile = await res.json(); console.log(profile); return profile - } catch (err) { console.error(err); } diff --git a/ui/site/index.js b/ui/site/index.js index a22a951..d0acae8 100644 --- a/ui/site/index.js +++ b/ui/site/index.js @@ -1,5 +1,8 @@ import Socket from "./ws/Socket.js" import "./components/Home.js" +import util from "./util.js" +window.util = util + window.Socket = new Socket() Home() \ No newline at end of file diff --git a/ui/site/util.js b/ui/site/util.js new file mode 100644 index 0000000..43c3bd1 --- /dev/null +++ b/ui/site/util.js @@ -0,0 +1,9 @@ +export default class util { + static formatTime(str) { + const match = str.match(/-(\d+:\d+):\d+.*(am|pm)/i); + if (!match) return null; + + const [_, hourMin, ampm] = match; + return hourMin + ampm.toLowerCase(); + } +} \ No newline at end of file diff --git a/ui/site/ws/Socket.js b/ui/site/ws/Socket.js index 4497de6..49357ef 100644 --- a/ui/site/ws/Socket.js +++ b/ui/site/ws/Socket.js @@ -38,7 +38,6 @@ export default class Socket { } onBroadcast(msg) { - console.log(msg.msg) window.dispatchEvent(new CustomEvent(msg.event, { detail: msg.msg }));