diff --git a/src/Home/Home.js b/src/Home/Home.js index 104e717..729ff3e 100644 --- a/src/Home/Home.js +++ b/src/Home/Home.js @@ -41,7 +41,7 @@ class Home extends Shadow { .top(20, px) .borderLeft("1px solid var(--accent)") .onTouch((start, e) => { - console.log(e) + // console.log(e) if(this.sidebarOpen) { this.sidebarOpenDrag(start, e) } else { @@ -81,7 +81,7 @@ class Home extends Shadow { } sidebarClosedDrag(start, e) { - console.log(e) + // console.log(e) if(start) { let oneTenth = window.outerWidth / 10 let amount = e.targetTouches[0].clientX diff --git a/src/_/code/bridge/handlers.js b/src/_/code/bridge/handlers.js index 9950b3c..41da64e 100644 --- a/src/_/code/bridge/handlers.js +++ b/src/_/code/bridge/handlers.js @@ -83,6 +83,54 @@ const handlers = { } }, + async addAnnouncement(text, networkId, userId) { + try { + return await global.db.announcements.add(text, networkId, userId) + } catch (e) { + return { status: e.status, error: e.message } + } + }, + + async editAnnouncement(newAnnouncement, userId) { + try { + return await global.db.announcements.edit(newAnnouncement, userId); + } catch (e) { + return { status: e.status, error: e.message } + } + }, + + async deleteAnnouncement(id, userId) { + try { + return await global.db.announcements.delete(id, userId); + } catch (e) { + return { status: e.status, error: e.message } + } + }, + + async getAnnouncement(id) { + try { + return global.db.announcements.getById(id) + } catch (e) { + return { status: e.status, error: e.message } + } + }, + + async getAnnouncements(networkId) { + try { + return global.db.announcements.getByNetwork(networkId) + } catch (e) { + return { status: e.status, error: e.message } + } + }, + + async getUserAnnouncements(userId) { + try { + return global.db.announcements.getByCreator(userId) + } catch (e) { + return { status: e.status, error: e.message } + } + }, + async editBio(newBio, userId) { try { return global.db.members.editBio(newBio, userId) diff --git a/src/apps/Announcements/Announcements.js b/src/apps/Announcements/Announcements.js index 3d555d4..993e998 100644 --- a/src/apps/Announcements/Announcements.js +++ b/src/apps/Announcements/Announcements.js @@ -1,20 +1,148 @@ -import "../../components/TopBar.js" +import './Panel.js' +import server from '../../_/code/bridge/serverFunctions.js' -class Announcements extends Shadow { - render() { - VStack(() => { - - }) - .boxSizing("border-box") - .backgroundColor("var(--main)") - .gap(0.5, em) - .boxSizing("border-box") - .width(100, pct) - .height(100, pct) - .minHeight(0) +css(` + announcements- { + font-family: 'Bona'; } - + announcements- input::placeholder { + font-family: 'Bona Nova'; + font-size: 0.9em; + color: var(--accent); + } + + input::placeholder { + font-family: Arial; + } + + input[type="checkbox"] { + appearance: none; /* remove default style */ + -webkit-appearance: none; + width: 1em; + height: 1em; + border: 1px solid var(--accent); + } + + input[type="checkbox"]:checked { + background-color: var(--red); + } +`) + +class Announcements extends Shadow { + constructor() { + super() + this.announcements = global.currentNetwork.data.announcements.sort((a, b) => new Date(b.created) - new Date(a.created)); + console.log(this.announcements) + } + + render() { + ZStack(() => { + VStack(() => { + + Panel(this.announcements) + + input("Message", "70%") + .paddingVertical(0.75, em) + .boxSizing("border-box") + .paddingHorizontal(2, em) + .color("var(--text)") + .background("var(--searchbackground)") + .marginBottom(1, em) + .border("0.5px solid var(--accent)") + .outline("none") + .borderRadius(100, px) + .fontFamily("Arial") + .fontSize(1, em) + .onKeyDown(async function(e) { + if (e.key === "Enter") { + const result = await server.addAnnouncement(this.value, global.currentNetwork.id, global.profile.id) + if (result.data.status === 200) { + window.dispatchEvent(new CustomEvent('new-announcement', { + detail: { announcement: result.data.announcement } + })); + } else { + // error + } + this.value = "" + } + }) + }) + .gap(1, em) + .boxSizing("border-box") + .width(100, pct) + .height(100, pct) + .horizontalAlign("center") + .verticalAlign("end") + .minHeight(0) + }) + .backgroundColor("var(--main)") + .boxSizing("border-box") + .paddingVertical(1, em) + .width(100, pct) + .height(100, pct) + .flex("1 1 auto") + .onEvent("new-announcement", this.onNewAnnouncement) + .onEvent("deleted-announcement", this.onDeletedAnnouncement) + .onEvent("edited-announcement", this.onEditedAnnouncement) + } + + connectedCallback() { + this.getAnnouncements(global.currentNetwork.id) + } + + checkForUpdates(currentAnnouncements, fetchedAnnouncements) { + if (currentAnnouncements.length !== fetchedAnnouncements.length) return true; + + const currentMap = new Map(currentAnnouncements.map(ann => [ann.id, ann])); + + for (const fetchedAnn of fetchedAnnouncements) { + const currentAnn = currentMap.get(fetchedAnn.id); + + // new event added + if (!currentAnn) return true; + + // existing event changed + if (currentAnn.updated_at !== fetchedAnn.updated_at) { + return true; + } + } + return false; + } + + async getAnnouncements(networkId) { + const fetchedAnnouncements = await server.getAnnouncements(networkId) + if (this.checkForUpdates(this.announcements, fetchedAnnouncements.data)) { + console.log("found updates") + this.announcements = fetchedAnnouncements.data.sort((a, b) => new Date(b.created) - new Date(a.created)); + global.currentNetwork.data.announcements = this.announcements + this.rerender() + } + } + + onNewAnnouncement = (e) => { + let newAnnouncement = e.detail.announcement; + this.announcements.push(newAnnouncement) + this.announcements.sort((a, b) => new Date(b.created) - new Date(a.created)); + this.rerender() + } + + onDeletedAnnouncement = (e) => { + let deletedId = e.detail.id + const i = this.announcements.findIndex(ann => ann.id === deletedId) + if (i !== -1) this.announcements.splice(i, 1); + this.rerender() + } + + onEditedAnnouncement = (e) => { + let editedAnnouncement = e.detail + const i = this.announcements.findIndex(ann => ann.id === editedPost.id) + if (i !== -1) { + this.announcements.splice(i, 1) + this.announcements.unshift(editedAnnouncement) + } + this.rerender() + } } register(Announcements) \ No newline at end of file diff --git a/src/apps/Announcements/Panel.js b/src/apps/Announcements/Panel.js new file mode 100644 index 0000000..0bdae03 --- /dev/null +++ b/src/apps/Announcements/Panel.js @@ -0,0 +1,151 @@ +import "../../components/LoadingCircle.js" +import server from "../../_/code/bridge/serverFunctions.js" + +css(` + panel- { + scrollbar-width: none; + -ms-overflow-style: none; + } + + panel-::-webkit-scrollbar { + display: none; + width: 0px; + height: 0px; + } + + panel-::-webkit-scrollbar-thumb { + background: transparent; + } + + panel-::-webkit-scrollbar-track { + background: transparent; + } +`) + +class Panel extends Shadow { + announcements = [] + isSending = false + + constructor(announcements) { + super() + this.announcements = announcements + } + + render() { + VStack(() => { + if(this.announcements.length > 0) { + let previousDate = null + + for(let i=0; i { + HStack(() => { + h3(isMe ? "Me" : this.getAuthorName(announcement)) + .color(isMe ? "var(--quillred)" : "var(--headertext") + .opacity(0.75) + .margin(0) + + h3(`${date} ${time}`) + .opacity(0.5) + .color("var(--headertext)") + .margin(0) + .marginLeft(0.5, em) + .fontSize(1, em) + + if (announcement.created !== announcement.updated_at) { + p("(edited)") + .color("var(--headertext)") + .letterSpacing(0.8, "px") + .opacity(0.5) + .fontWeight("bold") + .paddingLeft(0.25, em) + .fontSize(0.9, em) + } + }) + .verticalAlign("center") + .marginBottom(0.1, em) + + p(announcement.text) + .color("var(--text)") + .marginHorizontal(0.2, em) + .paddingVertical(0.2, em) + .boxSizing("border-box") + }) + .marginBottom(0.05, em) + } + } else { + LoadingCircle() + } + }) + .gap(1, em) + .fontSize(1.1, em) + .boxSizing("border-box") + .flex("1 1 auto") + .minHeight(0) + .overflowY("auto") + .width(100, pct) + .paddingBottom(2, em) + .paddingHorizontal(4, pct) + .backgroundColor("var(--main)") + .onAppear(async () => { + requestAnimationFrame(() => { + this.scrollTo({ top: 0, behavior: "smooth" }); + }); + }) + } + + getAuthorName(announcement) { + const members = global.currentNetwork.data.members; + const creator = members.find(m => m.id === announcement.creator_id); + if (creator) { + return `${creator.first_name} ${creator.last_name}` + } else { + return "No name" + } + } + + parseDate(str) { + // Format: YYYY-MM-DDTHH:MM:SS.mmmZ + const match = str.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):\d{2}\.\d+Z$/); + if (!match) return null; + + const [, yyyy, mm, dd, hh, min] = match; + + // Convert 24h to 12h + const hour24 = parseInt(hh, 10); + const ampm = hour24 >= 12 ? 'pm' : 'am'; + const hour12 = hour24 % 12 || 12; + + const date = `${mm}/${dd}/${yyyy}`; + const time = `${hour12}:${min}${ampm}`; + + return { date, time }; + } + + formatTime(str) { + const match = str.match(/-(\d+:\d+):\d+.*(am|pm)/i); + if (!match) return null; + + const [_, hourMin, ampm] = match; + return hourMin + ampm.toLowerCase(); + } +} + +register(Panel) \ No newline at end of file