From d3df5bb6cbc1ebae96f1b410245efd99cb7b6f6a Mon Sep 17 00:00:00 2001 From: metacryst Date: Fri, 16 Jan 2026 05:22:52 -0600 Subject: [PATCH] switching networks works, established server functions --- db/personal/1/db.json | 4 + notes.js | 31 ++- server/auth.js | 10 +- server/db/db.js | 4 + server/db/model/member.js | 14 ++ server/index.js | 15 ++ server/ws/ws.js | 12 +- ui/_/code/bridge/bridge.js | 38 ++++ ui/_/code/bridge/serverFunctions.js | 11 + ui/_/code/quill.js | 10 +- ui/_/code/ws/Connection.js | 85 ++++---- ui/_/code/ws/Socket.js | 4 + ui/_/code/ws/shim/fs.js | 1 + ui/desktop/apps/Dashboard/Dashboard.js | 13 +- ui/desktop/apps/Forum/Forum.js | 2 +- ui/desktop/apps/Messages/Messages.js | 4 +- ui/desktop/apps/Messages/MessagesPanel.js | 2 +- ui/desktop/apps/Messages/MessagesSidebar.js | 2 +- ui/desktop/apps/People/People.js | 4 +- ui/desktop/components/AppMenu.js | 10 +- ui/desktop/components/AppWindow.js | 11 +- ui/desktop/components/ProfileMenu.js | 8 +- ui/desktop/components/Sidebar.js | 40 +++- ui/desktop/index.html | 7 + ui/desktop/index.js | 221 +++++++++++--------- ui/desktop/ws/Connection.js | 62 ------ ui/desktop/ws/Socket.js | 45 ---- ui/mobile/Home.js | 4 +- ui/mobile/apps/Dashboard/Dashboard.js | 2 +- ui/mobile/index.js | 40 ++-- ui/public/pages/Home.js | 4 +- 31 files changed, 410 insertions(+), 310 deletions(-) create mode 100644 db/personal/1/db.json create mode 100644 ui/_/code/bridge/bridge.js create mode 100644 ui/_/code/bridge/serverFunctions.js create mode 100644 ui/_/code/ws/shim/fs.js delete mode 100644 ui/desktop/ws/Connection.js delete mode 100644 ui/desktop/ws/Socket.js diff --git a/db/personal/1/db.json b/db/personal/1/db.json new file mode 100644 index 0000000..0a4930f --- /dev/null +++ b/db/personal/1/db.json @@ -0,0 +1,4 @@ +{ + "nodes": {}, + "edges": {} +} \ No newline at end of file diff --git a/notes.js b/notes.js index dd314e3..53b6dcd 100644 --- a/notes.js +++ b/notes.js @@ -7,15 +7,36 @@ img(`db/images/${networks[i].logo}`, "2.25em", "2.25em") } }) .cursor("default") - .DEFAULT() + .Default() .opacity(0) .borderLeft(0) .paddingLeft(10, px) - .HOVERED() + .Hovered() .opacity(0.8) - .classStyle("selected") - .borderLeft("1px solid var(--accent)") - .paddingLeft(9, px) + .Watch(global.currentNetwork) + .If((val) => val === this.getAttribute("network")) + .borderLeft("1px solid black") + .paddingLeft(9, px) + .ElseIf((val) => val !== this.getAttribute("network")) + .borderLeft("0") + .paddingLeft(10, px) + + +render() { + ZStack(() => { + Watch(global.currentNetwork) + .Case("Dashboard", () => { + Dashboard() + }) + .Case("People", () => { + People() + }) + }) + .overflow("scroll") + .position("absolute") + .onEvent("resize", () => { + this.rerender() + }) diff --git a/server/auth.js b/server/auth.js index 8a1f8aa..499df71 100644 --- a/server/auth.js +++ b/server/auth.js @@ -29,7 +29,7 @@ export default class AuthHandler { } getProfile(req, res) { - const token = req.cookies.auth_token; + const token = req.cookies?.auth_token; if (!token) return res.status(401).send({ error: "No auth token" }); try { @@ -42,7 +42,13 @@ export default class AuthHandler { return db.networks.get(c.to) }) - res.send({ email: user.email, name: user.firstName + " " + user.lastName, networks: userOrgs}); + res.send({ + id: user.id, + email: user.email, + name: user.firstName + " " + user.lastName, + networks: userOrgs, + apps: user.apps + }); } catch (e) { console.error("Error getting profile: ", e) res.status(401).send({ error: "Invalid token" }); diff --git a/server/db/db.js b/server/db/db.js index 62cf853..4a15ba0 100644 --- a/server/db/db.js +++ b/server/db/db.js @@ -3,9 +3,13 @@ import chalk from 'chalk'; import path from 'path'; import {nodeModels, edgeModels} from './model/import.js' import Edge from "./model/edge.js" +import { fileURLToPath } from "url" +const __dirname = path.dirname(fileURLToPath(import.meta.url)) export default class Database { + PERSONAL_DATA_PATH = path.join(__dirname, '../../db/personal') + nodes = new Array(10000).fill(0); edges = new Array(10000).fill(0); diff --git a/server/db/model/member.js b/server/db/model/member.js index 4f6ecdd..95d9e92 100644 --- a/server/db/model/member.js +++ b/server/db/model/member.js @@ -1,3 +1,5 @@ +import path from 'path'; +import fs from 'fs'; import argon2 from 'argon2'; import { z } from 'zod'; @@ -15,6 +17,7 @@ export default class Member { firstName: z.string(), lastName: z.string(), password: z.string(), + apps: z.array(z.string()), created: z.string() }) @@ -48,6 +51,17 @@ export default class Member { return members } + async getPersonalData(id) { + const filePath = path.join(global.db.PERSONAL_DATA_PATH, id, "db.json"); + + const [raw] = await Promise.all([ + fs.promises.readFile(filePath, "utf8"), + ]); + + const result = raw.trim() ? JSON.parse(raw) : []; + return result + } + getByID(id) { if(typeof id === 'string') { id = id.split("-")[1] diff --git a/server/index.js b/server/index.js index a63f138..e25bdeb 100644 --- a/server/index.js +++ b/server/index.js @@ -38,10 +38,25 @@ class Server { router.post('/free', this.newUserSubmission) router.get('/db/images/*', this.getUserImage) router.get('/app/orgdata/*', this.getOrgData) + router.get('/app/mydata/*', this.getPersonalData) router.get('/*', this.get) return router } + getPersonalData = async (req, res, next) => { + try { + const memberId = req.params[0] + + let data = await global.db.members.getPersonalData(memberId) + + res.json({ + data + }); + } catch (err) { + next(err); + } + } + getOrgData = async (req, res, next) => { try { const networkId = req.params[0] diff --git a/server/ws/ws.js b/server/ws/ws.js index c7edd8c..1fadcb2 100644 --- a/server/ws/ws.js +++ b/server/ws/ws.js @@ -2,6 +2,7 @@ import { WebSocket, WebSocketServer } from 'ws'; import { z } from 'zod'; import jwt from 'jsonwebtoken'; +import * as serverFunctions from "../../ui/_/code/bridge/serverFunctions.js" import ForumHandler from "./handlers/ForumHandler.js" import MessagesHandler from "./handlers/MessagesHandler.js" @@ -9,7 +10,7 @@ export default class Socket { wss; messageSchema = z.object({ id: z.string(), - app: z.string(), + app: z.string().optional(), operation: z.string().optional(), msg: z.union([ z.object({}).passthrough(), // allows any object @@ -67,7 +68,7 @@ export default class Socket { const text = msg.toString(); const req = JSON.parse(text); if(!this.messageSchema.safeParse(req).success) throw new Error("Socket.handleMessage: Incoming ws message has incorrect format!") - + let responseData; switch (req.app) { case "FORUM": @@ -79,7 +80,12 @@ export default class Socket { break; default: - console.error("unknown ws message") + if(!req.app) { + let func = req.msg + responseData = serverFunctions[func.name](...args) + } else { + console.error("unknown ws message") + } } let response = { diff --git a/ui/_/code/bridge/bridge.js b/ui/_/code/bridge/bridge.js new file mode 100644 index 0000000..be3ae76 --- /dev/null +++ b/ui/_/code/bridge/bridge.js @@ -0,0 +1,38 @@ +const IS_NODE = + typeof process !== "undefined" && + process.versions?.node != null + +async function bridgeSend(name, args) { + // Example browser implementation: send function call to server + const res = await global.Socket.send({ + name: name, + args: args + }) + + const json = await res.json() + if (!res.ok) throw new Error(json.error) + return json.result +} + +/** + * Wraps an object of functions so that: + * - Node calls the real function + * - Browser calls bridgeSend + */ +export function createBridge(funcs) { + return new Proxy(funcs, { + get(target, prop) { + const orig = target[prop] + + if (typeof orig !== "function") return orig + + return function (...args) { + if (IS_NODE) { + return orig(...args) + } else { + return bridgeSend(prop, args) + } + } + } + }) +} diff --git a/ui/_/code/bridge/serverFunctions.js b/ui/_/code/bridge/serverFunctions.js new file mode 100644 index 0000000..52682f1 --- /dev/null +++ b/ui/_/code/bridge/serverFunctions.js @@ -0,0 +1,11 @@ +import fs from "fs" +import { createBridge } from "./bridge.js" + +const handlers = { + getProfile(one, two) { + fs.writeFileSync("output.txt", `${one} ${two}`) + return "written to disk" + }, +} + +export const { getProfile } = createBridge(handlers) diff --git a/ui/_/code/quill.js b/ui/_/code/quill.js index 3be0247..e144543 100644 --- a/ui/_/code/quill.js +++ b/ui/_/code/quill.js @@ -1,6 +1,7 @@ /* Sam Russell Captured Sun + 1.16.26 - Moving nav event dispatch out of pushState, adding null feature to attr() 1.5.26 - Switching verticalAlign and horizontalAlign names, adding borderVertical and Horizontal 12.26.25 - State for arrays, nested objects. State for stacks (Shadow-only) 12.17.25 - [Hyperia] - adding width, height functions. adding "e" to onClick. adding the non-window $$ funcs. @@ -27,7 +28,6 @@ let oldPushState = history.pushState; history.pushState = function pushState() { let ret = oldPushState.apply(this, arguments); window.dispatchEvent(new Event('pushstate')); - window.dispatchEvent(new Event('navigate')); return ret; }; @@ -53,8 +53,8 @@ window.setQuery = function(key, value) { }; window.navigateTo = function(url) { - window.dispatchEvent(new Event('navigate')); window.history.pushState({}, '', url); + window.dispatchEvent(new Event('navigate')); } window.setLocation = function(url) { @@ -1197,7 +1197,11 @@ HTMLElement.prototype.attr = function(attributes) { } for (const [key, value] of Object.entries(attributes)) { - this.setAttribute(key, value); + if(value === null) { + this.removeAttribute(key) + } else { + this.setAttribute(key, value); + } } return this; }; diff --git a/ui/_/code/ws/Connection.js b/ui/_/code/ws/Connection.js index 7d1d52c..9119a35 100644 --- a/ui/_/code/ws/Connection.js +++ b/ui/_/code/ws/Connection.js @@ -1,62 +1,61 @@ class Connection { - connectionTries = 0 + connectionTries = 0; ws; - linkCreated; - wsStatus; + receiveCB; constructor(receiveCB) { - this.init() - this.receiveCB = receiveCB + this.receiveCB = receiveCB; } - - init() { - if(window.location.hostname.includes("local")) { - this.ws = new WebSocket("ws://" + window.location.host) - } else { - this.ws = new WebSocket("wss://" + window.location.hostname + window.location.pathname) - } - this.ws.addEventListener('open', () => { - this.connectionTries = 0 - console.log("Websocket connection established."); - this.ws.addEventListener('message', this.receiveCB) + + init = async () => { + return new Promise((resolve, reject) => { + const url = window.location.hostname.includes("local") + ? "ws://" + window.location.host + : "wss://" + window.location.hostname + window.location.pathname; + + this.ws = new WebSocket(url); + + this.ws.addEventListener('open', () => { + this.connectionTries = 0; + console.log("WebSocket connection established."); + this.ws.addEventListener('message', this.receiveCB); + resolve(this.ws); // resolve when open + }); + + this.ws.addEventListener('close', () => { + console.log('WebSocket closed'); + this.checkOpen(); // attempt reconnection + }); + + this.ws.addEventListener('error', (err) => { + console.error('WebSocket error', err); + reject(err); // reject if error occurs + }); }); - this.ws.addEventListener("close", () => { - this.checkOpen(); - console.log('Websocket Closed') - }) } - - async checkOpen() { + + checkOpen = async () => { if (this.ws.readyState === WebSocket.OPEN) { - return true + return true; } else { - await this.sleep(this.connectionTries < 20 ? 5000 : 60000) - this.connectionTries++ - console.log('Reestablishing connection') - this.init() + await this.sleep(this.connectionTries < 20 ? 5000 : 60000); + this.connectionTries++; + console.log('Reestablishing connection'); + await this.init(); } } - sleep = (time) => { - return new Promise(resolve => { - setTimeout(resolve, time); - }); - } - + sleep = (time) => new Promise(resolve => setTimeout(resolve, time)); + send = (msg) => { - console.log("sending") if (this.ws.readyState === WebSocket.OPEN) { this.ws.send(msg); - } - else if(this.connectionTries === 0) { - setTimeout(() => { - this.send(msg) - }, 100) - } - else { - console.error('No websocket connection: Cannot send message'); + } else if (this.connectionTries === 0) { + setTimeout(() => this.send(msg), 100); + } else { + console.error('No WebSocket connection: Cannot send message'); } } } -export default Connection \ No newline at end of file +export default Connection; diff --git a/ui/_/code/ws/Socket.js b/ui/_/code/ws/Socket.js index 49357ef..55c249c 100644 --- a/ui/_/code/ws/Socket.js +++ b/ui/_/code/ws/Socket.js @@ -10,6 +10,10 @@ export default class Socket { this.connection = new Connection(this.receive); } + async init() { + await this.connection.init() + } + isOpen() { if(this.connection.checkOpen()) { return true; diff --git a/ui/_/code/ws/shim/fs.js b/ui/_/code/ws/shim/fs.js new file mode 100644 index 0000000..56004c9 --- /dev/null +++ b/ui/_/code/ws/shim/fs.js @@ -0,0 +1 @@ +export default {} \ No newline at end of file diff --git a/ui/desktop/apps/Dashboard/Dashboard.js b/ui/desktop/apps/Dashboard/Dashboard.js index f722b9b..e16bdd4 100644 --- a/ui/desktop/apps/Dashboard/Dashboard.js +++ b/ui/desktop/apps/Dashboard/Dashboard.js @@ -20,6 +20,15 @@ class Dashboard extends Shadow { render() { VStack(() => { + + if(window.location.pathname.startsWith("/my")) { + h1(global.profile.name); + return + } + else if(!window.location.pathname.includes("comalyr")) { + return + } + h1("Website Inquiries"); p("Contact Us") @@ -41,7 +50,7 @@ class Dashboard extends Shadow { .maxWidth(95, pct) .gap(8); - window.currentNetwork.data.contact.forEach((entry) => { + global.currentNetwork.data.contact.forEach((entry) => { HStack(() => { this.cell("time", entry.time); this.cell("fname", entry.fname); @@ -74,7 +83,7 @@ class Dashboard extends Shadow { .maxWidth(95, pct) .gap(8); - window.currentNetwork.data.join.forEach((entry) => { + global.currentNetwork.data.join.forEach((entry) => { HStack(() => { this.cell("time", entry.time); this.cell("fname", entry.fname); diff --git a/ui/desktop/apps/Forum/Forum.js b/ui/desktop/apps/Forum/Forum.js index 95ace7a..1e61268 100644 --- a/ui/desktop/apps/Forum/Forum.js +++ b/ui/desktop/apps/Forum/Forum.js @@ -79,7 +79,7 @@ class Forum extends Shadow { .fontSize(1, em) .onKeyDown(function (e) { if (e.key === "Enter") { - window.Socket.send({app: "FORUM", operation: "SEND", msg: {forum: "HY", text: this.value }}) + global.Socket.send({app: "FORUM", operation: "SEND", msg: {forum: "HY", text: this.value }}) this.value = "" } }) diff --git a/ui/desktop/apps/Messages/Messages.js b/ui/desktop/apps/Messages/Messages.js index 350c880..1432e1b 100644 --- a/ui/desktop/apps/Messages/Messages.js +++ b/ui/desktop/apps/Messages/Messages.js @@ -65,7 +65,7 @@ class Messages extends Shadow { .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 }}) + global.Socket.send({app: "MESSAGES", operation: "SEND", msg: { conversation: `CONVERSATION-${this.selectedConvoID}`, text: e.target.value }}) e.target.value = "" } }) @@ -104,7 +104,7 @@ class Messages extends Shadow { .color("var(--accent)") .onKeyDown(function (e) { if (e.key === "Enter") { - window.Socket.send({app: "MESSAGES", operation: "ADDCONVERSATION", msg: {email: this.value }}) + global.Socket.send({app: "MESSAGES", operation: "ADDCONVERSATION", msg: {email: this.value }}) this.value = "" } }) diff --git a/ui/desktop/apps/Messages/MessagesPanel.js b/ui/desktop/apps/Messages/MessagesPanel.js index b608212..d535baa 100644 --- a/ui/desktop/apps/Messages/MessagesPanel.js +++ b/ui/desktop/apps/Messages/MessagesPanel.js @@ -13,7 +13,7 @@ class MessagesPanel extends Shadow { if(this.messages) { for(let i=0; i { HStack(() => { p(message.from.firstName + " " + message.from.lastName) diff --git a/ui/desktop/apps/Messages/MessagesSidebar.js b/ui/desktop/apps/Messages/MessagesSidebar.js index 453a91d..d0aeb10 100644 --- a/ui/desktop/apps/Messages/MessagesSidebar.js +++ b/ui/desktop/apps/Messages/MessagesSidebar.js @@ -57,7 +57,7 @@ class MessagesSidebar extends Shadow { let membersString = "" for(let i=0; i 2) { diff --git a/ui/desktop/apps/People/People.js b/ui/desktop/apps/People/People.js index d44e801..7f0c219 100644 --- a/ui/desktop/apps/People/People.js +++ b/ui/desktop/apps/People/People.js @@ -5,8 +5,8 @@ class People extends Shadow { .fontWeight("bold") .marginBottom(2, em) - for(let i = 0; i < window.currentNetwork.data.members.length; i++) { - let member = window.currentNetwork.data.members[i] + for(let i = 0; i < global.currentNetwork.data.members.length; i++) { + let member = global.currentNetwork.data.members[i] HStack(() => { p(member.firstName + " " + member.lastName) .width(10, pct) diff --git a/ui/desktop/components/AppMenu.js b/ui/desktop/components/AppMenu.js index ed6fbd5..0e22b5a 100644 --- a/ui/desktop/components/AppMenu.js +++ b/ui/desktop/components/AppMenu.js @@ -15,9 +15,9 @@ class AppMenu extends Shadow { } HStack(() => { - let currentNetwork = window.currentNetwork + let currentNetwork = global.currentNetwork if(!currentNetwork) return - let currentApp = window.currentApp + let currentApp = global.currentApp for(let i = 0; i < currentNetwork.apps.length; i++) { let app = currentNetwork.apps[i] @@ -27,7 +27,7 @@ class AppMenu extends Shadow { .paddingBottom(currentApp === app ? 4 : 5, px) .borderBottom(currentApp === app ? "1px solid var(--accent)" : "") .onClick((done) => { - if(done) window.openApp(app) + if(done) global.openApp(app) }) .onHover(function (hovering) { if(hovering) { @@ -49,6 +49,10 @@ class AppMenu extends Shadow { // console.log("app hello?") // BUG: Quill is not acknowledging this event unless there is something else in the function body this.rerender() }) + .onEvent("networkchange", () => { + // console.log(global.currentApp) + this.rerender() + }) .position("fixed") .x(0).yBottom(0) .width(100, vw) diff --git a/ui/desktop/components/AppWindow.js b/ui/desktop/components/AppWindow.js index e495712..61d71c5 100644 --- a/ui/desktop/components/AppWindow.js +++ b/ui/desktop/components/AppWindow.js @@ -22,7 +22,7 @@ class AppWindow extends Shadow { render() { ZStack(() => { - switch(window.currentApp) { + switch(global.currentApp) { case "Dashboard": Dashboard() break; @@ -33,17 +33,20 @@ class AppWindow extends Shadow { }) .overflow("scroll") .position("absolute") - .onEvent("resize", () => { - this.rerender() - }) .width(window.innerWidth - this.calculateWidth(), px) .height(window.innerHeight - this.calculateHeight(), px) .background("var(--app)") .x(this.calculateWidth(), px) .yBottom(this.calculateHeight(), px) + .onEvent("resize", () => { + this.rerender() + }) .onEvent("appchange", () => { this.rerender() }) + .onEvent("networkchange", () => { + this.rerender() + }) } } diff --git a/ui/desktop/components/ProfileMenu.js b/ui/desktop/components/ProfileMenu.js index f46a87e..01504e2 100644 --- a/ui/desktop/components/ProfileMenu.js +++ b/ui/desktop/components/ProfileMenu.js @@ -8,7 +8,7 @@ class ProfileMenu extends Shadow { p("Email: ") .fontWeight("bold") - p(window.profile?.email) + p(global.profile?.email) }) .gap(1, em) @@ -16,7 +16,7 @@ class ProfileMenu extends Shadow { p("Name: ") .fontWeight("bold") - p(window.profile?.name) + p(global.profile?.name) }) .gap(1, em) @@ -37,8 +37,8 @@ class ProfileMenu extends Shadow { .center() .display("none") .onAppear(async () => { - if(!window.profile) { - window.profile = await this.fetchProfile() + if(!global.profile) { + global.profile = await this.fetchProfile() this.rerender() } }) diff --git a/ui/desktop/components/Sidebar.js b/ui/desktop/components/Sidebar.js index a68f62b..233d533 100644 --- a/ui/desktop/components/Sidebar.js +++ b/ui/desktop/components/Sidebar.js @@ -1,18 +1,43 @@ class Sidebar extends Shadow { - currentNetwork = null + + toggleSelectedStyles(el) { + let currentlySelected = $("img[selected]") + if(currentlySelected) { + currentlySelected.removeAttribute("selected") + currentlySelected.style.borderLeft = "" + currentlySelected.style.paddingLeft = "10px" + } + + el.setAttribute("selected", "") + el.style.borderLeft = "1px solid var(--accent)" + el.style.paddingLeft = "9px" + } render() { VStack(() => { + let selected = window.location.pathname.startsWith("/my") + img(document.documentElement.classList.contains("red") ? "/_/icons/quillblack.svg" : "/_/icons/quill.svg", "2.5em", "2.5em") .marginTop(6, vh) .marginBottom(2, vh) + .attr({selected: selected ? "" : null}) + .paddingRight(0.5, em) + .paddingLeft(selected ? 9 : 10, px) + .borderLeft(selected ? "1px solid var(--accent)" : "0") + .onClick((done, e) => { + if(done) { + this.toggleSelectedStyles(e.target) + window.navigateTo("/my") + } + }) - let networks = window.profile.networks + let networks = global.profile.networks for(let i=0; i { + if(done) { + this.toggleSelectedStyles(e.target) + window.navigateTo(`/${networks[i].abbreviation}`) } }) .cursor("default") @@ -41,7 +65,7 @@ class Sidebar extends Shadow { .borderRight("1px solid var(--accent)") .zIndex(3) .onEvent("themechange", () => { - console.log("change") + console.log("why is this needed smg") this.rerender() }) } diff --git a/ui/desktop/index.html b/ui/desktop/index.html index 1813cea..94be050 100644 --- a/ui/desktop/index.html +++ b/ui/desktop/index.html @@ -14,6 +14,13 @@ } } + diff --git a/ui/desktop/index.js b/ui/desktop/index.js index 9bf2636..ddcb0dc 100644 --- a/ui/desktop/index.js +++ b/ui/desktop/index.js @@ -1,118 +1,149 @@ import Socket from "/_/code/ws/Socket.js" import "./Home.js" - import util from "./util.js" -window.util = util -window.Socket = new Socket() +let Global = class { + Socket = new Socket() + profile = null + currentNetwork = "" + currentApp = "" + util = util -window.currentNetwork = "" -window.currentApp = "" - -async function openNetworkAndApp() { - - if(window.currentNetwork !== networkFromPath()) { - window.currentNetwork = networkFromPath() - const event = new CustomEvent('networkchanged', { - detail: { name: currentNetwork } - }); - window.dispatchEvent(event) - } - - if(!window.currentNetwork.data) { - let appData = await fetch("/app/orgdata/" + window.profile.networks[0].id, {method: "GET"}) - let json = await appData.json() - window.currentNetwork.data = json - } - - if(window.currentApp !== appFromPath()) { - window.currentApp = appFromPath() + openApp = function(appName) { + const appUrl = appName.charAt(0).toLowerCase() + appName.slice(1); + let parts = window.location.pathname.split('/').filter(Boolean); + let newPath = "/" + parts[0] + "/" + appUrl + window.navigateTo(newPath) const event = new CustomEvent('appchange', { - detail: { name: window.currentApp } + detail: { name: appName } }); window.dispatchEvent(event) } - if(window.currentNetwork) { // 2 navigates fire on load: 1 initial, and one after the org redirect - document.title = `${window.currentNetwork.abbreviation} | Parchment` + async fetchAppData() { + let personalSpace = this.currentNetwork === this.profile + let appData = await fetch(`/app/${personalSpace ? "my" : "org"}data/` + this.currentNetwork.id, {method: "GET"}) + let json = await appData.json() + return json } -} -window.addEventListener("navigate", openNetworkAndApp) + onNavigate = async () => { -window.openApp = function(appName) { - const appUrl = appName.charAt(0).toLowerCase() + appName.slice(1); - let parts = window.location.pathname.split('/').filter(Boolean); - let newPath = "/" + parts[0] + "/" + appUrl - window.navigateTo(newPath) - const event = new CustomEvent('appchange', { - detail: { name: appName } - }); - window.dispatchEvent(event) -} + let selectedNetwork = this.networkFromPath() + let selectedApp = this.appFromPath() -window.networkFromPath = function () { - const pathname = window.location.pathname; - const firstSegment = pathname.split('/').filter(Boolean)[0] || ''; - let networks = window.profile?.networks - for(let i = 0; i < networks.length; i++) { - let network = networks[i] - if(network.abbreviation === firstSegment) { - return network + if(!selectedNetwork) { + if(this.profile.networks.length > 0) { + let path = `/${this.getDefaultNetworkName()}/${this.getDefaultAppName()}` + history.replaceState({}, '', path) + } + } else if(!selectedApp) { + if(this.currentNetwork === window.profile) { + history.replaceState({}, '', `${window.location.pathname}/${window.profile.apps[0]}`) + } else { + history.replaceState({}, '', `${window.location.pathname}/${this.getDefaultAppName()}`) + } + } + + selectedNetwork = this.networkFromPath() + selectedApp = this.appFromPath() + + let networkChanged = this.currentNetwork !== selectedNetwork + let appChanged = this.currentApp !== selectedApp + if(networkChanged) { + this.currentNetwork = selectedNetwork + this.currentApp = selectedApp + const event = new CustomEvent('networkchange', { + detail: { name: this.currentNetwork } + }); + window.dispatchEvent(event) + } + + if(!this.currentNetwork.data) { + this.currentNetwork.data = await this.fetchAppData() + } + + if(appChanged && !networkChanged) { + this.currentApp = selectedApp + const event = new CustomEvent('appchange', { + detail: { name: this.currentApp } + }); + window.dispatchEvent(event) + } + + document.title = (this.currentNetwork === this.profile) ? "Parchment" : `${this.currentNetwork.abbreviation} | Parchment` + } + + setCurrentNetworkAndApp() { + this.currentNetwork = this.networkFromPath() + + } + + getDefaultNetworkName() { + let defaultNetwork = this.profile.networks[0] + return defaultNetwork.abbreviation + } + + getDefaultAppName() { + let defaultNetwork = this.profile.networks[0] + return defaultNetwork.apps[0].toLowerCase() + } + + networkFromPath = function () { + const pathname = window.location.pathname; + const firstSegment = pathname.split('/').filter(Boolean)[0] || ''; + if(firstSegment === "my") { + return this.profile + } else { + let networks = this.profile.networks + for(let i = 0; i < networks.length; i++) { + let network = networks[i] + if(network.abbreviation === firstSegment) { + return network + } + } } } -} -window.appFromPath = function() { - const pathname = window.location.pathname; - const segments = pathname.split('/').filter(Boolean); - const secondSegment = segments[1] || ""; - const capitalized = secondSegment.charAt(0).toUpperCase() + secondSegment.slice(1); - return capitalized -} + appFromPath = function() { + const pathname = window.location.pathname; + const segments = pathname.split('/').filter(Boolean); + const secondSegment = segments[1] || "" + const capitalized = secondSegment.charAt(0).toUpperCase() + secondSegment.slice(1); + return capitalized + } -async function getProfile() { - try { - const res = await fetch("/profile", { - method: "GET", - credentials: "include", - headers: { - "Content-Type": "application/json" - } - }); + async getProfile() { + try { + const res = await fetch("/profile", { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json" + } + }); - if (!res.ok) throw new Error("Failed to fetch profile"); + if (!res.ok) throw new Error("Failed to fetch profile"); - const profile = await res.json(); - console.log("getProfile: ", profile); - window.profile = profile - } catch (err) { - console.error(err); + const profile = await res.json(); + console.log("getProfile: ", profile); + this.profile = profile + } catch (err) { + console.error(err); + } + } + + constructor() { + window.addEventListener("navigate", this.onNavigate) + + this.getProfile().then(async () => { + + await this.onNavigate() + + Home() + + }) } } -function getInitialNetworkPath() { - let path = "" - let defaultNetwork = window.profile.networks[0] - - if(!networkFromPath()) { - path += (defaultNetwork.abbreviation + "/") - } - - if(!appFromPath()) { - let defaultApp = defaultNetwork.apps[0] - path += defaultApp.toLowerCase() - } - - return path -} - -getProfile().then(async () => { - - if(window.profile.networks.length > 0) { - let path = getInitialNetworkPath() - window.navigateTo(path) - } - - Home() -}) \ No newline at end of file +window.global = new Global() \ No newline at end of file diff --git a/ui/desktop/ws/Connection.js b/ui/desktop/ws/Connection.js deleted file mode 100644 index 7d1d52c..0000000 --- a/ui/desktop/ws/Connection.js +++ /dev/null @@ -1,62 +0,0 @@ -class Connection { - connectionTries = 0 - ws; - linkCreated; - wsStatus; - - constructor(receiveCB) { - this.init() - this.receiveCB = receiveCB - } - - init() { - if(window.location.hostname.includes("local")) { - this.ws = new WebSocket("ws://" + window.location.host) - } else { - this.ws = new WebSocket("wss://" + window.location.hostname + window.location.pathname) - } - this.ws.addEventListener('open', () => { - this.connectionTries = 0 - console.log("Websocket connection established."); - this.ws.addEventListener('message', this.receiveCB) - }); - this.ws.addEventListener("close", () => { - this.checkOpen(); - console.log('Websocket Closed') - }) - } - - async checkOpen() { - if (this.ws.readyState === WebSocket.OPEN) { - return true - } else { - await this.sleep(this.connectionTries < 20 ? 5000 : 60000) - this.connectionTries++ - console.log('Reestablishing connection') - this.init() - } - } - - sleep = (time) => { - return new Promise(resolve => { - setTimeout(resolve, time); - }); - } - - send = (msg) => { - console.log("sending") - if (this.ws.readyState === WebSocket.OPEN) { - this.ws.send(msg); - } - else if(this.connectionTries === 0) { - setTimeout(() => { - this.send(msg) - }, 100) - } - else { - console.error('No websocket connection: Cannot send message'); - } - } -} - -export default Connection \ No newline at end of file diff --git a/ui/desktop/ws/Socket.js b/ui/desktop/ws/Socket.js deleted file mode 100644 index 49357ef..0000000 --- a/ui/desktop/ws/Socket.js +++ /dev/null @@ -1,45 +0,0 @@ -import Connection from "./Connection.js"; - -export default class Socket { - connection; - disabled = true; - requestID = 1; - pending = new Map(); - - constructor() { - this.connection = new Connection(this.receive); - } - - isOpen() { - if(this.connection.checkOpen()) { - return true; - } else { - return false; - } - } - - send(msg) { - return new Promise(resolve => { - const id = (++this.requestID).toString(); - this.pending.set(id, resolve); - this.connection.send(JSON.stringify({ id, ...msg })); - }); - } - - receive = (event) => { - const msg = JSON.parse(event.data); - if (msg.id && this.pending.has(msg.id)) { - this.pending.get(msg.id)(msg); - this.pending.delete(msg.id); - return; - } else { - this.onBroadcast(msg) - } - } - - onBroadcast(msg) { - window.dispatchEvent(new CustomEvent(msg.event, { - detail: msg.msg - })); - } -} \ No newline at end of file diff --git a/ui/mobile/Home.js b/ui/mobile/Home.js index a5f095e..22d6533 100644 --- a/ui/mobile/Home.js +++ b/ui/mobile/Home.js @@ -8,8 +8,8 @@ class Home extends Shadow { ZStack(() => { ZStack(() => { - console.log(window.currentApp) - switch(window.currentApp) { + console.log(global.currentApp) + switch(global.currentApp) { case "Dashboard": Dashboard() break; diff --git a/ui/mobile/apps/Dashboard/Dashboard.js b/ui/mobile/apps/Dashboard/Dashboard.js index 0445bc4..27cb803 100644 --- a/ui/mobile/apps/Dashboard/Dashboard.js +++ b/ui/mobile/apps/Dashboard/Dashboard.js @@ -1,7 +1,7 @@ class Dashboard extends Shadow { render() { VStack(() => { - console.log(window.currentNetwork) + console.log(global.currentNetwork) }) .width(100, pct) .height(100, pct) diff --git a/ui/mobile/index.js b/ui/mobile/index.js index 327f6b3..957e2f2 100644 --- a/ui/mobile/index.js +++ b/ui/mobile/index.js @@ -4,45 +4,45 @@ import "./Home.js" import util from "./util.js" window.util = util -window.Socket = new Socket() +global.Socket = new Socket() -window.currentNetwork = "" -window.currentApp = "" +global.currentNetwork = "" +global.currentApp = "" async function openNetworkAndApp() { // console.log("currentApp: ", currentApp, "currentnet: ", currentNetwork, "nfrompath: ", networkFromPath(), "afrompath", appFromPath()) - if(window.currentNetwork !== networkFromPath()) { - window.currentNetwork = networkFromPath() - const event = new CustomEvent('networkchanged', { + if(global.currentNetwork !== networkFromPath()) { + global.currentNetwork = networkFromPath() + const event = new CustomEvent('networkchange', { detail: { name: currentNetwork } }); window.dispatchEvent(event) } - if(!window.currentNetwork.data) { - let appData = await fetch("/app/orgdata/" + window.profile.networks[0].id, {method: "GET"}) + if(!global.currentNetwork.data) { + let appData = await fetch("/app/orgdata/" + global.profile.networks[0].id, {method: "GET"}) let json = await appData.json() - window.currentNetwork.data = json + global.currentNetwork.data = json } - console.log("current: ", window.currentApp, "afrompath: ", appFromPath()) - if(window.currentApp !== appFromPath()) { - window.currentApp = appFromPath() + console.log("current: ", global.currentApp, "afrompath: ", appFromPath()) + if(global.currentApp !== appFromPath()) { + global.currentApp = appFromPath() const event = new CustomEvent('appchange', { - detail: { name: window.currentApp } + detail: { name: global.currentApp } }); window.dispatchEvent(event) } - if(window.currentNetwork) { // 2 navigates fire on load: 1 initial, and one after the org redirect - document.title = `${window.currentNetwork.abbreviation} | Parchment` + if(global.currentNetwork) { // 2 navigates fire on load: 1 initial, and one after the org redirect + document.title = `${global.currentNetwork.abbreviation} | Parchment` } } window.addEventListener("navigate", openNetworkAndApp) -window.openApp = function(appName) { +global.currentApp = function(appName) { const appUrl = appName.charAt(0).toLowerCase() + appName.slice(1); let parts = window.location.pathname.split('/').filter(Boolean); let newPath = "/" + parts[0] + "/" + appUrl @@ -56,7 +56,7 @@ window.openApp = function(appName) { window.networkFromPath = function () { const pathname = window.location.pathname; const firstSegment = pathname.split('/').filter(Boolean)[0] || ''; - let networks = window.profile?.networks + let networks = global.profile?.networks for(let i = 0; i < networks.length; i++) { let network = networks[i] if(network.abbreviation === firstSegment) { @@ -87,7 +87,7 @@ async function getProfile() { const profile = await res.json(); console.log("getProfile: ", profile); - window.profile = profile + global.profile = profile } catch (err) { console.error(err); } @@ -95,7 +95,7 @@ async function getProfile() { function getInitialNetworkPath() { let path = "" - let defaultNetwork = window.profile.networks[0] + let defaultNetwork = global.profile.networks[0] if(!networkFromPath()) { path += (defaultNetwork.abbreviation + "/") @@ -111,7 +111,7 @@ function getInitialNetworkPath() { getProfile().then(async () => { - if(window.profile.networks.length > 0) { + if(global.profile.networks.length > 0) { let path = getInitialNetworkPath() window.navigateTo(path) } diff --git a/ui/public/pages/Home.js b/ui/public/pages/Home.js index 3660507..13c4a35 100644 --- a/ui/public/pages/Home.js +++ b/ui/public/pages/Home.js @@ -35,10 +35,12 @@ class Home extends Shadow { p("Parchment is a platform for small to medium-sized communities.") .color("var(--quillred)") .borderTop("1px solid var(--quillred)") + .verticalAlign("center") .borderHorizontal("1px solid var(--quillred)") .height(5, em) - .fontSize(window.isMobile() ? 1.5 : 1.2, vmax) + .fontSize(window.isMobile() ? 2 : 1.2, vmax) .paddingTop(2, vmax) + .paddingBottom(window.isMobile() ? 1 : 0, em) .paddingHorizontal(2, vmax) })