From 419c8b11516e380baa3d684a531845a1875e0592 Mon Sep 17 00:00:00 2001 From: metacryst Date: Mon, 12 Jan 2026 14:10:17 -0600 Subject: [PATCH] url scheme done, switch between apps and orgs mostly done --- db/{ => images}/comalyr.svg | 0 db/images/hyperia.svg | 38 +++++++++ notes.js | 62 ++++++++++++++ server/db/model/network.js | 3 +- server/index.js | 9 +- ui/_/code/quill.js | 25 +++++- ui/_/code/shared.css | 35 +++++--- ui/_/icons/house.svg | 4 +- ui/_/icons/nodesblack.svg | 11 --- ui/_/icons/people.svg | 6 +- ui/_/icons/peopleblack.svg | 3 - ui/desktop/Home.js | 81 +++++++++--------- ui/desktop/apps/Dashboard/Dashboard.js | 11 +++ ui/desktop/components/AppMenu.js | 110 +++++++++---------------- ui/desktop/components/AppWindow.js | 44 +++++----- ui/desktop/components/Sidebar.js | 70 ++++++++-------- ui/desktop/index.html | 9 ++ ui/desktop/index.js | 94 ++++++++++++++++++++- ui/public/index.html | 2 +- ui/public/pages/Home.js | 4 +- 20 files changed, 406 insertions(+), 215 deletions(-) rename db/{ => images}/comalyr.svg (100%) create mode 100644 db/images/hyperia.svg create mode 100644 notes.js delete mode 100644 ui/_/icons/nodesblack.svg delete mode 100644 ui/_/icons/peopleblack.svg create mode 100644 ui/desktop/apps/Dashboard/Dashboard.js diff --git a/db/comalyr.svg b/db/images/comalyr.svg similarity index 100% rename from db/comalyr.svg rename to db/images/comalyr.svg diff --git a/db/images/hyperia.svg b/db/images/hyperia.svg new file mode 100644 index 0000000..67b933e --- /dev/null +++ b/db/images/hyperia.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/notes.js b/notes.js new file mode 100644 index 0000000..dcc75ef --- /dev/null +++ b/notes.js @@ -0,0 +1,62 @@ +img(`db/images/${networks[i].logo}`, "2.25em", "2.25em") + .marginTop(3, vh) + .paddingRight(0.5, em) + .onClick(function (finished) { + if(finished) { + this.setAttribute("selected", "") + } + }) + .cursor("default") + .DEFAULT() + .opacity(0) + .borderLeft(0) + .paddingLeft(10, px) + .HOVERED() + .opacity(0.8) + .classStyle("selected") + .borderLeft("1px solid var(--accent)") + .paddingLeft(9, px) + + + +quill gotchas + image width / height + forgetting to put a "/" at the beginning of the url for window.navigateTo (if you don't want to add to the existing url) + forgetting to define the event callback with or without the word "function", like so: + + if you want "this" to be scoped to the element the listener is on: + .onClick(function (finished) { + if(finished) { + this.setAttribute("selected", "") + } + }) + + if you want "this" to be scoped to the parent shadow: + .onClick((finished) => { + if(finished) { + this.setAttribute("selected", "") + } + }) + + +window.svg = async function svg(src, width, height) { + const res = await fetch(src); + const svgText = await res.text(); + + let container = document.createElement("div") + container.innerHTML = svgText; + + const svg = container.querySelector("svg"); + if(width) + svg.setAttribute("width", width); + if(height) + svg.setAttribute("height", height); + + svg.style.display = "block"; + svg.setAttribute("aria-hidden", "true"); + + quill.render(container) + return container +} + +rendered 1 svg but gave an error on the attr() function \ No newline at end of file diff --git a/server/db/model/network.js b/server/db/model/network.js index 93b82a8..a6f7017 100644 --- a/server/db/model/network.js +++ b/server/db/model/network.js @@ -12,7 +12,8 @@ export default class Network { id: z.number(), name: z.string(), apps: z.array(z.string()), - logo: z.string() + logo: z.string(), + abbreviation: z.string() }) .strict() diff --git a/server/index.js b/server/index.js index d1c9236..0f6021a 100644 --- a/server/index.js +++ b/server/index.js @@ -97,9 +97,14 @@ class Server { }); return match ? path.join(dir, match) : null; } - let filePath = getFileByNumber(path.join(this.DBPath, "images"), path.basename(req.url)) - res.sendFile(filePath) + let filePath = path.join(this.DBPath, "images", path.basename(req.url)) + + if(filePath) { + res.sendFile(filePath) + } else { + return res.status(404).json({error: "Can't find image"}) + } } get = async (req, res) => { diff --git a/ui/_/code/quill.js b/ui/_/code/quill.js index 19242eb..c8a0f7a 100644 --- a/ui/_/code/quill.js +++ b/ui/_/code/quill.js @@ -51,12 +51,17 @@ window.setQuery = function(key, value) { return newUrl; }; - + window.navigateTo = function(url) { window.dispatchEvent(new Event('navigate')); window.history.pushState({}, '', url); } +window.setLocation = function(url) { + window.dispatchEvent(new Event('navigate')); + window.history.replaceState({}, '', url); +} + /* $ SELECTOR */ HTMLElement.prototype.$ = function(selector) { @@ -91,8 +96,6 @@ console.green = function(message) { } /* GET CSS VARIABLES FOR DARK OR LIGHT MODE */ -window.darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; -document.documentElement.classList.add(darkMode ? 'dark' : 'light'); window.getColor = function(name) { const rootStyles = getComputedStyle(document.documentElement); @@ -815,6 +818,20 @@ window.input = function(placeholder = "", width, height) { return el } +window.select = function(cb) { + let el = document.createElement("select") + el.render = cb + quill.render(el) + return el +} + +window.option = function(placeholder = "") { + let el = document.createElement("option") + el.innerText = placeholder + quill.render(el) + return el +} + window.label = function(inside) { let el = document.createElement("label") if(typeof inside === "string") { @@ -1034,7 +1051,7 @@ HTMLElement.prototype.onInput = function(cb) { }; HTMLElement.prototype.onChange = function(cb) { - if(!this.matches('input, textarea, [contenteditable=""], [contenteditable="true"]')) + if(!this.matches('input, textarea, select, [contenteditable=""], [contenteditable="true"]')) throw new Error("Can't put input event on non-input element!") this._storeListener("change", cb); return this; diff --git a/ui/_/code/shared.css b/ui/_/code/shared.css index ebe0f26..1816c45 100644 --- a/ui/_/code/shared.css +++ b/ui/_/code/shared.css @@ -1,8 +1,10 @@ :root { - --main: var(--brown); - --accent: var(--gold); - --accent2: var(--green); + --main: var(--parchment); + --app: var(--parchment); + --accent: black; + --accent2: black; + --parchment: #FFEBCC; --gold: #FEBA7D; --divider: #bb7c36; --green: #0857265c; @@ -11,15 +13,15 @@ --brown: #812A18; --darkbrown: #3f0808; - --house-src: "/_/icons/house.svg"; - --nodes-src: "/_/icons/nodes.svg"; - --forum-src: "/_/icons/forum.svg"; - --people-src: "/_/icons/people.svg"; + --house-src: /_/icons/house.svg; + --nodes-src: /_/icons/nodes.svg; + --forum-src: /_/icons/forum.svg; + --people-src: /_/icons/people.svg; } -@media (prefers-color-scheme: dark) { -:root { +:root.dark { --main: #000000; + --app: #180404; --accent: #695b4d; --accent2: var(--gold); @@ -28,12 +30,23 @@ --forum-src: /_/icons/forumdark.svg; --people-src: /_/icons/peopledark.svg; } -} :root.red { --main: var(--red); - --accent: #6a0000; + --app: var(--red); + --accent: black; --accent2: var(--green); + + --house-src: /_/icons/house.svg; + --nodes-src: /_/icons/nodes.svg; + --forum-src: /_/icons/forum.svg; + --people-src: /_/icons/people.svg; + + --quill-src: /_/icons/people.svg; +} + +:root.public { + --accent: var(--gold); } @font-face { diff --git a/ui/_/icons/house.svg b/ui/_/icons/house.svg index f2e6f86..f1916d4 100644 --- a/ui/_/icons/house.svg +++ b/ui/_/icons/house.svg @@ -1,4 +1,4 @@ - - + + diff --git a/ui/_/icons/nodesblack.svg b/ui/_/icons/nodesblack.svg deleted file mode 100644 index 51c5e6b..0000000 --- a/ui/_/icons/nodesblack.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/ui/_/icons/people.svg b/ui/_/icons/people.svg index 9ec5c7f..f2b266a 100644 --- a/ui/_/icons/people.svg +++ b/ui/_/icons/people.svg @@ -1,3 +1,3 @@ - - - + + + \ No newline at end of file diff --git a/ui/_/icons/peopleblack.svg b/ui/_/icons/peopleblack.svg deleted file mode 100644 index e05077c..0000000 --- a/ui/_/icons/peopleblack.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ui/desktop/Home.js b/ui/desktop/Home.js index b1ef75d..a2eee4c 100644 --- a/ui/desktop/Home.js +++ b/ui/desktop/Home.js @@ -9,43 +9,49 @@ class Home extends Shadow { ZStack(() => { Sidebar() - - switch(window.location.pathname) { - case "/": - AppWindow() - AppMenu() - break - case "/app/jobs": - AppWindow("Jobs") - AppMenu("Jobs") - break; - case "/app/messages": - AppWindow("Messages") - AppMenu("Messages") - break; - case "/app/market": - AppWindow("Market") - AppMenu("Market") - break; - case "/app/forum": - AppWindow("Forum") - AppMenu("Forum") - break; - default: - throw new Error("Unknown route!") - } - + + AppMenu() + + AppWindow() HStack(() => { - // ProfileButton() - // .zIndex(1) - // .cursor("default") - + let selected = document.documentElement.className + console.log(selected) + select(() => { + option("") + option("Light").attr({value: "light"}) + option("Dark").attr({value: "dark"}) + option("Red").attr({value: "red"}) + + $(`option[value=${selected}]`).selected = "true" + }) + .outline("none") + .background("transparent") + .height(2, em) + .color("var(--accent)") + .border("1px solid var(--accent)") + .attr({value: "dark"}) + .onChange((e) => { + console.log("onchange") + document.documentElement.className = e.target.value + localStorage.setItem("theme", e.target.value); + const event = new CustomEvent('themechange', { + detail: {} + }); + window.dispatchEvent(event) + }) + .onHover(function (hovering) { + if(hovering) { + this.style.background = "var(--green)" + } else { + this.style.background = "transparent" + } + }) + a("/signout", "Sign Out") .background("transparent") - .border(window.location.pathname === "/" ? "1px solid var(--tan)" : "0.5px solid #bb7c36") - .color(window.location.pathname === "/" ? "var(--tan)" : "var(--accent)") - .borderRadius(5, px) + .border("1px solid var(--accent") + .color("var(--accent)") .onHover(function (hovering) { if(hovering) { this.style.background = "var(--green)" @@ -53,15 +59,6 @@ class Home extends Shadow { this.style.background = "" } }) - .onNavigate(function () { - if(window.location.pathname === "/") { - this.style.border = "1px solid var(--tan)" - this.style.color = "var(--tan)" - } else { - this.style.border = "0.5px solid #bb7c36" - this.style.color = "var(--accent)" - } - }) }) .gap(1, em) .xRight(2, em).y(2.3, em) diff --git a/ui/desktop/apps/Dashboard/Dashboard.js b/ui/desktop/apps/Dashboard/Dashboard.js new file mode 100644 index 0000000..c58ccbc --- /dev/null +++ b/ui/desktop/apps/Dashboard/Dashboard.js @@ -0,0 +1,11 @@ +class Dashboard extends Shadow { + render() { + VStack(() => { + + }) + .width(100, pct) + .height(100, pct) + } +} + +register(Dashboard) \ No newline at end of file diff --git a/ui/desktop/components/AppMenu.js b/ui/desktop/components/AppMenu.js index 83d1419..7832973 100644 --- a/ui/desktop/components/AppMenu.js +++ b/ui/desktop/components/AppMenu.js @@ -1,17 +1,8 @@ -css(` - app-menu img:hover { - opacity: 0.8; - } -`) - -register( - class AppMenu extends Shadow { - selected; - constructor(selected) { - super() - this.selected = selected + images = { + "Dashboard": {src: "house-src", size: "1.5em"}, + "People": {src: "people-src", size: "1.7em"} } render() { @@ -22,76 +13,49 @@ class AppMenu extends Shadow { .getPropertyValue("--" + value) .trim(); } + HStack(() => { - img(cssVariable("house-src"), "1.5em") - img(cssVariable("people-src"), "1.7em") + let currentNetwork = window.currentNetwork + if(!currentNetwork) return + let currentApp = window.currentApp + + for(let i = 0; i < currentNetwork.apps.length; i++) { + let app = currentNetwork.apps[i] + img(cssVariable(this.images[app].src), this.images[app].size) + .attr({app: app}) + .padding(0.3, em) + .paddingBottom(currentApp === app ? 4 : 5, px) + .borderBottom(currentApp === app ? "1px solid var(--accent)" : "") + .onClick((done) => { + if(done) window.openApp(app) + }) + .onHover(function (hovering) { + if(hovering) { + this.style.opacity = 0.8 + } else { + this.style.opacity = "" + } + }) + } }) .justifyContent("center") .gap(3.5, em) .paddingRight(2, em) }) + .onEvent("themechange", () => { + this.rerender() + }) + .onEvent("appchanged", () => { + console.log("event firing successfully") + this.rerender() + }) .position("fixed") .x(0).yBottom(0) .width(100, vw) - .paddingVertical(1, em) - .borderTop("0.5px solid var(--accent)") - .onNavigate(() => { - if(window.location.pathname === "/") { - this.styleMaximized() - $("app-window").close() - } else { - this.styleMinimized() - $("app-window").open(this.selected) - } - }) - .onAppear(() => { - Array.from(this.querySelectorAll("img")).forEach((el) => { - el.addEventListener("mousedown", (e) => { - el.classList.add("touched") - }) - }) - window.addEventListener("mouseup", (e) => { - let target = e.target - if(!target.matches("app-menu img")) { - return - } - - target.classList.remove("touched") - - if(target.classList.contains("selected")) { - this.selected = "" - window.navigateTo("/") - } else { - this.selected = target.innerText - window.navigateTo("/app/" + target.innerText.toLowerCase()) - } - }) - }) - - if(this.selected) { - this.styleMinimized() - } - } - - styleMaximized() { - $$("app-menu p").forEach((el) => { - el.classList.remove("selected") - }) - this.classList.remove("minimized") - $("#divider").style.display = "" - } - - styleMinimized() { - $$("app-menu p").forEach((el) => { - if(el.innerText !== this.selected) { - el.classList.remove("selected") - } else { - el.classList.add("selected") - } - }) - this.classList.add("minimized") - $("#divider").style.display = "none" + .height(2.5, em) + .paddingVertical(0.7, em) + .borderTop("1px solid var(--accent)") } } -, "app-menu") \ No newline at end of file +register(AppMenu, "app-menu") \ No newline at end of file diff --git a/ui/desktop/components/AppWindow.js b/ui/desktop/components/AppWindow.js index 3f34b4a..6ca2b25 100644 --- a/ui/desktop/components/AppWindow.js +++ b/ui/desktop/components/AppWindow.js @@ -1,3 +1,4 @@ +import "../apps/Dashboard/Dashboard.js" import "../apps/Forum/Forum.js" import "../apps/Tasks/Tasks.js" import "../apps/Messages/Messages.js" @@ -5,16 +6,25 @@ import "../apps/Market/Market.js" import "../apps/Jobs/Jobs.js" class AppWindow extends Shadow { - app; - constructor(app) { - super() - this.app = app + calculateWidth() { + let sidebar = $("sidebar-").getBoundingClientRect() + let w = sidebar.width + return w + } + + calculateHeight() { + let appmenu = $("app-menu").getBoundingClientRect() + let h = appmenu.height + return h } render() { ZStack(() => { - switch(this.app) { + switch(window.currentApp) { + case "Dashboard": + Dashboard() + break; case "Forum": Forum() break; @@ -30,25 +40,13 @@ class AppWindow extends Shadow { } }) .position("fixed") - .display(this.app ? 'block' : 'none') - .width(100, "vw") - .height(100, "vh") - .background("#591d10") - .x(0) - .y(0) - // .backgroundImage("/_/images/fabric.png") - // .backgroundSize("33vw auto") + .width(window.innerWidth - this.calculateWidth(), px) + .height(window.innerHeight - this.calculateHeight(), px) + .background("var(--app)") + .x(this.calculateWidth(), px) + .yBottom(this.calculateHeight(), px) + .onEvent("appchange", () => this.rerender()) } - - open(app) { - this.app = app - this.rerender() - } - - close() { - this.style.display = "none" - } - } register(AppWindow, "app-window") \ No newline at end of file diff --git a/ui/desktop/components/Sidebar.js b/ui/desktop/components/Sidebar.js index 62494b8..15c8f68 100644 --- a/ui/desktop/components/Sidebar.js +++ b/ui/desktop/components/Sidebar.js @@ -1,47 +1,45 @@ class Sidebar extends Shadow { + currentNetwork = null + render() { VStack(() => { - img(document.documentElement.classList.contains("red") ? "/_/icons/quillblack.svg" : "/_/icons/quill.svg", "2.25em") - .paddingLeft(3, em) - .paddingTop(5, vh) - .onClick(() => { - window.navigateTo("/") - }) + img(document.documentElement.classList.contains("red") ? "/_/icons/quillblack.svg" : "/_/icons/quill.svg", "2.5em", "2.5em") + .marginTop(6, vh) + .marginBottom(2, vh) + + let networks = window.profile.networks + for(let i=0; i { - if(!window.profile) { - window.profile = await this.fetchProfile() - if(profile) { - this.rerender() - } - } - }) - } - - async fetchProfile() { - 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"); - - const profile = await res.json(); - console.log(profile); - return profile - } catch (err) { - console.error(err); - } } } diff --git a/ui/desktop/index.html b/ui/desktop/index.html index 7d7cc78..1813cea 100644 --- a/ui/desktop/index.html +++ b/ui/desktop/index.html @@ -5,6 +5,15 @@ + diff --git a/ui/desktop/index.js b/ui/desktop/index.js index a02be9c..5688cd4 100644 --- a/ui/desktop/index.js +++ b/ui/desktop/index.js @@ -5,4 +5,96 @@ import util from "./util.js" window.util = util window.Socket = new Socket() -Home() \ No newline at end of file + +window.currentNetwork = "" +window.currentApp = "" + +window.addEventListener("navigate", () => { + if(window.currentNetwork !== selectedNetwork()) { + window.currentNetwork = selectedNetwork() + const event = new CustomEvent('networkchanged', { + detail: { name: currentNetwork } + }); + window.dispatchEvent(event) + } + + if(window.currentApp !== selectedApp()) { + window.currentApp = selectedApp() + const event = new CustomEvent('appchanged', { + detail: { name: window.currentApp } + }); + window.dispatchEvent(event) + } + + document.title = `${window.currentNetwork.abbreviation} | Parchment` +}) + +window.selectedNetwork = 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 + } + } +} + +window.selectedApp = 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 +} + +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 + console.log(newPath) + window.navigateTo(newPath) + // window.history.replaceState({}, '', newPath); + const event = new CustomEvent('appchanged', { + detail: { name: appName } + }); + window.dispatchEvent(event) +} + +async function 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"); + + const profile = await res.json(); + console.log(profile); + window.profile = profile + } catch (err) { + console.error(err); + } +} + +getProfile().then(() => { + let path = ""; + let defaultNetwork = window.profile.networks[0] + + if(!selectedNetwork()) { + path += (defaultNetwork.abbreviation + "/") + } + + if(!selectedApp()) { + let defaultApp = defaultNetwork.apps[0] + path += defaultApp.toLowerCase() + } + + window.navigateTo(path) + Home() +}) \ No newline at end of file diff --git a/ui/public/index.html b/ui/public/index.html index 25d29f6..f51ed57 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -1,5 +1,5 @@ - + Parchment diff --git a/ui/public/pages/Home.js b/ui/public/pages/Home.js index e7d2127..b6eda06 100644 --- a/ui/public/pages/Home.js +++ b/ui/public/pages/Home.js @@ -14,7 +14,7 @@ class Home extends Shadow { .fontFamily("Nabla") .fontSize(6.5, em) .marginLeft(1, rem) - .color("var(--accent2)") + .color("var(--accent)") }) .marginBottom(1, rem) @@ -24,7 +24,7 @@ class Home extends Shadow { HStack(() => { span("The Community OS") .fontFamily("Canterbury") - .color("var(--accent2)") + .color("var(--accent)") .fontSize(2.5, em) .paddingBottom(1, rem) })