From 7f3f266d52a819f629c66e1a2ec01a85233052c4 Mon Sep 17 00:00:00 2001 From: metacryst Date: Sun, 25 Jan 2026 06:27:57 -0600 Subject: [PATCH] init --- src/Home.js | 229 +++------------------------ src/_/code/quill.js | 12 +- src/_/code/styles.css | 2 +- src/_/icons/column.svg | 7 + src/_/icons/column2.svg | 4 + src/_/icons/columnwhite.svg | 7 + src/_/icons/forum.png | Bin 0 -> 48327 bytes src/_/icons/jobs.svg | 5 + src/_/icons/letter.svg | 4 + src/apps/Forum/Forum.js | 66 ++++++++ src/apps/Forum/ForumPanel.js | 88 ++++++++++ src/apps/Jobs/Jobs.js | 101 ++++++++++++ src/apps/Jobs/JobsGrid.js | 60 +++++++ src/apps/Jobs/JobsSidebar.js | 26 +++ src/apps/Market/Market.js | 105 ++++++++++++ src/apps/Market/MarketGrid.js | 140 ++++++++++++++++ src/apps/Market/MarketSidebar.js | 85 ++++++++++ src/apps/Messages/Messages.js | 188 ++++++++++++++++++++++ src/apps/Messages/MessagesPanel.js | 56 +++++++ src/apps/Messages/MessagesSidebar.js | 73 +++++++++ src/apps/Tasks/Tasks.js | 153 ++++++++++++++++++ src/components/AppMenu.js | 68 ++++++++ src/components/LoadingCircle.js | 25 +++ src/index.html | 4 +- 24 files changed, 1298 insertions(+), 210 deletions(-) create mode 100644 src/_/icons/column.svg create mode 100644 src/_/icons/column2.svg create mode 100644 src/_/icons/columnwhite.svg create mode 100644 src/_/icons/forum.png create mode 100644 src/_/icons/jobs.svg create mode 100644 src/_/icons/letter.svg create mode 100644 src/apps/Forum/Forum.js create mode 100644 src/apps/Forum/ForumPanel.js create mode 100644 src/apps/Jobs/Jobs.js create mode 100644 src/apps/Jobs/JobsGrid.js create mode 100644 src/apps/Jobs/JobsSidebar.js create mode 100644 src/apps/Market/Market.js create mode 100644 src/apps/Market/MarketGrid.js create mode 100644 src/apps/Market/MarketSidebar.js create mode 100644 src/apps/Messages/Messages.js create mode 100644 src/apps/Messages/MessagesPanel.js create mode 100644 src/apps/Messages/MessagesSidebar.js create mode 100644 src/apps/Tasks/Tasks.js create mode 100644 src/components/AppMenu.js create mode 100644 src/components/LoadingCircle.js diff --git a/src/Home.js b/src/Home.js index 88e78a8..d1cade4 100644 --- a/src/Home.js +++ b/src/Home.js @@ -1,216 +1,39 @@ import "./components/Sidebar.js" - +import "./components/AppMenu.js" +import "./apps/Forum/Forum.js" +import "./apps/Messages/Messages.js" +import "./apps/Jobs/Jobs.js" + class Home extends Shadow { - tracking = false - logs = [] - timer = null - render() { ZStack(() => { Sidebar() - // Title bar - HStack(() => { - img("./_/icons/runner.svg", "2em", "2em") + ZStack(() => { + switch(window.location.pathname) { + case "/": + Forum() + break; + + case "/messages": + Messages() + break; + + case "/jobs": + Jobs() + break; + } + }) + .onNavigate(function () { + console.log("navigate") + this.rerender() + }) - p("San Marcos, TX") - .fontSize(1.2, em) - - img("./_/icons/hamburger.svg", "2em", "2em") - .transition("scale .3s") - .zIndex(2) - .onTouch(function (start) { - if(start) { - this.style.scale = 0.8 - } else { - this.style.scale = 1 - } - - if(start === false) { - $("sidebar-").toggle() - } - }) - }) - .gap(15, vw) - .position("fixed") - .top(0) - .left(0) - .width(100, vw) - .paddingTop(2, em) - .paddingBottom(2, em) - .borderBottom("0.1rem solid var(--text)") - .backgroundColor("var(--yellow)") - .fontWeight("bold") - .display("flex") - .alignItems("center") - .justifyContent("center") - .zIndex(10) - - VStack(() => { - button(() => { - if(this.tracking) { - Rectangle("48%", "48%") - .fill("#264B61") - .stroke("0.2em", "black") - } else { - Triangle("58%", "58%") - .fill("#9F292B") - .stroke("0.2em", "black") - } - }) - .attr({"id": "playButton"}) - .width(120, px) - .height(120, px) - .x(50, vw).y(50, vh) - .center() - .borderRadius(50, "%") - .backgroundColor(this.tracking ? "#CD593E" : "#A6EABD") - .border("0.2em solid black") - .cursor("pointer") - .display("flex") - .alignItems("center") - .justifyContent("center") - .onTap(() => { - console.log("tapped") - if (this.tracking) { - this.stopTracking(); - this.rerender() - } else { - this.startTracking(); - this.rerender() - } - }) - - VStack(() => { - this.logs.map(log => - div(log) - .paddingHorizontal("8px") - .paddingVertical("12px") - .backgroundColor("rgba(255,255,255,0.9)") - .borderRadius(6, px) - .marginBottom(6, px) - .fontSize(0.9, rem) - .color("#222") - .fontFamily("monospace") - ) - }) - .attr({"id": "logList"}) - .flex(1) - .overflowY("auto") - .paddingHorizontal(16, px) - }) - .marginTop(7, em) + AppMenu() }) - .overflowX("hidden") - .width(100, vw) - .height(100, vh) - .display("block") - .margin(0) - .color("var(--text)") - .fontFamily("Arial") + .overflowX("hidden") } - - async startTracking() { - if (!navigator.geolocation) { - alert("Geolocation is not supported by your browser."); - return; - } - - this.tracking = true - - navigator.geolocation.requestPermission?.() || Promise.resolve('granted'); - - const permission = await new Promise((resolve) => { - navigator.geolocation.getCurrentPosition( - () => resolve('granted'), - () => resolve('denied') - ); - }); - - if (permission === 'denied') { - alert("Location permission required"); - this.tracking = false - return; - } - - const timer = setInterval(async () => { - navigator.geolocation.getCurrentPosition( - async (pos) => { - const { latitude, longitude } = pos.coords; - const now = new Date(); - const log = `${now.toISOString()} — (${latitude}, ${longitude})`; - const timestamp = this.formatCentralTime(now); - - this.logs = [log, ...this.logs] - - this.updateLogs() - await this.sendLocation(latitude, longitude, timestamp); - }, - (err) => console.error("Location error:", err), - { enableHighAccuracy: true } - ); - }, 1000); - - this.timer = timer - } - - stopTracking() { - if (this.timer) { - clearInterval(this.timer); - this.timer = null; - this.tracking = false; - } - } - - showTracking() { - this.$("#playButton").rerender() - this.$("#playButton").style.backgroundColor = this.tracking ? "#CD593E" : "#A6EABD" - } - - updateLogs() { - let list = this.$("#logList") - list.rerender() - list.attr({"id": "logList"}) - } - - formatCentralTime(date) { - return new Intl.DateTimeFormat('en-US', { - timeZone: 'America/Chicago', - month: '2-digit', - day: '2-digit', - year: '2-digit', - hour: 'numeric', - minute: '2-digit', - second: '2-digit', - hour12: true - }) - .format(date) - .replace(/,/, '') // "04/05/25, 2:30:00 PM" → "04/05/25 2:30:00 PM" - .replace(/\//g, '.') // → "04.05.25 2:30:00 PM" - .toLowerCase() - .replace(' pm', 'pm') - .replace(' am', 'am'); - } - - async sendLocation(lat, lon, timestamp) { - try { - const resp = await fetch('http://localhost:3009/api/location', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - name: "Freddy Krueger", - latitude: lat, - longitude: lon, - timestamp - }) - }); - if (!resp.ok) throw new Error(resp.status); - console.log('Location sent'); - } catch (e) { - console.error('Failed to send location:', e); - } - } } register(Home) \ No newline at end of file diff --git a/src/_/code/quill.js b/src/_/code/quill.js index c8a0f7a..e144543 100644 --- a/src/_/code/quill.js +++ b/src/_/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) { @@ -1084,7 +1084,7 @@ HTMLElement.prototype.onTap = function(cb) { - We can't just put a listener on the element, because a window "navigate" event won't trigger it - We can't just put a listener on the window, because the "this" variable will only refer to the window - And, if we try to re-add that scope using bind(), it makes the return value of .toString() unreadable, which means we cannot detect duplicate listeners. -- Therefore, we attach a global navigate event to the window, and each navigate event in this array, and manually trigger each event when the global one fires. +- Therefore, we attach a global navigate event to the window, and store each navigate event in this navigateListeners array, and manually trigger each event on the elements when the global one fires. */ navigateListeners = [] window.addEventListener("navigate", () => { @@ -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/src/_/code/styles.css b/src/_/code/styles.css index b04acbe..7ca742d 100644 --- a/src/_/code/styles.css +++ b/src/_/code/styles.css @@ -1,5 +1,5 @@ :root { - --main: #AEBDFF; + --main: #FFE9C8; --accent: #60320c; --text: #340000; --yellow: #f1f3c3; diff --git a/src/_/icons/column.svg b/src/_/icons/column.svg new file mode 100644 index 0000000..841bf5f --- /dev/null +++ b/src/_/icons/column.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/_/icons/column2.svg b/src/_/icons/column2.svg new file mode 100644 index 0000000..ab79e11 --- /dev/null +++ b/src/_/icons/column2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/_/icons/columnwhite.svg b/src/_/icons/columnwhite.svg new file mode 100644 index 0000000..5be3f63 --- /dev/null +++ b/src/_/icons/columnwhite.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/_/icons/forum.png b/src/_/icons/forum.png new file mode 100644 index 0000000000000000000000000000000000000000..4b0fa1b93fc136be8a0117d9e62a9b1c93ec19e3 GIT binary patch literal 48327 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 + + + + diff --git a/src/_/icons/letter.svg b/src/_/icons/letter.svg new file mode 100644 index 0000000..9a1d4d8 --- /dev/null +++ b/src/_/icons/letter.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/apps/Forum/Forum.js b/src/apps/Forum/Forum.js new file mode 100644 index 0000000..a507efc --- /dev/null +++ b/src/apps/Forum/Forum.js @@ -0,0 +1,66 @@ +import './ForumPanel.js' + +css(` + forum- { + font-family: 'Bona'; + } + + forum- input::placeholder { + font-family: 'Bona Nova'; + font-size: 0.9em; + color: var(--accent); + } + + 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 Forum extends Shadow { + + selectedForum = "HY" + + render() { + ZStack(() => { + VStack(() => { + + ForumPanel() + + input("Message Hyperia", "98%") + .paddingVertical(1, em) + .paddingLeft(2, pct) + .color("var(--accent)") + .background("var(--darkbrown)") + .marginBottom(6, em) + .border("none") + .fontSize(1, em) + .onKeyDown(function (e) { + if (e.key === "Enter") { + window.Socket.send({app: "FORUM", operation: "SEND", msg: {forum: "HY", text: this.value }}) + this.value = "" + } + }) + }) + .gap(0.5, em) + .width(100, pct) + .height(100, vh) + .horizontalAlign("center") + .verticalAlign("end") + }) + .onAppear(() => document.body.style.backgroundColor = "var(--darkbrown)") + .width(100, pct) + .height(100, pct) + } + + +} + +register(Forum) \ No newline at end of file diff --git a/src/apps/Forum/ForumPanel.js b/src/apps/Forum/ForumPanel.js new file mode 100644 index 0000000..9dfdde6 --- /dev/null +++ b/src/apps/Forum/ForumPanel.js @@ -0,0 +1,88 @@ +import "../../components/LoadingCircle.js" + +class ForumPanel extends Shadow { + forums = [ + "HY" + ] + messages = [] + + render() { + VStack(() => { + if(this.messages.length > 0) { + + let previousDate = null + + for(let i=0; i { + HStack(() => { + p(message.sentBy) + .fontWeight("bold") + .marginBottom(0.3, em) + + p(util.formatTime(message.time)) + .opacity(0.2) + .marginLeft(1, em) + }) + p(message.text) + }) + } + } else { + LoadingCircle() + } + }) + .gap(1, em) + .position("relative") + .overflow("scroll") + .height(100, pct) + .width(96, pct) + .paddingTop(5, em) + .paddingBottom(2, em) + .paddingLeft(4, pct) + .backgroundColor("var(--darkbrown)") + .onAppear(async () => { + 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) { + this.messages = res.msg + this.rerender() + } + 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() + } + }) + }) + } + + 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 [, mm, dd, yyyy, hh, min, ampm] = match; + const date = `${mm}/${dd}/${yyyy}`; + const time = `${hh}:${min}${ampm.toLowerCase()}`; + + return { date, time }; + } +} + +register(ForumPanel) \ No newline at end of file diff --git a/src/apps/Jobs/Jobs.js b/src/apps/Jobs/Jobs.js new file mode 100644 index 0000000..bb72598 --- /dev/null +++ b/src/apps/Jobs/Jobs.js @@ -0,0 +1,101 @@ +import "./JobsSidebar.js" +import "./JobsGrid.js" + +css(` + jobs- { + font-family: 'Bona'; + } + + jobs- input::placeholder { + font-family: 'Bona Nova'; + font-size: 0.9em; + color: var(--accent); + } + + 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 Jobs extends Shadow { + jobs = [ + { + title: "Austin Chapter Lead", + salary: "1% of Local Revenue", + company: "Hyperia", + city: "Austin", + state: "TX" + } + ] + + render() { + ZStack(() => { + HStack(() => { + JobsSidebar() + + JobsGrid(this.jobs) + }) + .width(100, "%") + .x(0).y(13, vh) + + HStack(() => { + input("Search jobs... (Coming Soon!)", "45vw") + .attr({ + "type": "text", + "disabled": "true" + }) + .fontSize(1.1, em) + .paddingLeft(1.3, em) + .background("transparent") + .border("0.5px solid var(--divider)") + .outline("none") + .color("var(--accent)") + .opacity(0.5) + .borderRadius(10, px) + .background("grey") + .cursor("not-allowed") + + button("+ Add Job") + .width(7, em) + .marginLeft(1, em) + .borderRadius(10, px) + .background("transparent") + .border("0.3px solid var(--accent2)") + .color("var(--accent)") + .fontFamily("Bona Nova") + .onHover(function (hovering) { + if(hovering) { + this.style.background = "var(--green)" + + } else { + this.style.background = "transparent" + + } + }) + .onClick((clicking) => { + console.log(this, "clicked") + }) + + }) + .x(55, vw).y(4, vh) + .position("absolute") + .transform("translateX(-50%)") + }) + .width(100, "%") + .height(100, "%") + } + + connectedCallback() { + // Optional additional logic + } +} + +register(Jobs) \ No newline at end of file diff --git a/src/apps/Jobs/JobsGrid.js b/src/apps/Jobs/JobsGrid.js new file mode 100644 index 0000000..2af5d4f --- /dev/null +++ b/src/apps/Jobs/JobsGrid.js @@ -0,0 +1,60 @@ +class JobsGrid extends Shadow { + jobs; + + constructor(jobs) { + super() + this.jobs = jobs + } + + boldUntilFirstSpace(text) { + const index = text.indexOf(' '); + if (index === -1) { + // No spaces — bold the whole thing + return `${text}`; + } + return `${text.slice(0, index)}${text.slice(index)}`; + } + + render() { + VStack(() => { + h3("Results") + .marginTop(0.1, em) + .marginBottom(1, em) + .marginLeft(0.4, em) + .color("var(--accent2)") + + if (this.jobs.length > 0) { + ZStack(() => { + for (let i = 0; i < this.jobs.length; i++) { + VStack(() => { + p(this.jobs[i].title) + .fontSize(1.2, em) + .fontWeight("bold") + .marginBottom(0.5, em) + p(this.jobs[i].company) + p(this.jobs[i].city + ", " + this.jobs[i].state) + .marginBottom(0.5, em) + p(this.boldUntilFirstSpace(this.jobs[i].salary)) + }) + .padding(1, em) + .borderRadius(5, "px") + .background("var(--darkbrown)") + } + }) + .display("grid") + .gridTemplateColumns("repeat(auto-fill, minmax(250px, 1fr))") + .gap(1, em) + } else { + p("No Jobs!") + } + }) + .height(100, vh) + .paddingLeft(2, em) + .paddingRight(2, em) + .paddingTop(2, em) + .gap(0, em) + .width(100, "%") + } +} + +register(JobsGrid) diff --git a/src/apps/Jobs/JobsSidebar.js b/src/apps/Jobs/JobsSidebar.js new file mode 100644 index 0000000..1546cec --- /dev/null +++ b/src/apps/Jobs/JobsSidebar.js @@ -0,0 +1,26 @@ +class JobsSidebar extends Shadow { + render() { + VStack(() => { + h3("Location") + .color("var(--accent2)") + .marginBottom(0, em) + + HStack(() => { + input("Location", "100%") + .paddingLeft(3, em) + .paddingVertical(0.75, em) + .backgroundImage("/_/icons/locationPin.svg") + .backgroundRepeat("no-repeat") + .backgroundSize("18px 18px") + .backgroundPosition("10px center") + }) + }) + .paddingTop(1, em) + .paddingLeft(3, em) + .paddingRight(3, em) + .gap(1, em) + .minWidth(10, vw) + } +} + +register(JobsSidebar) \ No newline at end of file diff --git a/src/apps/Market/Market.js b/src/apps/Market/Market.js new file mode 100644 index 0000000..e3bceeb --- /dev/null +++ b/src/apps/Market/Market.js @@ -0,0 +1,105 @@ +import "./MarketSidebar.js" +import "./MarketGrid.js" + +css(` + market- { + font-family: 'Bona'; + } + + market- input::placeholder { + font-family: 'Bona Nova'; + font-size: 0.9em; + color: var(--accent); + } + + 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 Market extends Shadow { + + listings = [ + { + title: "Shield Lapel Pin", + stars: "5", + reviews: 1, + price: "$12", + company: "Hyperia", + type: "new", + image: "/db/images/1", + madeIn: "America" + } + ] + + render() { + ZStack(() => { + HStack(() => { + MarketSidebar() + + MarketGrid(this.listings) + }) + .width(100, "%") + .x(0).y(13, vh) + + HStack(() => { + input("Search for products... (Coming Soon!)", "45vw") + .attr({ + "type": "text", + "disabled": "true" + }) + .fontSize(1.1, em) + .paddingLeft(1.3, em) + .background("transparent") + .border("0.5px solid var(--divider)") + .outline("none") + .color("var(--accent)") + .opacity(0.5) + .borderRadius(10, px) + .background("grey") + .cursor("not-allowed") + + button("+ Add Item") + .width(7, em) + .marginLeft(1, em) + .borderRadius(10, px) + .background("transparent") + .border("0.5px solid var(--accent2)") + .color("var(--accent)") + .fontFamily("Bona Nova") + .onHover(function (hovering) { + if(hovering) { + this.style.background = "var(--green)" + + } else { + this.style.background = "transparent" + + } + }) + .onClick((clicking) => { + console.log(this, "clicked") + }) + + }) + .x(55, vw).y(4, vh) + .position("absolute") + .transform("translateX(-50%)") + }) + .width(100, "%") + .height(100, "%") + } + + connectedCallback() { + // Optional additional logic + } +} + +register(Market) diff --git a/src/apps/Market/MarketGrid.js b/src/apps/Market/MarketGrid.js new file mode 100644 index 0000000..8740f9e --- /dev/null +++ b/src/apps/Market/MarketGrid.js @@ -0,0 +1,140 @@ +class MarketGrid extends Shadow { + listings; + + constructor(listings) { + super() + this.listings = listings + } + + boldUntilFirstSpace(text) { + if(!text) return + const index = text.indexOf(' '); + if (index === -1) { + // No spaces — bold the whole thing + return `${text}`; + } + return `${text.slice(0, index)}${text.slice(index)}`; + } + + render() { + VStack(() => { + h3("Results") + .marginTop(0.1, em) + .marginBottom(1, em) + .marginLeft(0.4, em) + .color("var(--accent)") + .opacity(0.7) + + if (this.listings.length > 0) { + ZStack(() => { + // BuyModal() + + let params = new URLSearchParams(window.location.search); + + const hyperiaMade = params.get("hyperia-made") === "true"; + const americaMade = params.get("america-made") === "true"; + const newItem = params.get("new") === "true"; + const usedItem = params.get("used") === "true"; + + + let filtered = this.listings; + if (hyperiaMade) { + filtered = filtered.filter(item => item.madeIn === "Hyperia"); + } + if (americaMade) { + filtered = filtered.filter(item => item.madeIn === "America"); + } + if (newItem) { + filtered = filtered.filter(item => item.type === "new"); + } + if (usedItem) { + filtered = filtered.filter(item => item.type === "used"); + } + + for (let i = 0; i < filtered.length; i++) { + const rating = filtered[i].stars + const percent = (rating / 5) + + VStack(() => { + img(filtered[i].image) + .marginBottom(0.5, em) + + p(filtered[i].company) + .marginBottom(0.5, em) + + p(filtered[i].title) + .fontSize(1.2, em) + .fontWeight("bold") + .marginBottom(0.5, em) + + HStack(() => { + p(filtered[i].stars) + .marginRight(0.2, em) + + ZStack(() => { + div("★★★★★") // Empty stars (background) + .color("#ccc") + + div("★★★★★") // Filled stars (foreground, clipped by width) + .color("#ffa500") + .position("absolute") + .top(0) + .left(0) + .whiteSpace("nowrap") + .overflow("hidden") + .width(percent * 5, em) + }) + .display("inline-block") + .position("relative") + .fontSize(1.2, em) + .lineHeight(1) + + p(filtered[i].reviews) + .marginLeft(0.2, em) + }) + .marginBottom(0.5, em) + + p(filtered[i].price) + .fontSize(1.75, em) + .marginBottom(0.5, em) + + button("Coming Soon!") + .onClick((finished) => { + if(finished) { + + } + }) + .onHover(function (hovering) { + if(hovering) { + this.style.backgroundColor = "var(--green)" + } else { + this.style.backgroundColor = "" + } + }) + + }) + .padding(1, em) + .border("1px solid var(--accent2)") + .borderRadius(5, "px") + } + }) + .display("grid") + .gridTemplateColumns("repeat(auto-fill, minmax(250px, 1fr))") + .gap(1, em) + } else { + p("No Listings!") + } + }) + .onQueryChanged(() => { + console.log("query did change yup") + this.rerender() + }) + .height(100, vh) + .paddingLeft(2, em) + .paddingRight(2, em) + .gap(0, em) + .width(100, "%") + } +} + +register(MarketGrid) \ No newline at end of file diff --git a/src/apps/Market/MarketSidebar.js b/src/apps/Market/MarketSidebar.js new file mode 100644 index 0000000..9324618 --- /dev/null +++ b/src/apps/Market/MarketSidebar.js @@ -0,0 +1,85 @@ +class MarketSidebar extends Shadow { + + handleChecked(e) { + let checked = e.target.checked + let label = $(`label[for="${e.target.id}"]`).innerText + if(checked) { + window.setQuery(label.toLowerCase(), true) + } else { + window.setQuery(label.toLowerCase(), null) + } + } + + render() { + VStack(() => { + + p("Make") + + HStack(() => { + input() + .attr({ + "type": "checkbox", + "id": "hyperia-check" + }) + .onChange(this.handleChecked) + label("Hyperia-Made") + .attr({ + "for": "hyperia-check" + }) + .marginLeft(0.5, em) + }) + + HStack(() => { + input() + .attr({ + "type": "checkbox", + "id": "america-check" + }) + .onChange(this.handleChecked) + label("America-Made") + .attr({ + "for": "america-check" + }) + .marginLeft(0.5, em) + }) + + p("Condition") + + HStack(() => { + input() + .attr({ + "type": "checkbox", + "id": "new-check" + }) + .onChange(this.handleChecked) + label("New") + .attr({ + "for": "new-check" + }) + .marginLeft(0.5, em) + }) + + HStack(() => { + input() + .attr({ + "type": "checkbox", + "id": "used-check" + }) + .onChange(this.handleChecked) + label("Used") + .attr({ + "for": "used-check" + }) + .marginLeft(0.5, em) + }) + }) + .paddingTop(12, vh) + .paddingLeft(3, em) + .paddingRight(3, em) + .gap(1, em) + .minWidth(10, vw) + .userSelect('none') + } +} + +register(MarketSidebar) \ No newline at end of file diff --git a/src/apps/Messages/Messages.js b/src/apps/Messages/Messages.js new file mode 100644 index 0000000..40b9722 --- /dev/null +++ b/src/apps/Messages/Messages.js @@ -0,0 +1,188 @@ +import "./MessagesSidebar.js" +import "./MessagesPanel.js" + +css(` + messages- { + font-family: 'Bona'; + } + + messages- input::placeholder { + font-family: 'Bona Nova'; + font-size: 0.9em; + color: var(--accent); + } + + 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 Messages extends Shadow { + conversations = [] + selectedConvoID = null + onConversationSelect(i) { + console.log("convo selected: ", i) + this.selectedConvoID = i + this.$("messagessidebar-").rerender() + this.$("messagespanel-").rerender() + } + + getConvoFromID(id) { + for(let i=0; i { + HStack(() => { + MessagesSidebar(this.conversations, this.selectedConvoID, this.onConversationSelect) + + VStack(() => { + if(this.getConvoFromID(this.selectedConvoID)) { + MessagesPanel(this.getConvoFromID(this.selectedConvoID).messages) + } else { + 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) + .horizontalAlign("center") + .verticalAlign("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() + }) + }) + .width(100, "%") + .height(87, vh) + .x(0).y(13, vh) + + VStack(() => { + p("Add Message") + + input("enter email...") + .color("var(--accent)") + .onKeyDown(function (e) { + if (e.key === "Enter") { + window.Socket.send({app: "MESSAGES", operation: "ADDCONVERSATION", msg: {email: this.value }}) + this.value = "" + } + }) + + p("x") + .onClick(function (done) { + if(done) { + this.parentElement.style.display = "none" + } + }) + .xRight(2, em).y(2, em) + .fontSize(1.4, em) + .cursor("pointer") + + }) + .gap(1, em) + .alignVertical("center") + .alignHorizontal("center") + .backgroundColor("black") + .border("1px solid var(--accent)") + .position("fixed") + .x(50, vw).y(50, vh) + .center() + .width(60, vw) + .height(60, vh) + .display("none") + .attr({id: "addPanel"}) + + HStack(() => { + input("Search messages... (Coming Soon!)", "45vw") + .attr({ + "type": "text", + "disabled": "true" + }) + .fontSize(1.1, em) + .paddingLeft(1.3, em) + .background("transparent") + .border("0.5px solid var(--divider)") + .outline("none") + .color("var(--accent)") + .opacity(0.5) + .borderRadius(10, px) + .background("grey") + .cursor("not-allowed") + + button("+ New Message") + .width(13, em) + .marginLeft(1, em) + .borderRadius(10, px) + .background("transparent") + .border("0.5px solid var(--divider)") + .color("var(--accent)") + .fontFamily("Bona Nova") + .onHover(function (hovering) { + if(hovering) { + this.style.background = "var(--green)" + + } else { + this.style.background = "transparent" + + } + }) + .onClick((done) => { + console.log("click") + if(done) { + this.$("#addPanel").style.display = "flex" + } + console.log(this, "clicked") + }) + + }) + .x(55, vw).y(4, vh) + .position("absolute") + .transform("translateX(-50%)") + }) + .width(100, "%") + .height(100, "%") + } +} + +register(Messages) \ No newline at end of file diff --git a/src/apps/Messages/MessagesPanel.js b/src/apps/Messages/MessagesPanel.js new file mode 100644 index 0000000..b608212 --- /dev/null +++ b/src/apps/Messages/MessagesPanel.js @@ -0,0 +1,56 @@ +import "../../components/LoadingCircle.js" + +class MessagesPanel extends Shadow { + messages + + constructor(messages) { + super() + this.messages = messages + } + + render() { + VStack(() => { + if(this.messages) { + for(let i=0; i { + HStack(() => { + p(message.from.firstName + " " + message.from.lastName) + .fontWeight("bold") + .marginBottom(0.3, em) + + p(util.formatTime(message.time)) + .opacity(0.2) + .marginLeft(1, em) + }) + p(message.text) + }) + .paddingVertical(0.5, em) + .marginLeft(fromMe ? 70 : 0, pct) + .paddingRight(fromMe ? 10 : 0, pct) + .marginRight(fromMe ? 0 : 70, pct) + .paddingLeft(fromMe ? 5 : 10, pct) + .background(fromMe ? "var(--brown)" : "var(--green)") + } + } else { + LoadingCircle() + } + }) + .onAppear(async () => { + requestAnimationFrame(() => { + this.scrollTop = this.scrollHeight + }); + }) + .gap(1, em) + .position("relative") + .overflow("scroll") + .height(95, pct) + .width(100, pct) + .paddingTop(2, em) + .paddingBottom(2, em) + .backgroundColor("var(--darkbrown)") + } +} + +register(MessagesPanel) \ No newline at end of file diff --git a/src/apps/Messages/MessagesSidebar.js b/src/apps/Messages/MessagesSidebar.js new file mode 100644 index 0000000..453a91d --- /dev/null +++ b/src/apps/Messages/MessagesSidebar.js @@ -0,0 +1,73 @@ +class MessagesSidebar extends Shadow { + conversations = [] + selectedConvoID + onSelect + + constructor(conversations, selectedConvoID, onSelect) { + super() + this.conversations = conversations + this.selectedConvoID = selectedConvoID + this.onSelect = onSelect + } + + render() { + VStack(() => { + this.conversations.forEach((convo, i) => { + + VStack(() => { + HStack(() => { + + p(this.makeConvoTitle(convo.between)) + .textAlign("left") + .marginLeft(0.5, inches) + .paddingTop(0.2, inches) + .width(100, pct) + .marginTop(0) + .fontSize(1, em) + .fontWeight("bold") + + p(util.formatTime(convo.messages.last.time)) + .paddingTop(0.2, inches) + .fontSize(0.8, em) + .marginRight(0.1, inches) + .color("var(--divider") + }) + .justifyContent("space-between") + .marginBottom(0) + + p(convo.messages.last.text) + .fontSize(0.8, em) + .textAlign("left") + .marginLeft(0.5, inches) + .marginBottom(2, em) + .color("var(--divider)") + }) + .background(convo.id === this.selectedConvoID ? "var(--darkbrown)" : "") + .onClick(() => { + this.onSelect(i) + }) + }) + }) + .minWidth(15, vw) + .height(100, vh) + .gap(0, em) + } + + makeConvoTitle(members) { + let membersString = "" + for(let i=0; i 2) { + membersString += member.firstName + } else { + membersString += member.firstName + " " + member.lastName + } + } + return membersString + } +} + +register(MessagesSidebar) \ No newline at end of file diff --git a/src/apps/Tasks/Tasks.js b/src/apps/Tasks/Tasks.js new file mode 100644 index 0000000..4b0e733 --- /dev/null +++ b/src/apps/Tasks/Tasks.js @@ -0,0 +1,153 @@ +css(` + tasks- { + font-family: 'Bona'; + } + + tasks- input::placeholder { + font-family: 'Bona Nova'; + font-size: 0.9em; + color: var(--accent); + } + + 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 Tasks extends Shadow { + projects = [ + { + "title": "Blockcatcher", + "tasks": {} + } + ] + columns = [ + { + "title": "backlog", + "tasks": {} + } + ] + + render() { + ZStack(() => { + HStack(() => { + VStack(() => { + h3("Projects") + .marginTop(0) + .marginBottom(1, em) + .marginLeft(0.4, em) + + if (this.projects.length >= 1) { + for(let i = 0; i < this.projects.length; i++) { + p(this.projects[i].title) + } + } else { + p("No Projects!") + } + }) + .height(100, vh) + .paddingLeft(2, em) + .paddingRight(2, em) + .paddingTop(2, em) + .gap(0, em) + .borderRight("0.5px solid var(--accent2)") + + HStack(() => { + if (this.columns.length >= 1) { + for(let i = 0; i < this.columns.length; i++) { + p(this.columns[i].name) + } + } else { + p("No Conversations!") + } + }) + .height(100, vh) + .paddingLeft(2, em) + .paddingRight(2, em) + .paddingTop(2, em) + .gap(0, em) + .borderRight("0.5px solid var(--accent2)") + }) + .width(100, "%") + .x(0).y(13, vh) + .borderTop("0.5px solid var(--accent2)") + + p("0 Items") + .position("absolute") + .x(50, vw).y(50, vh) + .transform("translate(-50%, -50%)") + + HStack(() => { + input("Search tasks...", "45vw") + .attr({ + "type": "text" + }) + .fontSize(1.1, em) + .paddingLeft(1.3, em) + .background("transparent") + .border("0.5px solid var(--accent2)") + .outline("none") + .color("var(--accent)") + .borderRadius(10, px) + + button("Search") + .marginLeft(2, em) + .borderRadius(10, px) + .background("transparent") + .border("0.5px solid var(--accent2)") + .color("var(--accent)") + .fontFamily("Bona Nova") + .onHover(function (hovering) { + if(hovering) { + this.style.background = "var(--green)" + + } else { + this.style.background = "transparent" + + } + }) + + button("+ New Task") + .width(9, em) + .marginLeft(1, em) + .borderRadius(10, px) + .background("transparent") + .border("0.5px solid var(--accent2)") + .color("var(--accent)") + .fontFamily("Bona Nova") + .onHover(function (hovering) { + if(hovering) { + this.style.background = "var(--green)" + + } else { + this.style.background = "transparent" + + } + }) + .onClick((clicking) => { + console.log(this, "clicked") + }) + + }) + .x(55, vw).y(4, vh) + .position("absolute") + .transform("translateX(-50%)") + }) + .width(100, "%") + .height(100, "%") + } + + connectedCallback() { + // Optional additional logic + } +} + +register(Tasks) \ No newline at end of file diff --git a/src/components/AppMenu.js b/src/components/AppMenu.js new file mode 100644 index 0000000..73aa805 --- /dev/null +++ b/src/components/AppMenu.js @@ -0,0 +1,68 @@ +class AppMenu extends Shadow { + selected = "" + + onNewSelection() { + this.$$("img").forEach((image) => { + image.style.background = "" + }) + } + + render() { + console.log("rendering") + HStack(() => { + img("/_/icons/Column.svg", "1.5em", "1.5em") + .attr({app: "forum"}) + .padding(0.5, em) + .borderRadius(10, px) + .onClick((finished, e) => { + if(finished) { + this.onNewSelection() + } + e.target.style.background = "var(--accent)" + console.log(e.target, e.target.style.background) + if(finished) { + window.navigateTo("/") + } + }) + img("/_/icons/letter.svg", "1.5em", "1.5em") + .attr({app: "messages"}) + .padding(0.5, em) + .borderRadius(10, px) + .onClick((finished, e) => { + if(finished) { + this.onNewSelection() + } + e.target.style.background = "rgb(112 150 114)" + if(finished) { + window.navigateTo("/messages") + } + }) + img("/_/icons/jobs.svg", "1.5em", "1.5em") + .attr({app: "jobs"}) + .padding(0.5, em) + .borderRadius(10, px) + .onClick((finished, e) => { + if(finished) { + this.onNewSelection() + } + e.target.style.background = "#9392bb" + if(finished) { + window.navigateTo("/jobs") + } + }) + }) + .borderTop("1px solid black") + .height("auto") + .position('fixed') + .background("var(--main)") + .zIndex(1) + .x(0).yBottom(0) + .justifyContent("space-between") + .paddingHorizontal(4, em) + .paddingVertical(1, em) + .width(100, vw) + .boxSizing("border-box") + } +} + +register(AppMenu) \ No newline at end of file diff --git a/src/components/LoadingCircle.js b/src/components/LoadingCircle.js new file mode 100644 index 0000000..f71a86c --- /dev/null +++ b/src/components/LoadingCircle.js @@ -0,0 +1,25 @@ +class LoadingCircle extends Shadow { + render() { + div() + .borderRadius(100, pct) + .width(2, em).height(2, em) + .x(45, pct).y(50, pct) + .center() + .backgroundColor("var(--accent") + .transition("transform 1.75s ease-in-out") + .onAppear(function () { + let growing = true; + + setInterval(() => { + if (growing) { + this.style.transform = "scale(1.5)"; + } else { + this.style.transform = "scale(0.7)"; + } + growing = !growing; + }, 750); + }); + } +} + +register(LoadingCircle) \ No newline at end of file diff --git a/src/index.html b/src/index.html index bbb5f72..39e98bc 100644 --- a/src/index.html +++ b/src/index.html @@ -2,7 +2,7 @@ - Blockcatcher + Forum - +