diff --git a/capacitor.config.json b/capacitor.config.json index 14befbe..532b34f 100644 --- a/capacitor.config.json +++ b/capacitor.config.json @@ -5,10 +5,6 @@ "ios": { "allowsBackForwardNavigationGestures": true }, - "server": { - "url": "http://sam.local:5173", - "cleartext": true - }, "plugins": { "SplashScreen": { "launchAutoHide": true diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist index 65f8357..8e8d560 100644 --- a/ios/App/App/Info.plist +++ b/ios/App/App/Info.plist @@ -29,6 +29,12 @@ NSAllowsLocalNetworking + NSLocationAlwaysAndWhenInUseUsageDescription + Used to find keep local information relevant. + NSLocationWhenInUseUsageDescription + Used to find forums and communities near you + NSPhotoLibraryUsageDescription + Access your photos to set a profile picture and share with others UIBackgroundModes remote-notification @@ -41,6 +47,8 @@ armv7 + UIRequiresFullScreen + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -49,15 +57,7 @@ UIInterfaceOrientationPortrait - UIRequiresFullScreen - UIViewControllerBasedStatusBarAppearance - NSPhotoLibraryUsageDescription - Access your photos to set a profile picture and share with others - NSLocationAlwaysAndWhenInUseUsageDescription - Used to find keep local information relevant. - NSLocationWhenInUseUsageDescription - Used to find forums and communities near you diff --git a/ios/App/Podfile b/ios/App/Podfile index 015c5f4..c4695eb 100644 --- a/ios/App/Podfile +++ b/ios/App/Podfile @@ -12,6 +12,7 @@ def capacitor_pods pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorCamera', :path => '../../node_modules/@capacitor/camera' + pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem' pod 'CapacitorGeolocation', :path => '../../node_modules/@capacitor/geolocation' pod 'CapacitorGoogleMaps', :path => '../../node_modules/@capacitor/google-maps' pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics' diff --git a/ios/App/Podfile.lock b/ios/App/Podfile.lock index d13a4a2..13b1afa 100644 --- a/ios/App/Podfile.lock +++ b/ios/App/Podfile.lock @@ -4,6 +4,9 @@ PODS: - CapacitorCamera (7.0.2): - Capacitor - CapacitorCordova (7.4.4) + - CapacitorFilesystem (7.1.8): + - Capacitor + - IONFilesystemLib (~> 1.1.1) - CapacitorGeolocation (7.1.5): - Capacitor - IONGeolocationLib (= 1.0.1) @@ -26,12 +29,14 @@ PODS: - GoogleMaps/Base (8.4.0) - GoogleMaps/Maps (8.4.0): - GoogleMaps/Base + - IONFilesystemLib (1.1.2) - IONGeolocationLib (1.0.1) DEPENDENCIES: - "Capacitor (from `../../node_modules/@capacitor/ios`)" - "CapacitorCamera (from `../../node_modules/@capacitor/camera`)" - "CapacitorCordova (from `../../node_modules/@capacitor/ios`)" + - "CapacitorFilesystem (from `../../node_modules/@capacitor/filesystem`)" - "CapacitorGeolocation (from `../../node_modules/@capacitor/geolocation`)" - "CapacitorGoogleMaps (from `../../node_modules/@capacitor/google-maps`)" - "CapacitorHaptics (from `../../node_modules/@capacitor/haptics`)" @@ -43,6 +48,7 @@ SPEC REPOS: trunk: - Google-Maps-iOS-Utils - GoogleMaps + - IONFilesystemLib - IONGeolocationLib EXTERNAL SOURCES: @@ -52,6 +58,8 @@ EXTERNAL SOURCES: :path: "../../node_modules/@capacitor/camera" CapacitorCordova: :path: "../../node_modules/@capacitor/ios" + CapacitorFilesystem: + :path: "../../node_modules/@capacitor/filesystem" CapacitorGeolocation: :path: "../../node_modules/@capacitor/geolocation" CapacitorGoogleMaps: @@ -69,6 +77,7 @@ SPEC CHECKSUMS: Capacitor: 09d9ff8e9618e8c4b3cab2bbee34a17215dd2fef CapacitorCamera: 6e18d54c8ab30d7dc7b8cd93d96f9b4f57e9202a CapacitorCordova: bf648a636f3c153f652d312ae145fb508b6ffced + CapacitorFilesystem: f54cd6b76be06fa7ceb219cf313d32e0d626ea87 CapacitorGeolocation: b96474c3259dd4a294227ea8ec19140b1837cceb CapacitorGoogleMaps: 20b5445a532f80dbb120fa99941fd094bcc88af6 CapacitorHaptics: d17da7dd984cae34111b3f097ccd3e21f9feec62 @@ -77,8 +86,9 @@ SPEC CHECKSUMS: CapacitorSplashScreen: d06ae8804808e9f649a08e7bb7f283c77b688084 Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321 GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d + IONFilesystemLib: 21a63377696b2d8fab5632ecfb7d2ac67bddb68a IONGeolocationLib: 20f9d0248a0b5264511fb57a37e25dd2badf797a -PODFILE CHECKSUM: 7fad0e16088b635c7bc1980fea245d4a60cc4bbe +PODFILE CHECKSUM: 32ad5bbf56056f7ee8d159f9eae42fe03e911a61 COCOAPODS: 1.15.2 diff --git a/package.json b/package.json index 0a52829..8df5c73 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dependencies": { "@capacitor/camera": "^7.0.2", "@capacitor/core": "^7.4.4", + "@capacitor/filesystem": "^7.1.8", "@capacitor/geolocation": "^7.1.5", "@capacitor/google-maps": "^7.2.0", "@capacitor/haptics": "^7.0.3", diff --git a/src/.env.development b/src/.env.development index 9206f33..6cf4f39 100644 --- a/src/.env.development +++ b/src/.env.development @@ -1 +1 @@ -VITE_API_URL=https://frm.so \ No newline at end of file +VITE_API_URL=http://localhost:10002 \ No newline at end of file diff --git a/src/Home/AuthPage/AuthPage.js b/src/Home/AuthPage/AuthPage.js deleted file mode 100644 index 5f58f19..0000000 --- a/src/Home/AuthPage/AuthPage.js +++ /dev/null @@ -1,106 +0,0 @@ -import { Preferences } from '@capacitor/preferences'; -import "./Login.js"; -import "./Signup.js" -import "./EnterCode.js" - -class AuthPage extends Shadow { - inputStyles(el) { - return el - .background("var(--main)") - .color("var(--text)") - .border("1px solid var(--accent)") - .fontSize(0.9, rem) - .backgroundColor("var(--searchbackground)") - .borderRadius(12, px) - .outline("none") - .onTouch((start) => { - if(start) { - this.style.backgroundColor = "var(--accent)" - } else { - this.style.backgroundColor = "var(--searchbackground)" - } - }) - } - - render() { - VStack(() => { - img(window.matchMedia('(prefers-color-scheme: dark)').matches ? "/_/icons/columnwhite.svg" : "/_/icons/logo.svg", window.isMobile() ? "5vmax" : "3vmax") - .marginTop(5, em) - .marginLeft(2, em) - .onClick((done) => { - window.navigateTo("/") - }) - - HStack(() => { - p("Login") - .state(this, "selected", function (selected) { - if(selected === "1") { - this.fontWeight("bold") - this.background("var(--loginButton)") - } else { - this.fontWeight("normal") - this.background("transparent") - } - }) - .padding(0.75, em) - .borderRadius(12, px) - .onTap(() => { - this.attr("selected", "1") - }) - - p("Enter Code") - .state(this, "selected", function (selected) { - if(selected === "2") { - this.fontWeight("bold") - this.background("var(--loginButton)") - } else { - this.fontWeight("normal") - this.background("transparent") - } - }) - .padding(0.75, em) - .borderRadius(12, px) - .onTap(() => { - this.attr("selected", "2") - }) - }) - .fontFamily("Arial") - .padding(0.25, em) - .borderRadius(12, px) - .background("var(--loginBackground)") - .color("var(--text)") - .horizontalAlign("center") - .margin("auto") - .marginTop(7.5, em) - .marginBottom(2, em) - .gap(0.5, em) - - ZStack(() => { - - Login() - .state(this, "selected", function (selected) { - if(selected === "1") { - this.display("") - } else { - this.display("none") - } - }) - - EnterCode() - .state(this, "selected", function (selected) { - if(selected === "2") { - this.display("flex") - } else { - this.display("none") - } - }) - }) - }) - .attr("selected", "1") - .width(100, vw) - .height(100, vh) - .margin(0) - } -} - -register(AuthPage) \ No newline at end of file diff --git a/src/Home/AuthPage/EnterCode.js b/src/Home/AuthPage/EnterCode.js deleted file mode 100644 index 7179e3c..0000000 --- a/src/Home/AuthPage/EnterCode.js +++ /dev/null @@ -1,101 +0,0 @@ -import util from "../../util.js" -import "./Signup.js" - -class EnterCode extends Shadow { - inputStyles(el) { - return el - .background("var(--main)") - .color("var(--text)") - .border("1px solid var(--accent)") - .fontSize(0.9, rem) - .backgroundColor("var(--searchbackground)") - .borderRadius(12, px) - .outline("none") - .onTouch((start) => { - if (start) { - this.style.backgroundColor = "var(--accent)" - } else { - this.style.backgroundColor = "var(--searchbackground)" - } - }) - } - - render() { - VStack(() => { - - VStack(() => { - p("Enter the code given to you by your organization.") - .color("#614945") - .maxWidth(70, vw) - .marginTop(3, em) - - input("Code", "70vw") - .attr({ name: "firstName", type: "text" }) - .margin("auto") - .marginVertical(1, em) - .padding(1, em) - .styles(this.inputStyles) - - 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)" - } - }) - .onClick((done) => { - if (done) this.submit() - }) - }) - .state(this, "codeaccepted", function (accepted) { - if(!accepted) { - this.display("flex") - } else { - this.display("none") - } - }) - - Signup() - .state(this, "codeaccepted", function (accepted) { - if(accepted) { - this.display("") - } else { - this.display("none") - } - }) - }) - .horizontalAlign("center") - .display("flex") - - this.style.display = "flex" - } - - async submit() { - console.log("submit") - const res = await fetch(`${util.HOST}/auth/joincode`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', "Accept": "application/json", "X-Client": "mobile" }, - body: JSON.stringify({ code: this.$("input").value }) - }); - - if (res.ok) { - console.log("got join code succ") - this.attr("codeaccepted", "true") - let { networkId } = await res.json() - $("signup-").attr("networkid", networkId) - } else { - const { error } = await res.json(); - console.error(error) - } - } -} - -register(EnterCode) \ No newline at end of file diff --git a/src/Home/AuthPage/Login.js b/src/Home/AuthPage/Login.js deleted file mode 100644 index b17e082..0000000 --- a/src/Home/AuthPage/Login.js +++ /dev/null @@ -1,147 +0,0 @@ -import { Preferences } from '@capacitor/preferences'; -import util from "../../util.js" - -class Login extends Shadow { - inputStyles(el) { - return el - .background("var(--main)") - .color("var(--text)") - .border("1px solid var(--accent)") - .fontSize(0.9, rem) - .backgroundColor("var(--searchbackground)") - .borderRadius(12, px) - .outline("none") - .onTouch((start) => { - if (start) { - this.style.backgroundColor = "var(--accent)" - } else { - this.style.backgroundColor = "var(--searchbackground)" - } - }) - } - - render() { - form(() => { - VStack(() => { - input("Email", "70vw") - .attr({ name: "email", type: "email" }) - .margin("auto") - .marginVertical(1, em) - .padding(1, em) - .styles(this.inputStyles) - - input("Password", "70vw") - .attr({ name: "password", type: "password" }) - .margin("auto") - .marginVertical(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("") - .state("errortype", function (type) { - const messages = { - email: "Please enter a valid email.", - password: "Please enter a valid password.", - emailwrong: "Could not find an account with this email.", - passwordwrong: "Incorrect password.", - }; - - if(messages[type]) { - this.display("") - this.innerText = messages[type] - } else { - this.display("none") - this.innerText = "" - } - }) - .margin("auto") - .marginTop(1, em) - .color("var(--text)") - .fontFamily("Arial") - .opacity(.7) - .padding(0.5, em) - .backgroundColor("var(--darkred)") - .display("none") - - }) - }) - .height(100, pct) - .onSubmit(async (e) => { - e.preventDefault(); - const data = { - email: e.target.$('[name="email"]').value, - password: e.target.$('[name="password"]').value, - }; - await this.requestLogin(data); - }) - } - - isValidEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); - isValidPassword = (password) => password.length >= 7; - - async requestLogin(data) { - const emailValid = this.isValidEmail(data.email.trim() || ""); - const passValid = this.isValidPassword(data.password.trim() || ""); - - if (!emailValid || !passValid) { - console.log("invalid", emailValid) - const errorType = !emailValid ? 'email' : 'password'; - - this.$("p").attr({ errorType }); - return; - } else { - this.$("p").attr({ errorType: "" }); - } - - const res = await fetch(`${util.HOST}/auth/login`, { - method: "POST", - headers: { "Content-Type": "application/json", "X-Client": "mobile" }, - body: JSON.stringify({ - email: data["email"], - password: data["password"] - }) - }); - - if (res.ok) { - const { token } = await res.json(); - await Preferences.set({ key: 'auth_token', value: token }); - global.renderHome(); - } else { - const { error } = await res.json(); - this.errorMessage = error; - console.error(error) - if(error.includes("email")) { - this.$("p").attr({ errorType: "emailwrong" }); - } else { - this.$("p").attr({ errorType: "passwordwrong" }); - } - } - } -} - -register(Login) \ No newline at end of file diff --git a/src/Home/AuthPage/Signup.js b/src/Home/AuthPage/Signup.js deleted file mode 100644 index 298a28b..0000000 --- a/src/Home/AuthPage/Signup.js +++ /dev/null @@ -1,214 +0,0 @@ -import { Preferences } from '@capacitor/preferences'; -import util from "../../util.js" - -class Signup extends Shadow { - inputStyles(el) { - return el - .background("var(--main)") - .color("var(--text)") - .border("1px solid var(--accent)") - .fontSize(0.9, rem) - .backgroundColor("var(--searchbackground)") - .borderRadius(12, px) - .outline("none") - .onTouch((start) => { - if (start) { - this.style.backgroundColor = "var(--accent)" - } else { - this.style.backgroundColor = "var(--searchbackground)" - } - }) - } - - render() { - form(() => { - VStack(() => { - input("First Name", "70vw") - .attr({ name: "firstName", type: "text" }) - .margin("auto") - .marginVertical(1, em) - .padding(1, em) - .styles(this.inputStyles) - input("Last Name", "70vw") - .attr({ name: "lastName", type: "text" }) - .margin("auto") - .marginVertical(1, em) - .padding(1, em) - .styles(this.inputStyles) - input("Email", "70vw") - .attr({ name: "email", type: "email" }) - .margin("auto") - .marginVertical(1, em) - .padding(1, em) - .styles(this.inputStyles) - input("Password", "70vw") - .attr({ name: "password", type: "password" }) - .margin("auto") - .marginVertical(1, em) - .padding(1, em) - .styles(this.inputStyles) - input("Confirm Password", "70vw") - .attr({ name: "confirmPassword", type: "password" }) - .margin("auto") - .marginVertical(1, em) - .padding(1, em) - .styles(this.inputStyles) - p("") - .state("errormessage", function (msg) { - if(msg) { - this.display("") - this.innerText = msg - } else { - this.display("none") - this.innerText = "" - } - }) - .margin("auto") - .marginTop(1, em) - .color("var(--text)") - .fontFamily("Arial") - .opacity(.7) - .padding(0.5, em) - .backgroundColor("var(--darkred)") - .display("none") - 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) - .marginBottom(10, em) - .border("1px solid transparent") - }) - }) - .height(100, pct) - .onSubmit(async (e) => { - e.preventDefault(); - const data = new FormData(e.target); - if (this.verifyInput(data)) { - await this.requestSignup(data); - } else { - console.log(this.errorMessage) - } - }) - } - - /* - ******************************************************************************* - NO LONGER WORKS - SEE AUTH ENDPOINT IN SERVER - ******************************************************************************* - */ - async requestSignup(data) { - const networkId = this.attr("networkid"); - const res = await fetch(`${util.HOST}/auth/signup`, { - method: "POST", - headers: { "Content-Type": "application/json", "X-Client": "mobile" }, - body: JSON.stringify({ - networkId: networkId, - firstName: data.get("firstName"), - lastName: data.get("lastName"), - email: data.get("email"), - password: data.get("password") - }) - }); - - if (res.ok) { - const { token } = await res.json(); - await Preferences.set({ key: 'auth_token', value: token }); - global.renderHome(); - } else { - const { error } = await res.json(); - console.error(error) - this.$("p").attr("errormessage", error) - } - } - - verifyInput(data) { - const firstName = data.get("firstName"); - const lastName = data.get("lastName"); - const email = data.get("email"); - const password = data.get("password"); - const confirmPassword = data.get("confirmPassword"); - - if (!firstName || firstName.trim() === "") { - this.$("p").attr("errormessage", "First name is required.") - return false - } else if (firstName.trim().length < 2) { - this.$("p").attr("errormessage", "First name must be at least 2 characters.") - return false - } else if (!/^[a-zA-Z\s'-]+$/.test(firstName.trim())) { - this.$("p").attr("errormessage", "First name contains invalid characters.") - return false - } - - if (!lastName || lastName.trim() === "") { - this.$("p").attr("errormessage", "Last name is required.") - return false - } else if (lastName.trim().length < 2) { - this.$("p").attr("errormessage", "Last name must be at least 2 characters.") - return false - } else if (!/^[a-zA-Z\s'-]+$/.test(lastName.trim())) { - this.$("p").attr("errormessage", "Last name contains invalid characters.") - return false - } - - if (!email || email.trim() === "") { - this.$("p").attr("errormessage", "Email is required.") - return false - } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) { - this.$("p").attr("errormessage", "Please enter a valid email address.") - return false - } - - let passwordError = `Password must be at least 7 characters and include an uppercase letter, a lowercase letter, a number, and a special character (!@#$%^&*(),.?":{}|<>)` - - if (!password) { - this.$("p").attr("errormessage", "Password is required.") - return false - } else if (password.length < 7) { - this.$("p").attr("errormessage", passwordError) - return false - } else if (!/[A-Z]/.test(password)) { - this.$("p").attr("errormessage", passwordError) - return false - } else if (!/[a-z]/.test(password)) { - this.$("p").attr("errormessage", passwordError) - return false - } else if (!/[0-9]/.test(password)) { - this.$("p").attr("errormessage", passwordError) - return false - } else if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) { - this.$("p").attr("errormessage", passwordError) - return false - } - - if (!confirmPassword) { - this.$("p").attr("errormessage", "Please confirm your password.") - return false - } else if (confirmPassword !== password) { - this.$("p").attr("errormessage", "Passwords do not match.") - return false - } - - return true; - } -} - -register(Signup) \ No newline at end of file diff --git a/src/Home/ConnectionError.js b/src/Home/ConnectionError.js deleted file mode 100644 index dea7068..0000000 --- a/src/Home/ConnectionError.js +++ /dev/null @@ -1,23 +0,0 @@ -class ConnectionError extends Shadow { - - render() { - VStack(() => { - img("/_/icons/column.svg", window.isMobile() ? "5vmax" : "3vmax") - .position("absolute") - .top(2, em) - .left(2, em) - .onClick((done) => { - window.navigateTo("/") - }) - - p("Error connecting to server. Please try again later!") - }) - .verticalAlign("center") - .paddingHorizontal(20, vw) - .backgroundColor("var(--main)") - .overflowX("hidden") - .height(100, vh) - } -} - -register(ConnectionError) \ No newline at end of file diff --git a/src/Home/Home.js b/src/Home/Home.js deleted file mode 100644 index 6d15977..0000000 --- a/src/Home/Home.js +++ /dev/null @@ -1,155 +0,0 @@ -import "../components/Sidebar.js" -import "../components/AppMenu.js" -import "../components/AppWindowContainer.js" - -css(` - #homeContainer { - -webkit-user-select: none; - } -`) - -/* -Sidebar Functionality Checklist: -- Open on Top left network logo touch (WITH transition) -- Follow finger on swipe from left side of the screen -- Open if finger swipw travels far enough to the right (WITH velocity-based transition) -- Re-close if not opened enough of the way (WITH transition) - -- Close on touch of home contents (WITH transition) -- Follow finger on swipe beginning near or anywhere on right of divider between sidebar and home contents -- Close if finger swipe travels far enough to the left (WITH velocity-based transition) -- Re-open if not closed enough of the way (WITH transition) -*/ - -class Home extends Shadow { - - dragStartX = null - sidebarOpen = false - - SIDEBAR_FULL_OPEN = (window.outerWidth * 5) / 6 - SIDEBAR_START_THRESHOLD = window.outerWidth / 10 - SIDEBAR_CLOSE_DECISION = (window.outerWidth * 2) / 3 - SIDEBAR_OPEN_DECISION = (window.outerWidth / 3) - - constructor() { - super() - } - - render() { - ZStack(() => { - Sidebar(this.SIDEBAR_FULL_OPEN) - - ZStack(() => { - - VStack(() => { - AppWindowContainer() - - AppMenu() - }) - .height(100, pct) - .minHeight(0) - }) - .left(0, px) - .backgroundColor("var(--main)") - .overflowX("hidden") - .height(window.visualViewport.height, px) - .position("fixed") - .borderLeft("1px solid var(--accent)") - .onTouch((start, e) => { - if(this.sidebarOpen) { - this.sidebarOpenTouch(start, e) - } else { - this.sidebarClosedTouch(start, e) - } - }) - .attr({ id: "homeContainer" }) - }) - .userSelect("none") - } - - openSidebar(duration = 200) { - const home = this.$("#homeContainer"); - home.style.transition = `left ${duration}ms`; - home.style.left = `${this.SIDEBAR_FULL_OPEN}px`; - this.sidebarOpen = true; - this.dragStartX = null - setTimeout(() => home.style.transition = "", duration); - } - - closeSidebar(duration = 200) { - const home = this.$("#homeContainer"); - home.style.transition = `left ${duration}ms`; - home.style.left = "0px"; - this.sidebarOpen = false; - this.dragStartX = null - setTimeout(() => home.style.transition = "", duration); - } - - sidebarOpenTouch(start, e) { - if(start) { - let amount = e.targetTouches[0].clientX - if(amount > (this.SIDEBAR_FULL_OPEN - this.SIDEBAR_START_THRESHOLD)) { - this.dragStartX = e.touches[0].clientX - this.dragStartTime = Date.now(); // ⬅ track start time - e.stopPropagation(); - document.addEventListener("touchmove", this.moveSidebar) - } - } else { - if(!this.dragStartX) return; - - let endX = e.changedTouches[0].clientX - let duration = this.getDuration(this.dragStartX, endX); - if(Math.abs(this.dragStartX - endX) < 5) { // 2 conditions are separated so this one doesn't close instantly - this.closeSidebar() - } else if(endX < this.SIDEBAR_CLOSE_DECISION) { - this.closeSidebar(duration) - } else { - this.openSidebar(duration) - } - - document.removeEventListener("touchmove", this.moveSidebar) - } - } - - sidebarClosedTouch(start, e) { - if(start) { - let amount = e.targetTouches[0].clientX - if(amount < this.SIDEBAR_START_THRESHOLD) { - this.dragStartX = e.touches[0].clientX - this.dragStartTime = Date.now(); - e.stopPropagation(); - document.addEventListener("touchmove", this.moveSidebar) - } - } else { - if(!this.dragStartX) return; - - let endX = e.changedTouches[0].clientX - let duration = this.getDuration(this.dragStartX, endX); - if(endX > this.SIDEBAR_OPEN_DECISION) { - this.openSidebar(duration) - } else { - this.closeSidebar(duration) - } - - document.removeEventListener("touchmove", this.moveSidebar) - } - } - - moveSidebar = (e) => { - let amount = e.targetTouches[0].clientX - if(e.targetTouches[0] && amount < this.SIDEBAR_FULL_OPEN) { - this.$("#homeContainer").style.left = `${amount}px` - } - } - - getDuration(startX, endX) { - const distance = Math.abs(endX - startX); - const elapsed = Date.now() - this.dragStartTime; - const velocity = distance / elapsed; // px per ms - - const duration = Math.round(distance / velocity); // time to cover remaining distance - return Math.min(Math.max(duration, 50), 200); // clamp between 50ms and 200ms - } -} - -register(Home) \ No newline at end of file diff --git a/src/Home/Login.js b/src/Home/Login.js deleted file mode 100644 index 5c65b80..0000000 --- a/src/Home/Login.js +++ /dev/null @@ -1,61 +0,0 @@ -class Login extends Shadow { - inputStyles(el) { - return el - .background("var(--main)") - .color("var(--text)") - .border("1px solid var(--accent)") - .fontSize(0.9, rem) - .backgroundColor("var(--accentdark)") - .borderRadius(12, px) - .outline("none") - .onTouch((start) => { - if(start) { - this.style.backgroundColor = "var(--accent)" - } else { - this.style.backgroundColor = "var(--accentdark)" - } - }) - } - - render() { - ZStack(() => { - img(window.matchMedia('(prefers-color-scheme: dark)') ? "/_/icons/columnwhite.svg" : "/_/icons/logo.svg", window.isMobile() ? "5vmax" : "3vmax") - .position("absolute") - .top(5, em) - .left(2, em) - .onClick((done) => { - window.navigateTo("/") - }) - - form(() => { - input("Email", "70vw") - .attr({name: "email", type: "email"}) - .margin(1, em) - .padding(1, em) - .styles(this.inputStyles) - input("Password", "70vw") - .attr({name: "password", type: "password"}) - .margin(1, em) - .padding(1, em) - .styles(this.inputStyles) - button("==>") - .margin(1, em) - .padding(1, em) - .fontSize(0.9, rem) - .borderRadius(12, px) - .background("var(--accent)") - .color("var(--text)") - .border("1px solid var(--accent)") - }) - .attr({action: "/login", method: "POST"}) - .x(50, vw).y(50, vh) - .center() - }) - .background("var(--main)") - .width(100, vw) - .height(100, pct) - .margin(0) - } -} - -register(Login) \ No newline at end of file diff --git a/src/Profile/Profile.js b/src/Profile/Profile.js deleted file mode 100644 index f0bbc3f..0000000 --- a/src/Profile/Profile.js +++ /dev/null @@ -1,222 +0,0 @@ -import util from "../util"; - -css(` - profile- textarea::-webkit-scrollbar { - display: none; - width: 0px; - height: 0px; - } - - profile- textarea::-webkit-scrollbar-thumb { - background: transparent; - } - - profile- textarea::-webkit-scrollbar-track { - background: transparent; - } -`) - -class Profile extends Shadow { - constructor() { - super() - this.profile = global.profile - this.bioText = global.profile.bio ?? "" - } - - render() { - ZStack(() => { - div("➩") - .width(3, rem) - .height(3, rem) - .borderRadius(50, pct) - .border("1.5px solid var(--divider)") - .position("absolute") - .fontSize(2, em) - .transform("rotate(180deg)") - .top(3, rem) - .left(2, rem) - .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(--darkaccent)") - } - }) - .onTouch(function (start) { - if(start) { - this.attr({touched: "true"}) - } else { - this.attr({touched: ""}) - } - }) - .onClick((done) => { - if(done) - $("appwindowcontainer-").closeProfile() - }) - - form(() => { - input("Image Upload", "0px", "0px") - .attr({ name: "image-upload", type: "file" }) - .display("none") - .visibility("hidden") - .onChange((e) => { - this.handleUpload(e.target.files[0]); - }) - - VStack(() => { - HStack(() => { - if (global.profile.image_path) { - img(`${util.HOST}${global.profile.image_path}`, "10em", "10em") - .borderRadius(100, pct) - } - }) - .boxSizing("border-box") - .height(10, em) - .width(10, em) - .border("1px solid var(--accent)") - .borderRadius(100, pct) - .background("var(--darkaccent)") - .onTap(() => { - const inputSelector = this.$('[name="image-upload"]'); - inputSelector.click() - }) - - p("Tap to edit") - .color("var(--headertext)") - .opacity(0.5) - .marginTop(0.5, em) - - h1(this.profile.first_name + " " + this.profile.last_name) - .color("var(--headertext") - .width(70, pct) - .marginVertical(0.25, em) - .textAlign("center") - - p("Joined " + this.convertDate(this.profile.created)) - .color("var(--headertext)") - .marginBottom(0.5, em) - - h2("Bio") - .color("var(--headertext") - .margin(0) - .paddingVertical(0.9, em) - .borderTop("2px solid var(--divider)") - .width(70, pct) - .textAlign("center") - - textarea(this.bioText ? this.bioText : "Tap to start typing...") - .attr({ name: "bioinput" }) - .padding(1, em) - .width(90, pct) - .height(15, em) - .boxSizing("border-box") - .background("var(--searchbackground)") - .color("var(--darktext)") - .border("1px solid color-mix(in srgb, var(--accent) 60%, transparent)") - .borderRadius(12, px) - .fontFamily("Arial") - .fontSize(1.1, em) - .outline("none") - .onAppear((e) => { - if (this.bioText) { - $("profile- textarea").innerText = this.bioText - } - }) - .lineHeight(1.2, em) - - button("Save Bio") - .padding(1, em) - .fontSize(1.1, em) - .borderRadius(12, px) - .background("var(--searchbackground)") - .color("var(--text)") - .border("1px solid var(--accent)") - .boxSizing("border-box") - .marginVertical(0.75, em) - }) - .horizontalAlign("center") - .marginTop(5, em) - }) - .onSubmit(async (e) => { - e.preventDefault(); - const newBio = new FormData(e.target).get("bioinput"); - console.log(this.profile) - if (newBio.trim() !== this.profile.bio?.trim()) { - const result = await server.editBio(newBio, this.profile.id) - const { bio, updated_at } = result.data - global.profile.bio = bio - global.profile.updated_at = updated_at - this.profile = global.profile - } - }) - }) - .backgroundColor("var(--main)") - .overflowX("hidden") - .height(window.visualViewport.height - 20, px) - .boxSizing("border-box") - .width(100, pct) - .position("fixed") - .top(100, vh) - .zIndex(5) - .transition("top .4s ") - .pointerEvents("none") - } - - 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); - } - } - - 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(Profile) \ No newline at end of file diff --git a/src/_/code/ws/Connection.js b/src/_/code/ws/Connection.js deleted file mode 100644 index f1ff450..0000000 --- a/src/_/code/ws/Connection.js +++ /dev/null @@ -1,68 +0,0 @@ -import { Preferences } from '@capacitor/preferences'; -import util from "../../../util.js" - -class Connection { - connectionTries = 0; - ws; - receiveCB; - - constructor(receiveCB) { - this.receiveCB = receiveCB; - } - - init = async () => { - const { value: token } = await Preferences.get({ key: 'auth_token' }); - return new Promise((resolve, reject) => { - let url = "" - if(util.HOST) { - url = "wss://" + util.HOST.replace(/^https?:\/\//, '') + "/ws" + `?token=${token}`; - } else { - url = "ws://" + window.location.host + "/ws" - } - - this.ws = new WebSocket(url); - - this.ws.addEventListener('open', () => { - this.connectionTries = 0; - console.log("WebSocket connection established."); - this.ws.addEventListener('message', this.receiveCB); - resolve(this.ws); // resolve when open - }); - - this.ws.addEventListener('close', () => { - console.log('WebSocket closed'); - this.checkOpen(); // attempt reconnection - }); - - this.ws.addEventListener('error', (err) => { - console.error('WebSocket error', err); - reject(err); // reject if error occurs - }); - }); - } - - checkOpen = async () => { - if (this.ws.readyState === WebSocket.OPEN) { - return true; - } else { - await this.sleep(this.connectionTries < 20 ? 5000 : 60000); - this.connectionTries++; - console.log('Reestablishing connection'); - await this.init(); - } - } - - sleep = (time) => new Promise(resolve => setTimeout(resolve, time)); - - send = (msg) => { - if (this.ws.readyState === WebSocket.OPEN) { - this.ws.send(msg); - } else if (this.connectionTries === 0) { - setTimeout(() => this.send(msg), 100); - } else { - console.error('No WebSocket connection: Cannot send message'); - } - } -} - -export default Connection; diff --git a/src/_/code/ws/Socket.js b/src/_/code/ws/Socket.js deleted file mode 100644 index 55c249c..0000000 --- a/src/_/code/ws/Socket.js +++ /dev/null @@ -1,49 +0,0 @@ -import Connection from "./Connection.js"; - -export default class Socket { - connection; - disabled = true; - requestID = 1; - pending = new Map(); - - constructor() { - this.connection = new Connection(this.receive); - } - - async init() { - await this.connection.init() - } - - isOpen() { - if(this.connection.checkOpen()) { - return true; - } else { - return false; - } - } - - send(msg) { - return new Promise(resolve => { - const id = (++this.requestID).toString(); - this.pending.set(id, resolve); - this.connection.send(JSON.stringify({ id, ...msg })); - }); - } - - receive = (event) => { - const msg = JSON.parse(event.data); - if (msg.id && this.pending.has(msg.id)) { - this.pending.get(msg.id)(msg); - this.pending.delete(msg.id); - return; - } else { - this.onBroadcast(msg) - } - } - - onBroadcast(msg) { - window.dispatchEvent(new CustomEvent(msg.event, { - detail: msg.msg - })); - } -} \ No newline at end of file diff --git a/src/_/code/ws/shim/fs.js b/src/_/code/ws/shim/fs.js deleted file mode 100644 index 56004c9..0000000 --- a/src/_/code/ws/shim/fs.js +++ /dev/null @@ -1 +0,0 @@ -export default {} \ No newline at end of file diff --git a/src/_/imgs/logo.png b/src/_/imgs/logo.png deleted file mode 100644 index b7895e9..0000000 Binary files a/src/_/imgs/logo.png and /dev/null differ diff --git a/src/apps/Forum/Forum.js b/src/apps/Forum/Forum.js deleted file mode 100644 index e5fa756..0000000 --- a/src/apps/Forum/Forum.js +++ /dev/null @@ -1,83 +0,0 @@ -// import './ForumPanel.js' - -// css(` -// forum- { -// font-family: 'Bona'; -// } - -// forum- input::placeholder { -// font-family: 'Bona Nova'; -// font-size: 0.9em; -// color: var(--accent); -// } - -// input::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 Forum extends Shadow { -// render() { -// ZStack(() => { -// VStack(() => { - -// 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") -// } - - -// } - -// register(Forum) \ No newline at end of file diff --git a/src/apps/Forum/ForumPanel.js b/src/apps/Forum/ForumPanel.js deleted file mode 100644 index eef24e2..0000000 --- a/src/apps/Forum/ForumPanel.js +++ /dev/null @@ -1,193 +0,0 @@ -// import "../../components/LoadingCircle.js" - -// css(` -// forumpanel- { -// scrollbar-width: none; -// -ms-overflow-style: none; -// } - -// forumpanel-::-webkit-scrollbar { -// display: none; -// width: 0px; -// height: 0px; -// } - -// forumpanel-::-webkit-scrollbar-thumb { -// background: transparent; -// } - -// forumpanel-::-webkit-scrollbar-track { -// background: transparent; -// } -// `) - -// class ForumPanel extends Shadow { -// messages = [] -// isSending = false - -// render() { -// VStack(() => { -// if(this.messages.length > 0) { -// let previousDate = null - -// for(let i=0; i { -// 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) - -// 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) -// } - -// 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() -// } - -// 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() -// } - -// 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 }; -// } - -// 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(ForumPanel) \ No newline at end of file diff --git a/src/apps/Market/Market.js b/src/apps/Market/Market.js deleted file mode 100644 index e3bceeb..0000000 --- a/src/apps/Market/Market.js +++ /dev/null @@ -1,105 +0,0 @@ -import "./MarketSidebar.js" -import "./MarketGrid.js" - -css(` - market- { - font-family: 'Bona'; - } - - market- input::placeholder { - font-family: 'Bona Nova'; - font-size: 0.9em; - color: var(--accent); - } - - input[type="checkbox"] { - appearance: none; /* remove default style */ - -webkit-appearance: none; - width: 1em; - height: 1em; - border: 1px solid var(--accent); - } - - input[type="checkbox"]:checked { - background-color: var(--red); - } -`) - -class Market extends Shadow { - - listings = [ - { - title: "Shield Lapel Pin", - stars: "5", - reviews: 1, - price: "$12", - company: "Hyperia", - type: "new", - image: "/db/images/1", - madeIn: "America" - } - ] - - render() { - ZStack(() => { - HStack(() => { - MarketSidebar() - - MarketGrid(this.listings) - }) - .width(100, "%") - .x(0).y(13, vh) - - HStack(() => { - input("Search for products... (Coming Soon!)", "45vw") - .attr({ - "type": "text", - "disabled": "true" - }) - .fontSize(1.1, em) - .paddingLeft(1.3, em) - .background("transparent") - .border("0.5px solid var(--divider)") - .outline("none") - .color("var(--accent)") - .opacity(0.5) - .borderRadius(10, px) - .background("grey") - .cursor("not-allowed") - - button("+ Add Item") - .width(7, em) - .marginLeft(1, em) - .borderRadius(10, px) - .background("transparent") - .border("0.5px solid var(--accent2)") - .color("var(--accent)") - .fontFamily("Bona Nova") - .onHover(function (hovering) { - if(hovering) { - this.style.background = "var(--green)" - - } else { - this.style.background = "transparent" - - } - }) - .onClick((clicking) => { - console.log(this, "clicked") - }) - - }) - .x(55, vw).y(4, vh) - .position("absolute") - .transform("translateX(-50%)") - }) - .width(100, "%") - .height(100, "%") - } - - connectedCallback() { - // Optional additional logic - } -} - -register(Market) diff --git a/src/apps/Market/MarketGrid.js b/src/apps/Market/MarketGrid.js deleted file mode 100644 index 8740f9e..0000000 --- a/src/apps/Market/MarketGrid.js +++ /dev/null @@ -1,140 +0,0 @@ -class MarketGrid extends Shadow { - listings; - - constructor(listings) { - super() - this.listings = listings - } - - boldUntilFirstSpace(text) { - if(!text) return - const index = text.indexOf(' '); - if (index === -1) { - // No spaces — bold the whole thing - return `${text}`; - } - return `${text.slice(0, index)}${text.slice(index)}`; - } - - render() { - VStack(() => { - h3("Results") - .marginTop(0.1, em) - .marginBottom(1, em) - .marginLeft(0.4, em) - .color("var(--accent)") - .opacity(0.7) - - if (this.listings.length > 0) { - ZStack(() => { - // BuyModal() - - let params = new URLSearchParams(window.location.search); - - const hyperiaMade = params.get("hyperia-made") === "true"; - const americaMade = params.get("america-made") === "true"; - const newItem = params.get("new") === "true"; - const usedItem = params.get("used") === "true"; - - - let filtered = this.listings; - if (hyperiaMade) { - filtered = filtered.filter(item => item.madeIn === "Hyperia"); - } - if (americaMade) { - filtered = filtered.filter(item => item.madeIn === "America"); - } - if (newItem) { - filtered = filtered.filter(item => item.type === "new"); - } - if (usedItem) { - filtered = filtered.filter(item => item.type === "used"); - } - - for (let i = 0; i < filtered.length; i++) { - const rating = filtered[i].stars - const percent = (rating / 5) - - VStack(() => { - img(filtered[i].image) - .marginBottom(0.5, em) - - p(filtered[i].company) - .marginBottom(0.5, em) - - p(filtered[i].title) - .fontSize(1.2, em) - .fontWeight("bold") - .marginBottom(0.5, em) - - HStack(() => { - p(filtered[i].stars) - .marginRight(0.2, em) - - ZStack(() => { - div("★★★★★") // Empty stars (background) - .color("#ccc") - - div("★★★★★") // Filled stars (foreground, clipped by width) - .color("#ffa500") - .position("absolute") - .top(0) - .left(0) - .whiteSpace("nowrap") - .overflow("hidden") - .width(percent * 5, em) - }) - .display("inline-block") - .position("relative") - .fontSize(1.2, em) - .lineHeight(1) - - p(filtered[i].reviews) - .marginLeft(0.2, em) - }) - .marginBottom(0.5, em) - - p(filtered[i].price) - .fontSize(1.75, em) - .marginBottom(0.5, em) - - button("Coming Soon!") - .onClick((finished) => { - if(finished) { - - } - }) - .onHover(function (hovering) { - if(hovering) { - this.style.backgroundColor = "var(--green)" - } else { - this.style.backgroundColor = "" - } - }) - - }) - .padding(1, em) - .border("1px solid var(--accent2)") - .borderRadius(5, "px") - } - }) - .display("grid") - .gridTemplateColumns("repeat(auto-fill, minmax(250px, 1fr))") - .gap(1, em) - } else { - p("No Listings!") - } - }) - .onQueryChanged(() => { - console.log("query did change yup") - this.rerender() - }) - .height(100, vh) - .paddingLeft(2, em) - .paddingRight(2, em) - .gap(0, em) - .width(100, "%") - } -} - -register(MarketGrid) \ No newline at end of file diff --git a/src/apps/Market/MarketSidebar.js b/src/apps/Market/MarketSidebar.js deleted file mode 100644 index 9324618..0000000 --- a/src/apps/Market/MarketSidebar.js +++ /dev/null @@ -1,85 +0,0 @@ -class MarketSidebar extends Shadow { - - handleChecked(e) { - let checked = e.target.checked - let label = $(`label[for="${e.target.id}"]`).innerText - if(checked) { - window.setQuery(label.toLowerCase(), true) - } else { - window.setQuery(label.toLowerCase(), null) - } - } - - render() { - VStack(() => { - - p("Make") - - HStack(() => { - input() - .attr({ - "type": "checkbox", - "id": "hyperia-check" - }) - .onChange(this.handleChecked) - label("Hyperia-Made") - .attr({ - "for": "hyperia-check" - }) - .marginLeft(0.5, em) - }) - - HStack(() => { - input() - .attr({ - "type": "checkbox", - "id": "america-check" - }) - .onChange(this.handleChecked) - label("America-Made") - .attr({ - "for": "america-check" - }) - .marginLeft(0.5, em) - }) - - p("Condition") - - HStack(() => { - input() - .attr({ - "type": "checkbox", - "id": "new-check" - }) - .onChange(this.handleChecked) - label("New") - .attr({ - "for": "new-check" - }) - .marginLeft(0.5, em) - }) - - HStack(() => { - input() - .attr({ - "type": "checkbox", - "id": "used-check" - }) - .onChange(this.handleChecked) - label("Used") - .attr({ - "for": "used-check" - }) - .marginLeft(0.5, em) - }) - }) - .paddingTop(12, vh) - .paddingLeft(3, em) - .paddingRight(3, em) - .gap(1, em) - .minWidth(10, vw) - .userSelect('none') - } -} - -register(MarketSidebar) \ No newline at end of file diff --git a/src/apps/Messages/Messages.js b/src/apps/Messages/Messages.js deleted file mode 100644 index 939ff72..0000000 --- a/src/apps/Messages/Messages.js +++ /dev/null @@ -1,189 +0,0 @@ -import "./MessagesSidebar.js" -import "./MessagesPanel.js" - -css(` - messages- { - font-family: 'Bona'; - } - - messages- input::placeholder { - font-family: 'Bona Nova'; - font-size: 0.9em; - color: var(--accent); - } - - input[type="checkbox"] { - appearance: none; /* remove default style */ - -webkit-appearance: none; - width: 1em; - height: 1em; - border: 1px solid var(--accent); - } - - input[type="checkbox"]:checked { - background-color: var(--red); - } -`) - -class Messages extends Shadow { - conversations = [] - selectedConvoID = null - onConversationSelect(i) { - console.log("convo selected: ", i) - this.selectedConvoID = i - this.$("messagessidebar-").rerender() - this.$("messagespanel-").rerender() - } - - getConvoFromID(id) { - for(let i=0; i { - 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") { - global.socket.send({app: "MESSAGES", operation: "SEND", msg: { conversation: `CONVERSATION-${this.selectedConvoID}`, text: e.target.value }}) - e.target.value = "" - } - }) - }) - .gap(1, em) - .width(100, pct) - .horizontalAlign("center") - .verticalAlign("end") - }) - .onAppear(async () => { - let res = await global.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, pct) - .height(87, pct) - .x(0).y(13, pct) - - VStack(() => { - p("Add Message") - - input("enter email...") - .color("var(--accent)") - .onKeyDown(function (e) { - if (e.key === "Enter") { - global.socket.send({app: "MESSAGES", operation: "ADDCONVERSATION", msg: {email: this.value }}) - this.value = "" - } - }) - - p("x") - .onClick(function (done) { - if(done) { - this.parentElement.style.display = "none" - } - }) - .xRight(2, em).y(2, em) - .fontSize(1.4, em) - .cursor("pointer") - - }) - .gap(1, em) - .verticalAlign("center") - .horizontalAlign("center") - .backgroundColor("black") - .border("1px solid var(--accent)") - .position("fixed") - .x(50, vw).y(50, pct) - .center() - .width(60, vw) - .height(60, pct) - .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, pct) - .position("absolute") - .transform("translateX(-50%)") - }) - .boxSizing("border-box") - .height(100, pct) - .width(100, pct) - } -} - -register(Messages) \ No newline at end of file diff --git a/src/apps/Messages/MessagesPanel.js b/src/apps/Messages/MessagesPanel.js deleted file mode 100644 index b608212..0000000 --- a/src/apps/Messages/MessagesPanel.js +++ /dev/null @@ -1,56 +0,0 @@ -import "../../components/LoadingCircle.js" - -class MessagesPanel extends Shadow { - messages - - constructor(messages) { - super() - this.messages = messages - } - - render() { - VStack(() => { - if(this.messages) { - for(let i=0; i { - HStack(() => { - p(message.from.firstName + " " + message.from.lastName) - .fontWeight("bold") - .marginBottom(0.3, em) - - p(util.formatTime(message.time)) - .opacity(0.2) - .marginLeft(1, em) - }) - p(message.text) - }) - .paddingVertical(0.5, em) - .marginLeft(fromMe ? 70 : 0, pct) - .paddingRight(fromMe ? 10 : 0, pct) - .marginRight(fromMe ? 0 : 70, pct) - .paddingLeft(fromMe ? 5 : 10, pct) - .background(fromMe ? "var(--brown)" : "var(--green)") - } - } else { - LoadingCircle() - } - }) - .onAppear(async () => { - requestAnimationFrame(() => { - this.scrollTop = this.scrollHeight - }); - }) - .gap(1, em) - .position("relative") - .overflow("scroll") - .height(95, pct) - .width(100, pct) - .paddingTop(2, em) - .paddingBottom(2, em) - .backgroundColor("var(--darkbrown)") - } -} - -register(MessagesPanel) \ No newline at end of file diff --git a/src/apps/Messages/MessagesSidebar.js b/src/apps/Messages/MessagesSidebar.js deleted file mode 100644 index 453a91d..0000000 --- a/src/apps/Messages/MessagesSidebar.js +++ /dev/null @@ -1,73 +0,0 @@ -class MessagesSidebar extends Shadow { - conversations = [] - selectedConvoID - onSelect - - constructor(conversations, selectedConvoID, onSelect) { - super() - this.conversations = conversations - this.selectedConvoID = selectedConvoID - this.onSelect = onSelect - } - - render() { - VStack(() => { - this.conversations.forEach((convo, i) => { - - VStack(() => { - HStack(() => { - - p(this.makeConvoTitle(convo.between)) - .textAlign("left") - .marginLeft(0.5, inches) - .paddingTop(0.2, inches) - .width(100, pct) - .marginTop(0) - .fontSize(1, em) - .fontWeight("bold") - - p(util.formatTime(convo.messages.last.time)) - .paddingTop(0.2, inches) - .fontSize(0.8, em) - .marginRight(0.1, inches) - .color("var(--divider") - }) - .justifyContent("space-between") - .marginBottom(0) - - p(convo.messages.last.text) - .fontSize(0.8, em) - .textAlign("left") - .marginLeft(0.5, inches) - .marginBottom(2, em) - .color("var(--divider)") - }) - .background(convo.id === this.selectedConvoID ? "var(--darkbrown)" : "") - .onClick(() => { - this.onSelect(i) - }) - }) - }) - .minWidth(15, vw) - .height(100, vh) - .gap(0, em) - } - - makeConvoTitle(members) { - let membersString = "" - for(let i=0; i 2) { - membersString += member.firstName - } else { - membersString += member.firstName + " " + member.lastName - } - } - return membersString - } -} - -register(MessagesSidebar) \ No newline at end of file diff --git a/src/apps/People/People.js b/src/apps/People/People.js deleted file mode 100644 index abe7dfd..0000000 --- a/src/apps/People/People.js +++ /dev/null @@ -1,105 +0,0 @@ -css(` - people- { - font-family: 'Arial'; - } - - people- h1 { - font-family: 'Bona'; - } - - people- p { - color: var(--accent); - } - - people- p b { - color: var(--darkbrown); - } -`) - -class People extends Shadow { - people = ""; - - constructor() { - super() - this.people = global.currentNetwork.data.members; - } - - render() { - VStack(() => { - h1("People") - .color("rgb(158 136 105)") - .textAlign("center") - - if (this.people == "") { - LoadingCircle() - } else if (this.people.length > 0) { - for (let i = 0; i < this.people.length; i++) { - HStack(() => { - HStack(() => { }) - .boxSizing("border-box") - .height(3.5, em) - .width(3.5, em) - .padding(0.5, em) - .border("1px solid var(--accent)") - .borderRadius(100, pct) - .background("black") - - VStack(() => { - h3(this.people[i].firstName + " " + this.people[i].lastName) - .color("var(--brown)") - .fontSize(1.2, em) - .fontWeight("bold") - .marginVertical(0, em) - p("Member since: " + " " + this.convertDate(this.people[i].created)) - }) - .verticalAlign("center") - .gap(0.5, em) - }) - .height(3.5, em) - .padding(0.75, em) - .gap(1, em) - } - } 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)") - } - }) - .position("relative") - .boxSizing("border-box") - .paddingVertical(1, em) - .height(100, pct) - .width(100, pct) - } - - convertDate(rawDate) { - const date = rawDate.split("-", 1)[0]; // "01.31.2026 - const [mm, dd, yyyy] = date.split(".").map(Number); - - const months = [ - "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" - ]; - - 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 `${months[mm - 1]} ${ordinal(dd)}, ${yyyy}`; - } -} - -register(People) \ No newline at end of file diff --git a/src/apps/Tasks/Tasks.js b/src/apps/Tasks/Tasks.js deleted file mode 100644 index 4b0e733..0000000 --- a/src/apps/Tasks/Tasks.js +++ /dev/null @@ -1,153 +0,0 @@ -css(` - tasks- { - font-family: 'Bona'; - } - - tasks- input::placeholder { - font-family: 'Bona Nova'; - font-size: 0.9em; - color: var(--accent); - } - - input[type="checkbox"] { - appearance: none; /* remove default style */ - -webkit-appearance: none; - width: 1em; - height: 1em; - border: 1px solid var(--accent); - } - - input[type="checkbox"]:checked { - background-color: var(--red); - } -`) - -class Tasks extends Shadow { - projects = [ - { - "title": "Blockcatcher", - "tasks": {} - } - ] - columns = [ - { - "title": "backlog", - "tasks": {} - } - ] - - render() { - ZStack(() => { - HStack(() => { - VStack(() => { - h3("Projects") - .marginTop(0) - .marginBottom(1, em) - .marginLeft(0.4, em) - - if (this.projects.length >= 1) { - for(let i = 0; i < this.projects.length; i++) { - p(this.projects[i].title) - } - } else { - p("No Projects!") - } - }) - .height(100, vh) - .paddingLeft(2, em) - .paddingRight(2, em) - .paddingTop(2, em) - .gap(0, em) - .borderRight("0.5px solid var(--accent2)") - - HStack(() => { - if (this.columns.length >= 1) { - for(let i = 0; i < this.columns.length; i++) { - p(this.columns[i].name) - } - } else { - p("No Conversations!") - } - }) - .height(100, vh) - .paddingLeft(2, em) - .paddingRight(2, em) - .paddingTop(2, em) - .gap(0, em) - .borderRight("0.5px solid var(--accent2)") - }) - .width(100, "%") - .x(0).y(13, vh) - .borderTop("0.5px solid var(--accent2)") - - p("0 Items") - .position("absolute") - .x(50, vw).y(50, vh) - .transform("translate(-50%, -50%)") - - HStack(() => { - input("Search tasks...", "45vw") - .attr({ - "type": "text" - }) - .fontSize(1.1, em) - .paddingLeft(1.3, em) - .background("transparent") - .border("0.5px solid var(--accent2)") - .outline("none") - .color("var(--accent)") - .borderRadius(10, px) - - button("Search") - .marginLeft(2, em) - .borderRadius(10, px) - .background("transparent") - .border("0.5px solid var(--accent2)") - .color("var(--accent)") - .fontFamily("Bona Nova") - .onHover(function (hovering) { - if(hovering) { - this.style.background = "var(--green)" - - } else { - this.style.background = "transparent" - - } - }) - - button("+ New Task") - .width(9, em) - .marginLeft(1, em) - .borderRadius(10, px) - .background("transparent") - .border("0.5px solid var(--accent2)") - .color("var(--accent)") - .fontFamily("Bona Nova") - .onHover(function (hovering) { - if(hovering) { - this.style.background = "var(--green)" - - } else { - this.style.background = "transparent" - - } - }) - .onClick((clicking) => { - console.log(this, "clicked") - }) - - }) - .x(55, vw).y(4, vh) - .position("absolute") - .transform("translateX(-50%)") - }) - .width(100, "%") - .height(100, "%") - } - - connectedCallback() { - // Optional additional logic - } -} - -register(Tasks) \ No newline at end of file diff --git a/src/components/AppMenu.js b/src/components/AppMenu.js deleted file mode 100644 index d403a81..0000000 --- a/src/components/AppMenu.js +++ /dev/null @@ -1,72 +0,0 @@ -import { Haptics, ImpactStyle } from '@capacitor/haptics'; -import util from "../util.js" - -class AppMenu extends Shadow { - selected = "" - apps = global.currentNetwork.apps.filter(app => (app !== "Settings" && app !== "Website")) - darkMode = window.matchMedia('(prefers-color-scheme: dark)').matches - - getImageURL(appName) { - let imgUrl = `${util.HOST}/apps/${appName}/icons/${appName}` - if(this.darkMode) { - imgUrl += "light" - } - imgUrl += ".svg" - imgUrl = imgUrl.toLowerCase() - return imgUrl - } - - onNewSelection() { - this.$$("img").forEach((image) => { - image.style.borderBottom = "1px solid transparent" - const appName = image.attributes.app.value - if (appName === global.currentApp()) { - image.style.borderBottom = "1px solid var(--text)" - } - image.src = this.getImageURL(appName) - }) - } - - render() { - let apps = this.apps - let appCount = apps.length - let horizontalMargin = { - 1: 50, - 2: 10, - 3: 2, - 4: 1.5, - 5: 0.5 - }[appCount] ?? 4 - - HStack(() => { - - for(let i = 0; i < apps.length; i++) { - let app = apps[i] - - img(this.getImageURL(app), "1.3em") - .attr({app: app}) - .padding(0.5, em) - .borderBottom(global.currentApp() === app ? "1px solid var(--text)" : "1px solid transparent") - .onTouch(async (done, e) => { - if(done) { - global.openApp(app) - this.onNewSelection() - await Haptics.impact({ style: ImpactStyle.Light }); - } - }) - } - }) - .display("grid") - .gridTemplateColumns(`repeat(${apps.filter(app => app !== "Settings").length}, 1fr)`) - .placeItems("center") - .borderTop("0.5px solid var(--divider)") - .height("auto") - .zIndex(1) - .paddingTop(0.5, em) - .paddingBottom(2, em) - .width(100, vw) - .boxSizing("border-box") - } -} - -register(AppMenu) \ No newline at end of file diff --git a/src/components/AppWindow.js b/src/components/AppWindow.js deleted file mode 100644 index c7da9d5..0000000 --- a/src/components/AppWindow.js +++ /dev/null @@ -1,30 +0,0 @@ -import util from "../util.js" - -class AppWindow extends Shadow { - render() { - ZStack(() => { - let app = global.currentApp() - if(window[app]) { - window[app]() - } else { - this.getCustomApp(app) - } - }) - .height(100, pct) - .overflowY("scroll") - .onNavigate(() => { - this.rerender() - }) - } - - async getCustomApp(app) { - await import(`${util.HOST}/apps/${app.toLowerCase()}/${app.toLowerCase()}.js`); - if(window[app]) { - this.rerender() - } else { - console.error("Could not get app: ", app) - } - } -} - -register(AppWindow) \ No newline at end of file diff --git a/src/components/AppWindowContainer.js b/src/components/AppWindowContainer.js deleted file mode 100644 index 2a3d212..0000000 --- a/src/components/AppWindowContainer.js +++ /dev/null @@ -1,37 +0,0 @@ -import "./AppWindow.js" -import "../Profile/Profile.js" -import "./TopBar.js" - -class AppWindowContainer extends Shadow { - render() { - ZStack(() => { - - VStack(() => { - TopBar() - - AppWindow() - }) - .width(100, pct) - .gap(0) - - Profile() - .zIndex(3) - }) - .height(100, pct) - .overflowY("hidden") - .display("flex") - .position("relative") - } - - openProfile() { - this.$("profile-").top(20, px) - this.$("profile-").pointerEvents("auto") - } - - closeProfile() { - this.$("profile-").top(100, vh) - this.$("profile-").pointerEvents("none") - } -} - -register(AppWindowContainer) \ No newline at end of file diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js deleted file mode 100644 index 93c1c84..0000000 --- a/src/components/Sidebar.js +++ /dev/null @@ -1,184 +0,0 @@ -import util from "../util" -import "./Toggle.js" - -class Sidebar extends Shadow { - SIDEBAR_WIDTH - - constructor(width) { - super() - this.SIDEBAR_WIDTH = width - this.showCalendarControls = global.currentApp() === "Calendar"; - } - - CalendarControls() { - const getButton = (label) => { - return button(label) - .fontSize(1.2, em) - .paddingVertical(0.45, em) - .paddingHorizontal(0.8, em) - .border("1px solid var(--divider)") - .borderRadius(0.6, em) - .background("var(--accent)") - .color("var(--headertext)") - .cursor("pointer") - } - - return VStack(() => { - p("Calendar View") - .fontSize(1, em) - .fontWeight("bold") - .color("var(--headertext)") - .fontFamily("Arial") - .textAlign("Center") - .marginBottom(0.5, em) - HStack(() => { - getButton("day") - .onTap(() => { - if ($("calendar-").changeView("day")) { - $("home-").closeSidebar(); - } - }) - getButton("week") - .onTap(() => { - if ($("calendar-").changeView("week")) { - $("home-").closeSidebar(); - } - }) - getButton("month") - .onTap(() => { - if ($("calendar-").changeView("month")) { - $("home-").closeSidebar(); - } - }) - }) - .gap(0.5, em) - .justifyContent("center") - }) - .width(100, pct) - } - - SidebarItem(text) { - return p(text) - .fontSize(1.2, em) - .fontWeight("bold") - .color("var(--headertext)") - .fontFamily("Arial") - .marginLeft(1, em) - .marginTop(2, em) - .onTap(function (done) { - if (done) { - if (this.innerText === "Logout") { - global.onLogout() - $("home-").closeSidebar(); - return - } - } - }) - } - - render() { - VStack(() => { - HStack(() => { - if (global.profile.image_path) { - img(`${util.HOST}${global.profile.image_path}`, "10em", "10em") - .borderRadius(100, pct) - } - }) - .boxSizing("border-box") - .height(10, em) - .width(10, em) - .border("1px solid var(--accent)") - .borderRadius(100, pct) - .background("var(--darkaccent)") - .alignSelf("center") - .onClick((done) => { - if(done) - this.openProfile() - }) - - h2(global.profile.first_name + " " + global.profile.last_name) - .color("var(--headertext") - .textAlign("center") - .marginVertical(0.25, em) - .paddingBottom(0.5, em) - .textAlign("center") - .alignSelf("center") - .overflowWrap("break-word") - .wordBreak("break-word") - .width(100, pct) - .borderBottom("2px solid var(--divider)") - .onClick((done) => { - if(done) - this.openProfile() - }) - .paddingBottom(1, em) - - if (this.showCalendarControls) { - this.CalendarControls() - } - - VStack(() => { - this.SidebarItem("Logout") - - Toggle("Enable Push Notifications") - .marginLeft(1, em) - - button("Delete Account") - .fontSize(0.9, em) - .marginBottom(2, em) - .background("var(--darkred)") - .paddingVertical(1, em) - .border("none") - .outline("1px solid var(--divider)") - .color("var(--text)") - .onTap((done) => this.deleteAccount()) - }) - .marginTop("auto") - .gap(2, em) - }) - .gap(1, em) - .paddingTop(15, vh) - .paddingHorizontal(1, em) - .height(105, vh) - .top(-5, vh) - .minWidth(0) - .boxSizing("border-box") - .width(this.SIDEBAR_WIDTH, px) - .borderLeft("1px solid var(--divider)") - .color("var(--text)") - .position("fixed") - .background("var(--sidebar)") - } - - toggleCalendarControls() { - this.showCalendarControls = !this.showCalendarControls; - this.rerender(); - } - - openProfile() { - $("appwindowcontainer-").openProfile() - $("home-").closeSidebar(); - } - - async deleteAccount() { - try { - const res = await util.authFetch(`${util.HOST}/auth/delete`, { - method: "DELETE", - credentials: "include", - headers: { - "X-Client": "mobile", - "Content-Type": "application/json", - "Accept": "application/json" - }, - body: JSON.stringify({ memberId: global.profile.id }) - }); - - if (!res.ok) return; - global.onLogout() - } catch (err) { - console.error(err) - } - } -} - -register(Sidebar) \ No newline at end of file diff --git a/src/components/Toggle.js b/src/components/Toggle.js deleted file mode 100644 index 511b0a3..0000000 --- a/src/components/Toggle.js +++ /dev/null @@ -1,50 +0,0 @@ -css(` - .toggle-input { - appearance: none; - width: 44px; - height: 24px; - background: #ccc; - border-radius: 12px; - position: relative; - cursor: pointer; - transition: background 0.2s; - } - - .toggle-input::after { - content: ''; - position: absolute; - width: 20px; - height: 20px; - background: white; - border-radius: 50%; - top: 2px; - left: 2px; - transition: left 0.2s; - } - - .toggle-input:checked { - background: var(--darkred); - } - - .toggle-input:checked::after { - left: 22px; - } -`) - -class Toggle extends Shadow { - render() { - HStack(() => { - input("", "44px", "24px") - .attr({ type: "checkbox", checked: global.profile.preferences.notifications ? "" : null, class: "toggle-input"}) - .onChange(async (e) => { - await server.updateNotificationPreferences(global.profile.id, e.target.checked) - }) - p("Enable Push Notifications") - .color("var(--text)") - }) - .verticalAlign("center") - .gap(10, px) - } -} - -register(Toggle) \ No newline at end of file diff --git a/src/components/TopBar.js b/src/components/TopBar.js deleted file mode 100644 index 1865fe6..0000000 --- a/src/components/TopBar.js +++ /dev/null @@ -1,54 +0,0 @@ -import util from "../util.js" - -class TopBar extends Shadow { - render() { - HStack(() => { - if (global.currentNetwork.logo) { - img(`${util.HOST}/db/images/${global.currentNetwork.logo}`, "2.5em", "2.5em") - .borderRadius("50", pct) - .objectFit("cover") - .padding(0.3, em) - .background("var(--accent)") - .onTouch(function (start) { - if(start) { - this.style.scale = "0.8" - } else if(start === false) { - this.style.scale = "" - if (!$("home-").sidebarOpen) { - $("home-").openSidebar() - } - } - }) - } else { - HStack(() => { }) - .height(2.5, em) - .width(2.5, em) - .padding(0.3, em) - .background("var(--accent)") - .borderRadius("50", pct) - } - - p() - .state("app", function () { - this.innerText = global.currentApp() - }) - .color("var(--headertext)") - .textAlign("center") - .fontFamily("Arial") - .fontSize("clamp(0.8rem, 40cqw, 7cqw)") - .fontWeight("bold") - }) - .containerType("inline-size") - .paddingLeft(1, em) - .paddingBottom(1.5, em) - .verticalAlign("center") - .gap(0.5, em) - .marginTop(4, em) - .onNavigate(() => { - $("sidebar-").toggleCalendarControls(); - this.$("p").attr({app: global.currentApp()}) - }) - } -} - -register(TopBar) \ No newline at end of file diff --git a/src/index.html b/src/index.html index e64185b..21b3a99 100644 --- a/src/index.html +++ b/src/index.html @@ -19,13 +19,28 @@ src="https://unpkg.com/@ionic/pwa-elements@latest/dist/ionicpwaelements/ionicpwaelements.js" > - - - + - + \ No newline at end of file diff --git a/src/index.js b/src/index.js index 77045d9..71dc997 100644 --- a/src/index.js +++ b/src/index.js @@ -1,211 +1,34 @@ -import { PushNotifications } from '@capacitor/push-notifications'; -import Socket from "/_/code/ws/Socket.js" -import "./Home/Home.js" -import "./Home/AuthPage/AuthPage.js" -import "./Home/ConnectionError.js" import util from "./util.js" -const env = import.meta.env -let Global = class { - socket = new Socket() - profile = null - currentNetwork = "" - lastApp = "" - util = util +window.__util = util - currentApp() { - const pathname = window.location.pathname; - const segments = pathname.split('/').filter(Boolean) - const secondSegment = segments[1] || "" - const capitalized = secondSegment.charAt(0).toUpperCase() + secondSegment.slice(1); - return capitalized - } - - openApp = function(appName) { - const appUrl = appName.charAt(0).toLowerCase() + appName.slice(1); - let parts = window.location.pathname.split('/').filter(Boolean); - let newPath = "/" + parts[0] + "/" + appUrl - window.navigateTo(newPath) - const event = new CustomEvent('appchange', { - detail: { name: appName } - }); - window.dispatchEvent(event) - } - - onNavigate = async () => { - if(!global.profile) return - let selectedNetwork = this.networkFromPath() - - if(!selectedNetwork) { - if (!this.currentNetwork || this.currentNetwork === this.profile) { - let path = `/${this.getDefaultNetworkName()}/${this.getDefaultAppName()}` - history.replaceState({}, '', path) - } - } else if(!this.currentApp()) { - if(this.currentNetwork === window.profile) { - history.replaceState({}, '', `${window.location.pathname}/${window.profile.apps[0]}`) - } else { - history.replaceState({}, '', `${window.location.pathname}/${this.getDefaultAppName()}`) - } - } - - selectedNetwork = this.networkFromPath() - let networkChanged = this.currentNetwork !== selectedNetwork - let appChanged = this.currentApp() !== this.lastApp - if(appChanged) { - this.lastApp = this.currentApp() - } - - if(networkChanged) { - this.currentNetwork = selectedNetwork - const event = new CustomEvent('networkchange', { - detail: { name: this.currentNetwork } - }); - window.dispatchEvent(event) - } - - if(appChanged && !networkChanged) { - const event = new CustomEvent('appchange', { - detail: { name: this.currentApp() } - }); - window.dispatchEvent(event) - } - - document.title = (this.currentNetwork === this.profile) ? "Forum" : `${this.currentNetwork.abbreviation} | Forum` - } - - setCurrentNetworkAndApp() { - this.currentNetwork = this.networkFromPath() - - } - - getDefaultNetworkName() { - let defaultNetwork = this.profile.networks[0] - if (!defaultNetwork) { return "my"; } - return defaultNetwork.abbreviation - } - - getDefaultAppName() { - let defaultNetwork = this.profile.networks[0] - if (!defaultNetwork) { return this.profile.apps[0].toLowerCase(); } - return defaultNetwork.apps[0].toLowerCase() - } - - networkFromPath = function () { - const pathname = window.location.pathname; - const firstSegment = pathname.split('/').filter(Boolean)[0] || ''; - if(firstSegment === "my") { - return this.profile - } else { - let networks = this.profile.networks - for(let i = 0; i < networks.length; i++) { - let network = networks[i] - if(network.abbreviation === firstSegment) { - return network - } - } - } - } - - async getProfile() { - try { - const res = await util.authFetch(`${util.HOST}/auth/profile`, { - method: "GET", - credentials: "include", - headers: { - "Accept": "application/json" - } - }); - - if(res.status === 401) { - return res.status - } - if (!res.ok) return res.status; - - const profile = await res.json(); - console.log("getProfile: ", profile); - this.profile = profile - return 200; - } catch (err) { // Network error / Error reaching server - console.error(err); - return 500; - } - } - - async renderHome() { - location.reload() - } - - async onLogout() { - await util.removeAuthToken() - await fetch(`${util.HOST}/auth/signout`, { +async function getProfile() { + try { + const res = await util.authFetch(`${util.HOST}/auth/profile`, { method: "GET", - credentials: "include" + headers: { "Accept": "application/json" } }); - this.profile = null - location.reload() - } - - async setupPushNotifications() { - if (!Capacitor.isNativePlatform()) return; - PushNotifications.addListener('registration', async (token) => { - console.log('Device token:', token.value) - const stored = localStorage.getItem('deviceToken') - if (stored === token.value) return; - console.log("new push token") - - await server.updatePushToken(token.value, global.profile.id, env.MODE) - localStorage.setItem('deviceToken', token.value) - }); - - PushNotifications.addListener('registrationError', (error) => { - console.error('Registration error:', JSON.stringify(error)) - }); - - PushNotifications.addListener('pushNotificationReceived', (notification) => { - console.log('Notification received:', notification) - }); - - PushNotifications.addListener('pushNotificationActionPerformed', (action) => { - console.log('Notification tapped:', action.notification) - // navigate somewhere based on action.notification.data - }); - - const permission = await PushNotifications.requestPermissions(); - if (permission.receive === 'granted') { - await PushNotifications.register(); - console.log('after register:') - } - } - - async init() { - try { - const module = await import(`${util.HOST}/@server/server.js`); - window.server = module.default; - } catch(E) { - console.error(E) - } - - window.addEventListener("navigate", this.onNavigate) - - this.getProfile().then(async (status) => { - if (status === 401) { - navigateTo("/") - AuthPage() - } else if(status === 500) { - ConnectionError() - } else { - await this.socket.init() - await this.setupPushNotifications() - await this.onNavigate() - Home() - } - }) - } - - constructor() { - this.init() + if (res.status === 401) return 401; + if (!res.ok) return res.status; + return { status: 200, profile: await res.json() }; + } catch { + return 500; } } -window.global = new Global() \ No newline at end of file +async function init() { + await import(`${util.HOST}/shell/Shell.js`) + + const result = await getProfile() + + if (result === 401) { + window.AuthPage() + } else if (result === 500) { + window.ConnectionError() + } else { + window.__profile = result.profile + window.Home() + } +} + +init() \ No newline at end of file diff --git a/src/public/_/code/quill.js b/src/public/_/code/quill.js deleted file mode 100644 index 2d6f3eb..0000000 --- a/src/public/_/code/quill.js +++ /dev/null @@ -1,1355 +0,0 @@ -/* - Sam Russell - Captured Sun - 3.29.26 - Fix attr() bug with empty or null values - 3.28.26 - Stopping state() from duplicating on rerender() - 3.27.26 - Adding quill router, removing dynamicText(), removing horizontal and verticalAlign() checks - 3.24.26 - Allowing state() to watch other elements - 3.21.26 - Making state() be called on initial definition, fixing fontSize so it works with clamp(), other strings - 3.20.26 - Adding state() - 3.19.26 - Adding dynamicText() - 3.4.26 - Making horizontalAlign() and verticalAlign() methods of checking for stacks more robust - 2.27.26 - Adding parentShadow() function - 2.16.26 - Adding event objects to the onTouch callbacks - 1.16.26 - Moving nav event dispatch out of pushState, adding null feature to attr() - 1.5.26 - Switching verticalAlign and horizontalAlign names, adding borderVertical and Horizontal - 12.26.25 - State for arrays, nested objects. State for stacks (Shadow-only) - 12.17.25 - [Hyperia] - adding width, height functions. adding "e" to onClick. adding the non-window $$ funcs. - 12.16.25 - [comalyr] - State - 11.25.25.1 - Added minHeight and minWidth to be counted as numerical styles - 11.25.25 - Added onChange for check boxes, added setQuery / onQueryChanged for easy filtering - 11.24.25 - Fixing onClick because it was reversed, adding event to onHover params - 11.23.25 - Added onSubmit() event for form submission, added marginHorizontal() and marginVertical() - 11.20.25 - Added "pct" style unit, added alignVertical and alignHorizontal for flex boxes - 11.19.25 - Allowing for "auto" values in otherwise numeric styles, adding and vmax units - 11.17.25.3 - Adding styles() and fixing dynamic function from earlier - 11.17.25.2 - Fixing onNavigate() and onAppear() - 11.17.25 - Added dynamic function to have units in style func parameters. - 11.14.25 - Added onTouch, onTap. Changed style setters to work with Safari. Added center() funcs. - 11.13.25 - changed onFocus() to be a boolean event, added onInput() - 11.9.25 - changed p(innerText) to p(innerHTML), adjusted onNavigate to work for multiple elements and with correct "this" scope - 11.7.25 - changed registerShadow() to register(), changed onClick() to be like onHover() - 11.6.25 - adding default value for "button()" "children" parameter - 10.29.25 - adding "gap()" and "label()" functions -*/ - -/* $ NAVIGATION */ -let oldPushState = history.pushState; -history.pushState = function pushState() { - let ret = oldPushState.apply(this, arguments); - window.dispatchEvent(new Event('pushstate')); - return ret; -}; - -window.addEventListener('popstate', () => { - window.dispatchEvent(new Event('navigate')); -}); - -window.setQuery = function(key, value) { - const url = new URL(window.location.href); - const params = url.searchParams; - - if (value === null || value === undefined) { - params.delete(key); - } else { - params.set(key, value); - } - - const newUrl = url.toString(); - history.replaceState(null, "", newUrl); - window.dispatchEvent(new Event('query-changed')); - - return newUrl; -}; - -window.navigateTo = function(url) { - window.history.pushState({}, '', url); - window.dispatchEvent(new Event('navigate')); -} - -window.setLocation = function(url) { - window.dispatchEvent(new Event('navigate')); - window.history.replaceState({}, '', url); -} - -/* $ SELECTOR */ - -HTMLElement.prototype.$ = function(selector) { - return window.$(selector, this) -} -DocumentFragment.prototype.$ = function(selector) { - return window.$(selector, this) -} -window.$ = function(selector, el = document) { - return el.querySelector(selector) -} - -window.$$ = function(selector, el = document) { - return Array.from(el.querySelectorAll(selector)) -} -HTMLElement.prototype.$$ = function(selector) { - return window.$$(selector, this) -} -DocumentFragment.prototype.$$ = function(selector) { - return window.$$(selector, this) -} - -/* CONSOLE */ - -console.red = function(message) { - this.log(`%c${message}`, "color: rgb(254, 79, 42);"); -}; - -console.green = function(message) { - this.log(`%c${message}`, "color: rgb(79, 254, 42);"); - -} - -/* GET CSS VARIABLES FOR DARK OR LIGHT MODE */ - -window.getColor = function(name) { - const rootStyles = getComputedStyle(document.documentElement); - const color = rootStyles.getPropertyValue(`--${name}`).trim(); - if(!color) { - throw new Error("Color not found") - } - return color -} - -/* MOBILE */ - -window.isMobile = function isMobile() { - return /Android|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i.test(navigator.userAgent); -} - -window.css = function css(cssString) { - let container = document.querySelector("style#pageStyle"); - if(!container) { - container = document.createElement('style'); - container.id = "pageStyle"; - document.head.appendChild(container); - } - - let primarySelector = cssString.substring(0, cssString.indexOf("{")).trim(); - primarySelector = primarySelector.replace(/\*/g, "all"); - primarySelector = primarySelector.replace(/#/g, "id-"); - primarySelector = primarySelector.replace(/,/g, ""); - let stylesheet = container.querySelector(`:scope > style[id='${primarySelector}']`) - if(!stylesheet) { - stylesheet = document.createElement('style'); - stylesheet.id = primarySelector; - stylesheet.appendChild(document.createTextNode(cssString)); - container.appendChild(stylesheet); - } else { - stylesheet.innerText = cssString - } -} - -window.html = function html(elementString) { - let parser = new DOMParser(); - let doc = parser.parseFromString(elementString, 'text/html'); - return doc.body.firstChild; -} - -window.util = {} -window.util.observeClassChange = (el, callback) => { - if (!el || !(el instanceof Element)) { - throw new Error("observeClassChange requires a valid DOM element."); - } - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === "attributes" && mutation.attributeName === "class") { - callback(el.classList); - } - } - }); - - observer.observe(el, { - attributes: true, - attributeFilter: ["class"] - }); - - return observer; // Optional: return it so you can disconnect later -} - -/* PAGE SETUP */ - -Object.defineProperty(Array.prototype, 'last', { - get() { - return this[this.length - 1]; - }, - enumerable: false, -}); - -/* QUILL */ - -window.quill = { - rendering: [], - lastState: null, - router: null, - storedState: new WeakMap(), - - router: class { - routes = [] - - route(route, cb) { - this.routes.push({route: route, cb: cb}) - } - - async findMatch() { - let matched; - for (const route of this.routes) { - const [match, params] = this.matchPath(route.route, window.location.pathname) - if (match) { - await route.cb(params) - return; - } - } - if(!matched) throw new Error("Quill: couldn't match route!") - return null - } - - matchPath(pattern, pathname) { - const patternParts = pattern.split('/').filter(Boolean) - const pathParts = pathname.split('/').filter(Boolean) - - if (patternParts.length !== pathParts.length) return [false] - - const params = [] - - for (let i = 0; i < patternParts.length; i++) { - if (patternParts[i].startsWith(':')) { - params.push(pathParts[i]) - } else if (patternParts[i] !== pathParts[i]) { - return [false] - } - } - - return [true, params] - } - }, - - init: async (home, router) => { - if(router) { - quill.router = router - window.addEventListener("navigate", () => { - if(quill.router) { - router.findMatch() - } - }) - if(quill.router) { - await router.findMatch() - } - } - home() - }, - - render: (el) => { - if(el instanceof Shadow) { - let parent = quill.rendering[quill.rendering.length-1] - if(!parent) { - parent = document.body - } - parent.appendChild(el) - } else { - if(!el.render) {el.render = () => {}} - let parent = quill.rendering[quill.rendering.length-1] - if(!parent) throw new Error("Quill: no parent for element") - parent.appendChild(el) - } - - quill.rendering.push(el) - el.render() - quill.rendering.pop(el) - }, - - removeState: (el) => { - let state = quill.storedState.get(el) - if(state) { - state.forEach(observer => { - observer.disconnect() - }) - } - quill.storedState.delete(el) - }, - - rerender: (el) => { - quill.removeState(el) - - Array.from(el.attributes).forEach(attr => el.removeAttribute(attr.name)); - el.innerHTML = "" - el.removeAllListeners() - - quill.rendering.push(el) - el.render() - quill.rendering.pop() - }, - - isStack: (el) => { - return el.classList.contains("HStack") || el.classList.contains("ZStack") || el.classList.contains("VStack") - }, -} - -window.Shadow = class extends HTMLElement { - constructor() { - super() - } -} - -window.register = (el, tagname) => { - if (typeof el.prototype.render !== 'function') { - throw new Error("Element must have a render: " + el.prototype.constructor.name) - } - if(!tagname) { - tagname = el.prototype.constructor.name.toLowerCase() + "-" - } - customElements.define(tagname, el) - if(el.css) { - css(el.css) - } - - window[el.prototype.constructor.name] = function (...params) { - let instance = new el(...params) - if(instance.state) { - const proxyCache = new WeakMap(); - - function reactive(value, path=[]) { - if (value && typeof value === "object") { - if (proxyCache.has(value)) return proxyCache.get(value); - - const p = new Proxy(value, createHandlers(path)); - proxyCache.set(value, p); - return p; - } - return value; - } - - function isNumericKey(prop) { - return typeof prop === "string" && prop !== "" && String(+prop) === prop; - } - - function createHandlers(path) { - return { - get(target, prop, receiver) { - if (typeof prop === "symbol") { - return Reflect.get(target, prop, receiver); - } - - let nextPath = (Array.isArray(target) && !isNumericKey(prop)) ? path : path.concat(prop) // To filter out arr.length, arr.map, arr.forEach, etc. - quill.lastState = nextPath.join("."); - - const v = Reflect.get(target, prop, receiver); - return reactive(v, nextPath); - }, - - set(target, prop, value, receiver) { - const oldLength = Array.isArray(target) ? target.length : undefined; - const oldValue = target[prop]; - if (oldValue === value) return true; - - const result = Reflect.set(target, prop, value, receiver); - - let changedPath = (Array.isArray(target) && (!isNumericKey(prop) || target.length !== oldLength)) ? path : path.concat(prop).join("."); // To filter out arr.length, arr.map, arr.forEach, and also a push/pop/unshift. - const watchers = instance.stateWatchers[changedPath]; - - if (watchers) { - watchers.forEach(cb => cb()); - } - - return result; - } - }; - } - - let proxy = reactive(instance.state) - - Object.defineProperty(instance, "state", { - value: proxy, - writable: false, - configurable: false, - enumerable: true - }); - - let stateWatchers = {} - Object.keys(instance.state).forEach((key) => stateWatchers[key] = []) - Object.defineProperty(instance, "stateWatchers", { - value: stateWatchers, - writable: false, - configurable: false, - enumerable: true - }); - } - quill.render(instance) - return instance - } -} - -HTMLElement.prototype.rerender = function() { - quill.rerender(this) -} - -HTMLElement.prototype.parentShadow = function(selector) { - let el = this - while(el !== document.body) { - el = el.parentElement - if(el instanceof Shadow) { - return el - } - } - return null -} - -/* Styling */ - -window.pct = "%" -window.vmin = "vmin" -window.vmax = "vmax" -window.vh = "vh" -window.vw = "vw" -window.px = "px" -window.em = "em" -window.rem = "rem" -window.inches = "in" - -HTMLElement.prototype.addStyle = function(func) { - return func(this) -} - -window.css = function css(cssString) { - let container = document.querySelector("style#pageStyle"); - if(!container) { - container = document.createElement('style'); - container.id = "pageStyle"; - document.head.appendChild(container); - } - - let primarySelector = cssString.substring(0, cssString.indexOf("{")).trim(); - primarySelector = primarySelector.replace(/\*/g, "all"); - primarySelector = primarySelector.replace(/#/g, "id-"); - primarySelector = primarySelector.replace(/,/g, ""); - let stylesheet = container.querySelector(`:scope > style[id='${primarySelector}']`) - if(!stylesheet) { - stylesheet = document.createElement('style'); - stylesheet.id = primarySelector; - stylesheet.appendChild(document.createTextNode(cssString)); - container.appendChild(stylesheet); - } else { - stylesheet.innerText = cssString - } -} - -function extendHTMLElementWithStyleSetters() { - - function cssValueType(prop) { - const div = document.createElement("div"); - const style = div.style; - if (!(prop in style)) return "invalid"; - - switch(prop) { - - case "gap": - case "borderRadius": - case "width": - case "height": - case "maxWidth": - case "maxHeight": - case "minWidth": - case "minHeight": - - case "left": - case "top": - case "bottom": - case "right": - - case "padding": - case "paddingLeft": - case "paddingTop": - case "paddingBottom": - case "paddingRight": - - case "margin": - case "marginLeft": - case "marginTop": - case "marginBottom": - case "marginRight": - - case "textUnderlineOffset": - case "letterSpacing": - - return "unit-number" - - default: - - return "string" - } - - } - - let allStyleProps = ["accentColor", "additiveSymbols", "alignContent", "alignItems", "alignSelf", "alignmentBaseline", "all", "anchorName", "anchorScope", "animation", "animationComposition", "animationDelay", "animationDirection", "animationDuration", "animationFillMode", "animationIterationCount", "animationName", "animationPlayState", "animationRange", "animationRangeEnd", "animationRangeStart", "animationTimeline", "animationTimingFunction", "appRegion", "appearance", "ascentOverride", "aspectRatio", "backdropFilter", "backfaceVisibility", "background", "backgroundAttachment", "backgroundBlendMode", "backgroundClip", "backgroundColor", "backgroundImage", "backgroundOrigin", "backgroundPosition", "backgroundPositionX", "backgroundPositionY", "backgroundRepeat", "backgroundSize", "basePalette", "baselineShift", "baselineSource", "blockSize", "border", "borderBlock", "borderBlockColor", "borderBlockEnd", "borderBlockEndColor", "borderBlockEndStyle", "borderBlockEndWidth", "borderBlockStart", "borderBlockStartColor", "borderBlockStartStyle", "borderBlockStartWidth", "borderBlockStyle", "borderBlockWidth", "borderBottom", "borderBottomColor", "borderBottomLeftRadius", "borderBottomRightRadius", "borderBottomStyle", "borderBottomWidth", "borderCollapse", "borderColor", "borderEndEndRadius", "borderEndStartRadius", "borderImage", "borderImageOutset", "borderImageRepeat", "borderImageSlice", "borderImageSource", "borderImageWidth", "borderInline", "borderInlineColor", "borderInlineEnd", "borderInlineEndColor", "borderInlineEndStyle", "borderInlineEndWidth", "borderInlineStart", "borderInlineStartColor", "borderInlineStartStyle", "borderInlineStartWidth", "borderInlineStyle", "borderInlineWidth", "borderLeft", "borderLeftColor", "borderLeftStyle", "borderLeftWidth", "borderRadius", "borderRight", "borderRightColor", "borderRightStyle", "borderRightWidth", "borderSpacing", "borderStartEndRadius", "borderStartStartRadius", "borderStyle", "borderTop", "borderTopColor", "borderTopLeftRadius", "borderTopRightRadius", "borderTopStyle", "borderTopWidth", "borderWidth", "bottom", "boxDecorationBreak", "boxShadow", "boxSizing", "breakAfter", "breakBefore", "breakInside", "bufferedRendering", "captionSide", "caretAnimation", "caretColor", "clear", "clip", "clipPath", "clipRule", "color", "colorInterpolation", "colorInterpolationFilters", "colorRendering", "colorScheme", "columnCount", "columnFill", "columnGap", "columnRule", "columnRuleColor", "columnRuleStyle", "columnRuleWidth", "columnSpan", "columnWidth", "columns", "contain", "containIntrinsicBlockSize", "containIntrinsicHeight", "containIntrinsicInlineSize", "containIntrinsicSize", "containIntrinsicWidth", "container", "containerName", "containerType", "content", "contentVisibility", "cornerBlockEndShape", "cornerBlockStartShape", "cornerBottomLeftShape", "cornerBottomRightShape", "cornerBottomShape", "cornerEndEndShape", "cornerEndStartShape", "cornerInlineEndShape", "cornerInlineStartShape", "cornerLeftShape", "cornerRightShape", "cornerShape", "cornerStartEndShape", "cornerStartStartShape", "cornerTopLeftShape", "cornerTopRightShape", "cornerTopShape", "counterIncrement", "counterReset", "counterSet", "cursor", "cx", "cy", "d", "descentOverride", "direction", "display", "dominantBaseline", "dynamicRangeLimit", "emptyCells", "fallback", "fieldSizing", "fill", "fillOpacity", "fillRule", "filter", "flex", "flexBasis", "flexDirection", "flexFlow", "flexGrow", "flexShrink", "flexWrap", "float", "floodColor", "floodOpacity", "font", "fontDisplay", "fontFamily", "fontFeatureSettings", "fontKerning", "fontOpticalSizing", "fontPalette", "fontSize", "fontSizeAdjust", "fontStretch", "fontStyle", "fontSynthesis", "fontSynthesisSmallCaps", "fontSynthesisStyle", "fontSynthesisWeight", "fontVariant", "fontVariantAlternates", "fontVariantCaps", "fontVariantEastAsian", "fontVariantEmoji", "fontVariantLigatures", "fontVariantNumeric", "fontVariantPosition", "fontVariationSettings", "fontWeight", "forcedColorAdjust", "gap", "grid", "gridArea", "gridAutoColumns", "gridAutoFlow", "gridAutoRows", "gridColumn", "gridColumnEnd", "gridColumnGap", "gridColumnStart", "gridGap", "gridRow", "gridRowEnd", "gridRowGap", "gridRowStart", "gridTemplate", "gridTemplateAreas", "gridTemplateColumns", "gridTemplateRows", "height", "hyphenateCharacter", "hyphenateLimitChars", "hyphens", "imageOrientation", "imageRendering", "inherits", "initialLetter", "initialValue", "inlineSize", "inset", "insetBlock", "insetBlockEnd", "insetBlockStart", "insetInline", "insetInlineEnd", "insetInlineStart", "interactivity", "interpolateSize", "isolation", "justifyContent", "justifyItems", "justifySelf", "left", "letterSpacing", "lightingColor", "lineBreak", "lineGapOverride", "lineHeight", "listStyle", "listStyleImage", "listStylePosition", "listStyleType", "margin", "marginBlock", "marginBlockEnd", "marginBlockStart", "marginBottom", "marginInline", "marginInlineEnd", "marginInlineStart", "marginLeft", "marginRight", "marginTop", "marker", "markerEnd", "markerMid", "markerStart", "mask", "maskClip", "maskComposite", "maskImage", "maskMode", "maskOrigin", "maskPosition", "maskRepeat", "maskSize", "maskType", "mathDepth", "mathShift", "mathStyle", "maxBlockSize", "maxHeight", "maxInlineSize", "maxWidth", "minBlockSize", "minHeight", "minInlineSize", "minWidth", "mixBlendMode", "navigation", "negative", "objectFit", "objectPosition", "objectViewBox", "offset", "offsetAnchor", "offsetDistance", "offsetPath", "offsetPosition", "offsetRotate", "opacity", "order", "orphans", "outline", "outlineColor", "outlineOffset", "outlineStyle", "outlineWidth", "overflow", "overflowAnchor", "overflowBlock", "overflowClipMargin", "overflowInline", "overflowWrap", "overflowX", "overflowY", "overlay", "overrideColors", "overscrollBehavior", "overscrollBehaviorBlock", "overscrollBehaviorInline", "overscrollBehaviorX", "overscrollBehaviorY", "pad", "padding", "paddingBlock", "paddingBlockEnd", "paddingBlockStart", "paddingBottom", "paddingInline", "paddingInlineEnd", "paddingInlineStart", "paddingLeft", "paddingRight", "paddingTop", "page", "pageBreakAfter", "pageBreakBefore", "pageBreakInside", "pageOrientation", "paintOrder", "perspective", "perspectiveOrigin", "placeContent", "placeItems", "placeSelf", "pointerEvents", "position", "positionAnchor", "positionArea", "positionTry", "positionTryFallbacks", "positionTryOrder", "positionVisibility", "prefix", "printColorAdjust", "quotes", "r", "range", "readingFlow", "readingOrder", "resize", "result", "right", "rotate", "rowGap", "rubyAlign", "rubyPosition", "rx", "ry", "scale", "scrollBehavior", "scrollInitialTarget", "scrollMargin", "scrollMarginBlock", "scrollMarginBlockEnd", "scrollMarginBlockStart", "scrollMarginBottom", "scrollMarginInline", "scrollMarginInlineEnd", "scrollMarginInlineStart", "scrollMarginLeft", "scrollMarginRight", "scrollMarginTop", "scrollMarkerGroup", "scrollPadding", "scrollPaddingBlock", "scrollPaddingBlockEnd", "scrollPaddingBlockStart", "scrollPaddingBottom", "scrollPaddingInline", "scrollPaddingInlineEnd", "scrollPaddingInlineStart", "scrollPaddingLeft", "scrollPaddingRight", "scrollPaddingTop", "scrollSnapAlign", "scrollSnapStop", "scrollSnapType", "scrollTargetGroup", "scrollTimeline", "scrollTimelineAxis", "scrollTimelineName", "scrollbarColor", "scrollbarGutter", "scrollbarWidth", "shapeImageThreshold", "shapeMargin", "shapeOutside", "shapeRendering", "size", "sizeAdjust", "speak", "speakAs", "src", "stopColor", "stopOpacity", "stroke", "strokeDasharray", "strokeDashoffset", "strokeLinecap", "strokeLinejoin", "strokeMiterlimit", "strokeOpacity", "strokeWidth", "suffix", "symbols", "syntax", "system", "tabSize", "tableLayout", "textAlign", "textAlignLast", "textAnchor", "textAutospace", "textBox", "textBoxEdge", "textBoxTrim", "textCombineUpright", "textDecoration", "textDecorationColor", "textDecorationLine", "textDecorationSkipInk", "textDecorationStyle", "textDecorationThickness", "textEmphasis", "textEmphasisColor", "textEmphasisPosition", "textEmphasisStyle", "textIndent", "textOrientation", "textOverflow", "textRendering", "textShadow", "textSizeAdjust", "textSpacingTrim", "textTransform", "textUnderlineOffset", "textUnderlinePosition", "textWrap", "textWrapMode", "textWrapStyle", "timelineScope", "top", "touchAction", "transform", "transformBox", "transformOrigin", "transformStyle", "transition", "transitionBehavior", "transitionDelay", "transitionDuration", "transitionProperty", "transitionTimingFunction", "translate", "types", "unicodeBidi", "unicodeRange", "userSelect", "vectorEffect", "verticalAlign", "viewTimeline", "viewTimelineAxis", "viewTimelineInset", "viewTimelineName", "viewTransitionClass", "viewTransitionGroup", "viewTransitionName", "visibility", "webkitAlignContent", "webkitAlignItems", "webkitAlignSelf", "webkitAnimation", "webkitAnimationDelay", "webkitAnimationDirection", "webkitAnimationDuration", "webkitAnimationFillMode", "webkitAnimationIterationCount", "webkitAnimationName", "webkitAnimationPlayState", "webkitAnimationTimingFunction", "webkitAppRegion", "webkitAppearance", "webkitBackfaceVisibility", "webkitBackgroundClip", "webkitBackgroundOrigin", "webkitBackgroundSize", "webkitBorderAfter", "webkitBorderAfterColor", "webkitBorderAfterStyle", "webkitBorderAfterWidth", "webkitBorderBefore", "webkitBorderBeforeColor", "webkitBorderBeforeStyle", "webkitBorderBeforeWidth", "webkitBorderBottomLeftRadius", "webkitBorderBottomRightRadius", "webkitBorderEnd", "webkitBorderEndColor", "webkitBorderEndStyle", "webkitBorderEndWidth", "webkitBorderHorizontalSpacing", "webkitBorderImage", "webkitBorderRadius", "webkitBorderStart", "webkitBorderStartColor", "webkitBorderStartStyle", "webkitBorderStartWidth", "webkitBorderTopLeftRadius", "webkitBorderTopRightRadius", "webkitBorderVerticalSpacing", "webkitBoxAlign", "webkitBoxDecorationBreak", "webkitBoxDirection", "webkitBoxFlex", "webkitBoxOrdinalGroup", "webkitBoxOrient", "webkitBoxPack", "webkitBoxReflect", "webkitBoxShadow", "webkitBoxSizing", "webkitClipPath", "webkitColumnBreakAfter", "webkitColumnBreakBefore", "webkitColumnBreakInside", "webkitColumnCount", "webkitColumnGap", "webkitColumnRule", "webkitColumnRuleColor", "webkitColumnRuleStyle", "webkitColumnRuleWidth", "webkitColumnSpan", "webkitColumnWidth", "webkitColumns", "webkitFilter", "webkitFlex", "webkitFlexBasis", "webkitFlexDirection", "webkitFlexFlow", "webkitFlexGrow", "webkitFlexShrink", "webkitFlexWrap", "webkitFontFeatureSettings", "webkitFontSmoothing", "webkitHyphenateCharacter", "webkitJustifyContent", "webkitLineBreak", "webkitLineClamp", "webkitLocale", "webkitLogicalHeight", "webkitLogicalWidth", "webkitMarginAfter", "webkitMarginBefore", "webkitMarginEnd", "webkitMarginStart", "webkitMask", "webkitMaskBoxImage", "webkitMaskBoxImageOutset", "webkitMaskBoxImageRepeat", "webkitMaskBoxImageSlice", "webkitMaskBoxImageSource", "webkitMaskBoxImageWidth", "webkitMaskClip", "webkitMaskComposite", "webkitMaskImage", "webkitMaskOrigin", "webkitMaskPosition", "webkitMaskPositionX", "webkitMaskPositionY", "webkitMaskRepeat", "webkitMaskSize", "webkitMaxLogicalHeight", "webkitMaxLogicalWidth", "webkitMinLogicalHeight", "webkitMinLogicalWidth", "webkitOpacity", "webkitOrder", "webkitPaddingAfter", "webkitPaddingBefore", "webkitPaddingEnd", "webkitPaddingStart", "webkitPerspective", "webkitPerspectiveOrigin", "webkitPerspectiveOriginX", "webkitPerspectiveOriginY", "webkitPrintColorAdjust", "webkitRtlOrdering", "webkitRubyPosition", "webkitShapeImageThreshold", "webkitShapeMargin", "webkitShapeOutside", "webkitTapHighlightColor", "webkitTextCombine", "webkitTextDecorationsInEffect", "webkitTextEmphasis", "webkitTextEmphasisColor", "webkitTextEmphasisPosition", "webkitTextEmphasisStyle", "webkitTextFillColor", "webkitTextOrientation", "webkitTextSecurity", "webkitTextSizeAdjust", "webkitTextStroke", "webkitTextStrokeColor", "webkitTextStrokeWidth", "webkitTransform", "webkitTransformOrigin", "webkitTransformOriginX", "webkitTransformOriginY", "webkitTransformOriginZ", "webkitTransformStyle", "webkitTransition", "webkitTransitionDelay", "webkitTransitionDuration", "webkitTransitionProperty", "webkitTransitionTimingFunction", "webkitUserDrag", "webkitUserModify", "webkitUserSelect", "webkitWritingMode", "whiteSpace", "whiteSpaceCollapse", "widows", "width", "willChange", "wordBreak", "wordSpacing", "wordWrap", "writingMode", "x", "y", "zIndex", "zoom"] - - allStyleProps.forEach(prop => { - if (prop === "translate") return; - - const type = cssValueType(prop); - - switch (type) { - case "unit-number": - HTMLElement.prototype[prop] = StyleFunction(function(value, unit = "px") { - if(value === "auto") { - this.style[prop] = value - return this - } - this.style[prop] = value + unit; - if (value !== "" && this.style[prop] === "") { - throw new Error(`Invalid CSS value for ${prop}: ` + value + unit); - } - return this; - }); - break; - - case "string": - HTMLElement.prototype[prop] = StyleFunction(function(value) { - this.style[prop] = value; - if (value !== "" && this.style[prop] === "") { - throw new Error(`Invalid CSS value for ${prop}: ` + value); - } - return this; - }); - break; - } - }); -} - -extendHTMLElementWithStyleSetters(); - -HTMLElement.prototype.addStateWatcher = function(field, cb) { - let parent = this - while(!(parent instanceof Shadow)) { - parent = parent.parentNode - } - parent.stateWatchers[field].push(cb) -} - -// Currently only works for one state variable in the function -// Could probably be fixed by just making lastState an array and clearing it out every function call? -HTMLElement.prototype.setUpState = function(styleFunc, cb) { - let format = (value) => {return Array.isArray(value) ? value : [value]} - - // 1. Run the callback to get the style argument and also update lastState - let styleArgs = format(cb()) - - // 2. Check if lastState has really been updated. If not, the user-provided cb did not access valid state - if(!quill.lastState) { - throw new Error("Quill: style state function does not access valid state") - } - - // 3. Construct function to run when state changes - let onStateChange = () => { - styleFunc.call(this, ...format(cb())) - } - - // 4. Now listen for the state to change - this.addStateWatcher(quill.lastState, onStateChange) - - // 5. Run the original function again, this time with the actual arguments - quill.lastState = null - styleFunc.call(this, ...styleArgs) -} - -function StyleFunction(func) { - let styleFunction = function(value, unit) { - if(typeof value === 'function') { - this.setUpState(styleFunction, value) - return this - } else { - func.call(this, value, unit); // ".call" ensures that "this" is correct - return this - } - } - - return styleFunction -} - -HTMLElement.prototype.styles = function(cb) { - cb.call(this, this) - return this -} - -/* Type 1 */ - -HTMLElement.prototype.paddingVertical = StyleFunction(function(value, unit = "px") { - this.style.paddingTop = value + unit - this.style.paddingBottom = value + unit - return this -}) - -HTMLElement.prototype.paddingHorizontal = StyleFunction(function(value, unit = "px") { - this.style.paddingRight = value + unit - this.style.paddingLeft = value + unit - return this -}) - -HTMLElement.prototype.marginVertical = StyleFunction(function(value, unit = "px") { - this.style.marginTop = value + unit - this.style.marginBottom = value + unit - return this -}) - -HTMLElement.prototype.marginHorizontal = StyleFunction(function(value, unit = "px") { - this.style.marginRight = value + unit - this.style.marginLeft = value + unit - return this -}) - -HTMLElement.prototype.borderVertical = StyleFunction(function(value) { - this.style.borderTop = value - this.style.borderBottom = value - return this -}) - -HTMLElement.prototype.borderHorizontal = StyleFunction(function(value) { - this.style.borderRight = value - this.style.borderLeft = value - return this -}) - -HTMLElement.prototype.fontSize = StyleFunction(function(value, unit) { - - switch(value) { - case "6xl": - value = "3.75"; unit = "rem" - break; - - case "5xl": - value = "3"; unit = "rem" - break; - - case "4xl": - value = "2.25"; unit = "rem" - break; - - case "3xl": - value = "1.875"; unit = "rem" - break; - - case "2xl": - value = "1.5"; unit = "rem" - break; - - case "xl": - value = "1.25"; unit = "rem" - break; - - case "l": - value = "1.125"; unit = "rem" - break; - - case "s": - value = "0.875"; unit = "rem" - break; - - case "xs": - value = "0.75"; unit = "rem" - break; - - default: - break; - } - - if(unit) { - this.style.fontSize = value + unit - } else { - this.style.fontSize = value - } - - return this -}) - - -HTMLElement.prototype.width = function(value, unit = "px") { - if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value)) - throw new Error(`Invalid value: ${value}. Expected a number.`); - this.style.width = value + unit - if(window.getComputedStyle(this).display === "inline") { - this.style.display = "block" - } - return this -} - -HTMLElement.prototype.height = function(value, unit = "px") { - if ((typeof value !== 'number' && value !== "auto") || Number.isNaN(value)) - throw new Error(`Invalid value: ${value}. Expected a number.`); - this.style.height = value + unit - if(window.getComputedStyle(this).display === "inline") { - this.style.display = "block" - } - return this -} - -function checkPositionType(el) { - let computed = window.getComputedStyle(el).position - if(!(computed === "absolute" || computed === "fixed")) { - el.style.position = "absolute" - } -} - -HTMLElement.prototype.x = function(value, unit = "px") { - if (typeof value !== 'number' || isNaN(value)) - throw new Error(`Invalid value: ${value}. Expected a number.`); - checkPositionType(this) - this.style.left = value + unit - return this -} - -HTMLElement.prototype.y = function(value, unit = "px") { - if (typeof value !== 'number' || isNaN(value)) - throw new Error(`Invalid value: ${value}. Expected a number.`); - checkPositionType(this) - this.style.top = value + unit - return this -} - -HTMLElement.prototype.xRight = function(value, unit = "px") { - if (typeof value !== 'number' || isNaN(value)) - throw new Error(`Invalid value: ${value}. Expected a number.`); - checkPositionType(this) - this.style.right = value + unit - return this -} - -HTMLElement.prototype.yBottom = function(value, unit = "px") { - if (typeof value !== 'number' || isNaN(value)) - throw new Error(`Invalid value: ${value}. Expected a number.`); - checkPositionType(this) - this.style.bottom = value + unit - return this -} - -HTMLElement.prototype.backgroundImage = function (...values) { - const formatted = values - .map(v => { - if(v.includes("/") && !v.includes("gradient")) { - v = "url(" + v + ")" - } - return String(v).trim(); - }) - .join(", "); - - this.style.backgroundImage = formatted; - return this; -}; - -HTMLElement.prototype.center = function () { - this.style.transform = "translate(-50%, -50%)" - return this; -}; - -HTMLElement.prototype.centerX = function () { - this.style.transform = "translateX(-50%)" - return this; -}; - -HTMLElement.prototype.centerY = function () { - this.style.transform = "translateY(-50%)" - return this; -}; - -HTMLElement.prototype.verticalAlign = function (value) { - const direction = getComputedStyle(this).flexDirection; - if (direction === "column" || direction === "column-reverse") { - this.style.justifyContent = value; - } else { - this.style.alignItems = value; - } - return this -} - -HTMLElement.prototype.horizontalAlign = function (value) { - const direction = getComputedStyle(this).flexDirection; - if (direction === "column" || direction === "column-reverse") { - this.style.alignItems = value; - } else { - this.style.justifyContent = value; - } - return this -} - - -/* Elements */ - -/* - STATE RULES: - reset on rerender() - elements not connected to the dom have their state listeners removed -*/ - -HTMLElement.prototype.state = function(arg1, arg2, arg3, arg4) { - let el; - let attr; - let cb; - let runImmediately; - - // element, attr, callback - // element, attr, callback, bool - // attr, callback - // attr, callback, bool - - if(arg1 instanceof Element) { - if(typeof arg4 === 'boolean') { - el = arg1; attr = arg2; cb = arg3; runImmediately = arg4; - } else { - el = arg1; attr = arg2; cb = arg3; runImmediately = true; - } - } else { - if(typeof arg3 === 'boolean') { - el = this; attr = arg1; cb = arg2; runImmediately = arg3 - } else { - el = this; attr = arg1; cb = arg2; runImmediately = true - } - } - - if (attr !== attr.toLowerCase()) { - throw new Error(`quill: state() attr "${attr}" must be lowercase`); - } - - let handler = () => { - const value = el.getAttribute(attr); - if(!this.isConnected) { - quill.removeState(this) - } else { - cb.call(this, value) - } - } - - let observer = new MutationObserver(handler) - observer.observe(el, { attributes: true, attributeFilter: [attr] }) - - if(!quill.storedState.get(this)) { - quill.storedState.set(this, []) - } - quill.storedState.get(this).push(observer) - - if(runImmediately) { - handler() - } - return this -} - -quill.setChildren = function(el, innerContent) { - if(typeof innerContent === "string") { - el.innerText = innerContent - } else if(typeof innerContent === "function") { - el.render = innerContent - } else { - throw new Error("Children of unknown type") - } -} - -window.a = function a( href, inner=href ) { - if(!href) throw new Error("quill a: missing href argument. Function: a( href, inner=href )") - let link = document.createElement("a") - link.setAttribute('href', href); - quill.setChildren(link, inner) - quill.render(link) - return link -} - -window.img = function img(src, width="", height="") { - let image = document.createElement("img") - - if(!src || !(typeof src==="string")) { - throw new Error("img: missing first argument: src | String") - } else { - image.src = src - } - if(width && typeof width === "string") { - image.style.width = width - } else if(width) { - image.style.width = width + "px" - } - if(height && typeof height === "string") { - image.style.height = height - } else if(height) { - image.style.height = height + "px" - } - quill.render(image) - return image -} - -HTMLImageElement.prototype.backgroundColor = function(value) { - if (this.src.endsWith('.svg') || this.src.startsWith('data:image/svg+xml')) { - fetch(this.src).then(response => response.text()) - .then(svgText => { - const modifiedSvg = svgText.replace(/\bfill="[^"]*"/g, `fill="${value}"`); - const blob = new Blob([modifiedSvg], { type: 'image/svg+xml' }); - this.src = URL.createObjectURL(blob); - }).catch(error => { - console.error('Error updating SVG fill:', error); - }); - } else { - this.style.backgroundColor = value; - } - - return this; // Always returns the element itself - }; - -window.p = function p(innerHTML) { - let el = document.createElement("p") - if(typeof innerText === "function") { - el.render = innerHTML - } else { - el.innerHTML = innerHTML - } - el.style.margin = "0"; - quill.render(el) - return el -} - -window.h1 = function h1(innerText) { - let el = document.createElement("h1") - el.innerText = innerText - quill.render(el) - return el -} - -window.h2 = function h2(innerText) { - let el = document.createElement("h2") - el.innerText = innerText - quill.render(el) - return el -} - -window.h3 = function h3(innerText) { - let el = document.createElement("h3") - el.innerText = innerText - quill.render(el) - return el -} - -window.div = function (innerText) { - let el = document.createElement("div") - el.innerText = innerText ?? "" - quill.render(el) - return el -} - -window.span = function (innerText) { - let el = document.createElement("span") - el.innerText = innerText - quill.render(el) - return el -} - -window.button = function (children = "") { - let el = document.createElement("button") - quill.setChildren(el, children) - quill.render(el) - return el -} - -window.form = function(cb) { - let el = document.createElement("form") - el.render = cb - quill.render(el) - return el -} - -window.input = function(placeholder = "", width, height) { - let el = document.createElement("input") - el.placeholder = placeholder - el.style.width = width - el.style.height = height - quill.render(el) - return el -} - -window.select = function(cb) { - let el = document.createElement("select") - el.render = cb - quill.render(el) - return el -} - -window.option = function(placeholder = "") { - let el = document.createElement("option") - el.innerText = placeholder - quill.render(el) - return el -} - -window.label = function(inside) { - let el = document.createElement("label") - if(typeof inside === "string") { - el.innerText = inside - } else { - el.render = inside - } - quill.render(el) - return el -} - -window.textarea = function(placeholder = "") { - let el = document.createElement("textarea") - el.placeholder = placeholder - quill.render(el) - return el -} - - -/* STACKS */ - -handleStack = function(cb, name, styles="") { - let nowRendering = quill.rendering[quill.rendering.length-1] - if (nowRendering.innerHTML.trim() === "" && !quill.isStack(nowRendering)) { - nowRendering.style.cssText += styles - nowRendering.classList.add(name) - cb() - if(quill.lastState) { - nowRendering.addStateWatcher(quill.lastState, () => { - nowRendering.innerHTML = "" - cb() - }) - } - return nowRendering - } else { - let div = document.createElement("div") - div.classList.add(name) - div.style.cssText += styles - div.render = cb - quill.render(div) - return div - } -} - -window.VStack = function (cb = () => {}) { - let styles = ` - display: flex; - flex-direction: column; - ` - return handleStack(cb, "VStack", styles) -} - -window.HStack = function (cb = () => {}) { - let styles = ` - display: flex; - flex-direction: row; - `; - return handleStack(cb, "HStack", styles) -}; - -window.ZStack = function (cb = () => {}) { - return handleStack(cb, "ZStack") -}; - -/* SHAPES */ - -window.svgMethods = function(svg) { - svg.pulse = function (duration = 600) { - this.style.transition = `transform ${duration}ms ease-in-out` - this.style.transform = "scale(1.2)" - setTimeout(() => { - this.style.transform = "scale(1)" - }, duration / 2) - return this - } - - // Rotate (e.g. loading spinner) - svg.rotate = function (degrees = 360, duration = 1000) { - this.style.transition = `transform ${duration}ms linear` - this.style.transform = `rotate(${degrees}deg)` - return this - } - - // Change color - svg.fill = function (color) { - this.setAttribute("fill", color) - return this - } - - svg.height = function (height) { - this.setAttribute("height", height) - return this - } - - svg.width = function (width) { - this.setAttribute("width", width) - return this - } - - svg.stroke = function (width, color) { - this.setAttribute("stroke", color) - this.setAttribute("stroke-width", width) - return this - } - - // Toggle visibility - svg.toggle = function () { - this.style.display = this.style.display === "none" ? "" : "none" - return this - } -} - -window.Rectangle = function (width = "40px", height = "40px") { - const svgNS = "http://www.w3.org/2000/svg"; - const svgEl = document.createElementNS(svgNS, "svg"); - const rectEl = document.createElementNS(svgNS, "rect"); - - // SVG size - svgEl.setAttribute("width", width); - svgEl.setAttribute("height", height); - svgEl.setAttribute("viewBox", "0 0 100 100"); - svgEl.setAttribute("preserveAspectRatio", "xMidYMid meet"); - - // Rectangle: full size, slightly rounded corners - rectEl.setAttribute("x", "15"); // 15% margin from edges - rectEl.setAttribute("y", "15"); - rectEl.setAttribute("width", "70"); // 70% of viewBox - rectEl.setAttribute("height", "70"); - // rectEl.setAttribute("rx", "8"); // rounded corners (optional) - // rectEl.setAttribute("ry", "8"); - - svgEl.appendChild(rectEl); - svgMethods(svgEl); // assuming you have this - quill.render(svgEl); - return svgEl; -} - -window.Triangle = function (width = "40px", height = "40px") { - const svgNS = "http://www.w3.org/2000/svg" - const svgEl = document.createElementNS(svgNS, "svg") - const pathEl = document.createElementNS(svgNS, "path") - - // SVG size - svgEl.setAttribute("width", width) - svgEl.setAttribute("height", height) - svgEl.setAttribute("viewBox", "0 0 100 100") - svgEl.setAttribute("preserveAspectRatio", "xMidYMid meet") - // Right-pointing triangle (Play icon) - pathEl.setAttribute("d", "M 25 15 L 90 50 L 25 85 Z") // ◄ adjust points if needed - - svgEl.appendChild(pathEl) - svgMethods(svgEl) - quill.render(svgEl) - return svgEl -} - - -/* EVENTS */ - -HTMLElement.prototype.onAppear = function(func) { - func.call(this); - return this; -}; - -HTMLElement.prototype.onClick = function(func) { - const onMouseDown = (e) => func.call(this, false, e); - const onMouseUp = (e) => func.call(this, true, e); - this._storeListener("mousedown", onMouseDown); - this._storeListener("mouseup", onMouseUp); - return this; -}; - -HTMLElement.prototype.onMouseDown = function(func) { - this._storeListener("mousedown", func); - return this; -}; - -HTMLElement.prototype.onMouseUp = function(func) { - this._storeListener("mouseup", func); - return this; -}; - -HTMLElement.prototype.onRightClick = function(func) { - this._storeListener("contextmenu", func); - return this; -}; - -HTMLElement.prototype.onHover = function(cb) { - const onEnter = (e) => cb.call(this, true, e); - const onLeave = (e) => cb.call(this, false, e); - this._storeListener("mouseover", onEnter); - this._storeListener("mouseleave", onLeave); - return this; -}; - -HTMLElement.prototype.onFocus = function(cb) { - if (!this.matches('input, textarea, select, button')) { - throw new Error("Can't put focus event on non-form element!"); - } - const onFocus = () => cb.call(this, true); - const onBlur = () => cb.call(this, false); - this._storeListener("focus", onFocus); - this._storeListener("blur", onBlur); - return this; -}; - -HTMLElement.prototype.onKeyDown = function(cb) { - this._storeListener("keydown", cb); - return this; -}; - -HTMLElement.prototype.onInput = function(cb) { - if(!this.matches('input, textarea, [contenteditable=""], [contenteditable="true"]')) - throw new Error("Can't put input event on non-input element!") - this._storeListener("input", cb); - return this; -}; - -HTMLElement.prototype.onChange = function(cb) { - if(!this.matches('input, textarea, select, [contenteditable=""], [contenteditable="true"]')) - throw new Error("Can't put input event on non-input element!") - this._storeListener("change", cb); - return this; -}; - - -HTMLElement.prototype.onSubmit = function(cb) { - if(!this.matches('form')) - throw new Error("Can't put form event on non-form element!") - this._storeListener("submit", cb); - return this; -}; - -HTMLElement.prototype.onTouch = function(cb) { - const onStart = (e) => cb.call(this, true, e); - const onEnd = (e) => cb.call(this, false, e); - const onCancel = (e) => cb.call(this, null, e); - this._storeListener("touchstart", onStart); - this._storeListener("touchend", onEnd); - this._storeListener("touchcancel", onCancel); - return this; -}; - -HTMLElement.prototype.onTap = function(cb) { - this._storeListener("touchend", cb); - return this; -}; - -/* WHY THIS LISTENER IS THE WAY IT IS: -- We can't just put a listener on the element, because a window "navigate" event won't trigger it -- We can't just put a listener on the window, because the "this" variable will only refer to the window -- And, if we try to re-add that scope using bind(), it makes the return value of .toString() unreadable, which means we cannot detect duplicate listeners. -- Therefore, we attach a global navigate event to the window, and store each navigate event in this navigateListeners array, and manually trigger each event on the elements when the global one fires. -*/ -navigateListeners = [] -window.addEventListener("navigate", () => { - for(entry of navigateListeners) { - entry.el.dispatchEvent(new CustomEvent("navigate")) - } -}) -HTMLElement.prototype.onNavigate = function(cb) { - this._storeListener("navigate", cb); - - let found = false - let elementIndex = Array.from(this.parentNode.children).indexOf(this) - for(entry of navigateListeners) { - if( - entry.cb.toString() === cb.toString() - && entry.index === elementIndex - && this.nodeName === entry.el.nodeName - ) { - found = true - break; - } - } - if(found === false) { - navigateListeners.push({el: this, cb: cb, index: elementIndex}) - } - - return this; -}; - -/* -Same principle applies -*/ -queryListeners = [] -HTMLElement.prototype.onQueryChanged = function(cb) { - this._storeListener("query-changed", cb); - - let found = false - for(entry of queryListeners) { - if(entry.cb.toString() === cb.toString() && - this.nodeName === entry.el.nodeName) { - found = true - break; - } - } - if(found === false) { - queryListeners.push({el: this, cb: cb}) - } - - return this; -}; -window.addEventListener("query-changed", () => { - for(entry of queryListeners) { - entry.el.dispatchEvent(new CustomEvent("query-changed")) - } -}) - -HTMLElement.prototype.onEvent = function(name, cb) { - window._storeListener(window, name, cb); - return this; -}; - -HTMLElement.prototype._storeListener = function(type, handler, options) { - window._storeListener(this, type, handler, options) -} - -window.__listeners = [] - -function _storeListener(target, type, handler, options) { - if (!target.__listeners) target.__listeners = []; - - const optionsString = JSON.stringify(options); - - const index = target.__listeners.findIndex(listener => - listener.type === type && - listener.handler.toString() === handler.toString() && - JSON.stringify(listener.options) === optionsString - ); - - if (index === -1) { // Listener is new - target.addEventListener(type, handler, options); - target.__listeners.push({ type, handler, options }); - } else { // Listener is a duplicate, can be replaced - const old = target.__listeners[index]; - target.removeEventListener(old.type, old.handler, old.options); - - // Replace with the new one - target.addEventListener(type, handler, options); - target.__listeners[index] = { type, handler, options }; - } -} - -HTMLElement.prototype.removeAllListeners = function() { - if (!this.__listeners) return; - for (const { type, handler, options } of this.__listeners) { - this.removeEventListener(type, handler, options); - } - this.__listeners = []; - return this; -}; - -/* ATTRIBUTES */ - -HTMLElement.prototype.attr = function(arg1, arg2) { - if(typeof arg1 === "object") { - for (const [key, value] of Object.entries(arg1)) { - if(value === null) { - this.removeAttribute(key) - } else { - this.setAttribute(key, value); - } - } - - return this; - } else if(typeof arg1 === "string" && (arg2 || arg2 === "" || arg2 === null || arg2 === 0)) { - this.setAttribute(arg1, arg2) - return this - } else if(typeof arg1 === "string") { - return this.getAttribute(arg1) - } else { - throw new TypeError("wrong parameter for attr(): ", arg1); - } -}; diff --git a/src/public/_/icons/column2.svg b/src/public/_/icons/column2.svg deleted file mode 100644 index ab79e11..0000000 --- a/src/public/_/icons/column2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/public/_/icons/columnwhite.png b/src/public/_/icons/columnwhite.png deleted file mode 100644 index 9e1ff13..0000000 Binary files a/src/public/_/icons/columnwhite.png and /dev/null differ diff --git a/src/public/_/icons/columnwhite.svg b/src/public/_/icons/columnwhite.svg deleted file mode 100644 index 11e6f6c..0000000 --- a/src/public/_/icons/columnwhite.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/public/_/icons/events.svg b/src/public/_/icons/events.svg deleted file mode 100644 index 6c2d257..0000000 --- a/src/public/_/icons/events.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/public/_/icons/eventslight.svg b/src/public/_/icons/eventslight.svg deleted file mode 100644 index 970a6c7..0000000 --- a/src/public/_/icons/eventslight.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/public/_/icons/eventslightselected.svg b/src/public/_/icons/eventslightselected.svg deleted file mode 100644 index 122d5c0..0000000 --- a/src/public/_/icons/eventslightselected.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/public/_/icons/extra/settings.svg b/src/public/_/icons/extra/settings.svg deleted file mode 100644 index 73807d5..0000000 --- a/src/public/_/icons/extra/settings.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/public/_/icons/extra/settingsdark.svg b/src/public/_/icons/extra/settingsdark.svg deleted file mode 100644 index a5f9e01..0000000 --- a/src/public/_/icons/extra/settingsdark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/public/_/icons/extra/settingslight.svg b/src/public/_/icons/extra/settingslight.svg deleted file mode 100644 index 6e1245d..0000000 --- a/src/public/_/icons/extra/settingslight.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/public/_/icons/favicon.ico b/src/public/_/icons/favicon.ico deleted file mode 100644 index 78c86dd..0000000 Binary files a/src/public/_/icons/favicon.ico and /dev/null differ diff --git a/src/public/_/icons/forum.png b/src/public/_/icons/forum.png deleted file mode 100644 index 4b0fa1b..0000000 Binary files a/src/public/_/icons/forum.png and /dev/null differ diff --git a/src/public/_/icons/hamburger.svg b/src/public/_/icons/hamburger.svg deleted file mode 100644 index 4da43ae..0000000 --- a/src/public/_/icons/hamburger.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/public/_/icons/home.svg b/src/public/_/icons/home.svg deleted file mode 100644 index c7d0035..0000000 --- a/src/public/_/icons/home.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/public/_/icons/homelight.svg b/src/public/_/icons/homelight.svg deleted file mode 100644 index 6d2788b..0000000 --- a/src/public/_/icons/homelight.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/public/_/icons/homelightselected.svg b/src/public/_/icons/homelightselected.svg deleted file mode 100644 index 788ae2f..0000000 --- a/src/public/_/icons/homelightselected.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/public/_/icons/jobs.svg b/src/public/_/icons/jobs.svg deleted file mode 100644 index 5eccad1..0000000 --- a/src/public/_/icons/jobs.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/public/_/icons/jobslight.svg b/src/public/_/icons/jobslight.svg deleted file mode 100644 index 5fce2c9..0000000 --- a/src/public/_/icons/jobslight.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/public/_/icons/jobslightselected.svg b/src/public/_/icons/jobslightselected.svg deleted file mode 100644 index 0002ad1..0000000 --- a/src/public/_/icons/jobslightselected.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/public/_/icons/letter.svg b/src/public/_/icons/letter.svg deleted file mode 100644 index 9a1d4d8..0000000 --- a/src/public/_/icons/letter.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/public/_/icons/logo.svg b/src/public/_/icons/logo.svg deleted file mode 100644 index 841bf5f..0000000 --- a/src/public/_/icons/logo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/public/_/icons/logowhite.svg b/src/public/_/icons/logowhite.svg deleted file mode 100644 index e6278eb..0000000 --- a/src/public/_/icons/logowhite.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/public/_/icons/noun-job-5995378.svg b/src/public/_/icons/noun-job-5995378.svg deleted file mode 100644 index e86b81a..0000000 --- a/src/public/_/icons/noun-job-5995378.svg +++ /dev/null @@ -1 +0,0 @@ -Created by nasrilfrom the Noun Project \ No newline at end of file diff --git a/src/public/_/icons/people.svg b/src/public/_/icons/people.svg deleted file mode 100644 index f2b266a..0000000 --- a/src/public/_/icons/people.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/public/_/icons/peoplelight.svg b/src/public/_/icons/peoplelight.svg deleted file mode 100644 index 3e83d03..0000000 --- a/src/public/_/icons/peoplelight.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/public/_/icons/peoplelightselected.svg b/src/public/_/icons/peoplelightselected.svg deleted file mode 100644 index 0f24e99..0000000 --- a/src/public/_/icons/peoplelightselected.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/public/_/icons/pin.svg b/src/public/_/icons/pin.svg deleted file mode 100644 index e662f1d..0000000 --- a/src/public/_/icons/pin.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/public/_/icons/pinlight.svg b/src/public/_/icons/pinlight.svg deleted file mode 100644 index a4b91eb..0000000 --- a/src/public/_/icons/pinlight.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/public/_/icons/play.svg b/src/public/_/icons/play.svg deleted file mode 100644 index e69de29..0000000 diff --git a/src/public/_/icons/runner.svg b/src/public/_/icons/runner.svg deleted file mode 100644 index 1e279a8..0000000 --- a/src/public/_/icons/runner.svg +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/public/_/icons/settings.svg b/src/public/_/icons/settings.svg deleted file mode 100644 index 90ed89e..0000000 --- a/src/public/_/icons/settings.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/public/_/icons/settingslight.svg b/src/public/_/icons/settingslight.svg deleted file mode 100644 index 4e082ee..0000000 --- a/src/public/_/icons/settingslight.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/public/_/icons/settingslightselected.svg b/src/public/_/icons/settingslightselected.svg deleted file mode 100644 index 0a845e1..0000000 --- a/src/public/_/icons/settingslightselected.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/public/_/icons/stop.svg b/src/public/_/icons/stop.svg deleted file mode 100644 index e69de29..0000000 diff --git a/src/public/_/icons/time.svg b/src/public/_/icons/time.svg deleted file mode 100644 index 30a9b5b..0000000 --- a/src/public/_/icons/time.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/public/_/icons/timelight.svg b/src/public/_/icons/timelight.svg deleted file mode 100644 index 615be77..0000000 --- a/src/public/_/icons/timelight.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/public/_/icons/trash.svg b/src/public/_/icons/trash.svg deleted file mode 100644 index 5d74e3b..0000000 --- a/src/public/_/icons/trash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/util.js b/src/util.js index 1dea37b..eab5ed2 100644 --- a/src/util.js +++ b/src/util.js @@ -1,9 +1,32 @@ +import { PushNotifications } from '@capacitor/push-notifications'; import { Preferences } from '@capacitor/preferences'; +import { Filesystem, Directory } from '@capacitor/filesystem'; +import { Haptics, ImpactStyle } from '@capacitor/haptics'; +import { Camera, CameraResultType, CameraSource } from '@capacitor/camera'; +import { Geolocation } from '@capacitor/geolocation'; +import { SplashScreen } from '@capacitor/splash-screen'; + +window.capacitor = { + Preferences, + PushNotifications, + Filesystem, + Directory, + Haptics, + ImpactStyle, + Camera, + CameraResultType, + CameraSource, + Geolocation, + SplashScreen, +} + const env = import.meta.env -export default class util { +window.util = class util { static HOST = env.VITE_API_URL + static PushNotifications = PushNotifications + static Preferences = Preferences static async authFetch(url, options = {}) { const { value: token } = await Preferences.get({ key: 'auth_token' });