init
This commit is contained in:
104
ui/desktop/apps/Forum/Forum.js
Normal file
104
ui/desktop/apps/Forum/Forum.js
Normal file
@@ -0,0 +1,104 @@
|
||||
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(() => {
|
||||
HStack(() => {
|
||||
VStack(() => {
|
||||
img("/_/icons/logo.svg", "2em")
|
||||
.padding(0.8, em)
|
||||
.borderRadius(12, px)
|
||||
.marginHorizontal(1, em)
|
||||
.onHover(function (hovering) {
|
||||
if(hovering) {
|
||||
this.style.background = "var(--darkbrown)"
|
||||
} else {
|
||||
this.style.background = ""
|
||||
}
|
||||
})
|
||||
.opacity(0)
|
||||
|
||||
img("/_/icons/place/austin.svg", "2em")
|
||||
.padding(0.8, em)
|
||||
.borderRadius(12, px)
|
||||
.marginHorizontal(1, em)
|
||||
.onHover(function (hovering) {
|
||||
if(hovering) {
|
||||
this.style.background = "var(--darkbrown)"
|
||||
} else {
|
||||
this.style.background = ""
|
||||
}
|
||||
})
|
||||
.opacity(0)
|
||||
|
||||
})
|
||||
.height(100, vh)
|
||||
.paddingLeft(2, em)
|
||||
.paddingRight(2, em)
|
||||
.gap(1, em)
|
||||
.marginTop(20, vh)
|
||||
|
||||
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)
|
||||
.alignHorizontal("center")
|
||||
.alignVertical("end")
|
||||
})
|
||||
.width(100, "%")
|
||||
.height(87, vh)
|
||||
.x(0).y(0, vh)
|
||||
})
|
||||
.width(100, pct)
|
||||
.height(100, pct)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
register(Forum)
|
||||
90
ui/desktop/apps/Forum/ForumPanel.js
Normal file
90
ui/desktop/apps/Forum/ForumPanel.js
Normal file
@@ -0,0 +1,90 @@
|
||||
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 () => {
|
||||
console.log("appear")
|
||||
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) {
|
||||
console.log("rerendering", res.msg)
|
||||
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)
|
||||
101
ui/desktop/apps/Jobs/Jobs.js
Normal file
101
ui/desktop/apps/Jobs/Jobs.js
Normal file
@@ -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)
|
||||
60
ui/desktop/apps/Jobs/JobsGrid.js
Normal file
60
ui/desktop/apps/Jobs/JobsGrid.js
Normal file
@@ -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 `<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)
|
||||
26
ui/desktop/apps/Jobs/JobsSidebar.js
Normal file
26
ui/desktop/apps/Jobs/JobsSidebar.js
Normal file
@@ -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)
|
||||
105
ui/desktop/apps/Market/Market.js
Normal file
105
ui/desktop/apps/Market/Market.js
Normal file
@@ -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)
|
||||
140
ui/desktop/apps/Market/MarketGrid.js
Normal file
140
ui/desktop/apps/Market/MarketGrid.js
Normal file
@@ -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 `<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 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)
|
||||
85
ui/desktop/apps/Market/MarketSidebar.js
Normal file
85
ui/desktop/apps/Market/MarketSidebar.js
Normal file
@@ -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)
|
||||
188
ui/desktop/apps/Messages/Messages.js
Normal file
188
ui/desktop/apps/Messages/Messages.js
Normal file
@@ -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<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)
|
||||
.alignHorizontal("center")
|
||||
.alignVertical("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)
|
||||
56
ui/desktop/apps/Messages/MessagesPanel.js
Normal file
56
ui/desktop/apps/Messages/MessagesPanel.js
Normal file
@@ -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<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)
|
||||
73
ui/desktop/apps/Messages/MessagesSidebar.js
Normal file
73
ui/desktop/apps/Messages/MessagesSidebar.js
Normal file
@@ -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<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)
|
||||
153
ui/desktop/apps/Tasks/Tasks.js
Normal file
153
ui/desktop/apps/Tasks/Tasks.js
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user