Compare commits

..

56 Commits

Author SHA1 Message Date
metacryst
2faa9e740e xcode changes for release 2026-03-27 06:03:28 -05:00
metacryst
a589977015 notifications setting 2026-03-26 07:44:51 -05:00
metacryst
06e2fabe81 push notifications working 2026-03-26 05:56:14 -05:00
metacryst
d107d68bcc push notifications working, server functions coming from backend 2026-03-26 02:32:59 -05:00
metacryst
472e69d3c0 adding error handling for signup, moving delete account button in sidebar to bottom 2026-03-25 23:41:45 -05:00
1c61a4d202 Delete account in sidebar 2026-03-24 16:39:53 -04:00
40b0855ca5 EnterCode Signup working
- After fetching join code in EnterCode(), it sets networkId attribute on "signup-" (Signup.js)
- Signup.js then includes the attribute in the call body
- Modified all calls to /signout, /profile, /login, /signup to be prefixed by '/auth'
- Added '/auth' to vite config file
- Modified final "else if" statement in .attr in quill.js to return `this.getAttribute(arg1)` instead of `this.getAttribute("")`
2026-03-24 15:48:23 -04:00
124066da59 Apps load from server
- Deleted /Jobs, /Announcements, /Events, /People
- Commented out Forum and ForumPanel
- Deleted /components/SearchBar, /components/LoadingCircle, /components/AddButton
2026-03-24 14:51:44 -04:00
metacryst
f3aceb69af accepting code for auth page 2026-03-24 10:39:57 -05:00
metacryst
a87d521a4f improving authpage 2026-03-24 06:07:29 -05:00
metacryst
c5b71add07 custom app rendering 2026-03-24 04:16:00 -05:00
metacryst
881c9408b6 new announcements page, adding searchbar and message input. 2026-03-23 05:26:55 -05:00
metacryst
35f0fe3654 smaller font for header, announcements full size always 2026-03-21 06:54:53 -05:00
metacryst
21b7b0a252 Sidebar fully functional 2026-03-21 03:10:50 -05:00
metacryst
1c6f12c210 preliminary sidebar fixes 2026-03-20 17:16:05 -05:00
56f7c7d3a3 Sidebar + Profile changes
- Sidebar displays profile photo and name, can be tapped on
- Profile now has "Tap to edit" button on photo
2026-03-20 14:13:14 -04:00
c7ddb02ac1 Sidebar content moved to new draggable sidebar 2026-03-20 13:43:11 -04:00
63fbab34ce Announcements page
- Updated handlers.js
- Repurposed Forum and ForumPanel for Announcements
- Adjusted styling
- Commented out long drag logs
2026-03-20 12:36:23 -04:00
metacryst
8fad5d7717 fix events icon, misc errors, new sidebar started 2026-03-20 09:27:56 -05:00
metacryst
41a9c9d269 adding state() to quill, after-request error handling to login 2026-03-20 00:36:33 -05:00
dd1ec2c374 Upload image
- Image upload works on profile
- added multer into package.json for handling image files
- files are saved under /db/images/users/user-id/profile.ext
2026-03-19 20:25:23 -04:00
58589c56dd Add event + add job form
- Modified handlers to catch errors
- Added placeholder "No location added", etc. messages to Job/Event cards
- Added EventForm.js and JobForm.js for adding
- EventForm and JobForm are animated to slide up from bottom
- Modified openProfile/closeProfile logic
- Fixed SidebarItem().onClick() firing twice bug (switched to .onTap)
- Profile is now animated to slide up from the bottom
2026-03-19 15:32:51 -04:00
metacryst
8dd2312aa0 fixing logout, people not scrolling, sidebar being too short 2026-03-19 08:54:53 -05:00
metacryst
5a56dfa051 improving styling, fixing bugs with profile, login error handling 2026-03-19 07:41:18 -05:00
3a5214ed45 Added missing Profile element
- User's "joined" info
2026-03-19 00:19:41 -04:00
72f0518f9d Profile + Edit Bio + Logout + styling
- Added handler for editing bio to handlers.js
- Added openProfile() and closeProfile() buttons in AppWindowContainer (Profile page is global)
- Added Logout and Profile functionality to Sidebar.js
  - SidebarItem(text).onClick() fires twice, unable to resolve
- Adjust Login page styling
- Added onLogout() to index.js (removes auth_token)
- Added Profile.js, displays user's profile picture (placeholder), name, and bio. User can edit bio.
- Added removeAuthToken() to util.js
- Added /signout to vite config
2026-03-19 00:14:29 -04:00
ede464fb0d Search in Events/Jobs
- SearchBar now dispatches 'jobsearch' or 'eventsearch' event whenever the user submits a search query
- Jobs/Events will then receive searchText to do general search
- Fixed bug where Jobs/Events wouldn't scroll anymore
2026-03-18 20:11:08 -04:00
2082e0c7bc Signup/Login + styling adjustments
- Modified SearchBar styling
- Modified TopBar to display blank circle if the user has no networks (previously missing image icon)
- Refactored Login into AuthPage.js
- AuthPage contains a tab selector for switching between Signup and Login
- Both Login/Signup send the request and either receive an auth_token or an error message
- If auth_token, user will be logged in as usual, in both cases
- Signup validates user input before sending request
- Added /signup target in vite config file
2026-03-18 17:36:03 -04:00
metacryst
d1e4814593 successfully connecting to prod 2026-03-17 07:00:27 -05:00
metacryst
530ea7da89 app icon, styling, light mode, top bar component 2026-03-17 05:33:28 -05:00
metacryst
5903bafee5 better styling 2026-03-16 23:55:56 -05:00
8452841460 Styled Jobs and Events like Figma mockups
- Modified some darkmode css values to match those on the figma
- Fixed misnamed calls to var(--darkaccent) from var(--accentdark)
- Commented out trash/delete button on EventCard and JobCard for now
- Hid scrollbar on Events/Jobs
- Fixed mis-centered People list
- Fixed colors in searchbar in events/jobs
2026-03-16 21:40:03 -04:00
metacryst
69b359d9a1 making login work for prod 2026-03-16 07:56:40 -05:00
metacryst
a626abe1c3 improve styling, fix bottom bar underline bug 2026-03-16 01:09:48 -05:00
834d5e763e Connected DB to events/jobs + more
- Modified handlers.js to be the same as on frm.so
- Added --trash-src to shared.css
- Modified Event and Job cards to include a trash icon for deleting
- Deleting works, it just does not smoothly re-render yet
- Adjusted visual bug on Events/Jobs where the contents of the AppWindow would overflow vertically. They now scroll and the title/search bar remain fixed.
- Refactored part of People.js into PeopleCard.js
2026-03-15 19:45:04 -04:00
dde27f9b31 Merge branch 'main' of https://git.sun.museum/sam/ForumMobile 2026-03-15 16:00:38 -04:00
0d5e68188d Set up bridge folder + changes to Events/Jobs
- Copied bridge folder from frm.so
- All handlers in handlers.js added from frm.so
- Modified Events and Jobs pages' default events/jobs to model data structure from SQL/server
- Set up getJobs(), checkForUpdates() on both Events/Jobs to fetch new items and update when needed
2026-03-15 16:00:37 -04:00
metacryst
fc2d9c2bc9 making ready for production, fixing app menu spacing, some better icons 2026-03-15 08:13:44 -05:00
cb11d68fa7 SearchBar component + Jobs changes + Events changes
- Added SearchBar.js to components (repeated in Jobs and Events)
- Set .position("relative") on AppWindow for LoadingCircles and future fixed/absolute elements
- Refactored Events.js and Jobs.js
- Added unfinished JobForm.js for editing/adding Jobs, preliminary designs/setup
2026-03-14 23:26:15 -04:00
metacryst
07f431e2a3 merge 2026-03-14 16:53:38 -05:00
e85ffc66f8 Jobs + Events Pages
- Added Jobs/Events pages, need to set up with backend once complete
- Added missing icons and fixed incorrect icons
- Made AppMenu icons highlight when selected
- Removed Settings from AppMenu
- Fixed undefined data in People
2026-03-14 17:01:06 -04:00
metacryst
cc8b5035fe Fixed screen size, added dark mode app menu icons, added haptics, added keyboard dismiss with swipe 2026-02-16 06:41:05 -06:00
metacryst
83a640433a adding app icon and splash screen 2026-02-15 05:32:58 -06:00
metacryst
cee8ebecc5 built ios app working 2026-02-14 21:43:55 -06:00
metacryst
f02f181058 Introducing dark mode, deriving apps in bottom bar from db 2026-02-13 20:25:46 -06:00
metacryst
4432acfea5 [BUG] fix double click to open app 2026-02-09 02:56:00 -06:00
metacryst
9d0149de75 Merge branch 'main' of https://git.sun.museum/sam/ForumMobile 2026-02-09 02:02:53 -06:00
metacryst
e548dbb6c9 add people icon 2026-02-09 02:02:19 -06:00
770d3bb012 Forum changes
- Added 2 new svg icons
- Fixed visual styling bugs on Forum page involving overflowing containers
- Added current network name underneath "Forum" title
- Input in Forum.js now sends ws message to server with new post, which is then broadcasted through the ws
- Forum app now displays a list of posts for the current network showing the author, time, text, whether it's been edited, and whether it's yours
- ForumPanel.js .onClick() contains the ws calls for DELETE and PUT
- Fixed bug where Forum would not scroll to top upon a new post/onAppear
- Modified ws GET call in ForumPanel.js to reflect new format
- Added .onEvent() handlers inForumPanel.js for "new-post", "deleted-post", and "edited-post"
- Changed AppMenu People icon
-
2026-02-08 22:44:19 -05:00
metacryst
3bf23daa7a Merge branch 'main' of https://git.sun.museum/sam/ForumMobile 2026-02-06 06:41:56 -06:00
metacryst
6cbb4c8dad basic sidebar toggle 2026-02-06 06:41:54 -06:00
b20ce6da06 People app and navigation + fixes
Index.js
- Fixed issue where incorrect pathname was set while not part of an organization.
- onNavigate(), fetchAppData(), getDefaultNetworkName(), and getDefaultAppName() all modified to account for user not being part of an organization (default path set to /my/dashboard)
- onNavigate() modified to properly set defaults for currentNetwork and currentApp, and modified to fix issue where app would crash when attempting to access nonexistent 'this.currentNetwork.data'
- onNavigate() now correctly switches currentApp

Home.js
- Changed ZStack to a VStack
- Replaced Jobs switch case with People
- Adjusted styling for AppMenu() and VStack height values
- Changed .onNavigate() from function() to () => {} for correct this binding
- !!!(TEMP FIX)!!!: Added extra rerender() call in an .onClick() after .onNavigate()

AppMenu()
- Modified app icons to be highlighted when active
- Changed Forum's app icon to redirect from "/" to "/dashboard"
- Removed fixed styling to account for Home's new responsive VStack layout

People.js
- Added People.js

Messages.js
- Fixed missing 'global." before Socket.send
- Changed 'vh' height values to 'pct' to account for AppMenu() at bottom
- Fixed misnamed modifiers

Forum.js
- Adjusted styling
- Changed 'vh' height values to 'pct' to account for AppMenu() at bottom

styles.css
- Added missing custom color vars
2026-02-02 04:34:26 -05:00
a68f35faf5 WS connection crash fix
- constructor() in index.js would attempt to fetch profile with getProfile() before websocket connection was established
2026-02-01 23:07:52 -05:00
metacryst
6262ce53aa establishing ws connection, starting on messages section 2026-02-01 16:11:11 -06:00
metacryst
6d50337e3b getting things going 2026-01-29 08:20:27 -06:00
sam
c09b08a474 Merge pull request 'master' (#1) from master into main
Reviewed-on: #1
2026-01-25 06:29:01 -06:00
94 changed files with 2651 additions and 2846 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ node_modules/
.DS_Store
.sourcemaps
dist/
package-lock.json

View File

@@ -1,10 +1,17 @@
{
"appId": "so.blockcatcher.app",
"appName": "Blockcatcher",
"appId": "so.forum.app",
"appName": "Forum",
"webDir": "dist",
"ios": {
"allowsBackForwardNavigationGestures": true
},
"server": {
"url": "http://sam.local:5173",
"cleartext": true
},
"plugins": {
"SplashScreen": {
"launchAutoHide": false
"launchAutoHide": true
}
}
}

158
doc.md Normal file
View File

@@ -0,0 +1,158 @@
Quill is a SwiftUI-style JavaScript framework. It makes use of components called Shadows, which are HTML Custom Elements.
### Getting Started:
Take index.js and put it in your app. Typically as quill.js. Then import it in the head of the HTML.
### Basic Overview:
Quill uses components called Shadows. Each Shadow is a Custom HTML Element (https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements)
```
class Home extends Shadow {
render() {
}
}
register(Home)
```
Once created, it can be imported like
```
import "Home.js"
```
(Not how we are NOT importing the actual class object. If that happens, it will fail.)
Here is an example of Hello World:
```
class Home extends Shadow {
render() {
p("Hello World")
.x(50, vw)
.y(50, vh)
}
}
register(Home)
```
This will render a paragraph tag in the middle of the screen.
Here's what it will look like in HTML:
```
<body>
<home->
<p style="position: absolute; top: 50vh; left: 50vw;">Hello World</p>
</home->
</body>
```
Note: .x() and .y() are quill-specific functions that were created simply for nice syntax. However, this would also be valid:
```
p("Hello World")
.top(50, vh)
.left(50, vw)
```
There are quill functions for every HTML style attribute. If they have units, they will follow the pattern directly above, where the first parameter is the amount and the second parameter is the unit.
### Real Basic Example:
First, you need your index.html. Here is one:
```
<!DOCTYPE html>
<html lang="en" class="public">
<head>
<title>Parchment</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="/_/icons/quill.svg">
<link rel="stylesheet" href="/_/code/shared.css">
<script src="/_/code/quill.js"></script>
<script type="module" src="75820185/index.js"></script>
</head>
<body>
</body>
</html>
```
When starting, it is typical to make a "Home" shadow and import it in index.js. Here is an example:
index.js:
```
import "./Home.js"
Home()
```
Home.js:
```
import "../components/NavBar.js"
import "./HomeContent.js"
import "./Why.js"
import "./Events.js"
import "./Join.js"
import "./SignIn.js"
import "./Success.js"
class Home extends Shadow {
render() {
ZStack(() => {
NavBar()
img("/_/icons/logo.svg", "2.5em")
.onClick((done) => {
if(!done) return
window.navigateTo("/")
})
.position("absolute")
.left(50, vw).top(4, em)
.center()
.transform(`translate(${window.isMobile() ? "-50%" : "-2em"}, -50%)`)
switch(window.location.pathname) {
case "/":
HomeContent()
break;
case "/why":
Why()
break;
case "/events":
Events()
break;
case "/join":
Join()
break;
case "/success":
Success()
break;
}
})
.onNavigate(() => {
this.rerender()
})
}
}
register(Home)
```
Success.js:
```
class Success extends Shadow {
render() {
p("Thanks for your purchase! You will receive a confirmation email shortly. <br><br> <b>Keep that email; it will be checked at the door.</b>")
.x(50, vw).y(50, vh)
.center()
}
}
register(Success)
```

View File

@@ -18,6 +18,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
22352DD22F74F93C0052EF07 /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -73,6 +74,7 @@
504EC3061FED79650016851F /* App */ = {
isa = PBXGroup;
children = (
22352DD22F74F93C0052EF07 /* App.entitlements */,
50379B222058CBB4000EE86E /* capacitor.config.json */,
504EC3071FED79650016851F /* AppDelegate.swift */,
504EC30B1FED79650016851F /* Main.storyboard */,
@@ -361,15 +363,16 @@
baseConfigurationReference = FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 53DK57C7ZF;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 1.0.5;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = so.hyperia.app;
PRODUCT_BUNDLE_IDENTIFIER = russell.sam.forum;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_VERSION = 5.0;
@@ -382,14 +385,15 @@
baseConfigurationReference = AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 53DK57C7ZF;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = so.hyperia.app;
MARKETING_VERSION = 1.0.5;
PRODUCT_BUNDLE_IDENTIFIER = russell.sam.forum;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
SWIFT_VERSION = 5.0;

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "504EC3031FED79650016851F"
BuildableName = "App.app"
BlueprintName = "App"
ReferencedContainer = "container:App.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "504EC3031FED79650016851F"
BuildableName = "App.app"
BlueprintName = "App"
ReferencedContainer = "container:App.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "504EC3031FED79650016851F"
BuildableName = "App.app"
BlueprintName = "App"
ReferencedContainer = "container:App.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>

View File

@@ -7,10 +7,38 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.enableInteractiveKeyboard()
}
// Override point for customization after application launch.
return true
}
func enableInteractiveKeyboard() {
guard let window = UIApplication.shared.windows.first,
let rootView = window.rootViewController?.view else {
return
}
// Find WKWebView recursively
func findWebView(in view: UIView) -> WKWebView? {
if let webView = view as? WKWebView {
return webView
}
for subview in view.subviews {
if let found = findWebView(in: subview) {
return found
}
}
return nil
}
if let webView = findWebView(in: rootView) {
webView.scrollView.keyboardDismissMode = .interactive
print("✅ Interactive keyboard enabled!")
}
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
@@ -46,4 +74,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken)
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

View File

@@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "AppIcon-512@2x.png",
"filename" : "Group 73 (6).png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -1,23 +1,23 @@
{
"images" : [
{
"filename" : "Group 74.png",
"idiom" : "universal",
"filename" : "splash-2732x2732-2.png",
"scale" : "1x"
},
{
"filename" : "Group 75.png",
"idiom" : "universal",
"filename" : "splash-2732x2732-1.png",
"scale" : "2x"
},
{
"filename" : "Group 76.png",
"idiom" : "universal",
"filename" : "splash-2732x2732.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Hyperia</string>
<string>Forum</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@@ -22,6 +22,17 @@
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
@@ -33,17 +44,20 @@
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIRequiresFullScreen</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<key>NSPhotoLibraryUsageDescription</key>
<string>Access your photos to set a profile picture and share with others</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Used to find keep local information relevant.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Used to find forums and communities near you</string>
</dict>
</plist>

View File

@@ -14,6 +14,9 @@ def capacitor_pods
pod 'CapacitorCamera', :path => '../../node_modules/@capacitor/camera'
pod 'CapacitorGeolocation', :path => '../../node_modules/@capacitor/geolocation'
pod 'CapacitorGoogleMaps', :path => '../../node_modules/@capacitor/google-maps'
pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
pod 'CapacitorPushNotifications', :path => '../../node_modules/@capacitor/push-notifications'
pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen'
end

View File

@@ -11,6 +11,12 @@ PODS:
- Capacitor
- Google-Maps-iOS-Utils (~> 5.0)
- GoogleMaps (~> 8.4)
- CapacitorHaptics (7.0.3):
- Capacitor
- CapacitorPreferences (7.0.4):
- Capacitor
- CapacitorPushNotifications (7.0.6):
- Capacitor
- CapacitorSplashScreen (7.0.3):
- Capacitor
- Google-Maps-iOS-Utils (5.0.0):
@@ -28,6 +34,9 @@ DEPENDENCIES:
- "CapacitorCordova (from `../../node_modules/@capacitor/ios`)"
- "CapacitorGeolocation (from `../../node_modules/@capacitor/geolocation`)"
- "CapacitorGoogleMaps (from `../../node_modules/@capacitor/google-maps`)"
- "CapacitorHaptics (from `../../node_modules/@capacitor/haptics`)"
- "CapacitorPreferences (from `../../node_modules/@capacitor/preferences`)"
- "CapacitorPushNotifications (from `../../node_modules/@capacitor/push-notifications`)"
- "CapacitorSplashScreen (from `../../node_modules/@capacitor/splash-screen`)"
SPEC REPOS:
@@ -47,6 +56,12 @@ EXTERNAL SOURCES:
:path: "../../node_modules/@capacitor/geolocation"
CapacitorGoogleMaps:
:path: "../../node_modules/@capacitor/google-maps"
CapacitorHaptics:
:path: "../../node_modules/@capacitor/haptics"
CapacitorPreferences:
:path: "../../node_modules/@capacitor/preferences"
CapacitorPushNotifications:
:path: "../../node_modules/@capacitor/push-notifications"
CapacitorSplashScreen:
:path: "../../node_modules/@capacitor/splash-screen"
@@ -56,11 +71,14 @@ SPEC CHECKSUMS:
CapacitorCordova: bf648a636f3c153f652d312ae145fb508b6ffced
CapacitorGeolocation: b96474c3259dd4a294227ea8ec19140b1837cceb
CapacitorGoogleMaps: 20b5445a532f80dbb120fa99941fd094bcc88af6
CapacitorHaptics: d17da7dd984cae34111b3f097ccd3e21f9feec62
CapacitorPreferences: d82a7e3b95fcab43a553268b803356522910d153
CapacitorPushNotifications: c6158ba6f3777f281a675aa43e4011e9723e822b
CapacitorSplashScreen: d06ae8804808e9f649a08e7bb7f283c77b688084
Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321
GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d
IONGeolocationLib: 20f9d0248a0b5264511fb57a37e25dd2badf797a
PODFILE CHECKSUM: 1f8c41a3cb5e4540693adb6a47064e328eec261d
PODFILE CHECKSUM: 7fad0e16088b635c7bc1980fea245d4a60cc4bbe
COCOAPODS: 1.15.2

2275
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,12 +13,15 @@
"preview": "vite preview"
},
"dependencies": {
"@capacitor/camera": "latest",
"@capacitor/core": "latest",
"@capacitor/camera": "^7.0.2",
"@capacitor/core": "^7.4.4",
"@capacitor/geolocation": "^7.1.5",
"@capacitor/google-maps": "^7.2.0",
"@capacitor/haptics": "^7.0.3",
"@capacitor/ios": "^7.4.4",
"@capacitor/splash-screen": "latest"
"@capacitor/preferences": "^7.0.4",
"@capacitor/push-notifications": "^7.0.6",
"@capacitor/splash-screen": "^7.0.3"
},
"devDependencies": {
"@capacitor/cli": "latest",

View File

@@ -1,29 +1,64 @@
## Created with Capacitor Create App
## Run in Browser
This app was created using [`@capacitor/create-app`](https://github.com/ionic-team/create-capacitor-app),
and comes with a very minimal shell for building an app.
```npm run start```
### Running this example
### Browser: Dev Frontend and Dev Backend (localhost)
This option should be at the top level of capacitor.config.json
"server": {
"url": "http://sam.local:5173",
"cleartext": true
},
To run the provided example, you can use `npm start` command.
### Browser: Prod Frontend and Prod Backend
Run:
vite build
npx serve dist
```bash
npm start
```
If you need to login again:
run localStorage.clear() in the browser dev tools console and then refresh the page.
### Background Color
In src/manifest.json, "#31d53d" refers to the green color which is visible in the background in the web version. This is not visible in the built version.
### Running iOS
## Run On Device
https://capacitorjs.com/docs/ios#adding-the-ios-platform
One-Time Install:
npm install @capacitor/ios
npx cap add ios
To Open XCode:
npx cap open ios
To Rerun:
Run this command to rebuild for iOS
npm run build && npx cap copy ios
### Note
You need to be in mobile mode in order for the app to work, in the top right corner of dev tools.
If getting black screen:
npx cap sync iOS
### iOS: Dev Frontend and Dev Backend (localhost)
This option should be at the top level of capacitor.config.json
"server": {
"url": "http://sam.local:5173",
"cleartext": true
},
### iOS: Dev Frontend with Prod Backend (frm.so)
Add "https://frm.so" to VITE_API_URL in .env.development
### iOS: Prod Frontend and Prod Backend (frm.so)
Remove the "server" object from capacitor.config
### Various Commands
npx cap config - this will list the full configuration currently being used
### Architecture
In Development, API routes are routed using the vite.config.js.
### Notes
Background Color:
In src/manifest.json, "#31d53d" refers to the green color which is visible in the background in the web version. This is not visible in the built version.
Test Push Notifications:
https://icloud.developer.apple.com/dashboard/notifications/teams/53DK57C7ZF/app/russell.sam.forum/notifications/create?notificationId=8bb87cf2-9590-4a63-b7e1-e4c7f2a2c879&environment=DEVELOPMENT&notificationType=push
Note: Even if built in "production" mode, the tokens will still be considered "development" by Apple until the app is actually deployed

1
src/.env.development Normal file
View File

@@ -0,0 +1 @@
VITE_API_URL=https://frm.so

1
src/.env.production Normal file
View File

@@ -0,0 +1 @@
VITE_API_URL=https://frm.so

View File

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

View File

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

View File

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

147
src/Home/AuthPage/Login.js Normal file
View File

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

210
src/Home/AuthPage/Signup.js Normal file
View File

@@ -0,0 +1,210 @@
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)) {
this.errorMessage = "";
await this.requestSignup(data);
} else {
console.log(this.errorMessage)
}
})
}
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)

View File

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

153
src/Home/Home.js Normal file
View File

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

222
src/Profile/Profile.js Normal file
View File

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

110
src/_/code/shared.css Normal file
View File

@@ -0,0 +1,110 @@
:root {
--main: #FFE9C8;
--accent: #570b0b;
--darkaccent: #dfc9ac;
--text: #340000;
--yellow: #f1f3c3;
--bone: #fff2e7;
--gold: #FEBA7D;
--divider: #bb7c36;
--green: #0857265c;
--red: #ff0000;
--quillred: #DE3F3F;
--darkred: #6b2c1d;
--brown: #812A18;
--sidebar: #698b6f;
--divider: #523636;
--darkbrown: #3f0808;
--darkgrey: #5c4646;
--headertext: #433c36e2;
--searchbackground: #ffeed8;
--loginButton: var(--main);
--loginBackground: #d96b6b;
--home-src: /_/icons/home.svg;
--home-selected-src: /_/icons/homelightselected.svg;
--people-src: /_/icons/people.svg;
--people-selected-src: /_/icons/peoplelightselected.svg;
--settings-src: /_/icons/settings.svg;
--settings-selected-src: /_/icons/settingslightselected.svg;
--events-src: /_/icons/events.svg;
--events-selected-src: /_/icons/eventslightselected.svg;
--jobs-src: /_/icons/jobs.svg;
--jobs-selected-src: /_/icons/jobslightselected.svg;
--column-src: /_/icons/column2.svg;
--nodes-src: /_/icons/nodes.svg;
--forum-src: /_/icons/forum.svg;
--trash-src: /_/icons/trash.svg;
--pin-src: /_/icons/pin.svg;
--time-src: /_/icons/time.svg;
--top-inset: env(safe-area-inset-top, 0px);
--bottom-inset: env(safe-area-inset-bottom, 0px);
}
@media (prefers-color-scheme: dark) {
:root {
--main: #2A150E;
--accent: #3D2622;
--darkaccent: #240609;
--text: #FADFB6;
--sidebar: #240609;
--divider: #523636;
--darktext: #62473E;
--headertext: #ffd8bb;
--darkred: #6b2c1d;
--searchbackground: #260F0C;
--loginButton: var(--darkaccent);
--loginBackground: var(--accent);
--home-src: /_/icons/homelight.svg;
--home-selected-src: /_/icons/homelightselected.svg;
--people-src: /_/icons/peoplelight.svg;
--people-selected-src: /_/icons/peoplelightselected.svg;
--settings-src: /_/icons/settingslight.svg;
--settings-selected-src: /_/icons/settingslightselected.svg;
--events-src: /_/icons/eventslight.svg;
--events-selected-src: /_/icons/eventslightselected.svg;
--jobs-src: /_/icons/jobslight.svg;
--jobs-selected-src: /_/icons/jobslightselected.svg;
--column-src: /_/icons/column2.svg;
--nodes-src: /_/icons/nodes.svg;
--forum-src: /_/icons/forum.svg;
--trash-src: /_/icons/trash.svg;
--pin-src: /_/icons/pinlight.svg;
--time-src: /_/icons/timelight.svg;
}
}
input {
outline: none;
caret-color: var(--text); /* hide real caret */
}
input::placeholder {
font-family: Arial;
color: #5C504D;
}
html,
body {
padding: 0;
margin: 0;
font-family: Arial;
}
body {
background-color: var(--main);
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}

View File

@@ -1,25 +0,0 @@
:root {
--main: #FFE9C8;
--accent: #60320c;
--text: #340000;
--yellow: #f1f3c3;
}
@media (prefers-color-scheme: dark) {
:root {
}
}
html,
body {
padding: 0;
margin: 0;
}
body {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}

View File

@@ -0,0 +1,68 @@
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;

49
src/_/code/ws/Socket.js Normal file
View File

@@ -0,0 +1,49 @@
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
}));
}
}

1
src/_/code/ws/shim/fs.js Normal file
View File

@@ -0,0 +1 @@
export default {}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -1,66 +1,83 @@
import './ForumPanel.js'
// import './ForumPanel.js'
css(`
forum- {
font-family: 'Bona';
}
// css(`
// forum- {
// font-family: 'Bona';
// }
forum- input::placeholder {
font-family: 'Bona Nova';
font-size: 0.9em;
color: var(--accent);
}
// forum- input::placeholder {
// font-family: 'Bona Nova';
// font-size: 0.9em;
// color: var(--accent);
// }
input[type="checkbox"] {
appearance: none; /* remove default style */
-webkit-appearance: none;
width: 1em;
height: 1em;
border: 1px solid var(--accent);
}
// input::placeholder {
// font-family: Arial;
// }
input[type="checkbox"]:checked {
background-color: var(--red);
}
`)
// input[type="checkbox"] {
// appearance: none; /* remove default style */
// -webkit-appearance: none;
// width: 1em;
// height: 1em;
// border: 1px solid var(--accent);
// }
class Forum extends Shadow {
// input[type="checkbox"]:checked {
// background-color: var(--red);
// }
// `)
selectedForum = "HY"
// class Forum extends Shadow {
// render() {
// ZStack(() => {
// VStack(() => {
render() {
ZStack(() => {
VStack(() => {
// ForumPanel()
ForumPanel()
input("Message Hyperia", "98%")
.paddingVertical(1, em)
.paddingLeft(2, pct)
.color("var(--accent)")
.background("var(--darkbrown)")
.marginBottom(6, em)
.border("none")
.fontSize(1, em)
.onKeyDown(function (e) {
if (e.key === "Enter") {
window.Socket.send({app: "FORUM", operation: "SEND", msg: {forum: "HY", text: this.value }})
this.value = ""
}
})
})
.gap(0.5, em)
.width(100, pct)
.height(100, vh)
.horizontalAlign("center")
.verticalAlign("end")
})
.onAppear(() => document.body.style.backgroundColor = "var(--darkbrown)")
.width(100, pct)
.height(100, pct)
}
// input("Message", "70%")
// .paddingVertical(0.75, em)
// .boxSizing("border-box")
// .paddingHorizontal(2, em)
// .color("var(--accent)")
// .background("black")
// .marginBottom(1, em)
// .border("0.5px solid #6f5e4e")
// .borderRadius(100, px)
// .fontFamily("Arial")
// .fontSize(1, em)
// .onKeyDown(async function(e) {
// if (e.key === "Enter") {
// let msg = {
// forum: global.currentNetwork.abbreviation,
// text: this.value
// }
// await global.Socket.send({
// app: "FORUM",
// operation: "SEND",
// msg: msg
// })
// this.value = ""
// }
// })
// })
// .gap(0.5, em)
// .boxSizing("border-box")
// .width(100, pct)
// .height(100, pct)
// .horizontalAlign("center")
// .verticalAlign("end")
// .minHeight(0)
// })
// .backgroundColor("var(--main)")
// .boxSizing("border-box")
// .paddingVertical(1, em)
// .width(100, pct)
// .minHeight(0)
// .flex("1 1 auto")
// }
}
// }
register(Forum)
// register(Forum)

View File

@@ -1,88 +1,193 @@
import "../../components/LoadingCircle.js"
// import "../../components/LoadingCircle.js"
class ForumPanel extends Shadow {
forums = [
"HY"
]
messages = []
// css(`
// forumpanel- {
// scrollbar-width: none;
// -ms-overflow-style: none;
// }
render() {
VStack(() => {
if(this.messages.length > 0) {
// forumpanel-::-webkit-scrollbar {
// display: none;
// width: 0px;
// height: 0px;
// }
let previousDate = null
// forumpanel-::-webkit-scrollbar-thumb {
// background: transparent;
// }
for(let i=0; i<this.messages.length; i++) {
let message = this.messages[i]
const dateParts = this.parseDate(message.time);
const { date, time } = dateParts;
// forumpanel-::-webkit-scrollbar-track {
// background: transparent;
// }
// `)
if (previousDate !== date) {
previousDate = date;
// class ForumPanel extends Shadow {
// messages = []
// isSending = false
p(date)
.textAlign("center")
.opacity(0.5)
.marginVertical(1, em)
.color("var(--divider)")
}
// render() {
// VStack(() => {
// if(this.messages.length > 0) {
// let previousDate = null
VStack(() => {
HStack(() => {
p(message.sentBy)
.fontWeight("bold")
.marginBottom(0.3, em)
// for(let i=0; i<this.messages.length; i++) {
// let message = this.messages[i]
// const isMe = message.authorId === global.profile.id
// const dateParts = this.parseDate(message.time);
// const { date, time } = dateParts;
p(util.formatTime(message.time))
.opacity(0.2)
.marginLeft(1, em)
})
p(message.text)
})
}
} else {
LoadingCircle()
}
})
.gap(1, em)
.position("relative")
.overflow("scroll")
.height(100, pct)
.width(96, pct)
.paddingTop(5, em)
.paddingBottom(2, em)
.paddingLeft(4, pct)
.backgroundColor("var(--darkbrown)")
.onAppear(async () => {
requestAnimationFrame(() => {
this.scrollTop = this.scrollHeight
});
let res = await Socket.send({app: "FORUM", operation: "GET", msg: {forum: "HY", number: 100}})
if(!res) console.error("failed to get messages")
if(res.msg.length > 0 && this.messages.length === 0) {
this.messages = res.msg
this.rerender()
}
window.addEventListener("new-post", (e) => {
this.messages = e.detail
if(e.detail.length !== this.messages || e.detail.last.time !== this.messages.last.time || e.detail.first.time !== this.messages.first.time) {
this.rerender()
}
})
})
}
// if (previousDate !== date) {
// previousDate = date;
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;
// p(date)
// .textAlign("center")
// .opacity(0.6)
// .fontWeight("bold")
// .paddingTop(1, em)
// .paddingBottom(0.5, em)
// .color("var(--quillred)")
// .borderTop(`1px solid var(--${i == 0 ? "transparent" : "divider"})`)
// }
const [, mm, dd, yyyy, hh, min, ampm] = match;
const date = `${mm}/${dd}/${yyyy}`;
const time = `${hh}:${min}${ampm.toLowerCase()}`;
// VStack(() => {
// HStack(() => {
// h3(isMe ? "Me" : message.sentBy)
// .color(isMe ? "var(--quillred)" : "var(--brown")
// .margin(0)
return { date, time };
}
}
// h3(`${date} ${time}`)
// .opacity(0.5)
// .color("var(--brown)")
// .margin(0)
// .marginLeft(0.5, em)
// .fontSize(1, em)
register(ForumPanel)
// 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)

View File

@@ -1,101 +0,0 @@
import "./JobsSidebar.js"
import "./JobsGrid.js"
css(`
jobs- {
font-family: 'Bona';
}
jobs- input::placeholder {
font-family: 'Bona Nova';
font-size: 0.9em;
color: var(--accent);
}
input[type="checkbox"] {
appearance: none; /* remove default style */
-webkit-appearance: none;
width: 1em;
height: 1em;
border: 1px solid var(--accent);
}
input[type="checkbox"]:checked {
background-color: var(--red);
}
`)
class Jobs extends Shadow {
jobs = [
{
title: "Austin Chapter Lead",
salary: "1% of Local Revenue",
company: "Hyperia",
city: "Austin",
state: "TX"
}
]
render() {
ZStack(() => {
HStack(() => {
JobsSidebar()
JobsGrid(this.jobs)
})
.width(100, "%")
.x(0).y(13, vh)
HStack(() => {
input("Search jobs... (Coming Soon!)", "45vw")
.attr({
"type": "text",
"disabled": "true"
})
.fontSize(1.1, em)
.paddingLeft(1.3, em)
.background("transparent")
.border("0.5px solid var(--divider)")
.outline("none")
.color("var(--accent)")
.opacity(0.5)
.borderRadius(10, px)
.background("grey")
.cursor("not-allowed")
button("+ Add Job")
.width(7, em)
.marginLeft(1, em)
.borderRadius(10, px)
.background("transparent")
.border("0.3px solid var(--accent2)")
.color("var(--accent)")
.fontFamily("Bona Nova")
.onHover(function (hovering) {
if(hovering) {
this.style.background = "var(--green)"
} else {
this.style.background = "transparent"
}
})
.onClick((clicking) => {
console.log(this, "clicked")
})
})
.x(55, vw).y(4, vh)
.position("absolute")
.transform("translateX(-50%)")
})
.width(100, "%")
.height(100, "%")
}
connectedCallback() {
// Optional additional logic
}
}
register(Jobs)

View File

@@ -1,60 +0,0 @@
class JobsGrid extends Shadow {
jobs;
constructor(jobs) {
super()
this.jobs = jobs
}
boldUntilFirstSpace(text) {
const index = text.indexOf(' ');
if (index === -1) {
// No spaces — bold the whole thing
return `<b>${text}</b>`;
}
return `<b>${text.slice(0, index)}</b>${text.slice(index)}`;
}
render() {
VStack(() => {
h3("Results")
.marginTop(0.1, em)
.marginBottom(1, em)
.marginLeft(0.4, em)
.color("var(--accent2)")
if (this.jobs.length > 0) {
ZStack(() => {
for (let i = 0; i < this.jobs.length; i++) {
VStack(() => {
p(this.jobs[i].title)
.fontSize(1.2, em)
.fontWeight("bold")
.marginBottom(0.5, em)
p(this.jobs[i].company)
p(this.jobs[i].city + ", " + this.jobs[i].state)
.marginBottom(0.5, em)
p(this.boldUntilFirstSpace(this.jobs[i].salary))
})
.padding(1, em)
.borderRadius(5, "px")
.background("var(--darkbrown)")
}
})
.display("grid")
.gridTemplateColumns("repeat(auto-fill, minmax(250px, 1fr))")
.gap(1, em)
} else {
p("No Jobs!")
}
})
.height(100, vh)
.paddingLeft(2, em)
.paddingRight(2, em)
.paddingTop(2, em)
.gap(0, em)
.width(100, "%")
}
}
register(JobsGrid)

View File

@@ -1,26 +0,0 @@
class JobsSidebar extends Shadow {
render() {
VStack(() => {
h3("Location")
.color("var(--accent2)")
.marginBottom(0, em)
HStack(() => {
input("Location", "100%")
.paddingLeft(3, em)
.paddingVertical(0.75, em)
.backgroundImage("/_/icons/locationPin.svg")
.backgroundRepeat("no-repeat")
.backgroundSize("18px 18px")
.backgroundPosition("10px center")
})
})
.paddingTop(1, em)
.paddingLeft(3, em)
.paddingRight(3, em)
.gap(1, em)
.minWidth(10, vw)
}
}
register(JobsSidebar)

View File

@@ -76,7 +76,7 @@ class Messages extends Shadow {
.verticalAlign("end")
})
.onAppear(async () => {
let res = await Socket.send({app: "MESSAGES", operation: "GET"})
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) {
@@ -93,9 +93,9 @@ class Messages extends Shadow {
this.rerender()
})
})
.width(100, "%")
.height(87, vh)
.x(0).y(13, vh)
.width(100, pct)
.height(87, pct)
.x(0).y(13, pct)
VStack(() => {
p("Add Message")
@@ -121,15 +121,15 @@ class Messages extends Shadow {
})
.gap(1, em)
.alignVertical("center")
.alignHorizontal("center")
.verticalAlign("center")
.horizontalAlign("center")
.backgroundColor("black")
.border("1px solid var(--accent)")
.position("fixed")
.x(50, vw).y(50, vh)
.x(50, vw).y(50, pct)
.center()
.width(60, vw)
.height(60, vh)
.height(60, pct)
.display("none")
.attr({id: "addPanel"})
@@ -176,12 +176,13 @@ class Messages extends Shadow {
})
})
.x(55, vw).y(4, vh)
.x(55, vw).y(4, pct)
.position("absolute")
.transform("translateX(-50%)")
})
.width(100, "%")
.height(100, "%")
.boxSizing("border-box")
.height(100, pct)
.width(100, pct)
}
}

View File

@@ -1,65 +1,69 @@
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}/db/apps/${appName}/icons/${appName}`
if(this.darkMode) {
imgUrl += "light"
}
imgUrl += ".svg"
imgUrl = imgUrl.toLowerCase()
return imgUrl
}
onNewSelection() {
this.$$("img").forEach((image) => {
image.style.background = ""
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() {
console.log("rendering")
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(() => {
img("/_/icons/Column.svg", "1.5em", "1.5em")
.attr({app: "forum"})
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)
.borderRadius(10, px)
.onClick((finished, e) => {
if(finished) {
.borderBottom(global.currentApp() === app ? "1px solid var(--text)" : "1px solid transparent")
.onTouch(async (done, e) => {
if(done) {
global.openApp(app)
this.onNewSelection()
}
e.target.style.background = "var(--accent)"
console.log(e.target, e.target.style.background)
if(finished) {
window.navigateTo("/")
await Haptics.impact({ style: ImpactStyle.Light });
}
})
img("/_/icons/letter.svg", "1.5em", "1.5em")
.attr({app: "messages"})
.padding(0.5, em)
.borderRadius(10, px)
.onClick((finished, e) => {
if(finished) {
this.onNewSelection()
}
e.target.style.background = "rgb(112 150 114)"
if(finished) {
window.navigateTo("/messages")
}
})
img("/_/icons/jobs.svg", "1.5em", "1.5em")
.attr({app: "jobs"})
.padding(0.5, em)
.borderRadius(10, px)
.onClick((finished, e) => {
if(finished) {
this.onNewSelection()
}
e.target.style.background = "#9392bb"
if(finished) {
window.navigateTo("/jobs")
}
})
})
.borderTop("1px solid black")
.display("grid")
.gridTemplateColumns(`repeat(${apps.filter(app => app !== "Settings").length}, 1fr)`)
.placeItems("center")
.borderTop("0.5px solid var(--divider)")
.height("auto")
.position('fixed')
.background("var(--main)")
.zIndex(1)
.x(0).yBottom(0)
.justifyContent("space-between")
.paddingHorizontal(4, em)
.paddingVertical(1, em)
.paddingTop(0.5, em)
.paddingBottom(2, em)
.width(100, vw)
.boxSizing("border-box")
}

View File

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

View File

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

View File

@@ -1,25 +0,0 @@
class LoadingCircle extends Shadow {
render() {
div()
.borderRadius(100, pct)
.width(2, em).height(2, em)
.x(45, pct).y(50, pct)
.center()
.backgroundColor("var(--accent")
.transition("transform 1.75s ease-in-out")
.onAppear(function () {
let growing = true;
setInterval(() => {
if (growing) {
this.style.transform = "scale(1.5)";
} else {
this.style.transform = "scale(0.7)";
}
growing = !growing;
}, 750);
});
}
}
register(LoadingCircle)

View File

@@ -1,44 +1,125 @@
import util from "../util"
import "./Toggle.js"
class Sidebar extends Shadow {
SIDEBAR_WIDTH
constructor(width) {
super()
this.SIDEBAR_WIDTH = width
}
SidebarItem(text) {
return p(text)
.fontSize(1.5, em)
.fontSize(1.2, em)
.fontWeight("bold")
.fontFamily("Sedan SC")
.marginLeft(2, em)
.fontStyle("italic")
.onClick(function () {
if(this.innerText === "Home") {
window.navigateTo("/")
.color("var(--headertext)")
.fontFamily("Arial")
.marginLeft(3, em)
.marginTop(2, em)
.onTap(function (done) {
if (done) {
if (this.innerText === "Logout") {
global.onLogout()
$("home-").closeSidebar();
return
}
window.navigateTo(this.innerText.toLowerCase().replace(/\s+/g, ""))
}
})
}
render() {
VStack(() => {
this.SidebarItem("Home")
this.SidebarItem("Map")
this.SidebarItem("Logout")
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)
this.SidebarItem("Logout")
VStack(() => {
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)
.paddingTop(30, vh)
.height(100, vh)
.width(70, vw)
.borderLeft("1px solid black")
})
.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(--main)")
.xRight(-70, vw)
.transition("right .3s")
.zIndex(1)
.background("var(--sidebar)")
}
toggle() {
if(this.style.right === "-70vw") {
this.style.right = "0vw"
} else {
this.style.right = "-70vw"
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)
}
}
}

50
src/components/Toggle.js Normal file
View File

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

53
src/components/TopBar.js Normal file
View File

@@ -0,0 +1,53 @@
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(() => {
this.$("p").attr({app: global.currentApp()})
})
}
}
register(TopBar)

View File

@@ -5,7 +5,7 @@
<title>Forum</title>
<meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
content="viewport-fit=auto, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
@@ -21,8 +21,8 @@
<link rel="icon" type="image/x-icon" href="./_/icons/columnwhite.svg" />
<link rel="manifest" href="./manifest.json" />
<link rel="stylesheet" href="./_/code/styles.css" />
<script src="./_/code/quill.js"></script>
<link rel="stylesheet" href="./_/code/shared.css" />
<script src="/_/code/quill.js"></script>
<script type="module" src="./index.js"></script>
<meta name="theme-color" content="#31d53d" />
</head>

View File

@@ -1,3 +1,223 @@
import "./Home.js"
Home()
document.body.style.backgroundColor = "var(--main)"
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
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)
}
async fetchAppData() {
let personalSpace = this.currentNetwork === this.profile
if (personalSpace) { return {} }
let appData = await fetch(`${util.HOST}/api/${personalSpace ? "my" : "org"}data/` + this.currentNetwork.id, {method: "GET"})
let json = await appData.json()
return json
}
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(!this.currentNetwork?.data) {
this.currentNetwork.data = await this.fetchAppData()
}
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`, {
method: "GET",
credentials: "include"
});
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()
}
}
window.global = new Global()

View File

@@ -4,9 +4,9 @@
"start_url": "index.html",
"display": "standalone",
"icons": [{
"src": "_/imgs/logo.png",
"src": "_/icons/logo.svg",
"sizes": "512x512",
"type": "image/png"
"type": "image/svg"
}],
"background_color": "#31d53d",
"theme_color": "#31d53d"

View File

@@ -1,6 +1,11 @@
/*
Sam Russell
Captured Sun
2.24.26 - Allowing state() to watch other elements
2.21.26 - Making state() be called on initial definition, fixing fontSize so it works with clamp(), other strings
2.20.26 - Adding state()
2.19.26 - Adding dynamicText()
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)
@@ -468,7 +473,7 @@ HTMLElement.prototype.setUpState = function(styleFunc, cb) {
}
function StyleFunction(func) {
let styleFunction = function(value, unit = "px") {
let styleFunction = function(value, unit) {
if(typeof value === 'function') {
this.setUpState(styleFunction, value)
return this
@@ -524,7 +529,7 @@ HTMLElement.prototype.borderHorizontal = StyleFunction(function(value) {
return this
})
HTMLElement.prototype.fontSize = StyleFunction(function(value, unit = "px") {
HTMLElement.prototype.fontSize = StyleFunction(function(value, unit) {
switch(value) {
case "6xl":
@@ -566,7 +571,13 @@ HTMLElement.prototype.fontSize = StyleFunction(function(value, unit = "px") {
default:
break;
}
if(unit) {
this.style.fontSize = value + unit
} else {
this.style.fontSize = value
}
return this
})
@@ -690,6 +701,54 @@ HTMLElement.prototype.horizontalAlign = function (value) {
/* Elements */
HTMLElement.prototype.state = function(arg1, arg2, arg3) {
let el;
let attr;
let cb;
if(arg3) {
el = arg1
attr = arg2
cb = arg3
} else {
el = this
attr = arg1
cb = arg2
}
if (attr !== attr.toLowerCase()) {
throw new Error(`quill: dynamicText() attr "${attr}" must be lowercase`);
}
let handler = () => {
const value = el.getAttribute(attr);
cb.call(this, value)
}
new MutationObserver(handler)
.observe(el, { attributes: true, attributeFilter: [attr] });
handler()
return this
}
HTMLElement.prototype.dynamicText = function(attr, template) {
// Set initial text if attr already has a value
if (attr !== attr.toLowerCase()) {
throw new Error(`quill: dynamicText() attr "${attr}" must be lowercase`);
}
if (this.getAttribute(attr)) {
this.innerText = template.replace('{{}}', this.getAttribute(attr));
}
new MutationObserver(() => {
const value = this.getAttribute(attr);
this.innerText = template.replace('{{}}', value ?? '');
}).observe(this, { attributes: true, attributeFilter: [attr] });
return this
};
quill.setChildren = function(el, innerContent) {
if(typeof innerContent === "string") {
el.innerText = innerContent
@@ -1066,9 +1125,9 @@ HTMLElement.prototype.onSubmit = function(cb) {
};
HTMLElement.prototype.onTouch = function(cb) {
const onStart = () => cb.call(this, true);
const onEnd = () => cb.call(this, false);
const onCancel = () => cb.call(this, null);
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);
@@ -1187,21 +1246,23 @@ HTMLElement.prototype.removeAllListeners = function() {
/* ATTRIBUTES */
HTMLElement.prototype.attr = function(attributes) {
if (
typeof attributes !== "object" ||
attributes === null ||
Array.isArray(attributes)
) {
throw new TypeError("attr() expects an object with key-value pairs");
}
for (const [key, value] of Object.entries(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) {
this.setAttribute(arg1, arg2)
return this
} else if(typeof arg1 === "string") {
return this.getAttribute(arg1)
} else {
throw new TypeError("wrong parameter for attr(): ", arg1);
}
};

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,9 @@
<svg width="306" height="315" viewBox="0 0 306 315" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M257.568 6.10352e-05H47.9351V28.2306H257.568V6.10352e-05Z" fill="#FFD5A4"/>
<path d="M207.362 174.274C205.545 172.318 203.134 171.234 200.549 171.234C195.203 171.234 190.836 176.021 190.836 181.891V314.449H210.262V181.891C210.262 178.991 209.249 176.301 207.362 174.274Z" fill="#FFD5A4"/>
<path d="M233.844 314.448V121.795C229.581 123.472 224.97 124.416 220.148 124.416H85.3542C80.5326 124.416 75.9207 123.472 71.6582 121.795V314.448H90.0011V181.89C90.0011 173.12 96.7093 165.993 104.955 165.993C109.008 165.993 112.781 167.67 115.611 170.71C118.371 173.679 119.909 177.662 119.909 181.89V314.448H137.797V156.524C137.797 147.755 144.506 140.627 152.751 140.627C156.769 140.627 160.542 142.304 163.407 145.309C166.203 148.314 167.705 152.297 167.705 156.524V314.448H185.594V181.89C185.594 173.12 192.302 165.993 200.547 165.993C204.6 165.993 208.374 167.67 211.204 170.71C213.964 173.679 215.501 177.662 215.501 181.89V314.448H233.844Z" fill="#FFD5A4"/>
<path d="M159.562 148.909C157.71 146.953 155.3 145.87 152.749 145.87C147.403 145.87 143.036 150.656 143.036 156.526V314.449H162.462V156.526C162.462 153.626 161.449 150.936 159.562 148.909Z" fill="#FFD5A4"/>
<path d="M111.769 174.274C109.952 172.318 107.542 171.234 104.956 171.234C99.6105 171.234 95.2432 176.021 95.2432 181.891V314.449H114.669V181.891C114.669 178.991 113.656 176.301 111.769 174.274Z" fill="#FFD5A4"/>
<path d="M245.831 106.423C233.916 98.9114 225.95 85.6696 225.95 70.611C225.95 70.4363 225.95 70.2965 225.95 70.1218H79.5569C79.5569 70.1218 79.5569 70.4363 79.5569 70.611C79.5569 85.7045 71.5909 98.9463 59.6768 106.423C65.5814 114.145 74.8751 119.176 85.3568 119.176H220.151C230.632 119.176 239.926 114.145 245.831 106.423Z" fill="#FFD5A4"/>
<path d="M37.1399 107.716C57.6141 107.716 74.2799 91.0507 74.2799 70.6115C74.2799 70.4368 74.2799 70.297 74.2799 70.1223H34.834C33.4015 70.1223 32.2136 68.9344 32.2136 67.5019C32.2136 66.0694 33.4015 64.8815 34.834 64.8815H270.671C272.103 64.8815 273.291 66.0694 273.291 67.5019C273.291 68.9344 272.103 70.1223 270.671 70.1223H231.225C231.225 70.1223 231.225 70.4368 231.225 70.6115C231.225 91.0856 247.891 107.716 268.365 107.716C288.839 107.716 305.505 91.0507 305.505 70.6115C305.505 50.1723 288.839 33.4716 268.365 33.4716H37.1399C16.6658 33.4716 0 50.1374 0 70.6115C0 91.0856 16.6658 107.716 37.1399 107.716Z" fill="#FFD5A4"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,3 @@
<svg width="92" height="73" viewBox="0 0 92 73" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34.7556 0C34.3767 0 34.0134 0.15234 33.7439 0.42188C33.4782 0.69141 33.3259 1.05469 33.3298 1.43358V5.24998H24.6189C21.2712 5.24998 18.5251 7.99608 18.5251 11.3477V21.1758C15.5368 34.2268 9.5993 46.4138 1.1541 56.8008C1.14629 56.8086 1.13848 56.8203 1.13457 56.832L0.736131 57.3477C-1.09197 59.7305 0.704881 63.3593 3.70883 63.3593H18.5248V65.2343C18.5248 69.1445 21.7201 72.3438 25.6303 72.3438H84.3183C88.2285 72.3438 91.4277 69.1445 91.4277 65.2343V21.4574V21.3988V19.3246V19.2894V11.348C91.4277 7.9964 88.6816 5.2542 85.33 5.2542H76.6308V1.4339C76.6308 1.05499 76.4824 0.68781 76.2129 0.4183C75.9433 0.14877 75.5761 0.000329971 75.1973 0.000329971C74.8184 0.000329971 74.4551 0.15267 74.1895 0.42221C73.9199 0.69174 73.7715 1.05502 73.7715 1.43391V5.25421L36.1895 5.2503V1.4339C36.1895 1.05499 36.0411 0.687814 35.7715 0.418303C35.502 0.148773 35.1344 0 34.7556 0ZM24.6186 8.1094H33.3295V11.9375C33.3334 12.7266 33.9701 13.3594 34.7553 13.3633C35.1342 13.3633 35.4975 13.2149 35.767 12.9492C36.0365 12.6797 36.1888 12.3164 36.1888 11.9375V8.10944H73.7748V11.9375H73.7709C73.7748 12.7227 74.4115 13.3594 75.1967 13.3633C75.5756 13.3633 75.9389 13.2149 76.2084 12.9493C76.478 12.6798 76.6264 12.3165 76.6303 11.9376V8.10948H85.3295C87.1342 8.10948 88.5678 9.53918 88.5678 11.3478V19.965H21.3798V19.3634V19.2892V11.3478C21.3798 9.5431 22.8139 8.1133 24.6186 8.1094ZM21.0874 22.8204H88.1774C85.0797 35.2464 79.2985 46.8514 71.2084 56.8004C71.1966 56.8082 71.1888 56.8199 71.1771 56.8317L70.7865 57.3473C69.2631 59.3356 66.9076 60.5035 64.4037 60.5035H19.9627C19.9588 60.4996 19.951 60.4996 19.9471 60.4996C19.9432 60.4996 19.9353 60.4996 19.9314 60.5035H3.70844C2.88813 60.5035 2.51314 59.7379 3.00922 59.0894L3.37641 58.605L3.38813 58.5855C11.8881 48.1285 17.9354 35.9104 21.0874 22.8204ZM88.5644 31.5274V65.2344C88.5644 67.6016 86.6816 69.4961 84.3144 69.4961H25.6304C23.2632 69.4961 21.3804 67.6016 21.3804 65.2344V63.3594H64.4074C67.798 63.3594 70.9933 61.7774 73.0558 59.086L73.423 58.6016C80.0127 50.5 85.1264 41.3354 88.5644 31.5274Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,3 @@
<svg width="88" height="69" viewBox="0 0 88 69" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M33.4525 0C33.0878 0 32.7382 0.145299 32.4788 0.40238C32.2231 0.659453 32.0765 1.00594 32.0802 1.36732V5.00732H23.6959C20.4737 5.00732 17.8306 7.6265 17.8306 10.8232V20.197C14.9543 32.6448 9.23941 44.2685 1.11083 54.1754C1.10331 54.1829 1.09579 54.1941 1.09203 54.2052L0.708533 54.697C-1.05103 56.9697 0.678454 60.4309 3.56978 60.4309H17.8303V62.2192C17.8303 65.9487 20.9058 69 24.6694 69H81.1571C84.9207 69 88 65.9487 88 62.2192V20.4656V20.4097V18.4314V18.3978V10.8235C88 7.6268 85.3568 5.01135 82.1309 5.01135H73.7579V1.36762C73.7579 1.00623 73.615 0.656019 73.3555 0.398966C73.0961 0.141894 72.7427 0.00031472 72.378 0.00031472C72.0133 0.00031472 71.6637 0.145613 71.408 0.402695C71.1486 0.659767 71.0057 1.00626 71.0057 1.36763V5.01136L34.8327 5.00763V1.36763C34.8327 1.00623 34.6898 0.656023 34.4304 0.398969C34.171 0.141897 33.8172 0 33.4525 0ZM23.6956 7.73458H32.0799V11.3857C32.0837 12.1383 32.6965 12.7419 33.4522 12.7456C33.817 12.7456 34.1666 12.6041 34.426 12.3507C34.6854 12.0937 34.8321 11.7472 34.8321 11.3858V7.73462H71.0089V11.3858H71.0052C71.0089 12.1347 71.6218 12.742 72.3775 12.7457C72.7422 12.7457 73.0919 12.6041 73.3513 12.3508C73.6107 12.0937 73.7536 11.7472 73.7573 11.3858V7.73466H82.1304C83.8674 7.73466 85.2473 9.09828 85.2473 10.8233V19.0422H20.5783V18.4684V18.3977V10.8233C20.5783 9.10202 21.9585 7.7383 23.6956 7.73458ZM20.2968 21.7656H84.8715C81.8899 33.6173 76.3255 44.6859 68.5387 54.1751C68.5274 54.1825 68.5199 54.1937 68.5086 54.2049L68.1326 54.6967C66.6663 56.5931 64.3991 57.707 61.9891 57.707H19.2143C19.2105 57.7033 19.203 57.7033 19.1992 57.7033C19.1955 57.7033 19.1879 57.7033 19.1842 57.707H3.56941C2.77985 57.707 2.41892 56.9768 2.8964 56.3582L3.24983 55.8963L3.26111 55.8776C11.4424 45.904 17.2629 34.2506 20.2968 21.7656ZM85.244 30.0702V62.2192C85.244 64.477 83.4318 66.284 81.1533 66.284H24.6695C22.391 66.284 20.5788 64.477 20.5788 62.2192V60.4309H61.9927C65.2561 60.4309 68.3316 58.922 70.3168 56.355L70.6702 55.893C77.013 48.1659 81.9349 39.4249 85.244 30.0702Z" fill="#FFE9C8"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,3 @@
<svg width="89" height="70" viewBox="0 0 89 70" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M72.8799 0.25C73.3087 0.25 73.7257 0.416994 74.0332 0.72168C74.3407 1.02634 74.5097 1.44034 74.5098 1.86719V5.26172H82.6328C85.9944 5.26172 88.7518 7.98625 88.752 11.3232V62.7188C88.752 66.5884 85.5587 69.75 81.6592 69.75H25.1709C21.2714 69.7497 18.082 66.5881 18.082 62.7188V61.1807H4.07129C0.977827 61.1803 -0.877206 57.485 1.0127 55.0439L1.01367 55.043L1.38574 54.5645C1.39618 54.5468 1.40769 54.5308 1.41992 54.5166L2.17188 53.5859C9.87157 43.9211 15.3033 32.6784 18.083 20.665V11.3232C18.083 7.98644 20.84 5.25781 24.1982 5.25781H32.332V1.86719C32.3283 1.43709 32.5035 1.02844 32.8037 0.726562L32.8047 0.724609C33.1103 0.421755 33.523 0.250102 33.9541 0.25C34.383 0.25 34.801 0.417052 35.1084 0.72168C35.4158 1.02632 35.5848 1.44043 35.585 1.86719V5.25781L71.2578 5.26074V1.86719C71.2579 1.44034 71.4264 1.03058 71.7324 0.726562L71.8516 0.618164C72.1394 0.381566 72.503 0.250021 72.8799 0.25ZM85.4961 31.9873C82.1901 40.8638 77.4261 49.1646 71.3701 56.5439L71.3711 56.5449L71.0176 57.0068L71.0166 57.0078C68.9839 59.6362 65.8347 61.1807 62.4941 61.1807H21.3311V62.7197C21.3313 64.838 23.0304 66.5342 25.1719 66.5342H81.6553C83.7967 66.5342 85.4958 64.838 85.4961 62.7197V31.9873ZM20.9941 22.5156C17.9401 34.9588 12.1246 46.5722 3.96777 56.5205L3.96582 56.5254L3.95898 56.5371L3.9502 56.5479L3.59668 57.0098V57.0107C3.40752 57.2558 3.40294 57.4916 3.4834 57.6533C3.56401 57.8154 3.75623 57.957 4.07129 57.957H19.6533C19.6787 57.9529 19.7005 57.9531 19.7012 57.9531C19.7016 57.9531 19.7235 57.9529 19.749 57.957H62.4912C64.8238 57.957 67.0177 56.8788 68.4365 55.0439L68.8115 54.5527L68.8223 54.5391L68.835 54.5273L68.8359 54.5264C68.8373 54.5249 68.8394 54.5221 68.8418 54.5195C68.8444 54.5167 68.8488 54.5135 68.8535 54.5088C76.5454 45.133 82.0603 34.211 85.0508 22.5156H20.9941ZM23.9014 8.5C22.4425 8.64669 21.3301 9.84168 21.3301 11.3232V19.292H85.499V11.3232C85.499 9.73882 84.2339 8.48461 82.6328 8.48438H74.5098V11.8887C74.5053 12.3144 74.3363 12.724 74.0293 13.0283L74.0283 13.0293C73.7219 13.3286 73.3091 13.496 72.8799 13.4961H72.8779C71.9873 13.4915 71.2613 12.7747 71.2568 11.8867L71.2559 11.6357H71.2607V8.48438H35.584V11.8857C35.584 12.3152 35.4093 12.7253 35.1035 13.0283L35.1025 13.0293C34.796 13.3287 34.3835 13.4961 33.9541 13.4961H33.9531C33.0628 13.4917 32.3365 12.779 32.332 11.8867V8.48438H24.1982L23.9014 8.5Z" fill="#BD2D2D" stroke="#FFD5A4" stroke-width="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,4 @@
<svg width="24" height="27" viewBox="0 0 24 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.5 8.95076V25.6174H23.5V8.95076L12 0.617432L0.5 8.95076Z" stroke="#000000"/>
<path d="M15.8357 25.6171V15.2004H8.16907V25.6171" stroke="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 261 B

View File

@@ -0,0 +1,4 @@
<svg width="24" height="27" viewBox="0 0 24 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.5 8.95076V25.6174H23.5V8.95076L12 0.617432L0.5 8.95076Z" stroke="#FFE9C8"/>
<path d="M15.8357 25.6171V15.2004H8.16907V25.6171" stroke="#FFE9C8"/>
</svg>

After

Width:  |  Height:  |  Size: 261 B

View File

@@ -0,0 +1 @@
<svg fill="none" height="27" viewBox="0 0 24 27" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m.5 8.95076v16.66664h23v-16.66664l-11.5-8.333328z" fill="#74000a" stroke="#ffe9c8"/><path d="m15.8357 25.6171v-10.4167h-7.66663v10.4167" fill="#74000a"/><path d="m15.8357 25.6171v-10.4167h-7.66663v10.4167" stroke="#ffe9c8"/></svg>

After

Width:  |  Height:  |  Size: 334 B

View File

@@ -0,0 +1,3 @@
<svg width="100" height="90" viewBox="0 0 100 90" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M93.3636 15.8877H73.7455V11.2591C73.7455 5.05435 68.7 0 62.5 0H37.2909C31.0909 0 26.0455 5.04529 26.0455 11.2591V15.8877H6.63636C2.98182 15.8877 0 18.8678 0 22.5272V41.2772C0 43.4783 1.46364 45.3895 3.57273 46.0054V83.3696C3.57273 87.029 6.54545 90 10.1909 90H89.8091C93.4636 90 96.4273 87.029 96.4273 83.3696V46.0054C98.5364 45.3895 100 43.4783 100 41.2772V22.5272C100 18.8678 97.0273 15.8877 93.3636 15.8877ZM93.7 83.3696C93.7 85.5254 91.9545 87.2826 89.8091 87.2826H10.1909C8.04545 87.2826 6.3 85.5254 6.3 83.3696V46.712L43.7364 56.2138V60.933C43.7364 63.1522 45.5455 64.9547 47.7636 64.9547H52.0364C54.2545 64.9547 56.0546 63.1522 56.0546 60.933V56.2772L93.7 46.712V83.3696ZM52.0273 50.1993C52.7455 50.1993 53.3182 50.788 53.3182 51.5036V60.933C53.3182 61.6576 52.7364 62.2373 52.0273 62.2373H47.7545C47.0364 62.2373 46.4545 61.6485 46.4545 60.933V51.5036C46.4545 50.779 47.0364 50.1993 47.7545 50.1993H52.0273ZM97.2636 41.2772C97.2636 42.2917 96.5727 43.1793 95.5818 43.433C55.2455 53.6775 56.0455 53.5688 56.0455 53.4783V51.5127C56.0455 49.2935 54.2364 47.4909 52.0273 47.4909H47.7545C45.5636 47.4909 43.7273 49.2482 43.7273 51.5127V53.4149L4.40909 43.433C3.41818 43.1793 2.72727 42.3007 2.72727 41.2772V22.5272C2.72727 20.3623 4.48182 18.6051 6.63636 18.6051H93.3636C95.5182 18.6051 97.2727 20.3623 97.2727 22.5272V41.2772H97.2636ZM28.7727 11.25C28.7727 6.53986 32.5909 2.70833 37.2909 2.70833H62.5C67.2 2.70833 71.0182 6.53986 71.0182 11.25V15.8786H66.3182V11.25C66.3182 9.13949 64.6 7.41848 62.5 7.41848H37.2909C35.1818 7.41848 33.4727 9.13949 33.4727 11.25V15.8786H28.7727V11.25ZM36.2 15.8786V11.25C36.2 10.6341 36.6909 10.1359 37.2909 10.1359H62.5C63.1 10.1359 63.5909 10.6341 63.5909 11.25V15.8786H36.1909H36.2Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,3 @@
<svg width="110" height="100" viewBox="0 0 110 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M102.7 17.54H81.12V12.43C81.12 5.58 75.57 0 68.75 0H41.02C34.2 0 28.65 5.57 28.65 12.43V17.54H7.3C3.28 17.54 0 20.83 0 24.87V45.57C0 48 1.61 50.11 3.93 50.79V92.04C3.93 96.08 7.2 99.36 11.21 99.36H98.79C102.81 99.36 106.07 96.08 106.07 92.04V50.79C108.39 50.11 110 48 110 45.57V24.87C110 20.83 106.73 17.54 102.7 17.54ZM103.07 92.04C103.07 94.42 101.15 96.36 98.79 96.36H11.21C8.85 96.36 6.93 94.42 6.93 92.04V51.57L48.11 62.06V67.27C48.11 69.72 50.1 71.71 52.54 71.71H57.24C59.68 71.71 61.66 69.72 61.66 67.27V62.13L103.07 51.57V92.04ZM57.23 55.42C58.02 55.42 58.65 56.07 58.65 56.86V67.27C58.65 68.07 58.01 68.71 57.23 68.71H52.53C51.74 68.71 51.1 68.06 51.1 67.27V56.86C51.1 56.06 51.74 55.42 52.53 55.42H57.23ZM106.99 45.57C106.99 46.69 106.23 47.67 105.14 47.95C60.77 59.26 61.65 59.14 61.65 59.04V56.87C61.65 54.42 59.66 52.43 57.23 52.43H52.53C50.12 52.43 48.1 54.37 48.1 56.87V58.97L4.85 47.95C3.76 47.67 3 46.7 3 45.57V24.87C3 22.48 4.93 20.54 7.3 20.54H102.7C105.07 20.54 107 22.48 107 24.87V45.57H106.99ZM31.65 12.42C31.65 7.22 35.85 2.99 41.02 2.99H68.75C73.92 2.99 78.12 7.22 78.12 12.42V17.53H72.95V12.42C72.95 10.09 71.06 8.19 68.75 8.19H41.02C38.7 8.19 36.82 10.09 36.82 12.42V17.53H31.65V12.42ZM39.82 17.53V12.42C39.82 11.74 40.36 11.19 41.02 11.19H68.75C69.41 11.19 69.95 11.74 69.95 12.42V17.53H39.81H39.82Z" fill="#FFE9C8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,3 @@
<svg width="102" height="92" viewBox="0 0 102 92" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M63.5 0.5C69.9767 0.5 75.245 5.77871 75.2451 12.2588V16.3877H94.3633C98.3036 16.3877 101.5 19.5923 101.5 23.5273V42.2773C101.5 44.5771 100.051 46.5844 97.9277 47.3652V84.3691C97.9277 88.3036 94.741 91.4997 90.8096 91.5H11.1904C7.26853 91.4997 4.07227 88.304 4.07227 84.3691V47.3652C1.94941 46.5844 0.500071 44.5771 0.5 42.2773V23.5273C0.5 19.5918 3.70596 16.3877 7.63672 16.3877H26.5459V12.2588C26.546 5.76939 31.8146 0.5 38.291 0.5H63.5ZM7.7998 84.3691C7.7998 86.2515 9.32416 87.782 11.1904 87.7822H90.8096C92.6758 87.782 94.2002 86.2515 94.2002 84.3691V48.3545L57.5547 57.665V61.9326C57.5547 64.4275 55.5309 66.4551 53.0361 66.4551H48.7637C46.2703 66.4551 44.2363 64.4289 44.2363 61.9326V57.6025L7.7998 48.3535V84.3691ZM48.7549 51.6992C48.3134 51.6992 47.9541 52.0548 47.9541 52.5039V61.9326C47.9541 62.3749 48.3157 62.7373 48.7549 62.7373H53.0273C53.4574 62.7373 53.8184 62.3839 53.8184 61.9326V52.5039C53.8184 52.0571 53.462 51.6993 53.0273 51.6992H48.7549ZM7.63672 20.1055C5.75962 20.1055 4.22754 21.6373 4.22754 23.5273V42.2773C4.22761 43.0695 4.76073 43.7505 5.5332 43.9482H5.53223L44.2275 53.7725V52.5127C44.2275 49.9652 46.2947 47.9912 48.7549 47.9912H53.0273C55.5123 47.9912 57.5459 50.0171 57.5459 52.5127V53.8721C58.1385 53.7243 59.2979 53.4278 61.5635 52.8467C66.4059 51.6045 76.2902 49.0704 96.458 43.9482L96.5996 43.9062C97.2939 43.665 97.7636 43.0127 97.7637 42.2773V41.7773H97.7725V23.5273C97.7725 21.6373 96.2404 20.1055 94.3633 20.1055H7.63672ZM38.291 4.20801C33.8686 4.20801 30.2725 7.81459 30.2725 12.25V16.3789H33.9727V12.25C33.9727 9.8655 35.9036 7.91895 38.291 7.91895H63.5C65.8775 7.91895 67.8184 9.86473 67.8184 12.25V16.3789H71.5186V12.25C71.5186 7.81459 67.9225 4.20801 63.5 4.20801H38.291ZM38.291 11.6357C37.9761 11.6357 37.7002 11.9012 37.7002 12.25V16.3789H64.0908V12.25C64.0908 11.9012 63.8149 11.6357 63.5 11.6357H38.291Z" fill="#BD2D2D" stroke="#FFE9C8"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,7 +1,7 @@
<svg width="118" height="108" viewBox="0 0 118 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M80.1955 59.9449H61.5784V107.52H80.1955V59.9449Z" fill="#FEE8C8"/>
<path d="M31.5063 59.9449H22.1965V107.52H31.5063V59.9449Z" fill="#FEE8C8"/>
<path d="M97.7112 0.0305315L97.7323 0H19.6963L19.7104 0.0305315C8.77192 0.356983 0 9.30504 0 20.3245C0 30.9295 8.12254 39.6251 18.4844 40.5563C22.3596 40.5458 31.0622 39.4924 31.4051 30.569C31.6188 24.9959 27.2599 20.2905 21.6879 20.0779L21.9063 14.3545C26.1361 14.5154 30.0476 16.3132 32.9223 19.418C35.7969 22.5216 37.2918 26.56 37.1298 30.7874C36.8068 39.1871 31.0998 44.8342 22.1987 46.0402V54.2168H95.2346V46.1436C91.7576 45.7373 88.6704 44.6558 86.1727 42.9272C82.1683 40.1618 79.9489 35.9801 79.7528 30.8332C79.5896 26.6058 81.0845 22.5686 83.9626 19.4661C86.835 16.3602 90.7477 14.5623 94.9751 14.4026L95.1947 20.1273C92.4962 20.2283 89.9997 21.3767 88.1642 23.3589C86.33 25.3411 85.3765 27.9175 85.4787 30.616C85.6043 33.9345 86.9348 36.4909 89.4313 38.2183C91.6249 39.7343 94.7203 40.5528 98.1985 40.5951C108.909 40.0232 117.429 31.1808 117.429 20.3245C117.429 9.30387 108.654 0.35346 97.7112 0.0305315Z" fill="#FEE8C8"/>
<path d="M95.2336 59.9449H85.9238V107.52H95.2336V59.9449Z" fill="#FEE8C8"/>
<path d="M55.8515 59.9449H37.2344V107.52H55.8515V59.9449Z" fill="#FEE8C8"/>
<path d="M80.1955 59.9449H61.5784V107.52H80.1955V59.9449Z" fill="#FFE9C8"/>
<path d="M31.5063 59.9449H22.1965V107.52H31.5063V59.9449Z" fill="#FFE9C8"/>
<path d="M97.7112 0.0305315L97.7323 0H19.6963L19.7104 0.0305315C8.77192 0.356983 0 9.30504 0 20.3245C0 30.9295 8.12254 39.6251 18.4844 40.5563C22.3596 40.5458 31.0622 39.4924 31.4051 30.569C31.6188 24.9959 27.2599 20.2905 21.6879 20.0779L21.9063 14.3545C26.1361 14.5154 30.0476 16.3132 32.9223 19.418C35.7969 22.5216 37.2918 26.56 37.1298 30.7874C36.8068 39.1871 31.0998 44.8343 22.1987 46.0402V54.2168H95.2346V46.1436C91.7576 45.7373 88.6704 44.6558 86.1727 42.9272C82.1683 40.1618 79.9489 35.9801 79.7528 30.8332C79.5896 26.6058 81.0845 22.5686 83.9626 19.4661C86.835 16.3602 90.7477 14.5623 94.9751 14.4026L95.1947 20.1273C92.4962 20.2283 89.9997 21.3767 88.1642 23.3589C86.33 25.3411 85.3765 27.9175 85.4787 30.616C85.6043 33.9345 86.9348 36.4909 89.4313 38.2183C91.6249 39.7343 94.7203 40.5528 98.1985 40.5951C108.909 40.0232 117.429 31.1808 117.429 20.3245C117.429 9.30387 108.654 0.35346 97.7112 0.0305315Z" fill="#FFE9C8"/>
<path d="M95.2336 59.9449H85.9238V107.52H95.2336V59.9449Z" fill="#FFE9C8"/>
<path d="M55.8515 59.9449H37.2344V107.52H55.8515V59.9449Z" fill="#FFE9C8"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 3" viewBox="0 0 128 160" x="0px" y="0px"><path d="M111.7,31.86h-21.58v-5.11c0-6.85-5.55-12.43-12.37-12.43h-27.73c-6.82,0-12.37,5.57-12.37,12.43v5.11H16.3c-4.02,0-7.3,3.29-7.3,7.33v20.7c0,2.43,1.61,4.54,3.93,5.22v41.25c0,4.04,3.27,7.32,7.28,7.32H107.79c4.02,0,7.28-3.28,7.28-7.32v-41.25c2.32-.68,3.93-2.79,3.93-5.22v-20.7c0-4.04-3.27-7.33-7.3-7.33Zm.37,74.5c0,2.38-1.92,4.32-4.28,4.32H20.21c-2.36,0-4.28-1.94-4.28-4.32v-40.47l41.18,10.49v5.21c0,2.45,1.99,4.44,4.43,4.44h4.7c2.44,0,4.42-1.99,4.42-4.44v-5.14l41.41-10.56v40.47Zm-45.84-36.62c.79,0,1.42,.65,1.42,1.44v10.41c0,.8-.64,1.44-1.42,1.44h-4.7c-.79,0-1.43-.65-1.43-1.44v-10.41c0-.8,.64-1.44,1.43-1.44h4.7Zm49.76-9.85c0,1.12-.76,2.1-1.85,2.38-44.37,11.31-43.49,11.19-43.49,11.09v-2.17c0-2.45-1.99-4.44-4.42-4.44h-4.7c-2.41,0-4.43,1.94-4.43,4.44v2.1L13.85,62.27c-1.09-.28-1.85-1.25-1.85-2.38v-20.7c0-2.39,1.93-4.33,4.3-4.33H111.7c2.37,0,4.3,1.94,4.3,4.33v20.7ZM40.65,26.74c0-5.2,4.2-9.43,9.37-9.43h27.73c5.17,0,9.37,4.23,9.37,9.43v5.11h-5.17v-5.11c0-2.33-1.89-4.23-4.2-4.23h-27.73c-2.32,0-4.2,1.9-4.2,4.23v5.11h-5.17v-5.11Zm8.17,5.11v-5.11c0-.68,.54-1.23,1.2-1.23h27.73c.66,0,1.2,.55,1.2,1.23v5.11h-30.14Z"/><text x="0" y="143" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">Created by nasril</text><text x="0" y="148" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">from the Noun Project</text></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,3 @@
<svg width="34" height="32" viewBox="0 0 34 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.4912 0.5C24.6609 0.500107 26.4463 2.29218 26.4463 4.50195C26.4461 5.9638 25.6634 7.24788 24.502 7.94824L23.6514 8.46094L24.5693 8.83887C26.587 9.66866 28.0146 11.6745 28.0146 14.0195V14.5029L28.4541 14.5566C30.4025 14.7942 31.9307 16.4869 31.9307 18.5342C31.9306 19.9965 31.1509 21.2779 29.9912 21.9766L29.1406 22.4893L30.0596 22.8672C32.0762 23.6969 33.5 25.7015 33.5 28.0469V31.4609C33.4998 31.4881 33.4804 31.4983 33.4697 31.499C33.466 31.4988 33.4624 31.4985 33.459 31.4971C33.4553 31.4955 33.4524 31.4944 33.4512 31.4932L33.4482 31.4902L33.4365 31.458V28.0469C33.4365 24.9911 31.019 22.5352 27.9805 22.5352C24.9421 22.5352 22.5225 24.9924 22.5225 28.0469V31.4629C22.5225 31.4743 22.5176 31.4844 22.5117 31.4902C22.5051 31.4967 22.4976 31.4988 22.4902 31.499C22.485 31.4985 22.4784 31.4966 22.4727 31.4912L22.4609 31.4609V28.0469C22.4609 24.9905 20.0379 22.5352 17 22.5352C13.9614 22.5352 11.544 24.9925 11.5439 28.0469V31.4609C11.5439 31.4744 11.5382 31.4841 11.5312 31.4912C11.5246 31.498 11.5191 31.5 11.5137 31.5C11.5104 31.5 11.508 31.4997 11.5049 31.499C11.4948 31.4967 11.4805 31.4846 11.4805 31.4629V28.0488C11.4805 24.9931 9.0629 22.5373 6.02441 22.5371C2.98626 22.5371 0.563477 24.9939 0.563477 28.0488V31.4609C0.563297 31.4882 0.542917 31.4999 0.530273 31.5C0.524251 31.5 0.518186 31.4977 0.511719 31.4912C0.505432 31.4847 0.501504 31.4756 0.500977 31.4629V28.0488C0.500977 25.7039 1.92786 23.7001 3.94531 22.8701L4.86426 22.4912L4.0127 21.9795C2.85121 21.2805 2.06934 19.997 2.06934 18.5352C2.06949 16.4873 3.60107 14.7963 5.55176 14.5586L5.99121 14.5049V14.0205C5.99124 11.6755 7.41775 9.67071 9.43652 8.84082L10.3555 8.46289L9.50391 7.9502C8.34227 7.24961 7.55957 5.96483 7.55957 4.50293C7.55962 2.29286 9.34267 0.502076 11.5137 0.501953H11.5977C13.73 0.546093 15.4668 2.31921 15.4668 4.50195C15.4666 5.96451 14.6864 7.24824 13.5254 7.94824L12.6758 8.46094L13.5938 8.83887C14.9033 9.37789 15.9663 10.4144 16.5479 11.7168L17.0039 12.7393L17.4609 11.7168C18.0426 10.4142 19.1043 9.3778 20.415 8.83887L21.334 8.46094L20.4834 7.94824C19.322 7.24792 18.5402 5.96407 18.54 4.50195C18.54 2.29132 20.3205 0.5 22.4912 0.5ZM11.5127 8.50879C8.47454 8.50879 6.05176 10.9655 6.05176 14.0205V14.5039L6.49219 14.5566C8.44316 14.7921 9.97363 16.4858 9.97363 18.5342C9.9735 19.9963 9.19367 21.2769 8.03418 21.9756L7.18359 22.4883L8.10156 22.8662C9.41094 23.4051 10.4731 24.4415 11.0547 25.7441L11.1865 26.04H11.8369L11.9688 25.7441C12.552 24.4411 13.6128 23.405 14.9219 22.8662L15.8398 22.4883L14.9893 21.9756C13.8297 21.2769 13.0499 19.9963 13.0498 18.5342C13.0498 16.4857 14.5807 14.793 16.5322 14.5566L16.9717 14.5029V14.0205C16.9717 10.9645 14.5521 8.50879 11.5137 8.50879H11.5127ZM22.4902 8.50781C19.4517 8.50781 17.0344 10.9643 17.0342 14.0186V14.5029L17.4736 14.5566C19.4239 14.7945 20.9541 16.4876 20.9541 18.5342C20.954 19.9961 20.173 21.2768 19.0117 21.9756L18.1602 22.4883L19.0791 22.8662C20.3897 23.4051 21.452 24.4414 22.0352 25.7441L22.168 26.04H22.8174L22.9492 25.7441C23.5309 24.4415 24.5929 23.4051 25.9023 22.8662L26.8213 22.4883L25.9697 21.9756C24.8102 21.2769 24.0304 19.9962 24.0303 18.5342C24.0303 16.4856 25.5595 14.793 27.5107 14.5566L27.9512 14.5029V14.0205C27.9512 11.1335 25.7892 8.78398 22.9893 8.53223V8.50781H22.4902ZM6.02344 14.6074C3.8627 14.6074 2.13208 16.3662 2.13184 18.5332C2.13184 20.7012 3.86338 22.4561 6.02344 22.4561C8.18395 22.4559 9.91211 20.7006 9.91211 18.5332C9.91187 16.3669 8.18463 14.6075 6.02344 14.6074ZM16.999 14.6074C14.8379 14.6076 13.1106 16.3669 13.1104 18.5332C13.1104 20.7005 14.8386 22.4559 16.999 22.4561C19.1591 22.4561 20.8916 20.7012 20.8916 18.5332C20.8914 16.3662 19.1598 14.6074 16.999 14.6074ZM27.9795 14.6074C25.8182 14.6075 24.0911 16.3668 24.0908 18.5332C24.0908 20.7006 25.8189 22.456 27.9795 22.4561C30.1404 22.4561 31.8672 20.7004 31.8672 18.5332C31.8669 16.3671 30.1411 14.6074 27.9795 14.6074ZM11.5137 0.579102C9.35372 0.579223 7.62207 2.33401 7.62207 4.50195C7.62232 6.66883 9.35304 8.42761 11.5137 8.42773C13.675 8.42773 15.4031 6.66835 15.4033 4.50195C15.4033 2.3345 13.6743 0.579102 11.5137 0.579102ZM22.4902 0.579102C20.3293 0.579102 18.6025 2.33478 18.6025 4.50195C18.6028 6.66807 20.3287 8.42773 22.4902 8.42773C24.6506 8.42762 26.3835 6.66912 26.3838 4.50195C26.3838 2.33373 24.6499 0.579213 22.4902 0.579102Z" fill="black" stroke="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,3 @@
<svg width="27" height="25" viewBox="0 0 27 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.14339 0.00116273C7.19618 0.00116273 5.60647 1.57928 5.60647 3.51799C5.60647 4.80237 6.30553 5.93008 7.34256 6.5453C5.59377 7.25256 4.36053 8.95969 4.36053 10.9536V10.9858C2.61174 11.1955 1.24591 12.6837 1.24591 14.4808C1.24591 15.7652 1.94497 16.8918 2.982 17.5058C1.23436 18.213 0.00115341 19.919 0.00115341 21.9129V24.5807H0C0.00115348 24.8134 0.190335 25.0011 0.423355 25C0.65522 24.9988 0.843255 24.8122 0.844407 24.5807V21.9129C0.844407 19.7381 2.59435 17.9977 4.78383 17.9977C6.97331 17.9977 8.71971 19.737 8.71971 21.9129V24.5807C8.71971 24.7777 8.85698 24.947 9.04848 24.9896C9.07962 24.9965 9.11077 25 9.14307 25C9.37493 25 9.56297 24.8122 9.56412 24.5807V21.9117C9.56412 19.7369 11.3106 17.9965 13.5 17.9965C15.6894 17.9965 17.4394 19.7359 17.4394 21.9117V24.5807C17.4406 24.8122 17.6274 24.9988 17.8593 25C17.9712 25 18.0785 24.9562 18.1581 24.8779C18.2377 24.7996 18.2827 24.6924 18.2827 24.5807V21.9117C18.2827 19.7369 20.0303 17.9965 22.2197 17.9965C24.4092 17.9965 26.1556 19.7359 26.1556 21.9117V24.5807C26.1568 24.6924 26.2017 24.7996 26.2813 24.8779C26.3598 24.9562 26.4682 25 26.5801 25C26.8108 24.9988 26.9988 24.8122 27 24.5807V21.9117C27 19.9178 25.7692 18.2106 24.0215 17.5034C25.0574 16.8895 25.7542 15.7641 25.7542 14.4797C25.7542 12.6839 24.3918 11.1943 22.6443 10.9847V10.9524C22.6443 8.95847 21.4099 7.25128 19.6622 6.54414C20.6993 5.92903 21.3983 4.8013 21.3983 3.51683C21.3983 1.57818 19.8064 0 17.8605 0C15.9133 0 14.326 1.57812 14.326 3.51683C14.326 4.80121 15.0239 5.92892 16.0609 6.54414C14.9246 7.00375 14.0064 7.88611 13.5035 8.99424C13.0005 7.88611 12.0811 7.00375 10.946 6.54414C11.9831 5.92903 12.6798 4.8013 12.6798 3.51683C12.6798 1.57818 11.0914 0 9.14527 0L9.14339 0.00116273ZM9.14339 0.843213C10.6361 0.843213 11.8346 2.03544 11.8346 3.51678C11.8346 4.99812 10.6361 6.1938 9.14339 6.1938C7.65069 6.1938 6.44985 4.99812 6.44985 3.51678C6.44985 2.03544 7.65069 0.843213 9.14339 0.843213ZM17.8597 0.843213C19.3524 0.843213 20.5544 2.03544 20.5544 3.51678C20.5544 4.99812 19.3524 6.1938 17.8597 6.1938C16.367 6.1938 15.1696 4.99812 15.1696 3.51678C15.1696 2.03544 16.367 0.843213 17.8597 0.843213ZM9.14339 7.03821C11.3328 7.03821 13.0804 8.77757 13.0804 10.9534V10.9845C11.3305 11.193 9.96582 12.6824 9.96582 14.4795C9.96582 15.7639 10.6626 16.8893 11.6985 17.5033C10.5634 17.9629 9.6463 18.8453 9.14221 19.9534H9.14105C8.63811 18.8453 7.71988 17.9629 6.5848 17.5033C7.62069 16.8894 8.31744 15.7639 8.31744 14.4795C8.31744 12.6825 6.95278 11.1921 5.20281 10.9845V10.9534C5.20281 8.77863 6.95275 7.03821 9.14223 7.03821H9.14339ZM17.8597 7.03821C20.0492 7.03821 21.7991 8.77757 21.7991 10.9534V10.9845C20.0492 11.193 18.6857 12.6824 18.6857 14.4795C18.6857 15.7639 19.3825 16.8893 20.4183 17.5033C19.2832 17.9629 18.365 18.8453 17.8621 19.9534H17.8609C17.3568 18.8453 16.4386 17.9629 15.3024 17.5033C16.3394 16.8894 17.0373 15.7639 17.0373 14.4795C17.0373 12.6837 15.6726 11.1942 13.9238 10.9845V10.9523C13.9238 8.77748 15.6703 7.03705 17.8597 7.03705L17.8597 7.03821ZM4.78315 11.8024C6.27585 11.8024 7.47439 12.9981 7.47439 14.4794C7.47439 15.9608 6.27585 17.153 4.78315 17.153C3.29045 17.153 2.08961 15.9608 2.08961 14.4794C2.08961 12.9981 3.29045 11.8024 4.78315 11.8024ZM13.4995 11.8024C14.9922 11.8024 16.193 12.9981 16.193 14.4794C16.193 15.9608 14.9922 17.153 13.4995 17.153C12.0068 17.153 10.8083 15.9608 10.8083 14.4794C10.8083 12.9981 12.0068 11.8024 13.4995 11.8024ZM22.2191 11.8024C23.7118 11.8024 24.9092 12.9981 24.9092 14.4794C24.9092 15.9608 23.7118 17.153 22.2191 17.153C20.7264 17.153 19.5278 15.9608 19.5278 14.4794C19.5278 12.9981 20.7264 11.8024 22.2191 11.8024Z" fill="#FFE9C8"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,13 @@
<svg width="27" height="25" viewBox="0 0 27 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="9" cy="3.5" rx="3" ry="3.5" fill="#74000A"/>
<ellipse cx="18" cy="3.3" rx="3" ry="3.3" fill="#74000A"/>
<circle cx="13.5" cy="14.5" r="3.5" fill="#74000A"/>
<circle cx="5.5" cy="14.5" r="3.5" fill="#74000A"/>
<circle cx="21.5" cy="14.5" r="3.5" fill="#74000A"/>
<ellipse cx="9.25" cy="11" rx="4.25" ry="4" fill="#74000A"/>
<ellipse cx="17.75" cy="11.5" rx="4.75" ry="4.5" fill="#74000A"/>
<ellipse cx="13.5" cy="19" rx="7" ry="6" fill="#74000A"/>
<ellipse cx="13.5" cy="20.5" rx="12.5" ry="4.5" fill="#74000A"/>
<rect x="0.5" y="20" width="26" height="5" fill="#74000A"/>
<path d="M9.14339 0.00116273C7.19618 0.00116273 5.60647 1.57928 5.60647 3.51799C5.60647 4.80237 6.30553 5.93008 7.34256 6.5453C5.59377 7.25256 4.36053 8.95969 4.36053 10.9536V10.9858C2.61174 11.1955 1.24591 12.6837 1.24591 14.4808C1.24591 15.7652 1.94497 16.8918 2.982 17.5058C1.23436 18.213 0.00115341 19.919 0.00115341 21.9129V24.5807H0C0.00115348 24.8134 0.190335 25.0011 0.423355 25C0.65522 24.9988 0.843255 24.8122 0.844407 24.5807V21.9129C0.844407 19.7381 2.59435 17.9977 4.78383 17.9977C6.97331 17.9977 8.71971 19.737 8.71971 21.9129V24.5807C8.71971 24.7777 8.85698 24.947 9.04848 24.9896C9.07962 24.9965 9.11077 25 9.14307 25C9.37493 25 9.56297 24.8122 9.56412 24.5807V21.9117C9.56412 19.7369 11.3106 17.9965 13.5 17.9965C15.6894 17.9965 17.4394 19.7359 17.4394 21.9117V24.5807C17.4406 24.8122 17.6274 24.9988 17.8593 25C17.9712 25 18.0785 24.9562 18.1581 24.8779C18.2377 24.7996 18.2827 24.6924 18.2827 24.5807V21.9117C18.2827 19.7369 20.0303 17.9965 22.2197 17.9965C24.4092 17.9965 26.1556 19.7359 26.1556 21.9117V24.5807C26.1568 24.6924 26.2017 24.7996 26.2813 24.8779C26.3598 24.9562 26.4682 25 26.5801 25C26.8108 24.9988 26.9988 24.8122 27 24.5807V21.9117C27 19.9178 25.7692 18.2106 24.0215 17.5034C25.0574 16.8895 25.7542 15.7641 25.7542 14.4797C25.7542 12.6839 24.3918 11.1943 22.6443 10.9847V10.9524C22.6443 8.95847 21.4099 7.25128 19.6622 6.54414C20.6993 5.92903 21.3983 4.8013 21.3983 3.51683C21.3983 1.57818 19.8064 0 17.8605 0C15.9133 0 14.326 1.57812 14.326 3.51683C14.326 4.80121 15.0239 5.92892 16.0609 6.54414C14.9246 7.00375 14.0064 7.88611 13.5035 8.99424C13.0005 7.88611 12.0811 7.00375 10.946 6.54414C11.9831 5.92903 12.6798 4.8013 12.6798 3.51683C12.6798 1.57818 11.0914 0 9.14527 0L9.14339 0.00116273ZM9.14339 0.843213C10.6361 0.843213 11.8346 2.03544 11.8346 3.51678C11.8346 4.99812 10.6361 6.1938 9.14339 6.1938C7.65069 6.1938 6.44985 4.99812 6.44985 3.51678C6.44985 2.03544 7.65069 0.843213 9.14339 0.843213ZM17.8597 0.843213C19.3524 0.843213 20.5544 2.03544 20.5544 3.51678C20.5544 4.99812 19.3524 6.1938 17.8597 6.1938C16.367 6.1938 15.1696 4.99812 15.1696 3.51678C15.1696 2.03544 16.367 0.843213 17.8597 0.843213ZM9.14339 7.03821C11.3328 7.03821 13.0804 8.77757 13.0804 10.9534V10.9845C11.3305 11.193 9.96582 12.6824 9.96582 14.4795C9.96582 15.7639 10.6626 16.8893 11.6985 17.5033C10.5634 17.9629 9.6463 18.8453 9.14221 19.9534H9.14105C8.63811 18.8453 7.71988 17.9629 6.5848 17.5033C7.62069 16.8894 8.31744 15.7639 8.31744 14.4795C8.31744 12.6825 6.95278 11.1921 5.20281 10.9845V10.9534C5.20281 8.77863 6.95275 7.03821 9.14223 7.03821H9.14339ZM17.8597 7.03821C20.0492 7.03821 21.7991 8.77757 21.7991 10.9534V10.9845C20.0492 11.193 18.6857 12.6824 18.6857 14.4795C18.6857 15.7639 19.3825 16.8893 20.4183 17.5033C19.2832 17.9629 18.365 18.8453 17.8621 19.9534H17.8609C17.3568 18.8453 16.4386 17.9629 15.3024 17.5033C16.3394 16.8894 17.0373 15.7639 17.0373 14.4795C17.0373 12.6837 15.6726 11.1942 13.9238 10.9845V10.9523C13.9238 8.77748 15.6703 7.03705 17.8597 7.03705L17.8597 7.03821ZM4.78315 11.8024C6.27585 11.8024 7.47439 12.9981 7.47439 14.4794C7.47439 15.9608 6.27585 17.153 4.78315 17.153C3.29045 17.153 2.08961 15.9608 2.08961 14.4794C2.08961 12.9981 3.29045 11.8024 4.78315 11.8024ZM13.4995 11.8024C14.9922 11.8024 16.193 12.9981 16.193 14.4794C16.193 15.9608 14.9922 17.153 13.4995 17.153C12.0068 17.153 10.8083 15.9608 10.8083 14.4794C10.8083 12.9981 12.0068 11.8024 13.4995 11.8024ZM22.2191 11.8024C23.7118 11.8024 24.9092 12.9981 24.9092 14.4794C24.9092 15.9608 23.7118 17.153 22.2191 17.153C20.7264 17.153 19.5278 15.9608 19.5278 14.4794C19.5278 12.9981 20.7264 11.8024 22.2191 11.8024Z" fill="#FFE9C8"/>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 64 80" x="0px" y="0px"><path d="M32,8c-10.504,0-19.05,8.546-19.05,19.05,0,13.12,17.024,27.849,17.749,28.469,.374,.321,.838,.481,1.301,.481s.927-.16,1.301-.481c.725-.621,17.749-15.35,17.749-28.469,0-10.504-8.546-19.05-19.05-19.05Zm0,27c-4.687,0-8.5-3.813-8.5-8.5s3.813-8.5,8.5-8.5,8.5,3.813,8.5,8.5-3.813,8.5-8.5,8.5Z"/></svg>

After

Width:  |  Height:  |  Size: 398 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 64 80" x="0px" y="0px"><path d="M32,8c-10.504,0-19.05,8.546-19.05,19.05,0,13.12,17.024,27.849,17.749,28.469,.374,.321,.838,.481,1.301,.481s.927-.16,1.301-.481c.725-.621,17.749-15.35,17.749-28.469,0-10.504-8.546-19.05-19.05-19.05Zm0,27c-4.687,0-8.5-3.813-8.5-8.5s3.813-8.5,8.5-8.5,8.5,3.813,8.5,8.5-3.813,8.5-8.5,8.5Z" fill="#FFE9C8"/></svg>

After

Width:  |  Height:  |  Size: 413 B

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-5.0 -10.0 110.0 135.0">
<path d="m50 96.016c-25.414 0-46.012-20.602-46.012-46.016s20.598-46.016 46.012-46.016 46.012 20.602 46.012 46.016-20.605 46.016-46.012 46.016zm-4.043-73.004v26.988c0 1.4961 0.80859 2.8008 2.0195 3.5l17.438 10.078c1.9414 1.1211 4.418 0.46094 5.543-1.4766 1.1133-1.9336 0.46094-4.4141-1.4805-5.5352l-15.43-8.9102v-24.645c0-2.2344-1.8125-4.0469-4.043-4.0469-2.2305 0-4.043 1.8125-4.043 4.0469zm30.84 0.19141c-14.801-14.801-38.793-14.801-53.59 0-14.801 14.797-14.801 38.793 0 53.594 14.797 14.801 38.793 14.801 53.59 0s14.797-38.793 0-53.594z" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 657 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-5.0 -10.0 110.0 135.0">
<path d="m50 96.016c-25.414 0-46.012-20.602-46.012-46.016s20.598-46.016 46.012-46.016 46.012 20.602 46.012 46.016-20.605 46.016-46.012 46.016zm-4.043-73.004v26.988c0 1.4961 0.80859 2.8008 2.0195 3.5l17.438 10.078c1.9414 1.1211 4.418 0.46094 5.543-1.4766 1.1133-1.9336 0.46094-4.4141-1.4805-5.5352l-15.43-8.9102v-24.645c0-2.2344-1.8125-4.0469-4.043-4.0469-2.2305 0-4.043 1.8125-4.043 4.0469zm30.84 0.19141c-14.801-14.801-38.793-14.801-53.59 0-14.801 14.797-14.801 38.793 0 53.594 14.797 14.801 38.793 14.801 53.59 0s14.797-38.793 0-53.594z" fill="#FFE9C8"/>
</svg>

After

Width:  |  Height:  |  Size: 652 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path d="M 20.5 4 A 1.50015 1.50015 0 0 0 19.066406 6 L 14.640625 6 C 12.803372 6 11.082924 6.9194511 10.064453 8.4492188 L 7.6972656 12 L 7.5 12 A 1.50015 1.50015 0 1 0 7.5 15 L 8.2636719 15 A 1.50015 1.50015 0 0 0 8.6523438 15.007812 L 11.125 38.085938 C 11.423352 40.868277 13.795836 43 16.59375 43 L 31.404297 43 C 34.202211 43 36.574695 40.868277 36.873047 38.085938 L 39.347656 15.007812 A 1.50015 1.50015 0 0 0 39.728516 15 L 40.5 15 A 1.50015 1.50015 0 1 0 40.5 12 L 40.302734 12 L 37.935547 8.4492188 C 36.916254 6.9202798 35.196001 6 33.359375 6 L 28.933594 6 A 1.50015 1.50015 0 0 0 27.5 4 L 20.5 4 z M 14.640625 9 L 33.359375 9 C 34.196749 9 34.974746 9.4162203 35.439453 10.113281 L 36.697266 12 L 11.302734 12 L 12.560547 10.113281 A 1.50015 1.50015 0 0 0 12.5625 10.111328 C 13.025982 9.4151428 13.801878 9 14.640625 9 z M 11.669922 15 L 36.330078 15 L 33.890625 37.765625 C 33.752977 39.049286 32.694383 40 31.404297 40 L 16.59375 40 C 15.303664 40 14.247023 39.049286 14.109375 37.765625 L 11.669922 15 z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

30
src/util.js Normal file
View File

@@ -0,0 +1,30 @@
import { Preferences } from '@capacitor/preferences';
const env = import.meta.env
export default class util {
static HOST = env.VITE_API_URL
static async authFetch(url, options = {}) {
const { value: token } = await Preferences.get({ key: 'auth_token' });
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`,
'X-Client': 'mobile'
}
});
}
static async removeAuthToken() {
await Preferences.remove({ key: 'auth_token'})
}
static cssVariable(value) {
return getComputedStyle(document.documentElement)
.getPropertyValue("--" + value)
.trim();
}
}

View File

@@ -6,9 +6,41 @@ export default defineConfig({
outDir: '../dist',
minify: false,
emptyOutDir: true,
sourcemap: true,
target: 'esnext' // modern version of browsers, allows top-level await
},
server: {
proxy: {
"/ws": {
target: "http://localhost:10002",
changeOrigin: true,
ws: true
},
"/profile/upload-image": {
target: "http://localhost:10002",
changeOrigin: true
},
"/api": {
target: "http://localhost:10002",
changeOrigin: true
},
"/db": {
target: "http://localhost:10002",
changeOrigin: true
},
"/apps": {
target: "http://localhost:10002",
changeOrigin: true
},
"/auth": {
target: "http://localhost:10002",
changeOrigin: true
}
},
host: true,
allowedHosts: ['sam.local'],
},
esbuild: {
keepNames: true
}
});