Well, sending messages took longer than it should have
This commit is contained in:
54
db/db.json
54
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": {}
|
||||
|
||||
@@ -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<arrs.length; i++) {
|
||||
let arr = arrs[i]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import OrderedObject from "./OrderedObject.js"
|
||||
import OrderedObject from "../OrderedObject.js"
|
||||
const { z } = require("zod")
|
||||
|
||||
export default class Posts extends OrderedObject {
|
||||
@@ -11,6 +11,7 @@ export default class Members extends OrderedObject {
|
||||
city: z.string()
|
||||
})
|
||||
schema = z.object({
|
||||
id: z.number(),
|
||||
email: z.string().email(),
|
||||
firstName: z.string(),
|
||||
lastName: z.string(),
|
||||
@@ -26,7 +27,7 @@ export default class Members extends OrderedObject {
|
||||
isHashed = (s) => {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)
|
||||
}
|
||||
|
||||
|
||||
62
server/db/model/Messages/Conversations.js
Normal file
62
server/db/model/Messages/Conversations.js
Normal file
@@ -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<ids.length; i++) {
|
||||
result[i] = global.db.members.get(ids[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
for(let i=0; i<this.entries.length; i++) {
|
||||
let convo = this.entries[i]
|
||||
console.log(convo, userID)
|
||||
if(convo.between.includes(userID)) {
|
||||
|
||||
console.log("found user convo: ", convo.id)
|
||||
let messages = global.db.messages.getByConversation(`CONVERSATION-${convo.id}`)
|
||||
let result = {
|
||||
...convo,
|
||||
messages,
|
||||
}
|
||||
result.between = populateMemberProfilesFromIDs(convo.between)
|
||||
|
||||
convos.push(result)
|
||||
}
|
||||
}
|
||||
return convos
|
||||
}
|
||||
}
|
||||
57
server/db/model/Messages/Messages.js
Normal file
57
server/db/model/Messages/Messages.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import OrderedObject from "../OrderedObject.js"
|
||||
const { z } = require("zod")
|
||||
|
||||
export default class Messages extends OrderedObject {
|
||||
|
||||
schema = z.object({
|
||||
id: z.number(),
|
||||
conversation: z.string(),
|
||||
from: z.string(),
|
||||
text: z.string(),
|
||||
time: z.string()
|
||||
}).strict()
|
||||
|
||||
save(msg) {
|
||||
let id = `DM-${msg.id}`
|
||||
let result = this.schema.safeParse(msg)
|
||||
if(result.success) {
|
||||
try {
|
||||
super.add(id, msg)
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
console.error(result.error)
|
||||
throw new global.ServerError(400, "Invalid Conversation Data!");
|
||||
}
|
||||
}
|
||||
|
||||
add(convo, text, userID) {
|
||||
let newMessage = {}
|
||||
newMessage.time = global.currentTime()
|
||||
newMessage.from = userID
|
||||
newMessage.conversation = convo
|
||||
newMessage.text = text
|
||||
newMessage.id = this.entries.length+1
|
||||
console.log(newMessage)
|
||||
this.save(newMessage)
|
||||
}
|
||||
|
||||
getByConversation(convoID) {
|
||||
let result = []
|
||||
for(let i=0; i<this.entries.length; i++) {
|
||||
let entry = this.entries[i]
|
||||
if(entry.conversation = convoID) {
|
||||
let userID = entry.from
|
||||
let fromUser = global.db.members.get(userID)
|
||||
let newObj = {
|
||||
...entry
|
||||
}
|
||||
newObj.from = fromUser
|
||||
result.push(newObj)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -172,11 +172,6 @@ class Server {
|
||||
this.registerRoutes(router)
|
||||
app.use('/', router);
|
||||
|
||||
setInterval(() => {
|
||||
console.log("saving db")
|
||||
global.db.saveData()
|
||||
}, 5000)
|
||||
|
||||
const server = http.createServer(app);
|
||||
global.Socket = new Socket(server);
|
||||
const PORT = 3003;
|
||||
|
||||
@@ -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)
|
||||
|
||||
40
server/ws/handlers/MessagesHandler.js
Normal file
40
server/ws/handlers/MessagesHandler.js
Normal file
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<this.messages.length; i++) {
|
||||
let message = this.messages[i]
|
||||
const dateParts = this.parseDate(message.time);
|
||||
const { date, time } = dateParts;
|
||||
|
||||
if (previousDate !== date) {
|
||||
previousDate = date;
|
||||
|
||||
p(date)
|
||||
.textAlign("center")
|
||||
.opacity(0.5)
|
||||
.marginVertical(1, em)
|
||||
.color("var(--divider)")
|
||||
}
|
||||
|
||||
VStack(() => {
|
||||
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)
|
||||
register(ForumPanel)
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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<this.conversations.length; i++) {
|
||||
if(this.conversations[i].id === id) {
|
||||
return this.conversations[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
ZStack(() => {
|
||||
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)
|
||||
56
ui/site/apps/Messages/MessagesPanel.js
Normal file
56
ui/site/apps/Messages/MessagesPanel.js
Normal file
@@ -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<this.messages.length; i++) {
|
||||
let message = this.messages[i]
|
||||
let fromMe = window.profile.email === message.from.email
|
||||
VStack(() => {
|
||||
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)
|
||||
73
ui/site/apps/Messages/MessagesSidebar.js
Normal file
73
ui/site/apps/Messages/MessagesSidebar.js
Normal file
@@ -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<members.length; i++) {
|
||||
let member = members[i]
|
||||
if(member.email === window.profile.email) {
|
||||
continue;
|
||||
}
|
||||
if(members.length > 2) {
|
||||
membersString += member.firstName
|
||||
} else {
|
||||
membersString += member.firstName + " " + member.lastName
|
||||
}
|
||||
}
|
||||
return membersString
|
||||
}
|
||||
}
|
||||
|
||||
register(MessagesSidebar)
|
||||
25
ui/site/components/LoadingCircle.js
Normal file
25
ui/site/components/LoadingCircle.js
Normal file
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
9
ui/site/util.js
Normal file
9
ui/site/util.js
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,6 @@ export default class Socket {
|
||||
}
|
||||
|
||||
onBroadcast(msg) {
|
||||
console.log(msg.msg)
|
||||
window.dispatchEvent(new CustomEvent(msg.event, {
|
||||
detail: msg.msg
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user