This commit is contained in:
metacryst
2026-01-15 17:57:35 -06:00
21 changed files with 173 additions and 1205 deletions

View File

@@ -4,11 +4,12 @@
--accent: black;
--accent2: black;
--bone: #fff2e7;
--parchment: #FEBA7D;
--gold: #FEBA7D;
--divider: #bb7c36;
--green: #0857265c;
--red: #CE0000;
--red: #ff0000;
--quillred: #DE3F3F;
--brown: #812A18;
--darkbrown: #3f0808;

View File

@@ -1,4 +1,4 @@
import Socket from "./ws/Socket.js"
import Socket from "/_/code/ws/Socket.js"
import "./Home.js"
import util from "./util.js"

33
ui/mobile/Home.js Normal file
View File

@@ -0,0 +1,33 @@
import "./components/AppMenu.js"
import "./apps/Dashboard/Dashboard.js"
import "./apps/People/People.js"
class Home extends Shadow {
render() {
ZStack(() => {
ZStack(() => {
console.log(window.currentApp)
switch(window.currentApp) {
case "Dashboard":
Dashboard()
break;
case "People":
People()
break;
}
})
.onNavigate(function () {
console.log("navigate")
this.rerender()
})
AppMenu()
})
.overflowX("hidden")
}
}
register(Home)

View File

@@ -0,0 +1,11 @@
class Dashboard extends Shadow {
render() {
VStack(() => {
console.log(window.currentNetwork)
})
.width(100, pct)
.height(100, pct)
}
}
register(Dashboard)

View File

@@ -1,66 +0,0 @@
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 Parchment", "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)

View File

@@ -1,88 +0,0 @@
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<this.messages.length; i++) {
let message = this.messages[i]
const dateParts = this.parseDate(message.time);
const { date, time } = dateParts;
if (previousDate !== date) {
previousDate = date;
p(date)
.textAlign("center")
.opacity(0.5)
.marginVertical(1, em)
.color("var(--divider)")
}
VStack(() => {
HStack(() => {
p(message.sentBy)
.fontWeight("bold")
.marginBottom(0.3, em)
p(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)

View File

@@ -1,101 +0,0 @@
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: "Parchment",
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)

View File

@@ -1,60 +0,0 @@
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 `<b>${text}</b>`;
}
return `<b>${text.slice(0, index)}</b>${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)

View File

@@ -1,26 +0,0 @@
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)

View File

@@ -1,105 +0,0 @@
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: "Parchment",
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)

View File

@@ -1,140 +0,0 @@
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 `<b>${text}</b>`;
}
return `<b>${text.slice(0, index)}</b>${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 ParchmentMade = params.get("Parchment-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 (ParchmentMade) {
filtered = filtered.filter(item => item.madeIn === "Parchment");
}
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)

View File

@@ -1,85 +0,0 @@
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": "Parchment-check"
})
.onChange(this.handleChecked)
label("Parchment-Made")
.attr({
"for": "Parchment-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)

View File

@@ -1,188 +0,0 @@
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<this.conversations.length; i++) {
if(this.conversations[i].id === id) {
return this.conversations[i]
}
}
}
render() {
ZStack(() => {
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)
.verticalAlign("center")
.horizontalAlign("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)

View File

@@ -1,56 +0,0 @@
import "../../components/LoadingCircle.js"
class MessagesPanel extends Shadow {
messages
constructor(messages) {
super()
this.messages = messages
}
render() {
VStack(() => {
if(this.messages) {
for(let i=0; i<this.messages.length; i++) {
let message = this.messages[i]
let fromMe = window.profile.email === message.from.email
VStack(() => {
HStack(() => {
p(message.from.firstName + " " + message.from.lastName)
.fontWeight("bold")
.marginBottom(0.3, em)
p(util.formatTime(message.time))
.opacity(0.2)
.marginLeft(1, em)
})
p(message.text)
})
.paddingVertical(0.5, em)
.marginLeft(fromMe ? 70 : 0, pct)
.paddingRight(fromMe ? 10 : 0, pct)
.marginRight(fromMe ? 0 : 70, pct)
.paddingLeft(fromMe ? 5 : 10, pct)
.background(fromMe ? "var(--brown)" : "var(--green)")
}
} else {
LoadingCircle()
}
})
.onAppear(async () => {
requestAnimationFrame(() => {
this.scrollTop = this.scrollHeight
});
})
.gap(1, em)
.position("relative")
.overflow("scroll")
.height(95, pct)
.width(100, pct)
.paddingTop(2, em)
.paddingBottom(2, em)
.backgroundColor("var(--darkbrown)")
}
}
register(MessagesPanel)

View File

@@ -1,73 +0,0 @@
class MessagesSidebar extends Shadow {
conversations = []
selectedConvoID
onSelect
constructor(conversations, selectedConvoID, onSelect) {
super()
this.conversations = conversations
this.selectedConvoID = selectedConvoID
this.onSelect = onSelect
}
render() {
VStack(() => {
this.conversations.forEach((convo, i) => {
VStack(() => {
HStack(() => {
p(this.makeConvoTitle(convo.between))
.textAlign("left")
.marginLeft(0.5, inches)
.paddingTop(0.2, inches)
.width(100, pct)
.marginTop(0)
.fontSize(1, em)
.fontWeight("bold")
p(util.formatTime(convo.messages.last.time))
.paddingTop(0.2, inches)
.fontSize(0.8, em)
.marginRight(0.1, inches)
.color("var(--divider")
})
.justifyContent("space-between")
.marginBottom(0)
p(convo.messages.last.text)
.fontSize(0.8, em)
.textAlign("left")
.marginLeft(0.5, inches)
.marginBottom(2, em)
.color("var(--divider)")
})
.background(convo.id === this.selectedConvoID ? "var(--darkbrown)" : "")
.onClick(() => {
this.onSelect(i)
})
})
})
.minWidth(15, vw)
.height(100, vh)
.gap(0, em)
}
makeConvoTitle(members) {
let membersString = ""
for(let i=0; i<members.length; i++) {
let member = members[i]
if(member.email === window.profile.email) {
continue;
}
if(members.length > 2) {
membersString += member.firstName
} else {
membersString += member.firstName + " " + member.lastName
}
}
return membersString
}
}
register(MessagesSidebar)

View File

@@ -0,0 +1,7 @@
class People extends Shadow {
render() {
}
}
register(People)

View File

@@ -1,153 +0,0 @@
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)

View File

@@ -8,9 +8,8 @@ class AppMenu extends Shadow {
}
render() {
console.log("rendering")
HStack(() => {
img("/_/icons/Column.svg", "1.5em", "1.5em")
img("/_/icons/House.svg", "1.5em", "1.5em")
.attr({app: "forum"})
.padding(0.5, em)
.borderRadius(10, px)
@@ -21,13 +20,12 @@ class AppMenu extends Shadow {
if(finished) {
this.onNewSelection()
}
e.target.style.background = "var(--accent)"
console.log(e.target, e.target.style.background)
e.target.style.background = "var(--bone)"
if(finished) {
window.navigateTo("/")
}
})
img("/_/icons/letter.svg", "1.5em", "1.5em")
img("/_/icons/people.svg", "1.5em", "1.5em")
.attr({app: "messages"})
.padding(0.5, em)
.borderRadius(10, px)
@@ -43,22 +41,6 @@ class AppMenu extends Shadow {
window.navigateTo("/messages")
}
})
img("/_/icons/jobs.svg", "1.5em", "1.5em")
.attr({app: "jobs"})
.padding(0.5, em)
.borderRadius(10, px)
.onAppear(function () {
this.style.border = "1px solid black"
})
.onClick((finished, e) => {
if(finished) {
this.onNewSelection()
}
e.target.style.background = "#9392bb"
if(finished) {
window.navigateTo("/jobs")
}
})
})
.borderTop("1px solid black")
.height("auto")
@@ -67,7 +49,7 @@ class AppMenu extends Shadow {
.zIndex(1)
.x(0).yBottom(0)
.justifyContent("space-between")
.paddingHorizontal(4, em)
.paddingHorizontal(7, em)
.paddingVertical(1, em)
.width(100, vw)
.boxSizing("border-box")

View File

@@ -1,37 +0,0 @@
import "./AppMenu.js"
import "../apps/Forum/Forum.js"
import "../apps/Messages/Messages.js"
import "../apps/Jobs/Jobs.js"
class Home extends Shadow {
render() {
ZStack(() => {
ZStack(() => {
switch(window.location.pathname) {
case "/":
Forum()
break;
case "/messages":
Messages()
break;
case "/jobs":
Jobs()
break;
}
})
.onNavigate(function () {
console.log("navigate")
this.rerender()
})
AppMenu()
})
.overflowX("hidden")
}
}
register(Home)

View File

@@ -3,7 +3,7 @@
<head>
<title>Parchment</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="/_/icons/logo.svg">
<link rel="icon" href="/_/icons/quill.svg">
<link rel="stylesheet" href="/_/code/shared.css">
<script src="/_/code/quill.js"></script>
<script src="/_/code/zod.js"></script>

View File

@@ -1,8 +1,120 @@
import Socket from "/_/code/ws/Socket.js"
import "./components/Home.js"
import "./Home.js"
import util from "./util.js"
window.util = util
window.Socket = new Socket()
window.currentNetwork = ""
window.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', {
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
}
console.log("current: ", window.currentApp, "afrompath: ", appFromPath())
if(window.currentApp !== appFromPath()) {
window.currentApp = appFromPath()
const event = new CustomEvent('appchange', {
detail: { name: window.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`
}
}
window.addEventListener("navigate", openNetworkAndApp)
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)
}
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
}
}
}
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
}
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("getProfile: ", profile);
window.profile = profile
} catch (err) {
console.error(err);
}
}
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()
})