Apps load from server
- Deleted /Jobs, /Announcements, /Events, /People - Commented out Forum and ForumPanel - Deleted /components/SearchBar, /components/LoadingCircle, /components/AddButton
This commit is contained in:
@@ -1 +1 @@
|
||||
VITE_API_URL=https://frm.so
|
||||
VITE_API_URL=
|
||||
@@ -1,90 +0,0 @@
|
||||
import util from "../../util"
|
||||
import server from "../../_/code/bridge/server.js"
|
||||
|
||||
css(`
|
||||
announcement- p {
|
||||
font-size: 0.85em;
|
||||
color: var(--darktext);
|
||||
}
|
||||
`)
|
||||
|
||||
class Announcement extends Shadow {
|
||||
constructor(announcement) {
|
||||
super()
|
||||
this.announcement = announcement
|
||||
}
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
h3(this.announcement.message)
|
||||
.color("var(--text)")
|
||||
.fontSize(1.3, em)
|
||||
.fontWeight("normal")
|
||||
.margin(0, em)
|
||||
|
||||
// Delete button
|
||||
// if (this.announcement.creator_id === global.profile.id) {
|
||||
// img(util.cssVariable("trash-src"), "1.5em")
|
||||
// .marginRight(0.5, em)
|
||||
// .onTap(() => {
|
||||
// this.deleteAnnouncement(this.announcement)
|
||||
// })
|
||||
// }
|
||||
})
|
||||
.justifyContent("space-between")
|
||||
.verticalAlign("center")
|
||||
|
||||
p(this.announcement.author ?? "Unknown author")
|
||||
.marginTop(0.75, em)
|
||||
p(this.convertDate(this.announcement.created) ?? "No date included")
|
||||
.marginTop(0.25, em)
|
||||
})
|
||||
.paddingVertical(1.5, em)
|
||||
.paddingHorizontal(3.5, em)
|
||||
.marginHorizontal(1, em)
|
||||
.borderRadius(10, px)
|
||||
.background("var(--darkaccent)")
|
||||
.border("1px solid var(--accent)")
|
||||
.boxSizing("border-box")
|
||||
}
|
||||
|
||||
async deleteAnnouncement(announcement) {
|
||||
const result = await server.deleteAnnouncement(announcement.id, announcement.network_id, global.profile.id)
|
||||
if (result.data === null) {
|
||||
console.log("Failed to delete announcement")
|
||||
}
|
||||
}
|
||||
|
||||
convertDate(rawDate) {
|
||||
const parsed = new Date(rawDate);
|
||||
|
||||
if (isNaN(parsed.getTime())) return rawDate;
|
||||
|
||||
const month = parsed.toLocaleString("en-US", { month: "long", timeZone: "UTC" });
|
||||
const day = parsed.getUTCDate();
|
||||
const year = parsed.getUTCFullYear();
|
||||
|
||||
const hours24 = parsed.getUTCHours();
|
||||
const minutes = parsed.getUTCMinutes();
|
||||
|
||||
const hours12 = hours24 % 12 || 12;
|
||||
const ampm = hours24 >= 12 ? "PM" : "AM";
|
||||
const paddedMinutes = String(minutes).padStart(2, "0");
|
||||
|
||||
const ordinal = (n) => {
|
||||
const mod100 = n % 100;
|
||||
if (mod100 >= 11 && mod100 <= 13) return `${n}th`;
|
||||
switch (n % 10) {
|
||||
case 1: return `${n}st`;
|
||||
case 2: return `${n}nd`;
|
||||
case 3: return `${n}rd`;
|
||||
default: return `${n}th`;
|
||||
}
|
||||
};
|
||||
|
||||
return `${month} ${ordinal(day)}, ${year} at ${hours12}:${paddedMinutes} ${ampm}`;
|
||||
}
|
||||
}
|
||||
|
||||
register(Announcement)
|
||||
@@ -1,271 +0,0 @@
|
||||
import './Announcement.js'
|
||||
import server from '../../_/code/bridge/server.js'
|
||||
import '../../components/SearchBar.js'
|
||||
|
||||
css(`
|
||||
announcements- {
|
||||
font-family: 'Arial';
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
announcements- h1 {
|
||||
font-family: 'Bona';
|
||||
}
|
||||
|
||||
announcements- .VStack::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
announcements- .VStack::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
announcements- .VStack::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
`)
|
||||
|
||||
class Announcements extends Shadow {
|
||||
static searchableKeys = ['message', 'author'];
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.announcements = global.currentNetwork.data.announcements.sort((a, b) => new Date(b.created) - new Date(a.created));
|
||||
this.searchedAnnouncements = [];
|
||||
this.searchText = "";
|
||||
}
|
||||
|
||||
render() {
|
||||
ZStack(() => {
|
||||
|
||||
VStack(() => {
|
||||
SearchBar(this.searchText, "90vw")
|
||||
|
||||
VStack(() => {
|
||||
if (!this.announcements || this.announcements == []) {
|
||||
LoadingCircle()
|
||||
} else if (this.searchText) {
|
||||
if (this.searchedAnnouncements.length > 0) {
|
||||
for (let i = 0; i < this.searchedAnnouncements.length; i++) {
|
||||
AnnouncementCard(this.searchedAnnouncements[i])
|
||||
}
|
||||
} else {
|
||||
h2("Could not find any announcements with your search criteria.")
|
||||
.color("var(--divider)")
|
||||
.fontWeight("bold")
|
||||
.marginTop(7.5, em)
|
||||
.marginBottom(0.5, em)
|
||||
.textAlign("center")
|
||||
}
|
||||
} else if (this.announcements.length > 0) {
|
||||
for (let i = 0; i < this.announcements.length; i++) {
|
||||
AnnouncementCard(this.announcements[i])
|
||||
}
|
||||
} else {
|
||||
h2("No Announcements")
|
||||
.color("var(--divider)")
|
||||
.fontWeight("bold")
|
||||
.marginTop(7.5, em)
|
||||
.marginBottom(0.5, em)
|
||||
.textAlign("center")
|
||||
}
|
||||
})
|
||||
.overflowY("scroll")
|
||||
.gap(0.75, em)
|
||||
|
||||
if(global.currentNetwork.permissions.includes("announcements.add")) {
|
||||
HStack(() => {
|
||||
input("Image Upload", "0px", "0px")
|
||||
.attr({ name: "image-upload", type: "file" })
|
||||
.display("none")
|
||||
.visibility("hidden")
|
||||
.onChange((e) => {
|
||||
const file = e.target.files[0]
|
||||
if (!file) return
|
||||
this.stagedImage = file
|
||||
this.stagedImageURL = URL.createObjectURL(file)
|
||||
this.rerender()
|
||||
})
|
||||
|
||||
div("+")
|
||||
.width(3, rem)
|
||||
.height(3, rem)
|
||||
.borderRadius(50, pct)
|
||||
.border("1px solid color-mix(in srgb, var(--accent) 60%, transparent)")
|
||||
.fontSize(2, em)
|
||||
.transform("rotate(180deg)")
|
||||
.zIndex(1001)
|
||||
.display("flex")
|
||||
.alignItems("center")
|
||||
.justifyContent("center")
|
||||
.transition("scale .2s")
|
||||
.state("touched", function (touched) {
|
||||
if(touched) {
|
||||
this.scale("1.5")
|
||||
this.color("var(--darkaccent)")
|
||||
this.backgroundColor("var(--divider)")
|
||||
} else {
|
||||
this.scale("")
|
||||
this.color("var(--divider)")
|
||||
this.backgroundColor("var(--searchbackground)")
|
||||
}
|
||||
})
|
||||
.onTouch(function (start) {
|
||||
if(start) {
|
||||
this.attr({touched: "true"})
|
||||
} else {
|
||||
this.attr({touched: ""})
|
||||
}
|
||||
})
|
||||
.onClick((done) => {
|
||||
if(done) {
|
||||
const inputSelector = this.$('[name="image-upload"]');
|
||||
inputSelector.click()
|
||||
}
|
||||
})
|
||||
|
||||
input("Add an Announcement")
|
||||
.flex("1 1 auto")
|
||||
.minWidth(0)
|
||||
.color("var(--text)")
|
||||
.background("var(--searchbackground)")
|
||||
.paddingVertical(0, rem)
|
||||
.fontSize(1, rem)
|
||||
.paddingHorizontal(1, rem)
|
||||
.borderRadius(100, px)
|
||||
.border("1px solid color-mix(in srgb, var(--accent) 60%, transparent)")
|
||||
.onTouch(function (start) {
|
||||
if (start) {
|
||||
this.style.backgroundColor = "var(--accent)"
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
$("appmenu-").display("none")
|
||||
this.focus()
|
||||
}, 20)
|
||||
console.log($("appmenu-"))
|
||||
this.style.backgroundColor = "var(--searchbackground)"
|
||||
}
|
||||
})
|
||||
.addEventListener("blur", () => {
|
||||
setTimeout(() => {
|
||||
$("appmenu-").display("grid")
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
.width(100, pct)
|
||||
.boxSizing("border-box")
|
||||
.position("absolute")
|
||||
.paddingHorizontal(1, rem)
|
||||
.bottom(1, vh)
|
||||
.gap(0.5, rem)
|
||||
}
|
||||
})
|
||||
.boxSizing("border-box")
|
||||
.height(100, pct)
|
||||
.width(100, pct)
|
||||
.onEvent("announcementsearch", this.onAnnouncementSearch)
|
||||
.onEvent("new-announcement", this.onNewAnnouncement)
|
||||
.onEvent("deleted-announcement", this.onDeletedAnnouncement)
|
||||
.onEvent("edited-announcement", this.onEditedAnnouncement)
|
||||
})
|
||||
}
|
||||
|
||||
async handleUpload(file) {
|
||||
try {
|
||||
const body = new FormData();
|
||||
body.append('image', file);
|
||||
const res = await util.authFetch(`${util.HOST}/profile/upload-image`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
},
|
||||
body: body
|
||||
});
|
||||
|
||||
if(res.status === 401) {
|
||||
return res.status
|
||||
}
|
||||
if (!res.ok) return res.status;
|
||||
const data = await res.json()
|
||||
global.profile = data.member
|
||||
console.log(global.profile)
|
||||
} catch (err) { // Network error / Error reaching server
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
addPhoto() {
|
||||
console.log("hey")
|
||||
}
|
||||
|
||||
onNewAnnouncement = (e) => {
|
||||
let newAnnouncement = e.detail.announcement;
|
||||
this.announcements.push(newAnnouncement)
|
||||
this.announcements.sort((a, b) => new Date(b.created) - new Date(a.created));
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
onDeletedAnnouncement = (e) => {
|
||||
let deletedId = e.detail.id
|
||||
const i = this.announcements.findIndex(ann => ann.id === deletedId)
|
||||
if (i !== -1) this.announcements.splice(i, 1);
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
onEditedAnnouncement = (e) => {
|
||||
let editedAnnouncement = e.detail
|
||||
const i = this.announcements.findIndex(ann => ann.id === editedAnnouncement.id)
|
||||
if (i !== -1) {
|
||||
this.announcements.splice(i, 1)
|
||||
this.announcements.unshift(editedAnnouncement)
|
||||
}
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
onAnnouncementSearch = (e) => {
|
||||
let searchText = e.detail.searchText.toLowerCase().trim();
|
||||
if (!searchText) {
|
||||
this.searchedAnnouncements = [];
|
||||
} else {
|
||||
this.searchedAnnouncements = this.announcements.filter(announcement =>
|
||||
Announcements.searchableKeys.some(key =>
|
||||
String(announcement[key]).toLowerCase().includes(searchText)
|
||||
)
|
||||
);
|
||||
}
|
||||
this.searchText = searchText
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
async getAnnouncements(networkId) {
|
||||
const fetchedAnnouncements = await server.getAnnouncements(networkId)
|
||||
if (this.checkForUpdates(this.announcements, fetchedAnnouncements.data)) {
|
||||
this.announcements = fetchedAnnouncements.data.sort((a, b) => new Date(b.created) - new Date(a.created));
|
||||
global.currentNetwork.data.announcements = this.announcements
|
||||
this.rerender()
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.getAnnouncements(global.currentNetwork.id)
|
||||
}
|
||||
|
||||
checkForUpdates(currentAnnouncements, fetchedAnnouncements) {
|
||||
if (currentAnnouncements.length !== fetchedAnnouncements.length) return true;
|
||||
|
||||
const currentMap = new Map(currentAnnouncements.map(ann => [ann.id, ann]));
|
||||
|
||||
for (const fetchedAnn of fetchedAnnouncements) {
|
||||
const currentAnn = currentMap.get(fetchedAnn.id);
|
||||
if (!currentAnn) return true;
|
||||
if (currentAnn.updated_at !== fetchedAnn.updated_at) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
register(Announcements)
|
||||
@@ -1,150 +0,0 @@
|
||||
import './Panel.js'
|
||||
import server from '../../_/code/bridge/serverFunctions.js'
|
||||
|
||||
css(`
|
||||
announcements- {
|
||||
font-family: 'Bona';
|
||||
}
|
||||
|
||||
announcements- input::placeholder {
|
||||
font-family: 'Bona Nova';
|
||||
font-size: 0.9em;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
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 Announcements extends Shadow {
|
||||
announcements;
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.announcements = global.currentNetwork.data.announcements.sort((a, b) => new Date(b.created) - new Date(a.created));
|
||||
console.log(this.announcements)
|
||||
}
|
||||
|
||||
render() {
|
||||
ZStack(() => {
|
||||
VStack(() => {
|
||||
|
||||
Panel(this.announcements)
|
||||
|
||||
input("Message", "70%")
|
||||
.paddingVertical(0.75, em)
|
||||
.boxSizing("border-box")
|
||||
.paddingHorizontal(2, em)
|
||||
.color("var(--text)")
|
||||
.background("var(--searchbackground)")
|
||||
.marginBottom(1, em)
|
||||
.border("0.5px solid var(--accent)")
|
||||
.outline("none")
|
||||
.borderRadius(100, px)
|
||||
.fontFamily("Arial")
|
||||
.fontSize(1, em)
|
||||
.onKeyDown(async function(e) {
|
||||
if (e.key === "Enter") {
|
||||
const result = await server.addAnnouncement(this.value, global.currentNetwork.id, global.profile.id)
|
||||
if (result.data.status === 200) {
|
||||
window.dispatchEvent(new CustomEvent('new-announcement', {
|
||||
detail: { announcement: result.data.announcement }
|
||||
}));
|
||||
} else {
|
||||
// error
|
||||
}
|
||||
this.value = ""
|
||||
}
|
||||
})
|
||||
})
|
||||
.gap(1, em)
|
||||
.boxSizing("border-box")
|
||||
.width(100, pct)
|
||||
.height(100, pct)
|
||||
.horizontalAlign("center")
|
||||
.verticalAlign("end")
|
||||
.minHeight(0)
|
||||
})
|
||||
.backgroundColor("var(--main)")
|
||||
.boxSizing("border-box")
|
||||
.paddingVertical(1, em)
|
||||
.width(100, pct)
|
||||
.height(100, pct)
|
||||
.flex("1 1 auto")
|
||||
.onEvent("new-announcement", this.onNewAnnouncement)
|
||||
.onEvent("deleted-announcement", this.onDeletedAnnouncement)
|
||||
.onEvent("edited-announcement", this.onEditedAnnouncement)
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.getAnnouncements(global.currentNetwork.id)
|
||||
}
|
||||
|
||||
checkForUpdates(currentAnnouncements, fetchedAnnouncements) {
|
||||
if (currentAnnouncements.length !== fetchedAnnouncements.length) return true;
|
||||
|
||||
const currentMap = new Map(currentAnnouncements.map(ann => [ann.id, ann]));
|
||||
|
||||
for (const fetchedAnn of fetchedAnnouncements) {
|
||||
const currentAnn = currentMap.get(fetchedAnn.id);
|
||||
|
||||
// new event added
|
||||
if (!currentAnn) return true;
|
||||
|
||||
// existing event changed
|
||||
if (currentAnn.updated_at !== fetchedAnn.updated_at) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async getAnnouncements(networkId) {
|
||||
const fetchedAnnouncements = await server.getAnnouncements(networkId)
|
||||
if (this.checkForUpdates(this.announcements, fetchedAnnouncements.data)) {
|
||||
console.log("found updates")
|
||||
this.announcements = fetchedAnnouncements.data.sort((a, b) => new Date(b.created) - new Date(a.created));
|
||||
global.currentNetwork.data.announcements = this.announcements
|
||||
this.rerender()
|
||||
}
|
||||
}
|
||||
|
||||
onNewAnnouncement = (e) => {
|
||||
let newAnnouncement = e.detail.announcement;
|
||||
this.announcements.push(newAnnouncement)
|
||||
this.announcements.sort((a, b) => new Date(b.created) - new Date(a.created));
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
onDeletedAnnouncement = (e) => {
|
||||
let deletedId = e.detail.id
|
||||
const i = this.announcements.findIndex(ann => ann.id === deletedId)
|
||||
if (i !== -1) this.announcements.splice(i, 1);
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
onEditedAnnouncement = (e) => {
|
||||
let editedAnnouncement = e.detail
|
||||
const i = this.announcements.findIndex(ann => ann.id === editedPost.id)
|
||||
if (i !== -1) {
|
||||
this.announcements.splice(i, 1)
|
||||
this.announcements.unshift(editedAnnouncement)
|
||||
}
|
||||
this.rerender()
|
||||
}
|
||||
}
|
||||
|
||||
register(Announcements)
|
||||
@@ -1,151 +0,0 @@
|
||||
import "../../components/LoadingCircle.js"
|
||||
import server from "../../_/code/bridge/server.js"
|
||||
|
||||
css(`
|
||||
panel- {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
panel-::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
panel-::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
panel-::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
`)
|
||||
|
||||
class Panel extends Shadow {
|
||||
announcements = []
|
||||
isSending = false
|
||||
|
||||
constructor(announcements) {
|
||||
super()
|
||||
this.announcements = announcements
|
||||
}
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
if(this.announcements.length > 0) {
|
||||
let previousDate = null
|
||||
|
||||
for(let i=0; i<this.announcements.length; i++) {
|
||||
let announcement = this.announcements[i]
|
||||
const isMe = announcement.creator_id === global.profile.id
|
||||
const dateParts = this.parseDate(announcement.created);
|
||||
const { date, time } = dateParts;
|
||||
|
||||
if (previousDate !== date) {
|
||||
previousDate = date;
|
||||
|
||||
p(date)
|
||||
.textAlign("center")
|
||||
.opacity(0.6)
|
||||
.fontWeight("bold")
|
||||
.paddingTop(1, em)
|
||||
.paddingBottom(0.5, em)
|
||||
.color("var(--quillred)")
|
||||
.borderTop(`1px solid var(--${i == 0 ? "transparent" : "divider"})`)
|
||||
}
|
||||
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
h3(isMe ? "Me" : this.getAuthorName(announcement))
|
||||
.color(isMe ? "var(--quillred)" : "var(--headertext")
|
||||
.opacity(0.75)
|
||||
.margin(0)
|
||||
|
||||
h3(`${date} ${time}`)
|
||||
.opacity(0.5)
|
||||
.color("var(--headertext)")
|
||||
.margin(0)
|
||||
.marginLeft(0.5, em)
|
||||
.fontSize(1, em)
|
||||
|
||||
if (announcement.created !== announcement.updated_at) {
|
||||
p("(edited)")
|
||||
.color("var(--headertext)")
|
||||
.letterSpacing(0.8, "px")
|
||||
.opacity(0.5)
|
||||
.fontWeight("bold")
|
||||
.paddingLeft(0.25, em)
|
||||
.fontSize(0.9, em)
|
||||
}
|
||||
})
|
||||
.verticalAlign("center")
|
||||
.marginBottom(0.1, em)
|
||||
|
||||
p(announcement.text)
|
||||
.color("var(--text)")
|
||||
.marginHorizontal(0.2, em)
|
||||
.paddingVertical(0.2, em)
|
||||
.boxSizing("border-box")
|
||||
})
|
||||
.marginBottom(0.05, em)
|
||||
}
|
||||
} else {
|
||||
LoadingCircle()
|
||||
}
|
||||
})
|
||||
.gap(1, em)
|
||||
.fontSize(1.1, em)
|
||||
.boxSizing("border-box")
|
||||
.flex("1 1 auto")
|
||||
.minHeight(0)
|
||||
.overflowY("auto")
|
||||
.width(100, pct)
|
||||
.paddingBottom(2, em)
|
||||
.paddingHorizontal(4, pct)
|
||||
.backgroundColor("var(--main)")
|
||||
.onAppear(async () => {
|
||||
requestAnimationFrame(() => {
|
||||
this.scrollTo({ top: 0, behavior: "smooth" });
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
getAuthorName(announcement) {
|
||||
const members = global.currentNetwork.data.members;
|
||||
const creator = members.find(m => m.id === announcement.creator_id);
|
||||
if (creator) {
|
||||
return `${creator.first_name} ${creator.last_name}`
|
||||
} else {
|
||||
return "No name"
|
||||
}
|
||||
}
|
||||
|
||||
parseDate(str) {
|
||||
// Format: YYYY-MM-DDTHH:MM:SS.mmmZ
|
||||
const match = str.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):\d{2}\.\d+Z$/);
|
||||
if (!match) return null;
|
||||
|
||||
const [, yyyy, mm, dd, hh, min] = match;
|
||||
|
||||
// Convert 24h to 12h
|
||||
const hour24 = parseInt(hh, 10);
|
||||
const ampm = hour24 >= 12 ? 'pm' : 'am';
|
||||
const hour12 = hour24 % 12 || 12;
|
||||
|
||||
const date = `${mm}/${dd}/${yyyy}`;
|
||||
const time = `${hour12}:${min}${ampm}`;
|
||||
|
||||
return { date, time };
|
||||
}
|
||||
|
||||
formatTime(str) {
|
||||
const match = str.match(/-(\d+:\d+):\d+.*(am|pm)/i);
|
||||
if (!match) return null;
|
||||
|
||||
const [_, hourMin, ampm] = match;
|
||||
return hourMin + ampm.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
register(Panel)
|
||||
@@ -1,92 +0,0 @@
|
||||
import util from "../../util"
|
||||
import server from "../../_/code/bridge/server.js"
|
||||
|
||||
css(`
|
||||
eventcard- p {
|
||||
font-size: 0.85em;
|
||||
color: var(--darktext);
|
||||
}
|
||||
`)
|
||||
|
||||
class EventCard extends Shadow {
|
||||
constructor(event) {
|
||||
super()
|
||||
this.event = event
|
||||
}
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
h3(this.event.title)
|
||||
.color("var(--text)")
|
||||
.fontSize(1.3, em)
|
||||
.fontWeight("normal")
|
||||
.margin(0, em)
|
||||
|
||||
// Delete button
|
||||
// if (this.event.creator_id === global.profile.id) {
|
||||
// img(util.cssVariable("trash-src"), "1.5em")
|
||||
// .marginRight(0.5, em)
|
||||
// .onTap(() => {
|
||||
// this.deleteEvent(this.event)
|
||||
// })
|
||||
// }
|
||||
})
|
||||
.justifyContent("space-between")
|
||||
.verticalAlign("center")
|
||||
|
||||
p(this.event.location ?? "No location added")
|
||||
.marginTop(0.75, em)
|
||||
p(this.convertDate(this.event.time_start) ?? "No time included")
|
||||
.marginTop(0.25, em)
|
||||
p(this.event.description ?? "No description included")
|
||||
.marginTop(0.75, em)
|
||||
})
|
||||
.paddingVertical(1.5, em)
|
||||
.paddingHorizontal(3.5, em)
|
||||
.marginHorizontal(1, em)
|
||||
.borderRadius(10, px)
|
||||
.background("var(--darkaccent)")
|
||||
.border("1px solid var(--accent)")
|
||||
.boxSizing("border-box")
|
||||
}
|
||||
|
||||
async deleteEvent(event) {
|
||||
const result = await server.deleteEvent(event.id, event.network_id, global.profile.id)
|
||||
if (result.data === null) {
|
||||
console.log("Failed to delete event")
|
||||
}
|
||||
}
|
||||
|
||||
convertDate(rawDate) {
|
||||
const parsed = new Date(rawDate);
|
||||
|
||||
if (isNaN(parsed.getTime())) return rawDate;
|
||||
|
||||
const month = parsed.toLocaleString("en-US", { month: "long", timeZone: "UTC" });
|
||||
const day = parsed.getUTCDate();
|
||||
const year = parsed.getUTCFullYear();
|
||||
|
||||
const hours24 = parsed.getUTCHours();
|
||||
const minutes = parsed.getUTCMinutes();
|
||||
|
||||
const hours12 = hours24 % 12 || 12;
|
||||
const ampm = hours24 >= 12 ? "PM" : "AM";
|
||||
const paddedMinutes = String(minutes).padStart(2, "0");
|
||||
|
||||
const ordinal = (n) => {
|
||||
const mod100 = n % 100;
|
||||
if (mod100 >= 11 && mod100 <= 13) return `${n}th`;
|
||||
switch (n % 10) {
|
||||
case 1: return `${n}st`;
|
||||
case 2: return `${n}nd`;
|
||||
case 3: return `${n}rd`;
|
||||
default: return `${n}th`;
|
||||
}
|
||||
};
|
||||
|
||||
return `${month} ${ordinal(day)}, ${year} at ${hours12}:${paddedMinutes} ${ampm}`;
|
||||
}
|
||||
}
|
||||
|
||||
register(EventCard)
|
||||
@@ -1,175 +0,0 @@
|
||||
import server from "../../_/code/bridge/server"
|
||||
|
||||
class EventForm extends Shadow {
|
||||
inputStyles(el) {
|
||||
return el
|
||||
.background("var(--main)")
|
||||
.color("var(--text)")
|
||||
.border("1px solid var(--accent)")
|
||||
.fontSize(0.9, rem)
|
||||
.backgroundColor("var(--darkaccent)")
|
||||
.borderRadius(12, px)
|
||||
.outline("none")
|
||||
.onTouch((start) => {
|
||||
if (start) {
|
||||
this.style.backgroundColor = "var(--accent)"
|
||||
} else {
|
||||
this.style.backgroundColor = "var(--darkaccent)"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
errorMessage = ""
|
||||
|
||||
render() {
|
||||
ZStack(() => {
|
||||
p("X")
|
||||
.color("var(--darkred)")
|
||||
.fontSize(2, em)
|
||||
.position("absolute")
|
||||
.fontFamily("Arial")
|
||||
.marginTop(1, rem)
|
||||
.marginLeft(1, rem)
|
||||
.onTap(() => {
|
||||
this.toggle()
|
||||
})
|
||||
|
||||
form(() => {
|
||||
VStack(() => {
|
||||
h1("Create an Event")
|
||||
.color("var(--text)")
|
||||
.textAlign("center")
|
||||
.fontFamily("Arial")
|
||||
.marginTop(1.5, em)
|
||||
|
||||
input("Title", "70%")
|
||||
.attr({ name: "title", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
input("Location", "70%")
|
||||
.attr({ name: "location", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
input("Start Time", "70%")
|
||||
.attr({ name: "time_start", type: "datetime-local" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
input("Description", "70%")
|
||||
.attr({ name: "description", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
HStack(() => {
|
||||
button("==>")
|
||||
.padding(1, em)
|
||||
.fontSize(0.9, rem)
|
||||
.borderRadius(12, px)
|
||||
.background("var(--searchbackground)")
|
||||
.color("var(--text)")
|
||||
.border("1px solid var(--accent)")
|
||||
.boxSizing("border-box")
|
||||
.onTouch(function (start) {
|
||||
if (start) {
|
||||
this.style.backgroundColor = "var(--accent)"
|
||||
} else {
|
||||
this.style.backgroundColor = "var(--searchbackground)"
|
||||
}
|
||||
})
|
||||
})
|
||||
.width(70, vw)
|
||||
.margin("auto")
|
||||
.fontSize(0.9, rem)
|
||||
.paddingLeft(0, em)
|
||||
.paddingRight(2, em)
|
||||
.marginVertical(1, em)
|
||||
.border("1px solid transparent")
|
||||
|
||||
p("")
|
||||
.dynamicText("errormessage", "{{}}")
|
||||
.margin("auto")
|
||||
.marginTop(1, em)
|
||||
.color("var(--text)")
|
||||
.fontFamily("Arial")
|
||||
.opacity(.7)
|
||||
.padding(0.5, em)
|
||||
.backgroundColor("var(--darkred)")
|
||||
.width(100, pct)
|
||||
.textAlign("center")
|
||||
.boxSizing("border-box")
|
||||
})
|
||||
.horizontalAlign("center")
|
||||
})
|
||||
.onSubmit((e) => {
|
||||
e.preventDefault()
|
||||
const data = {
|
||||
title: e.target.$('[name="title"]').value,
|
||||
location: e.target.$('[name="location"]').value,
|
||||
time_start: e.target.$('[name="time_start"]').value,
|
||||
description: e.target.$('[name="description"]').value,
|
||||
};
|
||||
this.handleSend(data)
|
||||
})
|
||||
})
|
||||
.position("fixed")
|
||||
.height(window.visualViewport.height - 20, px)
|
||||
.width(100, pct)
|
||||
.top(100, vh)
|
||||
.background("var(--main)")
|
||||
.zIndex(4)
|
||||
.borderTopLeftRadius("10px")
|
||||
.borderTopRightRadius("10px")
|
||||
.boxSizing("border-box")
|
||||
.border("1px solid var(--accent)")
|
||||
.transition("top .3s")
|
||||
}
|
||||
|
||||
async handleSend(eventData) {
|
||||
if (!eventData.title) {
|
||||
this.$(".VStack > p")
|
||||
.attr({ errorMessage: 'Events must include a title.' })
|
||||
.display("")
|
||||
|
||||
return;
|
||||
} else {
|
||||
this.$(".VStack > p").style.display = "none"
|
||||
}
|
||||
|
||||
const date = new Date(eventData.time_start);
|
||||
const timestamp = eventData.time_start ? date.toISOString() : null;
|
||||
const newEvent = {
|
||||
title: eventData.title,
|
||||
location: eventData.location ?? null,
|
||||
time_start: timestamp ?? null,
|
||||
description: eventData.description ?? null
|
||||
}
|
||||
|
||||
const { data } = await server.addEvent(newEvent, global.currentNetwork.id, global.profile.id)
|
||||
if (data.status === 200) {
|
||||
console.log("Added new event: ", data)
|
||||
this.toggle()
|
||||
window.dispatchEvent(new CustomEvent('new-event', {
|
||||
detail: { event: data.event }
|
||||
}));
|
||||
} else {
|
||||
console.log("Failed to add new event: ", data)
|
||||
this.$(".VStack > p")
|
||||
.attr({ errorMessage: data.error })
|
||||
.display("")
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if(this.style.top === "15vh") {
|
||||
this.style.top = "100vh"
|
||||
this.pointerEvents = "none"
|
||||
} else {
|
||||
this.style.top = "15vh"
|
||||
this.pointerEvents = "auto"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register(EventForm)
|
||||
@@ -1,178 +0,0 @@
|
||||
import "../../components/TopBar.js"
|
||||
import "../../components/LoadingCircle.js"
|
||||
import "./EventCard.js"
|
||||
import "./EventForm.js"
|
||||
import server from "../../_/code/bridge/server.js"
|
||||
import "../../components/SearchBar.js"
|
||||
|
||||
css(`
|
||||
events- {
|
||||
font-family: 'Arial';
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
events- h1 {
|
||||
font-family: 'Bona';
|
||||
}
|
||||
|
||||
events- .VStack::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
events- .VStack::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
events- .VStack::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
`)
|
||||
|
||||
class Events extends Shadow {
|
||||
static searchableKeys = ['title', 'description', 'location'];
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.events = global.currentNetwork.data.events;
|
||||
this.searchedEvents = [];
|
||||
this.searchText = "";
|
||||
}
|
||||
|
||||
render() {
|
||||
ZStack(() => {
|
||||
|
||||
EventForm()
|
||||
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
SearchBar(this.searchText, "75vw")
|
||||
AddButton()
|
||||
})
|
||||
|
||||
VStack(() => {
|
||||
if (!this.events || this.events == []) {
|
||||
LoadingCircle()
|
||||
} else if (this.searchText) {
|
||||
if (this.searchedEvents.length > 0) {
|
||||
for (let i = 0; i < this.searchedEvents.length; i++) {
|
||||
EventCard(this.searchedEvents[i])
|
||||
}
|
||||
} else {
|
||||
h2("Could not find any events with your search criteria.")
|
||||
.color("var(--divider)")
|
||||
.fontWeight("bold")
|
||||
.marginTop(7.5, em)
|
||||
.marginBottom(0.5, em)
|
||||
.textAlign("center")
|
||||
}
|
||||
} else if (this.events.length > 0) {
|
||||
for (let i = 0; i < this.events.length; i++) {
|
||||
EventCard(this.events[i])
|
||||
}
|
||||
} else {
|
||||
h2("No Events")
|
||||
.color("var(--divider)")
|
||||
.fontWeight("bold")
|
||||
.marginTop(7.5, em)
|
||||
.marginBottom(0.5, em)
|
||||
.textAlign("center")
|
||||
}
|
||||
})
|
||||
.overflowY("scroll")
|
||||
.gap(0.75, em)
|
||||
})
|
||||
.boxSizing("border-box")
|
||||
.height(100, pct)
|
||||
.width(100, pct)
|
||||
.onEvent("eventsearch", this.onEventSearch)
|
||||
.onEvent("new-event", this.onNewEvent)
|
||||
})
|
||||
}
|
||||
|
||||
onNewEvent = (e) => {
|
||||
let newEvent = e.detail.event;
|
||||
this.events.push(newEvent)
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
onEventSearch = (e) => {
|
||||
let searchText = e.detail.searchText.toLowerCase().trim();
|
||||
if (!searchText) {
|
||||
this.searchedEvents = [];
|
||||
} else {
|
||||
this.searchedEvents = this.events.filter(event =>
|
||||
Events.searchableKeys.some(key =>
|
||||
String(event[key]).toLowerCase().includes(searchText)
|
||||
)
|
||||
);
|
||||
}
|
||||
this.searchText = searchText
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
async getEvents(networkId) {
|
||||
const fetchedEvents = await server.getEvents(networkId)
|
||||
if (this.checkForUpdates(this.events, fetchedEvents.data)) {
|
||||
this.events = fetchedEvents.data
|
||||
this.rerender()
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.getEvents(global.currentNetwork.id)
|
||||
}
|
||||
|
||||
checkForUpdates(currentEvents, fetchedEvents) {
|
||||
if (currentEvents.length !== fetchedEvents.length) return true;
|
||||
|
||||
const currentMap = new Map(currentEvents.map(event => [event.id, event]));
|
||||
|
||||
for (const fetchedEvent of fetchedEvents) {
|
||||
const currentEvent = currentMap.get(fetchedEvent.id);
|
||||
|
||||
// new event added
|
||||
if (!currentEvent) return true;
|
||||
|
||||
// existing event changed
|
||||
if (currentEvent.updated_at !== fetchedEvent.updated_at) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
convertDate(rawDate) {
|
||||
const parsed = new Date(rawDate);
|
||||
|
||||
if (isNaN(parsed.getTime())) return rawDate;
|
||||
|
||||
const month = parsed.toLocaleString("en-US", { month: "long", timeZone: "UTC" });
|
||||
const day = parsed.getUTCDate();
|
||||
const year = parsed.getUTCFullYear();
|
||||
|
||||
const hours24 = parsed.getUTCHours();
|
||||
const minutes = parsed.getUTCMinutes();
|
||||
|
||||
const hours12 = hours24 % 12 || 12;
|
||||
const ampm = hours24 >= 12 ? "PM" : "AM";
|
||||
const paddedMinutes = String(minutes).padStart(2, "0");
|
||||
|
||||
const ordinal = (n) => {
|
||||
const mod100 = n % 100;
|
||||
if (mod100 >= 11 && mod100 <= 13) return `${n}th`;
|
||||
switch (n % 10) {
|
||||
case 1: return `${n}st`;
|
||||
case 2: return `${n}nd`;
|
||||
case 3: return `${n}rd`;
|
||||
default: return `${n}th`;
|
||||
}
|
||||
};
|
||||
|
||||
return `${month} ${ordinal(day)}, ${year} at ${hours12}:${paddedMinutes} ${ampm}`;
|
||||
}
|
||||
}
|
||||
|
||||
register(Events)
|
||||
@@ -1,83 +1,83 @@
|
||||
import './ForumPanel.js'
|
||||
// import './ForumPanel.js'
|
||||
|
||||
css(`
|
||||
forum- {
|
||||
font-family: 'Bona';
|
||||
}
|
||||
// css(`
|
||||
// forum- {
|
||||
// font-family: 'Bona';
|
||||
// }
|
||||
|
||||
forum- input::placeholder {
|
||||
font-family: 'Bona Nova';
|
||||
font-size: 0.9em;
|
||||
color: var(--accent);
|
||||
}
|
||||
// forum- input::placeholder {
|
||||
// font-family: 'Bona Nova';
|
||||
// font-size: 0.9em;
|
||||
// color: var(--accent);
|
||||
// }
|
||||
|
||||
input::placeholder {
|
||||
font-family: Arial;
|
||||
}
|
||||
// input::placeholder {
|
||||
// font-family: Arial;
|
||||
// }
|
||||
|
||||
input[type="checkbox"] {
|
||||
appearance: none; /* remove default style */
|
||||
-webkit-appearance: none;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border: 1px solid 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);
|
||||
}
|
||||
`)
|
||||
// input[type="checkbox"]:checked {
|
||||
// background-color: var(--red);
|
||||
// }
|
||||
// `)
|
||||
|
||||
class Forum extends Shadow {
|
||||
render() {
|
||||
ZStack(() => {
|
||||
VStack(() => {
|
||||
// class Forum extends Shadow {
|
||||
// render() {
|
||||
// ZStack(() => {
|
||||
// VStack(() => {
|
||||
|
||||
ForumPanel()
|
||||
// ForumPanel()
|
||||
|
||||
input("Message", "70%")
|
||||
.paddingVertical(0.75, em)
|
||||
.boxSizing("border-box")
|
||||
.paddingHorizontal(2, em)
|
||||
.color("var(--accent)")
|
||||
.background("black")
|
||||
.marginBottom(1, em)
|
||||
.border("0.5px solid #6f5e4e")
|
||||
.borderRadius(100, px)
|
||||
.fontFamily("Arial")
|
||||
.fontSize(1, em)
|
||||
.onKeyDown(async function(e) {
|
||||
if (e.key === "Enter") {
|
||||
let msg = {
|
||||
forum: global.currentNetwork.abbreviation,
|
||||
text: this.value
|
||||
}
|
||||
await global.Socket.send({
|
||||
app: "FORUM",
|
||||
operation: "SEND",
|
||||
msg: msg
|
||||
})
|
||||
this.value = ""
|
||||
}
|
||||
})
|
||||
})
|
||||
.gap(0.5, em)
|
||||
.boxSizing("border-box")
|
||||
.width(100, pct)
|
||||
.height(100, pct)
|
||||
.horizontalAlign("center")
|
||||
.verticalAlign("end")
|
||||
.minHeight(0)
|
||||
})
|
||||
.backgroundColor("var(--main)")
|
||||
.boxSizing("border-box")
|
||||
.paddingVertical(1, em)
|
||||
.width(100, pct)
|
||||
.minHeight(0)
|
||||
.flex("1 1 auto")
|
||||
}
|
||||
// input("Message", "70%")
|
||||
// .paddingVertical(0.75, em)
|
||||
// .boxSizing("border-box")
|
||||
// .paddingHorizontal(2, em)
|
||||
// .color("var(--accent)")
|
||||
// .background("black")
|
||||
// .marginBottom(1, em)
|
||||
// .border("0.5px solid #6f5e4e")
|
||||
// .borderRadius(100, px)
|
||||
// .fontFamily("Arial")
|
||||
// .fontSize(1, em)
|
||||
// .onKeyDown(async function(e) {
|
||||
// if (e.key === "Enter") {
|
||||
// let msg = {
|
||||
// forum: global.currentNetwork.abbreviation,
|
||||
// text: this.value
|
||||
// }
|
||||
// await global.Socket.send({
|
||||
// app: "FORUM",
|
||||
// operation: "SEND",
|
||||
// msg: msg
|
||||
// })
|
||||
// this.value = ""
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// .gap(0.5, em)
|
||||
// .boxSizing("border-box")
|
||||
// .width(100, pct)
|
||||
// .height(100, pct)
|
||||
// .horizontalAlign("center")
|
||||
// .verticalAlign("end")
|
||||
// .minHeight(0)
|
||||
// })
|
||||
// .backgroundColor("var(--main)")
|
||||
// .boxSizing("border-box")
|
||||
// .paddingVertical(1, em)
|
||||
// .width(100, pct)
|
||||
// .minHeight(0)
|
||||
// .flex("1 1 auto")
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
register(Forum)
|
||||
// register(Forum)
|
||||
@@ -1,193 +1,193 @@
|
||||
import "../../components/LoadingCircle.js"
|
||||
// import "../../components/LoadingCircle.js"
|
||||
|
||||
css(`
|
||||
forumpanel- {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
// css(`
|
||||
// forumpanel- {
|
||||
// scrollbar-width: none;
|
||||
// -ms-overflow-style: none;
|
||||
// }
|
||||
|
||||
forumpanel-::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
// forumpanel-::-webkit-scrollbar {
|
||||
// display: none;
|
||||
// width: 0px;
|
||||
// height: 0px;
|
||||
// }
|
||||
|
||||
forumpanel-::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
}
|
||||
// forumpanel-::-webkit-scrollbar-thumb {
|
||||
// background: transparent;
|
||||
// }
|
||||
|
||||
forumpanel-::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
`)
|
||||
// forumpanel-::-webkit-scrollbar-track {
|
||||
// background: transparent;
|
||||
// }
|
||||
// `)
|
||||
|
||||
class ForumPanel extends Shadow {
|
||||
messages = []
|
||||
isSending = false
|
||||
// class ForumPanel extends Shadow {
|
||||
// messages = []
|
||||
// isSending = false
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
if(this.messages.length > 0) {
|
||||
let previousDate = null
|
||||
// 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 isMe = message.authorId === global.profile.id
|
||||
const dateParts = this.parseDate(message.time);
|
||||
const { date, time } = dateParts;
|
||||
// for(let i=0; i<this.messages.length; i++) {
|
||||
// let message = this.messages[i]
|
||||
// const isMe = message.authorId === global.profile.id
|
||||
// const dateParts = this.parseDate(message.time);
|
||||
// const { date, time } = dateParts;
|
||||
|
||||
if (previousDate !== date) {
|
||||
previousDate = date;
|
||||
// if (previousDate !== date) {
|
||||
// previousDate = date;
|
||||
|
||||
p(date)
|
||||
.textAlign("center")
|
||||
.opacity(0.6)
|
||||
.fontWeight("bold")
|
||||
.paddingTop(1, em)
|
||||
.paddingBottom(0.5, em)
|
||||
.color("var(--quillred)")
|
||||
.borderTop(`1px solid var(--${i == 0 ? "transparent" : "divider"})`)
|
||||
}
|
||||
// p(date)
|
||||
// .textAlign("center")
|
||||
// .opacity(0.6)
|
||||
// .fontWeight("bold")
|
||||
// .paddingTop(1, em)
|
||||
// .paddingBottom(0.5, em)
|
||||
// .color("var(--quillred)")
|
||||
// .borderTop(`1px solid var(--${i == 0 ? "transparent" : "divider"})`)
|
||||
// }
|
||||
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
h3(isMe ? "Me" : message.sentBy)
|
||||
.color(isMe ? "var(--quillred)" : "var(--brown")
|
||||
.margin(0)
|
||||
// VStack(() => {
|
||||
// HStack(() => {
|
||||
// h3(isMe ? "Me" : message.sentBy)
|
||||
// .color(isMe ? "var(--quillred)" : "var(--brown")
|
||||
// .margin(0)
|
||||
|
||||
h3(`${date} ${time}`)
|
||||
.opacity(0.5)
|
||||
.color("var(--brown)")
|
||||
.margin(0)
|
||||
.marginLeft(0.5, em)
|
||||
.fontSize(1, em)
|
||||
// h3(`${date} ${time}`)
|
||||
// .opacity(0.5)
|
||||
// .color("var(--brown)")
|
||||
// .margin(0)
|
||||
// .marginLeft(0.5, em)
|
||||
// .fontSize(1, em)
|
||||
|
||||
if (message.edited) {
|
||||
p("(edited)")
|
||||
.color("var(--brown)")
|
||||
.letterSpacing(0.8, "px")
|
||||
.opacity(0.8)
|
||||
.fontWeight("bold")
|
||||
.paddingLeft(0.25, em)
|
||||
.fontSize(0.9, em)
|
||||
}
|
||||
})
|
||||
.verticalAlign("center")
|
||||
.marginBottom(0.1, em)
|
||||
// if (message.edited) {
|
||||
// p("(edited)")
|
||||
// .color("var(--brown)")
|
||||
// .letterSpacing(0.8, "px")
|
||||
// .opacity(0.8)
|
||||
// .fontWeight("bold")
|
||||
// .paddingLeft(0.25, em)
|
||||
// .fontSize(0.9, em)
|
||||
// }
|
||||
// })
|
||||
// .verticalAlign("center")
|
||||
// .marginBottom(0.1, em)
|
||||
|
||||
p(message.text)
|
||||
.color("var(--accent)")
|
||||
.borderLeft("1.5px solid var(--divider)")
|
||||
.borderBottomLeftRadius("7.5px")
|
||||
.paddingLeft(0.5, em)
|
||||
.marginHorizontal(0.2, em)
|
||||
.paddingVertical(0.2, em)
|
||||
.boxSizing("border-box")
|
||||
})
|
||||
.marginBottom(0.05, em)
|
||||
.onClick(async (finished, e) => {
|
||||
if (finished) {
|
||||
console.log(message.id)
|
||||
let msg = {
|
||||
forum: global.currentNetwork.abbreviation,
|
||||
id: message.id,
|
||||
text: "EDITED TEXT TEST!"
|
||||
}
|
||||
await global.Socket.send({
|
||||
app: "FORUM",
|
||||
operation: "PUT",
|
||||
msg: msg
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
LoadingCircle()
|
||||
}
|
||||
})
|
||||
.gap(1, em)
|
||||
.fontSize(1.1, em)
|
||||
.boxSizing("border-box")
|
||||
.flex("1 1 auto")
|
||||
.minHeight(0)
|
||||
.overflowY("auto")
|
||||
.width(100, pct)
|
||||
.paddingBottom(2, em)
|
||||
.paddingHorizontal(4, pct)
|
||||
.backgroundColor("var(--main)")
|
||||
.onAppear(async () => {
|
||||
requestAnimationFrame(() => {
|
||||
this.scrollTo({ top: 0, behavior: "smooth" });
|
||||
});
|
||||
if (!this.isSending) {
|
||||
this.isSending = true
|
||||
let res = await global.Socket.send({
|
||||
app: "FORUM",
|
||||
operation: "GET",
|
||||
msg: {
|
||||
forum: global.currentNetwork.abbreviation,
|
||||
by: "network",
|
||||
authorId: -999 // default
|
||||
}
|
||||
})
|
||||
if(!res) console.error("failed to get messages")
|
||||
if(res.msg.length > 0 && this.messages.length === 0) {
|
||||
this.messages = res.msg.reverse()
|
||||
this.rerender()
|
||||
}
|
||||
this.isSending = false
|
||||
}
|
||||
})
|
||||
.onEvent("new-post", this.onNewPost)
|
||||
.onEvent("deleted-post", this.onDeletedPost)
|
||||
.onEvent("edited-post", this.onEditedPost)
|
||||
}
|
||||
// p(message.text)
|
||||
// .color("var(--accent)")
|
||||
// .borderLeft("1.5px solid var(--divider)")
|
||||
// .borderBottomLeftRadius("7.5px")
|
||||
// .paddingLeft(0.5, em)
|
||||
// .marginHorizontal(0.2, em)
|
||||
// .paddingVertical(0.2, em)
|
||||
// .boxSizing("border-box")
|
||||
// })
|
||||
// .marginBottom(0.05, em)
|
||||
// .onClick(async (finished, e) => {
|
||||
// if (finished) {
|
||||
// console.log(message.id)
|
||||
// let msg = {
|
||||
// forum: global.currentNetwork.abbreviation,
|
||||
// id: message.id,
|
||||
// text: "EDITED TEXT TEST!"
|
||||
// }
|
||||
// await global.Socket.send({
|
||||
// app: "FORUM",
|
||||
// operation: "PUT",
|
||||
// msg: msg
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// } else {
|
||||
// LoadingCircle()
|
||||
// }
|
||||
// })
|
||||
// .gap(1, em)
|
||||
// .fontSize(1.1, em)
|
||||
// .boxSizing("border-box")
|
||||
// .flex("1 1 auto")
|
||||
// .minHeight(0)
|
||||
// .overflowY("auto")
|
||||
// .width(100, pct)
|
||||
// .paddingBottom(2, em)
|
||||
// .paddingHorizontal(4, pct)
|
||||
// .backgroundColor("var(--main)")
|
||||
// .onAppear(async () => {
|
||||
// requestAnimationFrame(() => {
|
||||
// this.scrollTo({ top: 0, behavior: "smooth" });
|
||||
// });
|
||||
// if (!this.isSending) {
|
||||
// this.isSending = true
|
||||
// let res = await global.Socket.send({
|
||||
// app: "FORUM",
|
||||
// operation: "GET",
|
||||
// msg: {
|
||||
// forum: global.currentNetwork.abbreviation,
|
||||
// by: "network",
|
||||
// authorId: -999 // default
|
||||
// }
|
||||
// })
|
||||
// if(!res) console.error("failed to get messages")
|
||||
// if(res.msg.length > 0 && this.messages.length === 0) {
|
||||
// this.messages = res.msg.reverse()
|
||||
// this.rerender()
|
||||
// }
|
||||
// this.isSending = false
|
||||
// }
|
||||
// })
|
||||
// .onEvent("new-post", this.onNewPost)
|
||||
// .onEvent("deleted-post", this.onDeletedPost)
|
||||
// .onEvent("edited-post", this.onEditedPost)
|
||||
// }
|
||||
|
||||
onNewPost = (e) => {
|
||||
let newPost = e.detail
|
||||
if (this.messages && !this.messages.some(post => post.id === newPost.id)) {
|
||||
this.messages.unshift(newPost)
|
||||
this.rerender()
|
||||
}
|
||||
}
|
||||
// onNewPost = (e) => {
|
||||
// let newPost = e.detail
|
||||
// if (this.messages && !this.messages.some(post => post.id === newPost.id)) {
|
||||
// this.messages.unshift(newPost)
|
||||
// this.rerender()
|
||||
// }
|
||||
// }
|
||||
|
||||
onDeletedPost = (e) => {
|
||||
let deletedId = e.detail
|
||||
const i = this.messages.findIndex(post => post.id === deletedId)
|
||||
if (i !== -1) this.messages.splice(i, 1);
|
||||
this.rerender()
|
||||
}
|
||||
// onDeletedPost = (e) => {
|
||||
// let deletedId = e.detail
|
||||
// const i = this.messages.findIndex(post => post.id === deletedId)
|
||||
// if (i !== -1) this.messages.splice(i, 1);
|
||||
// this.rerender()
|
||||
// }
|
||||
|
||||
onEditedPost = (e) => {
|
||||
let editedPost = e.detail
|
||||
const i = this.messages.findIndex(post => post.id === editedPost.id)
|
||||
if (i !== -1) {
|
||||
this.messages.splice(i, 1)
|
||||
this.messages.unshift(editedPost)
|
||||
}
|
||||
// onEditedPost = (e) => {
|
||||
// let editedPost = e.detail
|
||||
// const i = this.messages.findIndex(post => post.id === editedPost.id)
|
||||
// if (i !== -1) {
|
||||
// this.messages.splice(i, 1)
|
||||
// this.messages.unshift(editedPost)
|
||||
// }
|
||||
|
||||
this.rerender()
|
||||
}
|
||||
// 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;
|
||||
// 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()}`;
|
||||
// const [, mm, dd, yyyy, hh, min, ampm] = match;
|
||||
// const date = `${mm}/${dd}/${yyyy}`;
|
||||
// const time = `${hh}:${min}${ampm.toLowerCase()}`;
|
||||
|
||||
return { date, time };
|
||||
}
|
||||
// return { date, time };
|
||||
// }
|
||||
|
||||
formatTime(str) {
|
||||
const match = str.match(/-(\d+:\d+):\d+.*(am|pm)/i);
|
||||
if (!match) return null;
|
||||
// formatTime(str) {
|
||||
// const match = str.match(/-(\d+:\d+):\d+.*(am|pm)/i);
|
||||
// if (!match) return null;
|
||||
|
||||
const [_, hourMin, ampm] = match;
|
||||
return hourMin + ampm.toLowerCase();
|
||||
}
|
||||
}
|
||||
// const [_, hourMin, ampm] = match;
|
||||
// return hourMin + ampm.toLowerCase();
|
||||
// }
|
||||
// }
|
||||
|
||||
register(ForumPanel)
|
||||
// register(ForumPanel)
|
||||
@@ -1,75 +0,0 @@
|
||||
import util from "../../util.js"
|
||||
import server from "../../_/code/bridge/server.js"
|
||||
|
||||
css(`
|
||||
jobcard- p {
|
||||
font-size: 0.85em;
|
||||
color: var(--darktext);
|
||||
}
|
||||
`)
|
||||
|
||||
class JobCard extends Shadow {
|
||||
constructor(job) {
|
||||
super()
|
||||
this.job = job
|
||||
}
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
h3(this.job.title)
|
||||
.color("var(--text)")
|
||||
.fontSize(1.3, em)
|
||||
.fontWeight("normal")
|
||||
.margin(0, em)
|
||||
|
||||
// Delete button
|
||||
// if (this.job.creator_id === global.profile.id) {
|
||||
// img(util.cssVariable("trash-src"), "1.5em")
|
||||
// .marginRight(0.5, em)
|
||||
// .onTap(() => {
|
||||
// this.deleteJob(this.job)
|
||||
// })
|
||||
// }
|
||||
})
|
||||
.justifyContent("space-between")
|
||||
.verticalAlign("center")
|
||||
|
||||
p(this.job.company ?? "No company added")
|
||||
.marginTop(0.75, em)
|
||||
p(this.job.location ?? "No location added")
|
||||
.marginTop(0.25, em)
|
||||
p(this.salary_number ? this.salaryLabel(this.job.salary_number, this.job.salary_period) : "No salary added")
|
||||
.marginTop(0.75, em)
|
||||
})
|
||||
.paddingVertical(1.5, em)
|
||||
.paddingHorizontal(3.5, em)
|
||||
.marginHorizontal(1, em)
|
||||
.borderRadius(10, px)
|
||||
.background("var(--darkaccent)")
|
||||
.border("1px solid var(--accent)")
|
||||
.boxSizing("border-box")
|
||||
}
|
||||
|
||||
salaryLabel(number, period) {
|
||||
const formattedNumber = new Intl.NumberFormat('en-US', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
}).format(Number(number));
|
||||
|
||||
if (period === "one-time") {
|
||||
return `One-time payment of $${formattedNumber}`
|
||||
} else {
|
||||
return `$${formattedNumber}/${period}`
|
||||
}
|
||||
}
|
||||
|
||||
async deleteJob(job) {
|
||||
const result = await server.deleteJob(job.id, job.network_id, global.profile.id)
|
||||
if (result.data === null) {
|
||||
console.log("Failed to delete job")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register(JobCard)
|
||||
@@ -1,204 +0,0 @@
|
||||
import server from "../../_/code/bridge/server"
|
||||
|
||||
class JobForm extends Shadow {
|
||||
inputStyles(el) {
|
||||
return el
|
||||
.background("var(--main)")
|
||||
.color("var(--text)")
|
||||
.border("1px solid var(--accent)")
|
||||
.fontSize(0.9, rem)
|
||||
.backgroundColor("var(--darkaccent)")
|
||||
.borderRadius(12, px)
|
||||
.outline("none")
|
||||
.onTouch((start) => {
|
||||
if (start) {
|
||||
this.style.backgroundColor = "var(--accent)"
|
||||
} else {
|
||||
this.style.backgroundColor = "var(--darkaccent)"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
errorMessage = ""
|
||||
|
||||
render() {
|
||||
ZStack(() => {
|
||||
p("X")
|
||||
.color("var(--darkred)")
|
||||
.fontSize(2, em)
|
||||
.position("absolute")
|
||||
.fontFamily("Arial")
|
||||
.marginTop(1, rem)
|
||||
.marginLeft(1, rem)
|
||||
.onTap(() => {
|
||||
this.toggle()
|
||||
})
|
||||
|
||||
form(() => {
|
||||
VStack(() => {
|
||||
h1("Create a Job")
|
||||
.color("var(--text)")
|
||||
.textAlign("center")
|
||||
.fontFamily("Arial")
|
||||
.marginTop(1.5, em)
|
||||
|
||||
input("Title", "70%")
|
||||
.attr({ name: "title", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
input("Location", "70%")
|
||||
.attr({ name: "location", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
input("Company", "70%")
|
||||
.attr({ name: "company", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
HStack(() => {
|
||||
input("Salary", "30%")
|
||||
.attr({ name: "salary_number", type: "number", min: "0", step: "0.01" })
|
||||
.padding(1, em)
|
||||
.marginHorizontal(1, em)
|
||||
.styles(this.inputStyles)
|
||||
select(() => {
|
||||
option("One-time")
|
||||
.attr({ value: "one-time"})
|
||||
option("Hourly")
|
||||
.attr({ value: "hour"})
|
||||
option("Monthly")
|
||||
.attr({ value: "month"})
|
||||
option("Yearly")
|
||||
.attr({ value: "year"})
|
||||
})
|
||||
.attr({ name: "salary_period" })
|
||||
.width(40, pct)
|
||||
.padding(1, em)
|
||||
.marginHorizontal(1, em)
|
||||
.styles(this.inputStyles)
|
||||
})
|
||||
.margin(1, em)
|
||||
.boxSizing("border-box")
|
||||
.verticalAlign("center")
|
||||
.horizontalAlign("center")
|
||||
|
||||
input("Description", "70%")
|
||||
.attr({ name: "description", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
HStack(() => {
|
||||
button("==>")
|
||||
.padding(1, em)
|
||||
.fontSize(0.9, rem)
|
||||
.borderRadius(12, px)
|
||||
.background("var(--searchbackground)")
|
||||
.color("var(--text)")
|
||||
.border("1px solid var(--accent)")
|
||||
.boxSizing("border-box")
|
||||
.onTouch(function (start) {
|
||||
if (start) {
|
||||
this.style.backgroundColor = "var(--accent)"
|
||||
} else {
|
||||
this.style.backgroundColor = "var(--searchbackground)"
|
||||
}
|
||||
})
|
||||
})
|
||||
.width(70, vw)
|
||||
.margin("auto")
|
||||
.fontSize(0.9, rem)
|
||||
.paddingLeft(0, em)
|
||||
.paddingRight(2, em)
|
||||
.marginVertical(1, em)
|
||||
.border("1px solid transparent")
|
||||
|
||||
p("")
|
||||
.dynamicText("errormessage", "{{}}")
|
||||
.margin("auto")
|
||||
.marginTop(1, em)
|
||||
.color("var(--text)")
|
||||
.fontFamily("Arial")
|
||||
.opacity(.7)
|
||||
.padding(0.5, em)
|
||||
.backgroundColor("var(--darkred)")
|
||||
.width(100, pct)
|
||||
.textAlign("center")
|
||||
.boxSizing("border-box")
|
||||
})
|
||||
.horizontalAlign("center")
|
||||
})
|
||||
.onSubmit((e) => {
|
||||
e.preventDefault()
|
||||
const data = {
|
||||
title: e.target.$('[name="title"]').value,
|
||||
location: e.target.$('[name="location"]').value,
|
||||
company: e.target.$('[name="company"]').value,
|
||||
salary_number: e.target.$('[name="salary_number"]').value,
|
||||
salary_period: e.target.$('[name="salary_period"]').value,
|
||||
description: e.target.$('[name="description"]').value,
|
||||
};
|
||||
this.handleSend(data)
|
||||
})
|
||||
})
|
||||
.position("fixed")
|
||||
.height(window.visualViewport.height - 20, px)
|
||||
.width(100, pct)
|
||||
.top(100, vh)
|
||||
.background("var(--main)")
|
||||
.zIndex(4)
|
||||
.borderTopLeftRadius("10px")
|
||||
.borderTopRightRadius("10px")
|
||||
.boxSizing("border-box")
|
||||
.border("1px solid var(--accent)")
|
||||
.transition("top .3s")
|
||||
}
|
||||
|
||||
async handleSend(jobData) {
|
||||
if (!jobData.title) {
|
||||
this.$(".VStack > p")
|
||||
.attr({ errorMessage: 'Jobs must include a title.' })
|
||||
.display("")
|
||||
|
||||
return;
|
||||
} else {
|
||||
this.$(".VStack > p").style.display = "none"
|
||||
}
|
||||
|
||||
const newJob = {
|
||||
title: jobData.title,
|
||||
location: jobData.location.trim() === '' ? null : jobData.location.trim(),
|
||||
company: jobData.company.trim() === '' ? null : jobData.company.trim(),
|
||||
salary_number: jobData.salary_number.trim() === '' ? null : jobData.salary_number,
|
||||
salary_period: jobData.salary_number.trim() === '' ? null : jobData.salary_period,
|
||||
description: jobData.description.trim() === '' ? null : jobData.description.trim()
|
||||
}
|
||||
|
||||
const { data } = await server.addJob(newJob, global.currentNetwork.id, global.profile.id)
|
||||
if (data.status === 200) {
|
||||
console.log("Added new job: ", data)
|
||||
this.toggle()
|
||||
window.dispatchEvent(new CustomEvent('new-job', {
|
||||
detail: { job: data.job }
|
||||
}));
|
||||
} else {
|
||||
console.log("Failed to add new event: ", data)
|
||||
this.$(".VStack > p")
|
||||
.attr({ errorMessage: data.error })
|
||||
.display("")
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if(this.style.top === "15vh") {
|
||||
this.style.top = "100vh"
|
||||
this.pointerEvents = "none"
|
||||
} else {
|
||||
this.style.top = "15vh"
|
||||
this.pointerEvents = "auto"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register(JobForm)
|
||||
@@ -1,153 +0,0 @@
|
||||
import "./JobsSidebar.js"
|
||||
import "./JobsGrid.js"
|
||||
import "./JobCard.js"
|
||||
import "./JobForm.js"
|
||||
import "../../components/SearchBar.js"
|
||||
import "../../components/AddButton.js"
|
||||
import server from "../../_/code/bridge/server.js"
|
||||
|
||||
css(`
|
||||
jobs- {
|
||||
font-family: 'Arial';
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
jobs- h1 {
|
||||
font-family: 'Bona';
|
||||
}
|
||||
|
||||
jobs- .VStack::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
jobs- .VStack::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
jobs- .VStack::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
`)
|
||||
|
||||
class Jobs extends Shadow {
|
||||
static searchableKeys = ['title', 'description', 'location', 'company', 'salary_period', 'salary_number'];
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.jobs = global.currentNetwork.data.jobs;
|
||||
this.searchedJobs = [];
|
||||
this.searchText = ""; // for mainting searchText after re-render
|
||||
}
|
||||
|
||||
render() {
|
||||
ZStack(() => {
|
||||
|
||||
JobForm()
|
||||
|
||||
VStack(() => {
|
||||
HStack(() => {
|
||||
SearchBar(this.searchText, "75vw")
|
||||
AddButton()
|
||||
})
|
||||
|
||||
VStack(() => {
|
||||
if (!this.jobs || this.jobs == []) {
|
||||
LoadingCircle()
|
||||
} else if (this.searchText) {
|
||||
if (this.searchedJobs.length > 0) {
|
||||
for (let i = 0; i < this.searchedJobs.length; i++) {
|
||||
JobCard(this.searchedJobs[i])
|
||||
}
|
||||
} else {
|
||||
h2("Could not find any jobs with your search criteria.")
|
||||
.color("var(--divider)")
|
||||
.fontWeight("bold")
|
||||
.marginTop(7.5, em)
|
||||
.marginBottom(0.5, em)
|
||||
.textAlign("center")
|
||||
}
|
||||
} else if (this.jobs.length > 0) {
|
||||
for (let i = 0; i < this.jobs.length; i++) {
|
||||
JobCard(this.jobs[i])
|
||||
}
|
||||
} else {
|
||||
h2("No Jobs")
|
||||
.color("var(--divider)")
|
||||
.fontWeight("bold")
|
||||
.marginTop(7.5, em)
|
||||
.marginBottom(0.5, em)
|
||||
.textAlign("center")
|
||||
}
|
||||
})
|
||||
.overflowY("scroll")
|
||||
.gap(0.75, em)
|
||||
})
|
||||
.boxSizing("border-box")
|
||||
.height(100, pct)
|
||||
.width(100, pct)
|
||||
.onEvent("jobsearch", this.onJobSearch)
|
||||
.onEvent("new-job", this.onNewJob)
|
||||
})
|
||||
}
|
||||
|
||||
onNewJob = (e) => {
|
||||
let newJob = e.detail.job;
|
||||
this.jobs.push(newJob)
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
onJobSearch = (e) => {
|
||||
let searchText = e.detail.searchText.toLowerCase().trim();
|
||||
if (!searchText) {
|
||||
this.searchedJobs = [];
|
||||
} else {
|
||||
this.searchedJobs = this.jobs.filter(job =>
|
||||
Jobs.searchableKeys.some(key => {
|
||||
if (key === "salary_number" || key === "salary_period") {
|
||||
return String(job["salary_number"]).toLowerCase().includes(searchText.replace(/\/.*$/, '').replace(/[,$]/g, '')) && String(job["salary_period"]).toLowerCase().includes(searchText.replace(/^[^/]*\/?/, ''));
|
||||
} else {
|
||||
return String(job[key]).toLowerCase().includes(searchText);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
this.searchText = searchText
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
async getJobs(networkId) {
|
||||
const fetchedJobs = await server.getJobs(networkId)
|
||||
if (this.checkForUpdates(this.jobs, fetchedJobs.data)) {
|
||||
this.jobs = fetchedJobs
|
||||
this.rerender()
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.getJobs(global.currentNetwork.id)
|
||||
}
|
||||
|
||||
checkForUpdates(currentJobs, fetchedJobs) {
|
||||
if (currentJobs.length !== fetchedJobs.length) return true;
|
||||
|
||||
const currentMap = new Map(currentJobs.map(job => [job.id, job]));
|
||||
|
||||
for (const fetchedJob of fetchedJobs) {
|
||||
const currentJob = currentMap.get(fetchedJob.id);
|
||||
|
||||
// new job added
|
||||
if (!currentJob) return true;
|
||||
|
||||
// existing job changed
|
||||
if (currentJob.updated_at !== fetchedJob.updated_at) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
register(Jobs)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -1,81 +0,0 @@
|
||||
import "../../components/TopBar.js"
|
||||
import "./PeopleCard.js"
|
||||
|
||||
css(`
|
||||
people- {
|
||||
font-family: 'Arial';
|
||||
}
|
||||
|
||||
people- h1 {
|
||||
font-family: 'Bona';
|
||||
}
|
||||
|
||||
people- p {
|
||||
color: var(--darktext);
|
||||
}
|
||||
`)
|
||||
|
||||
class People extends Shadow {
|
||||
people = "";
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.people = global.currentNetwork.data.members;
|
||||
}
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
|
||||
VStack(() => {
|
||||
if (this.people == "") {
|
||||
LoadingCircle()
|
||||
} else if (this.people.length > 0) {
|
||||
for (let i = 0; i < this.people.length; i++) {
|
||||
PeopleCard(this.people[i])
|
||||
}
|
||||
} else {
|
||||
h2("No Members")
|
||||
.color("var(--brown)")
|
||||
.fontWeight("bold")
|
||||
.marginTop(7.5, em)
|
||||
.marginBottom(0.5, em)
|
||||
.textAlign("center")
|
||||
p("Invite people to this network!")
|
||||
.textAlign("center")
|
||||
.color("var(--darkbrown)")
|
||||
}
|
||||
})
|
||||
.overflow("scroll")
|
||||
.gap(0.8, em)
|
||||
})
|
||||
.position("relative")
|
||||
.boxSizing("border-box")
|
||||
.height(100, pct)
|
||||
.width(100, pct)
|
||||
}
|
||||
|
||||
convertDate(rawDate) {
|
||||
const parsed = new Date(rawDate);
|
||||
|
||||
if (isNaN(parsed.getTime())) return rawDate;
|
||||
|
||||
const month = parsed.toLocaleString("en-US", { month: "long", timeZone: "UTC" });
|
||||
const day = parsed.getUTCDate();
|
||||
const year = parsed.getUTCFullYear();
|
||||
|
||||
const ordinal = (n) => {
|
||||
const mod100 = n % 100;
|
||||
if (mod100 >= 11 && mod100 <= 13) return `${n}th`;
|
||||
switch (n % 10) {
|
||||
case 1: return `${n}st`;
|
||||
case 2: return `${n}nd`;
|
||||
case 3: return `${n}rd`;
|
||||
default: return `${n}th`;
|
||||
}
|
||||
};
|
||||
|
||||
return `${month} ${ordinal(day)}, ${year}`;
|
||||
}
|
||||
}
|
||||
|
||||
register(People)
|
||||
@@ -1,69 +0,0 @@
|
||||
import util from "../../util"
|
||||
|
||||
class PeopleCard extends Shadow {
|
||||
constructor(person) {
|
||||
super()
|
||||
this.person = person
|
||||
this.imgSrc = null
|
||||
}
|
||||
|
||||
render() {
|
||||
HStack(() => {
|
||||
HStack(() => {
|
||||
if (this.imgSrc && !this.imgSrc.includes("null")) {
|
||||
img(this.imgSrc, "3em", "3em")
|
||||
.borderRadius(100, pct)
|
||||
}
|
||||
})
|
||||
.boxSizing("border-box")
|
||||
.height(3, em)
|
||||
.width(3, em)
|
||||
.border("1px solid var(--accent)")
|
||||
.borderRadius(100, pct)
|
||||
.background("var(--darkaccent)")
|
||||
|
||||
VStack(() => {
|
||||
p(this.person.first_name + " " + this.person.last_name)
|
||||
.color("var(--headertext)")
|
||||
.marginVertical(0, em)
|
||||
})
|
||||
.verticalAlign("center")
|
||||
.horizontalAlign("left")
|
||||
})
|
||||
.width(100, pct)
|
||||
.boxSizing("border-box")
|
||||
.paddingHorizontal(1.75, em)
|
||||
.gap(1, em)
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if(this.person.image_path) {
|
||||
this.imgSrc = `${util.HOST}${this.person.image_path}`
|
||||
}
|
||||
}
|
||||
|
||||
convertDate(rawDate) {
|
||||
const parsed = new Date(rawDate);
|
||||
|
||||
if (isNaN(parsed.getTime())) return rawDate;
|
||||
|
||||
const month = parsed.toLocaleString("en-US", { month: "long", timeZone: "UTC" });
|
||||
const day = parsed.getUTCDate();
|
||||
const year = parsed.getUTCFullYear();
|
||||
|
||||
const ordinal = (n) => {
|
||||
const mod100 = n % 100;
|
||||
if (mod100 >= 11 && mod100 <= 13) return `${n}th`;
|
||||
switch (n % 10) {
|
||||
case 1: return `${n}st`;
|
||||
case 2: return `${n}nd`;
|
||||
case 3: return `${n}rd`;
|
||||
default: return `${n}th`;
|
||||
}
|
||||
};
|
||||
|
||||
return `${month} ${ordinal(day)}, ${year}`;
|
||||
}
|
||||
}
|
||||
|
||||
register(PeopleCard)
|
||||
@@ -1,33 +0,0 @@
|
||||
class AddButton extends Shadow {
|
||||
render() {
|
||||
p("+")
|
||||
.fontWeight("bolder")
|
||||
.paddingVertical(0.75, em)
|
||||
.boxSizing("border-box")
|
||||
.paddingHorizontal(1, em)
|
||||
.background("var(--searchbackground)")
|
||||
.color("var(--accent)")
|
||||
.marginBottom(1, em)
|
||||
.border("1px solid var(--accent)")
|
||||
.borderRadius(15, px)
|
||||
.onTap(() => {
|
||||
this.handleAdd()
|
||||
})
|
||||
}
|
||||
|
||||
handleAdd() {
|
||||
const app = global.currentApp()
|
||||
switch (app) {
|
||||
case "Jobs":
|
||||
$("jobform-").toggle()
|
||||
break;
|
||||
case "Events":
|
||||
$("eventform-").toggle()
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register(AddButton)
|
||||
@@ -1,40 +1,14 @@
|
||||
import "../apps/Forum/Forum.js"
|
||||
import "../apps/Messages/Messages.js"
|
||||
import "../apps/Jobs/Jobs.js"
|
||||
import "../apps/People/People.js"
|
||||
import "../apps/Events/Events.js"
|
||||
import "../apps/Announcements/Announcements.js"
|
||||
import util from "../util.js"
|
||||
|
||||
class AppWindow extends Shadow {
|
||||
render() {
|
||||
ZStack(() => {
|
||||
let app = global.currentApp()
|
||||
switch(app) {
|
||||
case "Dashboard":
|
||||
Announcements()
|
||||
break;
|
||||
|
||||
case "Jobs":
|
||||
Jobs()
|
||||
break;
|
||||
|
||||
case "Events":
|
||||
Events()
|
||||
break;
|
||||
|
||||
case "People":
|
||||
People()
|
||||
break;
|
||||
|
||||
default:
|
||||
if(window[app]) {
|
||||
window[app]()
|
||||
} else {
|
||||
this.getCustomApp(app)
|
||||
}
|
||||
break;
|
||||
}
|
||||
if(window[app]) {
|
||||
window[app]()
|
||||
} else {
|
||||
this.getCustomApp(app)
|
||||
}
|
||||
})
|
||||
.height(100, pct)
|
||||
.overflowY("scroll")
|
||||
@@ -44,6 +18,7 @@ class AppWindow extends Shadow {
|
||||
}
|
||||
|
||||
async getCustomApp(app) {
|
||||
if (app == "Dashboard") { app = "Announcements" }
|
||||
await import(`${util.HOST}/apps/${app.toLowerCase()}/${app.toLowerCase()}.js`);
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "./AppWindow.js"
|
||||
import "../Profile/Profile.js"
|
||||
import "./TopBar.js"
|
||||
|
||||
class AppWindowContainer extends Shadow {
|
||||
render() {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
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)
|
||||
@@ -1,72 +0,0 @@
|
||||
css(`
|
||||
searchbar- input::placeholder {
|
||||
color: #5C504D;
|
||||
}
|
||||
`)
|
||||
|
||||
class SearchBar extends Shadow {
|
||||
searchText
|
||||
width
|
||||
|
||||
constructor(searchText, width) {
|
||||
super()
|
||||
this.searchText = searchText
|
||||
this.width = width
|
||||
}
|
||||
|
||||
render() {
|
||||
form(() => {
|
||||
input("Search", this.width)
|
||||
.attr({ name: "searchText", type: "text" })
|
||||
.attr({ value: this.searchText ? this.searchText : "" })
|
||||
.paddingVertical(0.75, em)
|
||||
.boxSizing("border-box")
|
||||
.paddingHorizontal(1, em)
|
||||
.background("var(--searchbackground)")
|
||||
.color("gray")
|
||||
.marginBottom(1, em)
|
||||
.marginLeft(1, em)
|
||||
.marginRight(0.5, em)
|
||||
.border("1px solid color-mix(in srgb, var(--accent) 60%, transparent)")
|
||||
.borderRadius(100, px)
|
||||
.fontFamily("Arial")
|
||||
.fontSize(1, em)
|
||||
.cursor("not-allowed")
|
||||
.onTouch(function (start) {
|
||||
if (start) {
|
||||
this.style.backgroundColor = "var(--accent)"
|
||||
} else {
|
||||
this.style.backgroundColor = "var(--searchbackground)"
|
||||
}
|
||||
})
|
||||
})
|
||||
.onSubmit(async (e) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target);
|
||||
this.dispatchSearchEvent(data.get("searchText"))
|
||||
})
|
||||
}
|
||||
|
||||
dispatchSearchEvent(searchText) {
|
||||
const app = global.currentApp();
|
||||
switch (app) {
|
||||
case "Jobs":
|
||||
window.dispatchEvent(new CustomEvent('jobsearch', {
|
||||
detail: { searchText: searchText }
|
||||
}));
|
||||
break;
|
||||
case "Events":
|
||||
window.dispatchEvent(new CustomEvent('eventsearch', {
|
||||
detail: { searchText: searchText }
|
||||
}));
|
||||
break;
|
||||
case "Announcements":
|
||||
window.dispatchEvent(new CustomEvent('announcementsearch', {
|
||||
detail: { searchText: searchText }
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register(SearchBar)
|
||||
Reference in New Issue
Block a user