From 8d5a63b2624291a656f0bf8fcf31b5c1c2677a8e Mon Sep 17 00:00:00 2001 From: metacryst Date: Thu, 9 Apr 2026 06:22:32 -0500 Subject: [PATCH] fetch everything remotely --- capacitor.config.json | 4 - ios/App/App/Info.plist | 16 +- ios/App/Podfile | 1 + ios/App/Podfile.lock | 12 +- package.json | 1 + src/.env.development | 2 +- src/Home/AuthPage/AuthPage.js | 106 -- src/Home/AuthPage/EnterCode.js | 101 -- src/Home/AuthPage/Login.js | 147 -- src/Home/AuthPage/Signup.js | 214 --- src/Home/ConnectionError.js | 23 - src/Home/Home.js | 155 -- src/Home/Login.js | 61 - src/Profile/Profile.js | 222 --- src/_/code/ws/Connection.js | 68 - src/_/code/ws/Socket.js | 49 - src/_/code/ws/shim/fs.js | 1 - src/_/imgs/logo.png | Bin 12050 -> 0 bytes src/apps/Forum/Forum.js | 83 -- src/apps/Forum/ForumPanel.js | 193 --- src/apps/Market/Market.js | 105 -- src/apps/Market/MarketGrid.js | 140 -- src/apps/Market/MarketSidebar.js | 85 -- src/apps/Messages/Messages.js | 189 --- src/apps/Messages/MessagesPanel.js | 56 - src/apps/Messages/MessagesSidebar.js | 73 - src/apps/People/People.js | 105 -- src/apps/Tasks/Tasks.js | 153 -- src/components/AppMenu.js | 72 - src/components/AppWindow.js | 30 - src/components/AppWindowContainer.js | 37 - src/components/Sidebar.js | 184 --- src/components/Toggle.js | 50 - src/components/TopBar.js | 54 - src/index.html | 23 +- src/index.js | 229 +-- src/public/_/code/quill.js | 1355 ------------------ src/public/_/icons/column2.svg | 4 - src/public/_/icons/columnwhite.png | Bin 2966 -> 0 bytes src/public/_/icons/columnwhite.svg | 9 - src/public/_/icons/events.svg | 3 - src/public/_/icons/eventslight.svg | 3 - src/public/_/icons/eventslightselected.svg | 3 - src/public/_/icons/extra/settings.svg | 3 - src/public/_/icons/extra/settingsdark.svg | 3 - src/public/_/icons/extra/settingslight.svg | 3 - src/public/_/icons/favicon.ico | Bin 101772 -> 0 bytes src/public/_/icons/forum.png | Bin 48327 -> 0 bytes src/public/_/icons/hamburger.svg | 1 - src/public/_/icons/home.svg | 4 - src/public/_/icons/homelight.svg | 4 - src/public/_/icons/homelightselected.svg | 1 - src/public/_/icons/jobs.svg | 3 - src/public/_/icons/jobslight.svg | 3 - src/public/_/icons/jobslightselected.svg | 3 - src/public/_/icons/letter.svg | 4 - src/public/_/icons/logo.svg | 7 - src/public/_/icons/logowhite.svg | 7 - src/public/_/icons/noun-job-5995378.svg | 1 - src/public/_/icons/people.svg | 3 - src/public/_/icons/peoplelight.svg | 3 - src/public/_/icons/peoplelightselected.svg | 13 - src/public/_/icons/pin.svg | 1 - src/public/_/icons/pinlight.svg | 1 - src/public/_/icons/play.svg | 0 src/public/_/icons/runner.svg | 71 - src/public/_/icons/settings.svg | 3 - src/public/_/icons/settingslight.svg | 3 - src/public/_/icons/settingslightselected.svg | 3 - src/public/_/icons/stop.svg | 0 src/public/_/icons/time.svg | 3 - src/public/_/icons/timelight.svg | 3 - src/public/_/icons/trash.svg | 1 - src/util.js | 25 +- 74 files changed, 91 insertions(+), 4510 deletions(-) delete mode 100644 src/Home/AuthPage/AuthPage.js delete mode 100644 src/Home/AuthPage/EnterCode.js delete mode 100644 src/Home/AuthPage/Login.js delete mode 100644 src/Home/AuthPage/Signup.js delete mode 100644 src/Home/ConnectionError.js delete mode 100644 src/Home/Home.js delete mode 100644 src/Home/Login.js delete mode 100644 src/Profile/Profile.js delete mode 100644 src/_/code/ws/Connection.js delete mode 100644 src/_/code/ws/Socket.js delete mode 100644 src/_/code/ws/shim/fs.js delete mode 100644 src/_/imgs/logo.png delete mode 100644 src/apps/Forum/Forum.js delete mode 100644 src/apps/Forum/ForumPanel.js delete mode 100644 src/apps/Market/Market.js delete mode 100644 src/apps/Market/MarketGrid.js delete mode 100644 src/apps/Market/MarketSidebar.js delete mode 100644 src/apps/Messages/Messages.js delete mode 100644 src/apps/Messages/MessagesPanel.js delete mode 100644 src/apps/Messages/MessagesSidebar.js delete mode 100644 src/apps/People/People.js delete mode 100644 src/apps/Tasks/Tasks.js delete mode 100644 src/components/AppMenu.js delete mode 100644 src/components/AppWindow.js delete mode 100644 src/components/AppWindowContainer.js delete mode 100644 src/components/Sidebar.js delete mode 100644 src/components/Toggle.js delete mode 100644 src/components/TopBar.js delete mode 100644 src/public/_/code/quill.js delete mode 100644 src/public/_/icons/column2.svg delete mode 100644 src/public/_/icons/columnwhite.png delete mode 100644 src/public/_/icons/columnwhite.svg delete mode 100644 src/public/_/icons/events.svg delete mode 100644 src/public/_/icons/eventslight.svg delete mode 100644 src/public/_/icons/eventslightselected.svg delete mode 100644 src/public/_/icons/extra/settings.svg delete mode 100644 src/public/_/icons/extra/settingsdark.svg delete mode 100644 src/public/_/icons/extra/settingslight.svg delete mode 100644 src/public/_/icons/favicon.ico delete mode 100644 src/public/_/icons/forum.png delete mode 100644 src/public/_/icons/hamburger.svg delete mode 100644 src/public/_/icons/home.svg delete mode 100644 src/public/_/icons/homelight.svg delete mode 100644 src/public/_/icons/homelightselected.svg delete mode 100644 src/public/_/icons/jobs.svg delete mode 100644 src/public/_/icons/jobslight.svg delete mode 100644 src/public/_/icons/jobslightselected.svg delete mode 100644 src/public/_/icons/letter.svg delete mode 100644 src/public/_/icons/logo.svg delete mode 100644 src/public/_/icons/logowhite.svg delete mode 100644 src/public/_/icons/noun-job-5995378.svg delete mode 100644 src/public/_/icons/people.svg delete mode 100644 src/public/_/icons/peoplelight.svg delete mode 100644 src/public/_/icons/peoplelightselected.svg delete mode 100644 src/public/_/icons/pin.svg delete mode 100644 src/public/_/icons/pinlight.svg delete mode 100644 src/public/_/icons/play.svg delete mode 100644 src/public/_/icons/runner.svg delete mode 100644 src/public/_/icons/settings.svg delete mode 100644 src/public/_/icons/settingslight.svg delete mode 100644 src/public/_/icons/settingslightselected.svg delete mode 100644 src/public/_/icons/stop.svg delete mode 100644 src/public/_/icons/time.svg delete mode 100644 src/public/_/icons/timelight.svg delete mode 100644 src/public/_/icons/trash.svg 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 b7895e9b4118f87ad2e4844756ec9063ff74dfa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12050 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 nvGzZU|0v- { -// 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 9e1ff13c6b5ecca6fb705642ed1e7cba1e3fcf8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2966 zcmV;H3u*L;P)005u}1^@s6i_d2*00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yP%l~nDv9Iv#Br6f%RAZ0~TgWo6+ApgIGpPOQnAn-?30VuGE0B{Y{&lMw(Egj^a_HeFzr?hXn$_4P)#}aXyphQpZwB1C$f~WuhP@ z4S-U7i@f3e(D&j5?G_7Ysais)qM({d{r^AnjMW}-@);}M);*Yai{Dg1ce@X+uzI(rV z|K^d(=ox`EhBd@|#43B}+bw&wfVs<6>IntMM1Al&OIB*vrwF|XFkJD0D|8cK$3C2p z2Oz$vm@7_RnAwR|-|FZ<#06;P_8`6i=;hngHhpsX6?vdSfNs3AVt!4)KRrWQJy3kC zjmLt7sSsfQpxn4#!c@xz;#wcn#^ZTwSA0&hXBBh7XRddj0R7Pz;f}ORxg_U?^UfPGqm6Ec}f zkEsha&n@GE*`!uEmUPk6FCV)5Af4TPu&*uSJ#BFhDP&q#hy4qBdgNdH^YwePPj?<$ zbNejy0L4T)XrXFmWcEk^Vw3L6~6WNBRx`-&+-+F6Lm)`O_w*q z%d?Dbp1w6xFIs&2WTvHp?{Hl4sq+OdD@(n5^HzCV1*y*D&3@8W)BT)eJ^g2)(&Wz0 zuI&%`JS!(J&N`$Qm~|cB{@Y7(i~|d(Y)6*W-|41%`?u6~ZVfHd1m=9svI81|D)BXV zeC>Lz*1Hg&$uRQVsd!^a0?16mWb{ML8Pw^z<$s#18>i2U|MI2t^N}M9O-*#A?(vz9 z%0|2rKYQ`x=M3N+_?ReSqNu#^;D-vQ&fb3ul&wt5h>QnO1!WIk8u3bwh zB5dwX{qMi>$rmLgN{JbiH37PsLMjWQ#ku)marIE%Awr7-%h2(k?eOYPva3s_WZ5gp zN$}P&U{eQu7DT7ykS6zDWo(@X6I&y7O-VWRXx{%+66J4A5PgY2YXPoX$Z}b@wt{`F z^O$oTYzSmW4|F}HBqNx_0Or-M^hnga zc+vWYS^HeJLF2{EleO|To7!@)ms_dB+1&?ED$gNzc*}-47oo9IP0pv*EK6gSe2YLk zhRkR>+XS?R_9UX~+9uuKWR{ZUxtmD*JeV#2lp;EWG!F-yL9Pin8}%V4Vy z$de~fB=m(fxx%EFW#nFjW|I@qfl2o#L2Pa6QGmtmjYyI^J7yD6rx6z0uYACt-nWmo zdE?RfS&d3qbwHl8TW+m3Ppno@i{x3?x-02WDQD{Qs+dobK|`JVt@uPwdTgsqjaxF7 zq3rs4{q!xx$uWPpey?l;l1aQ!2|w40ol`ebE)rf-YK7bFwn5Cz8ljL`$1Q}os64rF z7cX_ZcUV_>*}FHdYp)kD+7&mw$u-@sjB<5qY;R;8 z@-6n#_!Xer@L`SIdfq(!S``~~+#+1|+S`vngtjUijR5nYe4uo7kC<(mUrNVsKKokK zF1WCGzbv%7HB&7M3Iq=g*l`3K9?=Q#Jz<7hzO2#7y#T3&L;E)*xz{<1)2;AV% zlZ;z{-1uN51XA`2Ptvx8BNiZ+R8-78rJYN4$UN&Q$Mfy=n9k^BcJXbdIze~h zHmBWOuyehr^~*z(lc1Xbg*%-({|A)Ow||C`$167onUDn=;!&+h4Ak%YWfzgO1#$wM zkOgb}sKQEhQswzYTo<(bdYFdkMazbYrc=5uX!-RZz+Ozi%nMs>2vCZ##K21SLFxq< zR7FPAs-rQEvJX-(z#jK^FBibk7+e3H#JlhV_f}2? z2+tfk;h6}F05Qud#^6;Oo{894KNPMVW2Fu=qJLMNqkB}^=(`PnZf)%}=40!Lv+PK% zlBZO6u?Fb-E!PfiZXT9q{B*oYq0BwoX4!VwZ)qe5l?0MLYCttD)A1*jYmP_65*gEL&+2><|G0;>Z600000 z005CNx&d4)59F?#lOBU(0wMy?NMP!q4*&qV0qYS1Xr)0wwF4l;?;gW(eZli?X8-^I M07*qoM6N<$f`USh!T - - - - - - - - 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 78c86dd8119307f91adabc6e65fc2d2cbd5ccb71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101772 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 bv)g_?KZDiB|L+8TZ<6{i^Yw>C diff --git a/src/public/_/icons/forum.png b/src/public/_/icons/forum.png deleted file mode 100644 index 4b0fa1b93fc136be8a0117d9e62a9b1c93ec19e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48327 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 \ 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' });