├── web ├── base.css └── test.html ├── .gitignore ├── InfiniteAppStore ├── WebViewHack.h ├── Assets.xcassets │ ├── Contents.json │ ├── close.imageset │ │ ├── close.pdf │ │ └── Contents.json │ ├── check.imageset │ │ ├── Checkbox.pdf │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── PREVIEW.png │ │ ├── PREVIEW (1).png │ │ └── Contents.json │ ├── icon95.imageset │ │ ├── Windows_logo_-_1992 1.pdf │ │ └── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── maximize.imageset │ │ ├── Contents.json │ │ └── maximize.pdf │ └── minimize.imageset │ │ ├── Contents.json │ │ └── minimize.pdf ├── Assets │ ├── MS Sans Serif.ttf │ ├── MS Sans Serif Bold.ttf │ └── Icons.swift ├── StaticWebFiles │ ├── Icons │ │ ├── agent.png │ │ ├── arrow.png │ │ ├── audio.png │ │ ├── camera.png │ │ ├── chart.png │ │ ├── doctor.png │ │ ├── error.png │ │ ├── file.png │ │ ├── fonts.png │ │ ├── globe.png │ │ ├── help.png │ │ ├── keys.png │ │ ├── modem.png │ │ ├── mouse.png │ │ ├── note.png │ │ ├── paint.png │ │ ├── plug.png │ │ ├── search.png │ │ ├── sound.png │ │ ├── spider.png │ │ ├── themes.png │ │ ├── tools.png │ │ ├── tree.png │ │ ├── users.png │ │ ├── battery.png │ │ ├── calendar.png │ │ ├── channels.png │ │ ├── desktop.png │ │ ├── directx.png │ │ ├── hardware.png │ │ ├── joystick.png │ │ ├── keyboard.png │ │ ├── mailbox.png │ │ ├── restrict.png │ │ ├── scandisk.png │ │ ├── scanner.png │ │ ├── standby.png │ │ ├── warning.png │ │ ├── windows.png │ │ ├── agent_file.png │ │ ├── certificate.png │ │ ├── clean_drive.png │ │ ├── executable.png │ │ ├── help_book.png │ │ ├── image_check.png │ │ ├── information.png │ │ ├── installer.png │ │ ├── minesweeper.png │ │ ├── multimedia.png │ │ ├── newspaper.png │ │ ├── recycle_bin.png │ │ ├── televisions.png │ │ ├── time_date.png │ │ ├── world_star.png │ │ ├── write_file.png │ │ ├── address_book.png │ │ ├── calendar_user.png │ │ ├── character_map.png │ │ ├── mouse_trails.png │ │ ├── write_yellow.png │ │ ├── connected_world.png │ │ ├── internet_wizard.png │ │ └── magnifying_glass.png │ ├── MS_Sans_Serif.ttf │ ├── MS_Sans_Serif_Bold.ttf │ ├── fonts.css │ └── 98.css ├── InfiniteAppStore-Bridging-Header.h ├── Mac │ ├── AppNSWindow.swift │ ├── ContactDevWindow.swift │ ├── StoreViewController.swift │ ├── AppDelegate.swift │ ├── SettingsWindow.swift │ ├── StatusMenu.swift │ ├── FloatingSwiftUIWindow.swift │ ├── Prompt.swift │ └── AppViewController.swift ├── Utils │ ├── String.swift │ ├── Color.swift │ ├── DataStore+SwiftUI.swift │ ├── WindowControlAction.swift │ ├── Task.swift │ ├── PerFrameAnimationView.swift │ ├── CrossPlatform.swift │ ├── Queue.swift │ ├── SwiftUI+Convenience.swift │ ├── Scripting.swift │ └── DataStore.swift ├── InfiniteAppStore.entitlements ├── Info.plist ├── WebViewHack.m ├── Data │ ├── AppState.swift │ ├── ProgramGen.swift │ ├── CSS.swift │ └── InfiniteAppStoreStore.json ├── MobileAppView.swift ├── Server.swift ├── Settings.swift ├── NewProgram.swift ├── AppMenuView.swift ├── AppView.swift ├── UI95.swift ├── InfiniteWebView.swift └── ContactDev.swift ├── InfiniteAppStoreiOS ├── Assets.xcassets │ ├── Contents.json │ ├── close.imageset │ │ ├── close.pdf │ │ └── Contents.json │ ├── icon95.imageset │ │ ├── Windows_logo_-_1992 1.pdf │ │ └── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── maximize.imageset │ │ ├── Contents.json │ │ └── maximize.pdf │ ├── minimize.imageset │ │ ├── Contents.json │ │ └── minimize.pdf │ └── AppIcon.appiconset │ │ └── Contents.json ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── InfiniteAppStoreiOSApp.swift ├── ContentView.swift ├── Info.plist └── PromptDialog.swift ├── README.md └── InfiniteAppStore.xcodeproj ├── project.xcworkspace ├── contents.xcworkspacedata ├── xcuserdata │ └── nateparrott.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ └── Package.resolved └── xcuserdata └── nateparrott.xcuserdatad └── xcschemes └── xcschememanagement.plist /web/base.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | InfiniteAppStore.xcodeproj/xcuserdata/** 4 | -------------------------------------------------------------------------------- /InfiniteAppStore/WebViewHack.h: -------------------------------------------------------------------------------- 1 | @import WebKit; 2 | 3 | void WDBSetWebSecurityEnabled(WKPreferences* prefs, bool enabled); 4 | -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /InfiniteAppStore/Assets/MS Sans Serif.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/Assets/MS Sans Serif.ttf -------------------------------------------------------------------------------- /InfiniteAppStore/Assets/MS Sans Serif Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/Assets/MS Sans Serif Bold.ttf -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/agent.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/arrow.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/audio.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/camera.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/chart.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/doctor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/doctor.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/error.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/file.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/fonts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/fonts.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/globe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/globe.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/help.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/keys.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/modem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/modem.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/mouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/mouse.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/note.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/paint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/paint.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/plug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/plug.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/search.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/sound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/sound.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/spider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/spider.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/themes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/themes.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/tools.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/tree.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/users.png -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/battery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/battery.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/calendar.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/channels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/channels.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/desktop.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/directx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/directx.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/hardware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/hardware.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/joystick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/joystick.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/keyboard.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/mailbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/mailbox.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/restrict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/restrict.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/scandisk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/scandisk.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/scanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/scanner.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/standby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/standby.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/warning.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/windows.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/MS_Sans_Serif.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/MS_Sans_Serif.ttf -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/agent_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/agent_file.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/certificate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/certificate.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/clean_drive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/clean_drive.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/executable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/executable.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/help_book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/help_book.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/image_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/image_check.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/information.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/installer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/installer.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/minesweeper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/minesweeper.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/multimedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/multimedia.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/newspaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/newspaper.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/recycle_bin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/recycle_bin.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/televisions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/televisions.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/time_date.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/time_date.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/world_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/world_star.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/write_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/write_file.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/address_book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/address_book.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/calendar_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/calendar_user.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/character_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/character_map.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/mouse_trails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/mouse_trails.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/write_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/write_yellow.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/MS_Sans_Serif_Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/MS_Sans_Serif_Bold.ttf -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/close.imageset/close.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/Assets.xcassets/close.imageset/close.pdf -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/connected_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/connected_world.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/internet_wizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/internet_wizard.png -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/Icons/magnifying_glass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/StaticWebFiles/Icons/magnifying_glass.png -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/check.imageset/Checkbox.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/Assets.xcassets/check.imageset/Checkbox.pdf -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/Assets.xcassets/close.imageset/close.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStoreiOS/Assets.xcassets/close.imageset/close.pdf -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/AppIcon.appiconset/PREVIEW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/Assets.xcassets/AppIcon.appiconset/PREVIEW.png -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/AppIcon.appiconset/PREVIEW (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/Assets.xcassets/AppIcon.appiconset/PREVIEW (1).png -------------------------------------------------------------------------------- /InfiniteAppStore/InfiniteAppStore-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "WebViewHack.h" 6 | -------------------------------------------------------------------------------- /InfiniteAppStore/Mac/AppNSWindow.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | 3 | class AppNSWindow: NSWindow { 4 | override var canBecomeKey: Bool { true } 5 | 6 | override var canBecomeMain: Bool { true } 7 | } 8 | -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/icon95.imageset/Windows_logo_-_1992 1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore/Assets.xcassets/icon95.imageset/Windows_logo_-_1992 1.pdf -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/Assets.xcassets/icon95.imageset/Windows_logo_-_1992 1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStoreiOS/Assets.xcassets/icon95.imageset/Windows_logo_-_1992 1.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Start Menu 2 | 3 | Details [here](https://www.threads.net/@nate_loved_an_image/post/C8vEvATPY81) 4 | 5 | ## Download 6 | 7 | In [Releases](https://github.com/nate-parrott/infinite-app-store/releases/tag/v1) 8 | -------------------------------------------------------------------------------- /InfiniteAppStore.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /web/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Test

8 |

This is a test page.

9 | 10 | -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "close.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/check.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Checkbox.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/maximize.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "maximize.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/minimize.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "minimize.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/Assets.xcassets/close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "close.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/Assets.xcassets/maximize.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "maximize.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/Assets.xcassets/minimize.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "minimize.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /InfiniteAppStore.xcodeproj/project.xcworkspace/xcuserdata/nateparrott.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-parrott/infinite-app-store/HEAD/InfiniteAppStore.xcodeproj/project.xcworkspace/xcuserdata/nateparrott.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /InfiniteAppStore/Utils/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // InfiniteAppStore 4 | // 5 | // Created by nate parrott on 6/8/24. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String { 11 | var nilIfEmpty: String? { 12 | isEmpty ? nil : self 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /InfiniteAppStore/InfiniteAppStore.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.automation.apple-events 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /InfiniteAppStore.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /InfiniteAppStore/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/icon95.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Windows_logo_-_1992 1.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/Assets.xcassets/icon95.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Windows_logo_-_1992 1.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/InfiniteAppStoreiOSApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfiniteAppStoreiOSApp.swift 3 | // InfiniteAppStoreiOS 4 | // 5 | // Created by nate parrott on 6/16/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct InfiniteAppStoreiOSApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /InfiniteAppStore/Utils/Color.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension Color { 4 | init(hex: Int) { 5 | let r = (hex >> 16) & 0xFF 6 | let g = (hex >> 8) & 0xFF 7 | let b = hex & 0xFF 8 | self.init( 9 | red: Double(r) / 0xFF, 10 | green: Double(g) / 0xFF, 11 | blue: Double(b) / 0xFF 12 | ) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // InfiniteAppStoreiOS 4 | // 5 | // Created by nate parrott on 6/16/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var body: some View { 12 | ScrollView(.vertical) { 13 | AppMenuView() 14 | .padding() 15 | } 16 | .background(Color.gray95) 17 | } 18 | } 19 | 20 | #Preview { 21 | ContentView() 22 | } 23 | -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | UIAppFonts 11 | 12 | MS Sans Serif.ttf 13 | MS Sans Serif Bold.ttf 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /InfiniteAppStore/Utils/DataStore+SwiftUI.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct WithSnapshot: View { 4 | var store: DataStore 5 | var snapshot: (S) -> Snapshot 6 | @ViewBuilder var main: (Snapshot?) -> V 7 | 8 | @State private var val: Snapshot? = nil 9 | 10 | 11 | var body: some View { 12 | ZStack { 13 | main(val) 14 | } 15 | .onReceive(store.publisher.map(snapshot).removeDuplicates(), perform: { self.val = $0 }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /InfiniteAppStore/Utils/WindowControlAction.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | enum WindowControlAction: Equatable { 4 | case minimize 5 | case maximize 6 | case close 7 | } 8 | 9 | // Define environment key 10 | struct WindowActionHandlerKey: EnvironmentKey { 11 | static var defaultValue: (WindowControlAction) -> Void = { _ in } 12 | } 13 | 14 | // Define environment value 15 | extension EnvironmentValues { 16 | var windowActionHandler: (WindowControlAction) -> Void { 17 | get { self[WindowActionHandlerKey.self] } 18 | set { self[WindowActionHandlerKey.self] = newValue } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /InfiniteAppStore/StaticWebFiles/fonts.css: -------------------------------------------------------------------------------- 1 | /* Serve MS Sans Serif Bold.ttf and MS Sans Serif.ttf (which are in this same dir) as webfonts with name "RetroFont" */ 2 | 3 | @font-face { 4 | font-family: "RetroFont"; 5 | src: url("MS_Sans_Serif_Bold.ttf") format("truetype"); 6 | font-weight: bold; 7 | } 8 | 9 | @font-face { 10 | font-family: "RetroFont"; 11 | src: url("MS_Sans_Serif.ttf") format("truetype"); 12 | font-weight: normal; 13 | } 14 | 15 | /* 16 | Import this file in your CSS file with @import url("http://localhost:50082/static/fonts.css"); 17 | Then use font-family: "RetroFont" in your CSS to use the font. 18 | */ 19 | -------------------------------------------------------------------------------- /InfiniteAppStore/Mac/ContactDevWindow.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @discardableResult 4 | func contactDevForProgram(id: String) -> BorderlessSwiftUIWindow { 5 | let win = BorderlessSwiftUIWindow(resizable: true, dialog: false) { 6 | ContactDevView(programId: id) 7 | } 8 | win.makeKeyAndOrderFront(nil) 9 | return win 10 | } 11 | 12 | func getOrCreateContactDevWindow(id: String) -> BorderlessSwiftUIWindow { 13 | if let existing = NSApp.windows.compactMap({ $0 as? BorderlessSwiftUIWindow }).first { 14 | return existing 15 | } 16 | return contactDevForProgram(id: id) 17 | } 18 | -------------------------------------------------------------------------------- /InfiniteAppStore/Mac/StoreViewController.swift: -------------------------------------------------------------------------------- 1 | //import SwiftUI 2 | //import AppKit 3 | // 4 | //// TODO: Remove this? 5 | //class StoreViewController: AppViewController { 6 | //// let vc = NSHostingController(rootView: StoreView()) 7 | //// 8 | // override func viewDidLoad() { 9 | // super.viewDidLoad() 10 | // self.programID = "Store" 11 | // } 12 | //// 13 | //// override func viewDidLayout() { 14 | //// super.viewDidLoad() 15 | //// vc.view.frame = view.bounds 16 | //// } 17 | //} 18 | // 19 | //struct StoreView: View { 20 | // var body: some View { 21 | // Color.gray.overlay { 22 | // Text("Hello") 23 | // } 24 | // } 25 | //} 26 | -------------------------------------------------------------------------------- /InfiniteAppStore/WebViewHack.m: -------------------------------------------------------------------------------- 1 | // From https://worthdoingbadly.com/disablesameorigin/ 2 | 3 | // Allows disabling Same-Origin Policy on iOS WKWebView. 4 | // Tested on iOS 12.4. 5 | // Uses private API; obviously can't be used on app store. 6 | 7 | @import WebKit; 8 | @import ObjectiveC; 9 | 10 | void WKPreferencesSetWebSecurityEnabled(id, bool); 11 | 12 | @interface WDBFakeWebKitPointer: NSObject 13 | @property (nonatomic) void* _apiObject; 14 | @end 15 | @implementation WDBFakeWebKitPointer 16 | @end 17 | 18 | void WDBSetWebSecurityEnabled(WKPreferences* prefs, bool enabled) { 19 | Ivar ivar = class_getInstanceVariable([WKPreferences class], "_preferences"); 20 | void* realPreferences = (void*)(((uintptr_t)prefs) + ivar_getOffset(ivar)); 21 | WDBFakeWebKitPointer* fake = [WDBFakeWebKitPointer new]; 22 | fake._apiObject = realPreferences; 23 | WKPreferencesSetWebSecurityEnabled(fake, enabled); 24 | } 25 | -------------------------------------------------------------------------------- /InfiniteAppStore/Utils/Task.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Task where Success == Never, Failure == Never { 4 | static func sleep(seconds: Double) async throws { 5 | let duration = UInt64(seconds * 1_000_000_000) 6 | try await Task.sleep(nanoseconds: duration) 7 | } 8 | } 9 | 10 | public extension DispatchQueue { 11 | func performAsyncThrowing(_ block: @escaping () throws -> Result) async throws -> Result { 12 | try await withCheckedThrowingContinuation { cont in 13 | self.async { 14 | do { 15 | let result = try block() 16 | cont.resume(returning: result) 17 | } catch { 18 | cont.resume(throwing: error) 19 | } 20 | } 21 | } 22 | } 23 | 24 | func performAsync(_ block: @escaping () -> Result) async -> Result { 25 | await withCheckedContinuation { cont in 26 | self.async { 27 | let result = block() 28 | cont.resume(returning: result) 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/minimize.imageset/minimize.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | 1.000000 0.000000 -0.000000 1.000000 2.830017 0.079987 cm 14 | 0.000000 0.000000 0.000000 scn 15 | 0.000000 2.920013 m 16 | 8.759999 2.920013 l 17 | 8.759999 0.000014 l 18 | 0.000000 0.000014 l 19 | 0.000000 2.920013 l 20 | h 21 | f 22 | n 23 | Q 24 | 25 | endstream 26 | endobj 27 | 28 | 3 0 obj 29 | 227 30 | endobj 31 | 32 | 4 0 obj 33 | << /Annots [] 34 | /Type /Page 35 | /MediaBox [ 0.000000 0.000000 15.000000 13.000000 ] 36 | /Resources 1 0 R 37 | /Contents 2 0 R 38 | /Parent 5 0 R 39 | >> 40 | endobj 41 | 42 | 5 0 obj 43 | << /Kids [ 4 0 R ] 44 | /Count 1 45 | /Type /Pages 46 | >> 47 | endobj 48 | 49 | 6 0 obj 50 | << /Pages 5 0 R 51 | /Type /Catalog 52 | >> 53 | endobj 54 | 55 | xref 56 | 0 7 57 | 0000000000 65535 f 58 | 0000000010 00000 n 59 | 0000000034 00000 n 60 | 0000000317 00000 n 61 | 0000000339 00000 n 62 | 0000000512 00000 n 63 | 0000000586 00000 n 64 | trailer 65 | << /ID [ (some) (id) ] 66 | /Root 6 0 R 67 | /Size 7 68 | >> 69 | startxref 70 | 645 71 | %%EOF -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/Assets.xcassets/minimize.imageset/minimize.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | 1.000000 0.000000 -0.000000 1.000000 2.830017 0.079987 cm 14 | 0.000000 0.000000 0.000000 scn 15 | 0.000000 2.920013 m 16 | 8.759999 2.920013 l 17 | 8.759999 0.000014 l 18 | 0.000000 0.000014 l 19 | 0.000000 2.920013 l 20 | h 21 | f 22 | n 23 | Q 24 | 25 | endstream 26 | endobj 27 | 28 | 3 0 obj 29 | 227 30 | endobj 31 | 32 | 4 0 obj 33 | << /Annots [] 34 | /Type /Page 35 | /MediaBox [ 0.000000 0.000000 15.000000 13.000000 ] 36 | /Resources 1 0 R 37 | /Contents 2 0 R 38 | /Parent 5 0 R 39 | >> 40 | endobj 41 | 42 | 5 0 obj 43 | << /Kids [ 4 0 R ] 44 | /Count 1 45 | /Type /Pages 46 | >> 47 | endobj 48 | 49 | 6 0 obj 50 | << /Pages 5 0 R 51 | /Type /Catalog 52 | >> 53 | endobj 54 | 55 | xref 56 | 0 7 57 | 0000000000 65535 f 58 | 0000000010 00000 n 59 | 0000000034 00000 n 60 | 0000000317 00000 n 61 | 0000000339 00000 n 62 | 0000000512 00000 n 63 | 0000000586 00000 n 64 | trailer 65 | << /ID [ (some) (id) ] 66 | /Root 6 0 R 67 | /Size 7 68 | >> 69 | startxref 70 | 645 71 | %%EOF -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "filename" : "PREVIEW (1).png", 40 | "idiom" : "mac", 41 | "scale" : "2x", 42 | "size" : "256x256" 43 | }, 44 | { 45 | "idiom" : "mac", 46 | "scale" : "1x", 47 | "size" : "512x512" 48 | }, 49 | { 50 | "filename" : "PREVIEW.png", 51 | "idiom" : "mac", 52 | "scale" : "2x", 53 | "size" : "512x512" 54 | } 55 | ], 56 | "info" : { 57 | "author" : "xcode", 58 | "version" : 1 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /InfiniteAppStore/Mac/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // InfiniteAppStore 4 | // 5 | // Created by nate parrott on 6/8/24. 6 | // 7 | 8 | import Cocoa 9 | import ChatToys 10 | 11 | func isPreview() -> Bool { 12 | return ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" 13 | } 14 | 15 | @main 16 | class AppDelegate: NSObject, NSApplicationDelegate { 17 | 18 | @IBAction func controlPanel(_ sender: Any?) { 19 | showSettingsView() 20 | } 21 | 22 | func applicationDidFinishLaunching(_ aNotification: Notification) { 23 | if !isPreview() { 24 | _ = Server.shared 25 | _ = StatusMenuManager.shared 26 | } 27 | // Insert code here to initialize your application 28 | } 29 | 30 | func applicationWillTerminate(_ aNotification: Notification) { 31 | // Insert code here to tear down your application 32 | } 33 | 34 | func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 35 | return true 36 | } 37 | 38 | // When app icon is clicked, and no windows are shown, show the store 39 | func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { 40 | if !flag { 41 | StatusMenuManager.shared.showMenu() 42 | } 43 | return true 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /InfiniteAppStore/Assets.xcassets/maximize.imageset/maximize.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 14 | 0.000000 0.000000 0.000000 scn 15 | 13.139999 13.140015 m 16 | 0.000000 13.140015 l 17 | 0.000000 0.000015 l 18 | 13.139999 0.000015 l 19 | 13.139999 13.140015 l 20 | h 21 | 11.679999 10.220015 m 22 | 1.460000 10.220015 l 23 | 1.460000 1.460015 l 24 | 11.679999 1.460015 l 25 | 11.679999 10.220015 l 26 | h 27 | f* 28 | n 29 | Q 30 | 31 | endstream 32 | endobj 33 | 34 | 3 0 obj 35 | 342 36 | endobj 37 | 38 | 4 0 obj 39 | << /Annots [] 40 | /Type /Page 41 | /MediaBox [ 0.000000 0.000000 13.140015 13.140015 ] 42 | /Resources 1 0 R 43 | /Contents 2 0 R 44 | /Parent 5 0 R 45 | >> 46 | endobj 47 | 48 | 5 0 obj 49 | << /Kids [ 4 0 R ] 50 | /Count 1 51 | /Type /Pages 52 | >> 53 | endobj 54 | 55 | 6 0 obj 56 | << /Pages 5 0 R 57 | /Type /Catalog 58 | >> 59 | endobj 60 | 61 | xref 62 | 0 7 63 | 0000000000 65535 f 64 | 0000000010 00000 n 65 | 0000000034 00000 n 66 | 0000000432 00000 n 67 | 0000000454 00000 n 68 | 0000000627 00000 n 69 | 0000000701 00000 n 70 | trailer 71 | << /ID [ (some) (id) ] 72 | /Root 6 0 R 73 | /Size 7 74 | >> 75 | startxref 76 | 760 77 | %%EOF -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/Assets.xcassets/maximize.imageset/maximize.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 14 | 0.000000 0.000000 0.000000 scn 15 | 13.139999 13.140015 m 16 | 0.000000 13.140015 l 17 | 0.000000 0.000015 l 18 | 13.139999 0.000015 l 19 | 13.139999 13.140015 l 20 | h 21 | 11.679999 10.220015 m 22 | 1.460000 10.220015 l 23 | 1.460000 1.460015 l 24 | 11.679999 1.460015 l 25 | 11.679999 10.220015 l 26 | h 27 | f* 28 | n 29 | Q 30 | 31 | endstream 32 | endobj 33 | 34 | 3 0 obj 35 | 342 36 | endobj 37 | 38 | 4 0 obj 39 | << /Annots [] 40 | /Type /Page 41 | /MediaBox [ 0.000000 0.000000 13.140015 13.140015 ] 42 | /Resources 1 0 R 43 | /Contents 2 0 R 44 | /Parent 5 0 R 45 | >> 46 | endobj 47 | 48 | 5 0 obj 49 | << /Kids [ 4 0 R ] 50 | /Count 1 51 | /Type /Pages 52 | >> 53 | endobj 54 | 55 | 6 0 obj 56 | << /Pages 5 0 R 57 | /Type /Catalog 58 | >> 59 | endobj 60 | 61 | xref 62 | 0 7 63 | 0000000000 65535 f 64 | 0000000010 00000 n 65 | 0000000034 00000 n 66 | 0000000432 00000 n 67 | 0000000454 00000 n 68 | 0000000627 00000 n 69 | 0000000701 00000 n 70 | trailer 71 | << /ID [ (some) (id) ] 72 | /Root 6 0 R 73 | /Size 7 74 | >> 75 | startxref 76 | 760 77 | %%EOF -------------------------------------------------------------------------------- /InfiniteAppStore/Utils/PerFrameAnimationView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PerFrameAnimationView: View { 4 | var t: CGFloat 5 | @ViewBuilder var content: (CGFloat) -> C // parameter is t 6 | 7 | var body: some View { 8 | PerFrameAnimationInnerView(content: content) 9 | .modifier(AnimationProgressSetter(t: t)) 10 | } 11 | } 12 | 13 | private struct PerFrameAnimationInnerView: View { 14 | @ViewBuilder var content: (CGFloat) -> C // parameter is t 15 | @Environment(\.animationProgress) private var animationProgress 16 | 17 | var body: some View { 18 | content(animationProgress) 19 | } 20 | } 21 | 22 | private struct AnimationProgressSetter: AnimatableModifier { 23 | var t: CGFloat 24 | 25 | var animatableData: CGFloat { 26 | get { t } 27 | set { t = newValue } 28 | } 29 | 30 | func body(content: Content) -> some View { 31 | content 32 | .environment(\.animationProgress, t) 33 | } 34 | } 35 | 36 | // Environment key for passing the current animation progress to the view 37 | 38 | private struct AnimationProgressKey: EnvironmentKey { 39 | static var defaultValue: CGFloat = 0 40 | } 41 | 42 | private extension EnvironmentValues { 43 | var animationProgress: CGFloat { 44 | get { self[AnimationProgressKey.self] } 45 | set { self[AnimationProgressKey.self] = newValue } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /InfiniteAppStore/Utils/CrossPlatform.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | 4 | #if os(macOS) 5 | import AppKit 6 | typealias UINSImage = NSImage 7 | typealias UINSView = NSView 8 | #else 9 | import UIKit 10 | typealias UINSImage = UIImage 11 | typealias UINSView = UIView 12 | #endif 13 | 14 | enum CrossPlatform { 15 | static func open(url: URL) { 16 | #if os(macOS) 17 | NSWorkspace.shared.open(url) 18 | #else 19 | UIApplication.shared.open(url) 20 | #endif 21 | } 22 | } 23 | 24 | extension Image { 25 | init(uinsImage: UINSImage) { 26 | #if os(macOS) 27 | self = .init(nsImage: uinsImage) 28 | #else 29 | self = .init(uiImage: uinsImage) 30 | #endif 31 | } 32 | } 33 | 34 | // a UINSView that provides unified lifecycle points 35 | class CrossPlatformView: UINSView { 36 | func layout_crossplatform() {} 37 | 38 | func didMoveToWindow_crossplatform() {} 39 | 40 | #if os(macOS) 41 | override func layout() { 42 | super.layout() 43 | layout_crossplatform() 44 | } 45 | override func viewDidMoveToWindow() { 46 | super.viewDidMoveToWindow() 47 | didMoveToWindow_crossplatform() 48 | } 49 | #else 50 | override func layoutSubviews() { 51 | super.layoutSubviews() 52 | layout_crossplatform() 53 | } 54 | override func didMoveToWindow() { 55 | super.didMoveToWindow() 56 | didMoveToWindow_crossplatform() 57 | } 58 | #endif 59 | } 60 | 61 | func isMac() -> Bool { 62 | #if os(macOS) 63 | return true 64 | #else 65 | return false 66 | #endif 67 | } 68 | -------------------------------------------------------------------------------- /InfiniteAppStore/Data/AppState.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Program: Equatable, Codable, Identifiable { 4 | var id: String 5 | 6 | var js = "" 7 | var css = "" 8 | var html = "" 9 | var title: String = "" 10 | var subtitle: String = "" 11 | var colorHex: String = "0000ff" 12 | var installProgress: Double? 13 | var iconName: String = "executable" 14 | var applescriptEnabled = false 15 | var llmEnabled = false 16 | } 17 | 18 | struct AppState: Equatable, Codable { 19 | var programs = [String: Program]() 20 | 21 | mutating func modifyProgram(id: String, _ block: (inout Program) -> Void) { 22 | var program = programs[id] ?? .init(id: id) 23 | block(&program) 24 | programs[id] = program 25 | } 26 | 27 | static var base: AppState { 28 | let data = try! Data(contentsOf: Bundle.main.url(forResource: "InfiniteAppStoreStore", withExtension: "json")!) 29 | return try! JSONDecoder().decode(Self.self, from: data) 30 | } 31 | } 32 | 33 | class AppStore: DataStore { 34 | static let shared = AppStore(persistenceKey: "InfiniteAppStoreStore", defaultModel: .base, queue: .main) 35 | } 36 | 37 | extension Program { 38 | static func stubsForMenu() -> [Program] { 39 | // Define 3 stub apps. Leave js, css, html blank 40 | [ 41 | Program(id: "1", title: "App 1", subtitle: "App 1 subtitle", colorHex: "ff0000"), 42 | Program(id: "2", title: "App 2", subtitle: "App 2 subtitle", colorHex: "00ff00"), 43 | Program(id: "3", title: "App 3", subtitle: "App 3 subtitle", colorHex: "0000ff") 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /InfiniteAppStore.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "2ef8fca4105428750bab3343c5f0762a803be17486adf3f2e60d2a86aa238a15", 3 | "pins" : [ 4 | { 5 | "identity" : "anycodable", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/Flight-School/AnyCodable", 8 | "state" : { 9 | "revision" : "862808b2070cd908cb04f9aafe7de83d35f81b05", 10 | "version" : "0.6.7" 11 | } 12 | }, 13 | { 14 | "identity" : "fuzi", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/cezheng/Fuzi", 17 | "state" : { 18 | "revision" : "f08c8323da21e985f3772610753bcfc652c2103f", 19 | "version" : "3.1.3" 20 | } 21 | }, 22 | { 23 | "identity" : "grdb.swift", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/groue/GRDB.swift.git", 26 | "state" : { 27 | "revision" : "639fa9168d36931b36a79e60dc06b7d23852f1f4", 28 | "version" : "6.27.0" 29 | } 30 | }, 31 | { 32 | "identity" : "swifter", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/httpswift/swifter.git", 35 | "state" : { 36 | "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", 37 | "version" : "1.5.0" 38 | } 39 | }, 40 | { 41 | "identity" : "swiftsoup", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/scinfu/SwiftSoup.git", 44 | "state" : { 45 | "revision" : "028487d4a8a291b2fe1b4392b5425b6172056148", 46 | "version" : "2.7.2" 47 | } 48 | } 49 | ], 50 | "version" : 3 51 | } 52 | -------------------------------------------------------------------------------- /InfiniteAppStore/Utils/Queue.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct Queue { 4 | public let id: String 5 | public let queue: DispatchQueue 6 | 7 | public static func create(label: String = "Unnamed Queue", qos: DispatchQoS = .userInitiated) -> Queue { 8 | let uuid = UUID().uuidString 9 | let id = "\(label):\(uuid)" 10 | return Queue(id: id, queue: DispatchQueue(label: id, qos: qos, attributes: [], autoreleaseFrequency: .inherit, target: nil)) 11 | } 12 | 13 | public static let main = Queue(id: "main", queue: DispatchQueue.main) 14 | public static let genericUserInitiated = create(label: "GenericUserInitiated") 15 | 16 | public var isCurrent: Bool { 17 | if id == "main" { 18 | return Thread.isMainThread 19 | } 20 | return DispatchQueue.currentQueueName() == id 21 | } 22 | 23 | public func assertCurrent() { 24 | assert(isCurrent) 25 | } 26 | 27 | public func run(_ block: @escaping () -> ()) { 28 | if isCurrent { 29 | block() 30 | } else { 31 | queue.async { 32 | block() 33 | } 34 | } 35 | } 36 | 37 | public func runSync(_ block: () -> ()) { 38 | if isCurrent { 39 | block() 40 | } else { 41 | if Queue.main.isCurrent { 42 | print("😤 Synchronous queue hop from main thread!") 43 | } 44 | queue.sync { 45 | block() 46 | } 47 | } 48 | } 49 | } 50 | 51 | extension DispatchQueue { 52 | static func currentQueueName() -> String? { 53 | let name = __dispatch_queue_get_label(nil) 54 | return String(cString: name, encoding: .utf8) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /InfiniteAppStore/Assets/Icons.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum Icons { 4 | static let iconNames: [String] = [ 5 | "plug", 6 | "address_book", 7 | "battery", 8 | "calendar", 9 | "camera", 10 | "audio", 11 | "certificate", 12 | "channels", 13 | "character_map", 14 | "chart", 15 | "clean_drive", 16 | "connected_world", 17 | "desktop", 18 | "fonts", 19 | "directx", 20 | "doctor", 21 | "globe", 22 | "hardware", 23 | "help_book", 24 | "help", 25 | "internet_wizard", 26 | "joystick", 27 | "keyboard", 28 | "keys", 29 | "mailbox", 30 | "minesweeper", 31 | "modem", 32 | "mouse", 33 | "mouse_trails", 34 | "agent", 35 | "agent_file", 36 | "error", 37 | "executable", 38 | "information", 39 | "warning", 40 | "multimedia", 41 | "arrow", 42 | "televisions", 43 | "newspaper", 44 | "note", 45 | "paint", 46 | "recycle_bin", 47 | "restrict", 48 | "scandisk", 49 | "scanner", 50 | "search", 51 | "sound", 52 | "spider", 53 | "standby", 54 | "themes", 55 | "time_date", 56 | "tools", 57 | "tree", 58 | "calendar_user", 59 | "users", 60 | "image_check", 61 | "windows", 62 | "magnifying_glass", 63 | "file", 64 | "world_star", 65 | "write_file", 66 | "write_yellow" 67 | ] 68 | 69 | static func iconWithName(_ name: String) -> UINSImage? { 70 | let url = Bundle.main.url(forResource: "StaticWebFiles", withExtension: "")! 71 | .appendingPathComponent("Icons") 72 | .appendingPathComponent(name) 73 | .appendingPathExtension("png") 74 | return UINSImage(contentsOfFile: url.path) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /InfiniteAppStore/MobileAppView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct MobileAppView: View { 4 | var id: String 5 | 6 | @State private var program: Program? 7 | @State private var errors = [String]() 8 | @State private var showingContactDevView: Bool = false 9 | @Environment(\.presentationMode) private var presentationMode 10 | 11 | var body: some View { 12 | Window95(title: program?.title.nilIfEmpty ?? "App", onControlAction: handleControlAction, additionalAccessoryIcon: AnyView(contactDevButton)) { 13 | AppViewRepresentable(id: id, onError: { errors.append($0) }) 14 | .overlay(alignment: .bottomTrailing) { 15 | if errors.count > 0 { 16 | ErrorView(errors: errors, id: id, onDismiss: { self.errors.removeAll() }) 17 | .padding() 18 | } 19 | } 20 | .overlay { 21 | if let program, let progress = program.installProgress { 22 | InstallShield(name: program.title, progress: progress) 23 | .onDisappear { 24 | self.errors.removeAll() 25 | } 26 | } 27 | } 28 | } 29 | .edgesIgnoringSafeArea(.all) 30 | .onReceive(AppStore.shared.publisher.map { $0.programs[id] }, perform: { self.program = $0 }) 31 | .sheet(isPresented: $showingContactDevView) { 32 | ContactDevView(programId: id) 33 | } 34 | } 35 | 36 | func handleControlAction(_ action: WindowControlAction) { 37 | if action == .close { 38 | presentationMode.wrappedValue.dismiss() 39 | } 40 | } 41 | 42 | @ViewBuilder var contactDevButton: some View { 43 | Button(action: { contactDev() }) { 44 | Text("?").bold() 45 | .foregroundStyle(Color.black) 46 | } 47 | .help("Contact Developer") 48 | } 49 | 50 | private func contactDev() { 51 | #if os(macOS) 52 | contactDevForProgram(id: id) 53 | #else 54 | showingContactDevView = true 55 | #endif 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /InfiniteAppStore/Mac/SettingsWindow.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @MainActor 4 | func showSettingsView() { 5 | if let existing = NSApp.windows.first(where: { $0 as? BorderlessSwiftUIWindow != nil }) { 6 | existing.makeKeyAndOrderFront(nil) 7 | return 8 | } 9 | 10 | let win = BorderlessSwiftUIWindow { 11 | SettingsView(onClose: nil) 12 | } 13 | win.rootView.onClose = { [weak win] in 14 | win?.close() 15 | } 16 | win.makeKeyAndOrderFront(nil) 17 | } 18 | 19 | struct SettingsView: View { 20 | var onClose: (() -> Void)? 21 | 22 | @AppStorage(DefaultsKeys.anthropicKey.rawValue) private var anthropicKey = "" 23 | @AppStorage(DefaultsKeys.openAIKey.rawValue) private var openAIKey = "" 24 | 25 | var body: some View { 26 | Window95(title: "Control Panel", onControlAction: { 27 | if $0 == .close { 28 | onClose?() 29 | } 30 | }) { 31 | HStack(alignment: .top, spacing: 16) { 32 | Image(uinsImage: Icons.iconWithName("channels")!) 33 | 34 | VStack(alignment: .leading, spacing: 16) { 35 | Text("Add an OpenAI or Anthropic API key to generate new programs. We'll use GPT 4o or Claude 3.5 Sonnet.") 36 | .font(.boldBody95) 37 | 38 | VStack(alignment: .leading) { 39 | Text("OpenAI Key (recommended):") 40 | 41 | TextField("Name", text: $openAIKey) 42 | .frame(width: 250) 43 | } 44 | 45 | VStack(alignment: .leading) { 46 | Text("Anthropic Key:") 47 | 48 | TextField("Name", text: $anthropicKey) 49 | .frame(width: 250) 50 | } 51 | 52 | Text("After entering a key, try creating a program again.") 53 | .bold().foregroundStyle(.red) 54 | 55 | Button(action: { onClose?() }) { 56 | Text("Done") 57 | } 58 | } 59 | } 60 | .padding() 61 | .frame(width: 450) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /InfiniteAppStore/Utils/SwiftUI+Convenience.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension View { 4 | var asAny: AnyView { AnyView(self) } 5 | 6 | func frame(both: CGFloat, alignment: Alignment = .center) -> some View { 7 | self.frame(width: both, height: both, alignment: alignment) 8 | } 9 | } 10 | 11 | extension String { 12 | var asText: Text { 13 | Text(self) 14 | } 15 | } 16 | 17 | struct IdentifiableWithIndex: Identifiable { 18 | let id: Item.ID 19 | let item: Item 20 | let index: Int 21 | } 22 | 23 | extension Array where Element: Identifiable { 24 | var identifiableWithIndices: [IdentifiableWithIndex] { 25 | return enumerated().map { tuple in 26 | let (index, item) = tuple 27 | return IdentifiableWithIndex(id: item.id, item: item, index: index) 28 | } 29 | } 30 | } 31 | 32 | extension Color { 33 | init(hex: UInt, alpha: Double = 1) { 34 | self.init( 35 | .displayP3, 36 | red: Double((hex >> 16) & 0xff) / 255, 37 | green: Double((hex >> 08) & 0xff) / 255, 38 | blue: Double((hex >> 00) & 0xff) / 255, 39 | opacity: alpha 40 | ) 41 | } 42 | } 43 | 44 | struct ForEachUnidentifiable: View { 45 | var items: [Element] 46 | @ViewBuilder var content: (Element) -> Content 47 | 48 | var body: some View { 49 | ForEach(itemsAsIdentifiable) { 50 | content($0.element) 51 | } 52 | } 53 | 54 | private var itemsAsIdentifiable: [CustomIdentifiable] { 55 | items.enumerated().map { CustomIdentifiable(id: $0.offset, element: $0.element) } 56 | } 57 | } 58 | 59 | private struct CustomIdentifiable: Identifiable { 60 | var id: Int 61 | var element: Element 62 | } 63 | 64 | // A @StateObject that remembers its first initial value 65 | class FrozenInitialValue: ObservableObject { 66 | private var value: T? 67 | func readOriginalOrStore(initial: () -> T) -> T { 68 | let val = value ?? initial() 69 | self.value = val 70 | return val 71 | } 72 | } 73 | 74 | extension View { 75 | func onAppearOrChange(of val: E, perform: @escaping (E) -> Void) -> some View { 76 | self.onChange(of: val, perform: perform).onAppear(perform: { perform(val) }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /InfiniteAppStoreiOS/PromptDialog.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIApplication { 4 | private func prompt(title: String?, message: String?, showTextField: Bool, placeholder: String?, callback: @escaping (Bool, String?) -> Void) { 5 | guard let vc = viewControllerForModalPresentation else { 6 | callback(false, nil) 7 | return 8 | } 9 | let dialog = UIAlertController(title: title, message: message, preferredStyle: .alert) 10 | if showTextField { 11 | dialog.addTextField { field in 12 | field.placeholder = placeholder 13 | } 14 | } 15 | dialog.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in 16 | callback(false, nil) 17 | })) 18 | dialog.addAction(UIAlertAction(title: "Okay", style: .default, handler: { _ in 19 | let text = dialog.textFields?.first?.text 20 | callback(true, text) 21 | })) 22 | vc.present(dialog, animated: true, completion: nil) 23 | } 24 | 25 | func prompt(title: String?, message: String?, showTextField: Bool, placeholder: String?) async -> (ok: Bool, text: String?) { 26 | return await withCheckedContinuation { cont in 27 | Task { 28 | await MainActor.run { 29 | self.prompt(title: title, message: message, showTextField: showTextField, placeholder: placeholder) { ok, text in 30 | cont.resume(returning: (ok, text)) 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | func showAlert(_ alert: UIAlertController) { 38 | viewControllerForModalPresentation?.present(alert, animated: true, completion: nil) 39 | } 40 | } 41 | 42 | extension UIViewController { 43 | var topmostPresentedViewController: UIViewController { 44 | return presentedViewController?.topmostPresentedViewController ?? self 45 | } 46 | } 47 | 48 | private extension UIScene { 49 | var activityScore: Int { 50 | switch activationState { 51 | case .foregroundActive: return 3 52 | case .foregroundInactive: return 2 53 | case .background: return 1 54 | default: return 0 55 | } 56 | } 57 | } 58 | 59 | extension UIApplication { 60 | var activeWindowScene: UIWindowScene? { 61 | self 62 | .connectedScenes 63 | .compactMap { $0 as? UIWindowScene } 64 | .sorted { $0.activityScore > $1.activityScore }.first 65 | } 66 | var activeWindow: UIWindow? { 67 | guard let window = activeWindowScene?.keyWindow ?? activeWindowScene?.windows.last else { 68 | return nil 69 | } 70 | return window 71 | } 72 | var viewControllerForModalPresentation: UIViewController? { 73 | return activeWindow?.rootViewController?.topmostPresentedViewController 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /InfiniteAppStore/Server.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Swifter 3 | 4 | class Server { 5 | static let shared = Server() 6 | 7 | // let queue = DispatchQueue(label: "Server", qos: .default) 8 | 9 | let server = HttpServer() 10 | 11 | init() { 12 | // queue.async { 13 | self.start() 14 | // } 15 | } 16 | 17 | func start() { 18 | server["/static/:path"] = shareFilesFromDirectory(Bundle.main.url(forResource: "StaticWebFiles", withExtension: nil)!.path) 19 | server["/icons/:path"] = shareFilesFromDirectory(Bundle.main.url(forResource: "StaticWebFiles", withExtension: nil)!.appendingPathComponent("Icons").path) 20 | 21 | // server["/"] = { request in 22 | // return HttpResponse.ok(.text("

heeeyyy

")) 23 | // } 24 | // server["/siteName"] = { request in 25 | // let title: String? = blockingAsync { 26 | // try? await siteTitle(url: URL(string: "https://nytimes.com")!) 27 | // } ?? nil 28 | // return HttpResponse.ok(.text("\(title ?? "none")")) 29 | // } 30 | // server["/batch_summary"] = { request in 31 | // do { 32 | // let urls = request.requestedURLsForSummary 33 | // let summaries = blockingAsync { 34 | // await siteSummaries(urls: urls.map(\.absoluteString)) 35 | // } 36 | // let json = try JSONSerialization.jsonObject(with: try! JSONEncoder().encode([summaries])) 37 | // return HttpResponse.ok(.json(json)) 38 | // } catch { 39 | // print("Error: \(error)") 40 | // return .internalServerError 41 | // } 42 | // } 43 | let port: in_port_t = 50082 44 | print("📦 Will start server on port \(port)") 45 | try! server.start(port, forceIPv4: true) 46 | } 47 | } 48 | 49 | //extension Server { 50 | // static let cache = NSCache() 51 | // 52 | // func serveLocalResource(path: String, resourceURL: URL, mimeType) { 53 | // self[path] = { request in 54 | // if let cached = Self.cache.object(forKey: resourceURL as NSURL) { 55 | // return HttpResponse.ok(.data(cached as Data, contentType: mimeType)) 56 | // } 57 | // } 58 | // } 59 | //} 60 | 61 | private extension HttpRequest { 62 | func params(name: String) -> String? { 63 | for param in queryParams { 64 | if param.0 == name { 65 | return param.1.removingPercentEncoding 66 | } 67 | } 68 | return nil 69 | } 70 | } 71 | 72 | private class Box { 73 | init() {} 74 | var t: T? 75 | } 76 | 77 | private func blockingAsync(block: @escaping () async -> T) -> T { 78 | // Block until this async block is done 79 | let result = Box() 80 | let g = DispatchGroup() 81 | g.enter() 82 | Task { 83 | result.t = await block() 84 | g.leave() 85 | } 86 | g.wait() 87 | return result.t! 88 | } 89 | 90 | -------------------------------------------------------------------------------- /InfiniteAppStore/Settings.swift: -------------------------------------------------------------------------------- 1 | #if os(iOS) 2 | import UIKit 3 | #endif 4 | 5 | import Foundation 6 | import ChatToys 7 | 8 | enum DefaultsKeys: String { 9 | case openAIKey 10 | case anthropicKey 11 | 12 | var stringValue: String? { 13 | get { 14 | return UserDefaults.standard.string(forKey: self.rawValue) 15 | } 16 | nonmutating set { 17 | UserDefaults.standard.set(newValue, forKey: self.rawValue) 18 | } 19 | } 20 | 21 | var boolValue: Bool { 22 | get { 23 | return UserDefaults.standard.bool(forKey: self.rawValue) 24 | } 25 | nonmutating set { 26 | UserDefaults.standard.set(newValue, forKey: self.rawValue) 27 | } 28 | } 29 | } 30 | 31 | enum Credentials { 32 | case anthropic(AnthropicCredentials) 33 | case openai(OpenAICredentials) 34 | 35 | var jsonLLM: any ChatLLM { 36 | switch self { 37 | case .anthropic(let anthropicCredentials): 38 | return Claude(credentials: anthropicCredentials, options: .init(model: .claude3_5Sonnet, responsePrefix: "```\n{")) 39 | case .openai(let openAICredentials): 40 | return ChatGPT(credentials: openAICredentials, options: .init(temp: 0.5, model: .gpt4_omni, jsonMode: true)) 41 | } 42 | } 43 | 44 | var chatWithDevLLM: any FunctionCallingLLM { 45 | switch self { 46 | case .anthropic(let anthropicCredentials): 47 | return Claude(credentials: anthropicCredentials, options: .init(model: .claude3_5Sonnet)) 48 | case .openai(let openAICredentials): 49 | return ChatGPT(credentials: openAICredentials, options: .init(temp: 0.5, model: .gpt4_omni)) 50 | } 51 | } 52 | 53 | var smallLLM: any ChatLLM { 54 | switch self { 55 | case .anthropic(let anthropicCredentials): 56 | return Claude(credentials: anthropicCredentials, options: .init(model: .claude3Haiku)) 57 | case .openai(let openAICredentials): 58 | return ChatGPT(credentials: openAICredentials, options: .init(temp: 0.5, model: .gpt35_turbo)) 59 | } 60 | 61 | } 62 | } 63 | 64 | extension Credentials { 65 | static func getOrPromptForCreds() async throws -> Credentials { 66 | enum Errors: Error { 67 | case noKey 68 | } 69 | 70 | if let key = DefaultsKeys.openAIKey.stringValue, !key.isEmpty { 71 | return .openai(OpenAICredentials(apiKey: key)) 72 | } 73 | if let key = DefaultsKeys.anthropicKey.stringValue, !key.isEmpty { 74 | return .anthropic(.init(apiKey: key)) 75 | } 76 | #if os(macOS) 77 | await showSettingsView() 78 | throw Errors.noKey 79 | #else 80 | let (_, key) = await UIApplication.shared.prompt(title: "No API Key", message: "Add an API key:", showTextField: true, placeholder: "sk-??????") 81 | guard let key = key, !key.isEmpty else { 82 | throw Errors.noKey 83 | } 84 | DefaultsKeys.openAIKey.stringValue = key 85 | return .openai(.init(apiKey: key)) 86 | #endif 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /InfiniteAppStore/Utils/Scripting.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if os(macOS) 4 | import AppKit 5 | 6 | enum Scripting { 7 | enum ScriptError: Error { 8 | case scriptError([String: AnyObject]) 9 | case invalidScript 10 | } 11 | 12 | private static let scriptQueue = DispatchQueue(label: "Scripting", qos: .userInitiated, attributes: .concurrent) 13 | 14 | static func runAppleScript(script: String) async throws -> String? { 15 | print("[Applescript] \(script)") 16 | return try await withCheckedThrowingContinuation { cont in 17 | self.scriptQueue.async { 18 | var error: NSDictionary? 19 | guard let scriptObject = NSAppleScript(source: script) else { 20 | cont.resume(throwing: ScriptError.invalidScript) 21 | return 22 | } 23 | let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(&error) 24 | if let error { 25 | cont.resume(throwing: ScriptError.scriptError(error as? [String: AnyObject] ?? [:])) 26 | return 27 | } 28 | cont.resume(returning: output.asString) 29 | } 30 | } 31 | } 32 | } 33 | 34 | extension NSAppleEventDescriptor { 35 | 36 | var asString: String? { 37 | switch descriptorType { 38 | case typeUnicodeText, typeUTF8Text: 39 | return stringValue 40 | case typeSInt32: 41 | return String(int32Value) 42 | case typeTrue: return "true" 43 | case typeFalse: return "false" 44 | case typeBoolean: 45 | return String(booleanValue) 46 | case typeAEList: 47 | let listCount = numberOfItems 48 | var listItems: [String] = [] 49 | if listCount > 0 { 50 | for i in 1...listCount { // AppleScript lists are 1-indexed 51 | if let itemString = self.atIndex(i)?.asString { 52 | listItems.append(itemString) 53 | } 54 | } 55 | return listItems.joined(separator: ", ") 56 | } else { 57 | return "(empty list)" 58 | } 59 | case typeAERecord: 60 | // Assuming you want key-value pairs for records 61 | var recordItems: [String] = [] 62 | for i in 1...numberOfItems { 63 | let key = self.atIndex(i)?.stringValue ?? "UnknownKey" 64 | let value = self.atIndex(i + 1)?.asString ?? "UnknownValue" 65 | recordItems.append("\(key): \(value)") 66 | } 67 | return recordItems.joined(separator: ", ") 68 | default: 69 | return nil // Handle other descriptor types as needed 70 | } 71 | } 72 | } 73 | 74 | #endif 75 | 76 | extension String { 77 | var quotedForApplescript: String { 78 | // TODO: DO better 79 | let esc = replacingOccurrences(of: "\\", with: "\\\\") 80 | .replacingOccurrences(of: "\"", with: "\\\"") 81 | return "\"\(esc)\"" 82 | // jsonString // is this correct?? 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /InfiniteAppStore/Mac/StatusMenu.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AppKit 3 | import SwiftUI 4 | 5 | class StatusMenuManager: NSObject { 6 | static let shared = StatusMenuManager() 7 | 8 | let statusItem: NSStatusItem 9 | private var window: BorderlessSwiftUIWindow? 10 | 11 | override init() { 12 | statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) 13 | if let button = statusItem.button { 14 | button.setAccessibilityLabel("Infinite App Store Apps") 15 | button.image = NSImage(named: "icon95") // NSImage(systemSymbolName: "app.gift.fill", accessibilityDescription: "Infinite App Store Apps") 16 | button.imageScaling = .scaleProportionallyDown 17 | } 18 | super.init() 19 | 20 | if let button = statusItem.button { 21 | button.target = self 22 | button.action = #selector(toggleMenu(sender:)) 23 | } 24 | } 25 | 26 | func showMenu() { 27 | if !isVisible { 28 | toggleMenu(sender: nil) 29 | } 30 | } 31 | 32 | var isVisible: Bool { 33 | window?.isVisible ?? false 34 | } 35 | 36 | @objc private func toggleMenu(sender: Any?) { 37 | let visibleWindow = window?.isVisible ?? false ? window : nil 38 | if visibleWindow == nil { 39 | let window = BorderlessSwiftUIWindow(resizable: false, dialog: false) { 40 | AppMenuView() 41 | } 42 | window.level = .popUpMenu 43 | window.makeKeyAndOrderFront(nil) 44 | 45 | // TODO: is there a better way to do this? 46 | window.makeMain() 47 | NSApp.activate() 48 | window.closeOnResignMain = true 49 | 50 | // Position menu to beneath the status item (aligned to the right side of the status item button) 51 | let buttonRect = statusItem.button!.convert(statusItem.button!.bounds, to: nil) 52 | let screenRect = statusItem.button!.window!.convertToScreen(buttonRect) 53 | let windowRect = window.frame 54 | window.setFrameOrigin(NSPoint(x: screenRect.maxX - windowRect.width, y: screenRect.minY - windowRect.height)) 55 | 56 | self.window = window 57 | } else { 58 | visibleWindow?.close() 59 | window = nil 60 | } 61 | } 62 | } 63 | 64 | extension NSApplication { 65 | @MainActor 66 | func openOrFocusProgram(id: String) { 67 | // view.window?.frameAutosaveName 68 | if let win = windows.first(where: { ($0.contentViewController as? AppViewController)?.programID == id }) { 69 | win.makeKeyAndOrderFront(nil) 70 | } else { 71 | let windowController = NSStoryboard.main!.instantiateController(withIdentifier: "AppWindowController") as! NSWindowController 72 | let vc = windowController.window!.contentViewController! as! AppViewController 73 | vc.programID = id 74 | windowController.window?.setFrameUsingName("Program:\(id)", force: false) 75 | windowController.window?.makeKeyAndOrderFront(nil) 76 | windowController.window?.setFrameAutosaveName("Program:\(id)") 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /InfiniteAppStore/Mac/FloatingSwiftUIWindow.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import AppKit 3 | 4 | // NSWindow subclass that displays a SwiftUI view and provides it with an environment key to perform window control actions 5 | // The swiftUI view should be borderless and shadowless, without a title bar. You need to override canBecomeKey 6 | // Use a NSHostingController as the contentViewController 7 | 8 | class BorderlessSwiftUIWindow: NSWindow { 9 | 10 | init(resizable: Bool = true, dialog: Bool = false, _ view: () -> V) { 11 | let currentMainWindowCenter: CGPoint? = NSApplication.shared.mainWindow?.frame.center 12 | 13 | var styleMask: NSWindow.StyleMask = [.fullSizeContentView] 14 | if resizable { 15 | styleMask.insert(.resizable) 16 | } 17 | super.init(contentRect: .zero, styleMask: styleMask, backing: .buffered, defer: true) 18 | rootVC = NSHostingController(rootView: RootView(content: view(), handleWindowAction: handleWindowAction(_:))) 19 | self.contentViewController = rootVC 20 | self.isMovableByWindowBackground = true 21 | self.titleVisibility = .hidden 22 | self.titlebarAppearsTransparent = true 23 | self.backgroundColor = .clear 24 | self.isOpaque = false 25 | self.isReleasedWhenClosed = false 26 | self.hasShadow = false 27 | self.level = dialog ? .floating : .normal 28 | // Size to fit the swiftui view 29 | let size = rootVC.view.intrinsicContentSize 30 | self.setContentSize(size) 31 | 32 | if dialog, let currentMainWindowCenter = currentMainWindowCenter { 33 | // Set center to currentMainWindowCenter 34 | self.setFrameOrigin(CGPoint(x: currentMainWindowCenter.x - size.width / 2, y: currentMainWindowCenter.y - size.height / 2)) 35 | } else { 36 | self.center() 37 | } 38 | } 39 | 40 | var rootView: V { 41 | get { rootVC.rootView.content } 42 | set { rootVC.rootView.content = newValue } 43 | } 44 | 45 | private var rootVC: NSHostingController! 46 | 47 | private struct RootView: View { 48 | var content: V 49 | var handleWindowAction: (WindowControlAction) -> Void 50 | 51 | var body: some View { 52 | content 53 | .environment(\.windowActionHandler, self.handleWindowAction) 54 | } 55 | } 56 | 57 | var closeOnResignMain = false 58 | override func resignMain() { 59 | super.resignMain() 60 | if closeOnResignMain { 61 | self.close() 62 | } 63 | } 64 | 65 | required init?(coder: NSCoder) { 66 | fatalError("init(coder:) has not been implemented") 67 | } 68 | 69 | override var canBecomeKey: Bool { 70 | true 71 | } 72 | 73 | override var canBecomeMain: Bool { true } 74 | 75 | func handleWindowAction(_ action: WindowControlAction) { 76 | switch action { 77 | case .minimize: 78 | self.miniaturize(self) 79 | case .maximize: 80 | self.zoom(self) 81 | case .close: 82 | self.close() 83 | } 84 | } 85 | } 86 | 87 | extension CGRect { 88 | var center: CGPoint { 89 | CGPoint(x: self.midX, y: self.midY) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /InfiniteAppStore/NewProgram.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct NewProgramParams: Equatable { 4 | var title: String 5 | var subtitle: String 6 | var applescript = false 7 | var llmEnabled = true 8 | } 9 | 10 | func promptForNewProgramDetails() async -> NewProgramParams? { 11 | // Ensure creds 12 | do { 13 | _ = try await Credentials.getOrPromptForCreds() 14 | } catch { 15 | return nil 16 | } 17 | 18 | #if os(iOS) 19 | guard let title = await prompt(question: "Choose a name for your app:", title: "Create App (1/2)"), 20 | let subtitle = await prompt(question: "Describe your app briefly:", title: "Create App (2/2)") 21 | else { 22 | return nil 23 | } 24 | return NewProgramParams(title: title, subtitle: subtitle) 25 | #else 26 | return await withCheckedContinuation { continuation in 27 | DispatchQueue.main.async { 28 | let win = BorderlessSwiftUIWindow(resizable: false, dialog: true) { 29 | NewProgramForm { _ in () } 30 | } 31 | win.rootView.onDone = { [weak win] params in 32 | win?.close() 33 | continuation.resume(returning: params) 34 | } 35 | win.makeKeyAndOrderFront(nil) 36 | } 37 | } 38 | #endif 39 | } 40 | 41 | struct NewProgramForm: View { 42 | var onDone: (NewProgramParams?) -> Void 43 | 44 | @State private var params = NewProgramParams(title: "", subtitle: "") 45 | 46 | var body: some View { 47 | Window95(title: "New Program", onControlAction: { 48 | if $0 == .close { 49 | onDone(nil) 50 | } 51 | }) { 52 | HStack(alignment: .top, spacing: 16) { 53 | Image(uinsImage: Icons.iconWithName("themes")!) 54 | 55 | VStack(alignment: .leading, spacing: 16) { 56 | 57 | VStack(alignment: .leading) { 58 | Text("Program Name:") 59 | .font(.boldBody95) 60 | 61 | TextField("Name", text: $params.title) 62 | .frame(width: 300) 63 | } 64 | 65 | VStack(alignment: .leading) { 66 | Text("Other details:") 67 | .font(.boldBody95) 68 | 69 | TextField("Details", text: $params.subtitle) 70 | .frame(width: 300) 71 | } 72 | 73 | 74 | VStack(alignment: .leading) { 75 | Toggle(isOn: $params.applescript) { 76 | Text("Can use AppleScript to control your computer") 77 | } 78 | 79 | Toggle(isOn: $params.llmEnabled) { 80 | Text("Program can use AI language model") 81 | } 82 | } 83 | 84 | HStack { 85 | Spacer() 86 | Button(action: { onDone(nil) }) { 87 | Text("Cancel") 88 | } 89 | Button(action: { if canSubmit { onDone(params) } }) { 90 | Text("Done") 91 | }.disabled(!canSubmit) 92 | } 93 | } 94 | .onSubmit { 95 | if canSubmit { onDone(params) } 96 | } 97 | } 98 | .padding(16) 99 | } 100 | } 101 | 102 | var canSubmit: Bool { 103 | params.title.nilIfEmpty != nil 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /InfiniteAppStore/AppMenuView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct AppMenuView: View { 4 | var body: some View { 5 | WithSnapshot(store: AppStore.shared, snapshot: { $0.programs.values.sorted(by: { $0.title < $1.title }) }) { programs in 6 | _AppMenuView(programs: programs ?? []) 7 | } 8 | } 9 | } 10 | 11 | struct _AppMenuView: View { 12 | var programs: [Program] 13 | 14 | @State private var hovered: Program.ID? = nil 15 | @State private var sheet: ProgramSheetModel? 16 | 17 | struct ProgramSheetModel: Equatable, Identifiable { 18 | var id: String 19 | } 20 | 21 | var body: some View { 22 | VStack(spacing: 0) { 23 | StatusMenuItem(idForHover: "new", hoveredId: $hovered, onTap: newProgram) { 24 | RetroIconView(name: "installer") 25 | Text("New App...") 26 | } 27 | 28 | if programs.count > 0 { 29 | HorizontalDivider95() 30 | } 31 | ForEach(programs) { 32 | cell(program: $0) 33 | } 34 | } 35 | .padding(1) 36 | .onHover { 37 | if !$0 { self.hovered = nil } 38 | } 39 | .background(Color.gray95) 40 | .with95DepthEffect(pushed: false) 41 | #if os(macOS) 42 | .frame(width: 250) 43 | #endif 44 | .modifier(Styling95()) 45 | .sheet(item: $sheet) { sheet in 46 | MobileAppView(id: sheet.id) 47 | } 48 | } 49 | 50 | private func newProgram() { 51 | Task { 52 | guard let params = await promptForNewProgramDetails() else { return } 53 | let id = UUID().uuidString 54 | open(programId: id) 55 | try? await AppStore.shared.generateProgram(id: id, params: params) 56 | } 57 | } 58 | 59 | private func open(programId: String) { 60 | #if os(iOS) 61 | sheet = .init(id: programId) 62 | #else 63 | DispatchQueue.main.async { 64 | NSApp.openOrFocusProgram(id: programId) 65 | } 66 | #endif 67 | } 68 | 69 | @ViewBuilder func cell(program: Program) -> some View { 70 | StatusMenuItem(idForHover: "program:\(program.id)", hoveredId: $hovered, onTap: { 71 | open(programId: program.id) 72 | }, label: { 73 | RetroIconView(name: program.iconName) 74 | 75 | Text(program.title) 76 | }) 77 | } 78 | } 79 | 80 | struct StatusMenuItem: View { 81 | var idForHover: String 82 | @Binding var hoveredId: String? 83 | var onTap: () -> Void 84 | @ViewBuilder var label: () -> L 85 | 86 | var body: some View { 87 | let hovered = idForHover == hoveredId 88 | 89 | HStack { 90 | label() 91 | } 92 | .padding(.horizontal, 6) 93 | .frame(height: isMac() ? 34 : 44) 94 | .frame(maxWidth: .infinity, alignment: .leading) 95 | .foregroundStyle(hovered ? Color.white : Color.black) 96 | .background { 97 | if hovered { 98 | Color.blue95 99 | } 100 | } 101 | .contentShape(.rect) 102 | .onHover { 103 | if $0 { self.hoveredId = idForHover } 104 | } 105 | .onTapGesture(perform: onTap) 106 | } 107 | } 108 | 109 | struct RetroIconView: View { 110 | var name: String 111 | 112 | var body: some View { 113 | Image(uinsImage: Icons.iconWithName(name) ?? Icons.iconWithName("executable")!) 114 | .resizable() 115 | .aspectRatio(contentMode: .fit) 116 | .frame(width: 20, height: 20) 117 | } 118 | } 119 | 120 | #Preview { 121 | _AppMenuView(programs: Program.stubsForMenu()) 122 | .frame(width: 300) 123 | } 124 | -------------------------------------------------------------------------------- /InfiniteAppStore/Mac/Prompt.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Foundation 3 | 4 | struct PromptDialogModel: Equatable { 5 | var title: String 6 | var message: String 7 | var cancellable: Bool 8 | var hasTextField: Bool 9 | var defaultText = "" 10 | } 11 | 12 | struct PromptDialogResult: Equatable { 13 | var text: String 14 | var cancelled: Bool 15 | } 16 | 17 | struct Prompt95: View { 18 | var model: PromptDialogModel 19 | var onResult: (PromptDialogResult) -> Void 20 | 21 | @State private var text: String = "" 22 | @Environment(\.windowActionHandler) var onControlAction 23 | 24 | var body: some View { 25 | // Use windows 95 style 26 | Window95(title: model.title, onControlAction: handleControlAction) { 27 | VStack(alignment: .leading) { 28 | Text(model.message) 29 | .withFont95() 30 | if model.hasTextField { 31 | AutofocusTextField(placeholder: "Enter text", text: $text) 32 | .frame(width: 300) 33 | .onSubmit { 34 | onResult(PromptDialogResult(text: text, cancelled: false)) 35 | onControlAction(.close) 36 | } 37 | } 38 | HStack { 39 | Spacer() 40 | if model.cancellable { 41 | Button("Cancel") { 42 | self.cancel() 43 | } 44 | } 45 | Button("OK") { 46 | onResult(PromptDialogResult(text: text, cancelled: false)) 47 | onControlAction(.close) 48 | } 49 | } 50 | } 51 | .padding() 52 | } 53 | .onAppear { 54 | self.text = model.defaultText 55 | } 56 | } 57 | 58 | private func handleControlAction(_ action: WindowControlAction) { 59 | if action == .close { 60 | cancel() 61 | } else { 62 | onControlAction(action) 63 | } 64 | } 65 | 66 | private func cancel() { 67 | onResult(PromptDialogResult(text: "", cancelled: true)) 68 | onControlAction(.close) 69 | } 70 | } 71 | 72 | // Use @FocusState 73 | struct AutofocusTextField: View { 74 | var placeholder: String 75 | @Binding var text: String 76 | 77 | @FocusState private var isFocused: Bool 78 | 79 | var body: some View { 80 | TextField(placeholder, text: $text) 81 | .focused($isFocused) 82 | .onAppear { 83 | isFocused = true 84 | } 85 | } 86 | } 87 | 88 | #if os(macOS) 89 | import AppKit 90 | 91 | // Use BorderlessSwiftUIWindow and Prompt95 92 | func prompt(question: String, title: String = "Question") async -> String? { 93 | let result = await withCheckedContinuation { continuation in 94 | DispatchQueue.main.async { 95 | let model = PromptDialogModel(title: title, message: question, cancellable: true, hasTextField: true) 96 | let window = BorderlessSwiftUIWindow(resizable: false, dialog: true) { 97 | Prompt95(model: model) { result in 98 | continuation.resume(returning: result.cancelled ? nil : result.text) 99 | } 100 | } 101 | window.makeKeyAndOrderFront(nil) 102 | } 103 | } 104 | return result 105 | } 106 | 107 | extension PromptDialogModel { 108 | func run() async -> PromptDialogResult { 109 | let result = await withCheckedContinuation { continuation in 110 | DispatchQueue.main.async { 111 | let window = BorderlessSwiftUIWindow(resizable: false, dialog: true) { 112 | Prompt95(model: self) { result in 113 | continuation.resume(returning: result) 114 | } 115 | } 116 | window.makeKeyAndOrderFront(nil) 117 | } 118 | } 119 | return result 120 | } 121 | } 122 | 123 | #else 124 | 125 | func prompt(question: String, title: String = "Question") async -> String? { 126 | let res = await PromptDialogModel(title: title, message: question, cancellable: true, hasTextField: true).run() 127 | return res.cancelled ? nil : res.text 128 | } 129 | 130 | extension PromptDialogModel { 131 | func run() async -> PromptDialogResult { 132 | let (ok, text) = await UIApplication.shared.prompt(title: title, message: message, showTextField: hasTextField, placeholder: nil) 133 | return .init(text: text ?? "", cancelled: !ok) 134 | } 135 | } 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /InfiniteAppStore/Mac/AppViewController.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import WebKit 3 | import Combine 4 | import SwiftUI 5 | 6 | struct AppWindowContentView: View { 7 | var id: String 8 | var onAction: ((WindowControlAction) -> Void)? 9 | 10 | @State private var program: Program? 11 | @State private var errors = [String]() 12 | 13 | var body: some View { 14 | Window95(title: program?.title.nilIfEmpty ?? "App", onControlAction: onAction ?? { _ in () }, additionalAccessoryIcon: AnyView(contactDevButton)) { 15 | AppViewRepresentable(id: id, onError: { errors.append($0) }) 16 | .overlay(alignment: .bottomTrailing) { 17 | if errors.count > 0 { 18 | ErrorView(errors: errors, id: id, onDismiss: { self.errors.removeAll() }) 19 | .padding() 20 | } 21 | } 22 | .overlay { 23 | if let program, let progress = program.installProgress { 24 | InstallShield(name: program.title, progress: progress) 25 | .onDisappear { 26 | self.errors.removeAll() 27 | } 28 | } 29 | } 30 | } 31 | .edgesIgnoringSafeArea(.all) 32 | .onReceive(AppStore.shared.publisher.map { $0.programs[id] }, perform: { self.program = $0 }) 33 | } 34 | 35 | @ViewBuilder var contactDevButton: some View { 36 | Button(action: { contactDevForProgram(id: id) }) { 37 | Text("?").bold() 38 | .foregroundStyle(Color.black) 39 | } 40 | .help("Contact Developer") 41 | } 42 | } 43 | 44 | class AppViewController: NSViewController { 45 | var programID: String? { 46 | didSet { 47 | if programID == oldValue { return } 48 | guard let programID else { return } 49 | mainView = NSHostingView(rootView: AppWindowContentView(id: programID)) 50 | mainView?.sizingOptions = [] 51 | mainView?.rootView.onAction = { [weak self] action in 52 | guard let self, let window = self.view.window else { return } 53 | switch action { 54 | case .minimize: 55 | window.miniaturize(nil) 56 | case .maximize: 57 | window.zoom(nil) 58 | case .close: 59 | window.close() 60 | } 61 | } 62 | } 63 | } 64 | 65 | @IBAction func deleteApp(_ sender: Any?) { 66 | guard let id = programID else { return } 67 | Task { 68 | let prompt = PromptDialogModel(title: "Uninstall this program?", message: "Can't be undone!", cancellable: true, hasTextField: false) 69 | let result = await prompt.run() 70 | if !result.cancelled { 71 | DispatchQueue.main.async { 72 | self.view.window?.close() 73 | AppStore.shared.model.programs.removeValue(forKey: id) 74 | } 75 | } 76 | } 77 | } 78 | 79 | private var mainView: NSHostingView? { 80 | didSet { 81 | oldValue?.removeFromSuperview() 82 | if let mainView = mainView { 83 | view.addSubview(mainView) 84 | mainView.frame = view.bounds 85 | } 86 | } 87 | } 88 | 89 | @IBAction func regenerate(_ sender: Any?) { 90 | guard let programID else { return } 91 | Task { 92 | do { 93 | guard let title = await prompt(question: "title:"), 94 | let subtitle = await prompt(question: "description") 95 | else { 96 | return 97 | } 98 | try await AppStore.shared.generateProgram(id: programID, params: .init(title: title, subtitle: subtitle)) 99 | // try await AppStore.shared.generateProgram(id: programID, title: title, subtitle: subtitle) 100 | } catch { 101 | print("[Program gen] Error: \(error)") 102 | } 103 | } 104 | 105 | } 106 | 107 | override func viewDidLoad() { 108 | super.viewDidLoad() 109 | self.programID = "test" 110 | } 111 | 112 | override func viewWillAppear() { 113 | super.viewWillAppear() 114 | let buttons: [NSWindow.ButtonType] = [.closeButton, .miniaturizeButton, .zoomButton] 115 | for btn in buttons { 116 | self.view.window?.standardWindowButton(btn)?.isHidden = true 117 | } 118 | self.view.window?.isMovableByWindowBackground = true 119 | } 120 | 121 | override func viewDidLayout() { 122 | super.viewDidLayout() 123 | mainView?.frame = view.bounds 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /InfiniteAppStore/AppView.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import SwiftUI 3 | 4 | #if os(macOS) 5 | 6 | struct AppViewRepresentable: NSViewRepresentable { 7 | var id: String 8 | var onError: ((String) -> Void)? 9 | 10 | func makeNSView(context: Context) -> AppView { 11 | AppView(id: id) 12 | } 13 | 14 | func updateNSView(_ nsView: AppView, context: Context) { 15 | nsView.webView.onError = onError 16 | // Can't update id 17 | } 18 | } 19 | 20 | #else 21 | 22 | struct AppViewRepresentable: UIViewRepresentable { 23 | var id: String 24 | var onError: ((String) -> Void)? 25 | 26 | func makeUIView(context: Context) -> AppView { 27 | AppView(id: id) 28 | } 29 | 30 | func updateUIView(_ nsView: AppView, context: Context) { 31 | nsView.webView.onError = onError 32 | // Can't update id 33 | } 34 | } 35 | 36 | #endif 37 | 38 | class AppView: CrossPlatformView { 39 | let webView = InfiniteWebView() 40 | private var subscriptions = Set() 41 | let id: String 42 | 43 | init(id: String) { 44 | self.id = id 45 | super.init(frame: .zero) 46 | 47 | AppStore.shared.publisher.map { $0.programs[id] } 48 | .removeDuplicates() 49 | .sink { [weak self] program in 50 | self?.program = program 51 | } 52 | .store(in: &subscriptions) 53 | 54 | AppStore.shared.publisher.map { $0.programs[id]?.fullCode ?? "" } 55 | .removeDuplicates() 56 | .sink { [weak self] code in 57 | self?.code = code 58 | } 59 | .store(in: &subscriptions) 60 | 61 | addSubview(webView) 62 | } 63 | 64 | override func layout_crossplatform() { 65 | super.layout_crossplatform() 66 | webView.frame = bounds 67 | } 68 | 69 | required init?(coder: NSCoder) { 70 | fatalError("init(coder:) has not been implemented") 71 | } 72 | 73 | private var program: Program? { 74 | didSet { 75 | webView.llmEnabled = program?.llmEnabled ?? false 76 | webView.applescriptEnabled = program?.applescriptEnabled ?? false 77 | #if os(macOS) 78 | self.window?.title = program?.title ?? "App" 79 | #endif 80 | } 81 | } 82 | 83 | override func didMoveToWindow_crossplatform() { 84 | super.didMoveToWindow_crossplatform() 85 | #if os(macOS) 86 | self.window?.title = program?.title ?? "App" 87 | #endif 88 | } 89 | 90 | private var code: String? { 91 | didSet { 92 | if code != oldValue, let code { 93 | webView.loadHTMLString(code, baseURL: URL(string: "http://localhost:50082")!) 94 | } 95 | } 96 | } 97 | } 98 | 99 | extension Program { 100 | var fullCode: String { 101 | let lines = [ 102 | "", 103 | html, 104 | "", 107 | "", 110 | "", 113 | ] 114 | return lines.joined(separator: "\n") 115 | } 116 | } 117 | 118 | struct ErrorView: View { 119 | var errors: [String] 120 | var id: String 121 | var onDismiss: () -> Void 122 | 123 | @State private var contactDevSheet: ContactDevSheetModel? 124 | 125 | struct ContactDevSheetModel: Identifiable { 126 | var id: String 127 | var initialMsg: String? 128 | } 129 | 130 | var body: some View { 131 | VStack { 132 | Text("Encountered error :(") 133 | HStack { 134 | Button(action: reportErrors) { 135 | Text("Report to Developer") 136 | } 137 | Button(action: onDismiss) { 138 | Text("Ignore") 139 | } 140 | } 141 | } 142 | .padding() 143 | .with95DepthEffect(pushed: false) 144 | .background { 145 | Color.black.opacity(0.2) 146 | .offset(x: 3, y: 3) 147 | } 148 | .sheet(item: $contactDevSheet, content: { msg in 149 | ContactDevView(programId: msg.id, initialMessage: msg.initialMsg) 150 | }) 151 | } 152 | 153 | private func reportErrors() { 154 | if errors.count == 0 { return } 155 | var lines = ["I'm seeing errors:"] 156 | lines += errors.map { "> " + $0 } 157 | lines.append("Why the errors? Write a few words explaining why, then fix the app.") 158 | let msg = lines.joined(separator: "\n") 159 | 160 | #if os(iOS) 161 | self.contactDevSheet = .init(id: id, initialMsg: msg) 162 | #else 163 | let win = getOrCreateContactDevWindow(id: id) 164 | win.makeKeyAndOrderFront(nil) 165 | // HACK: Avoid race condition where thread isn't init'd before view appears 166 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 167 | guard let thread = ContactDevThread.all.compactMap(\.value).first(where: { $0.program?.id == id }) else { 168 | return 169 | } 170 | thread.send(message: msg) 171 | } 172 | #endif 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /InfiniteAppStore.xcodeproj/xcuserdata/nateparrott.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AnyCodable (Playground) 1.xcscheme 8 | 9 | isShown 10 | 11 | orderHint 12 | 4 13 | 14 | AnyCodable (Playground) 2.xcscheme 15 | 16 | isShown 17 | 18 | orderHint 19 | 5 20 | 21 | AnyCodable (Playground).xcscheme 22 | 23 | isShown 24 | 25 | orderHint 26 | 3 27 | 28 | Associations (Playground) 1.xcscheme 29 | 30 | isShown 31 | 32 | orderHint 33 | 7 34 | 35 | Associations (Playground) 2.xcscheme 36 | 37 | isShown 38 | 39 | orderHint 40 | 8 41 | 42 | Associations (Playground) 3.xcscheme 43 | 44 | isShown 45 | 46 | orderHint 47 | 24 48 | 49 | Associations (Playground) 4.xcscheme 50 | 51 | isShown 52 | 53 | orderHint 54 | 25 55 | 56 | Associations (Playground) 5.xcscheme 57 | 58 | isShown 59 | 60 | orderHint 61 | 26 62 | 63 | Associations (Playground).xcscheme 64 | 65 | isShown 66 | 67 | orderHint 68 | 6 69 | 70 | InfiniteAppStore.xcscheme_^#shared#^_ 71 | 72 | orderHint 73 | 1 74 | 75 | InfiniteAppStoreiOS.xcscheme_^#shared#^_ 76 | 77 | orderHint 78 | 0 79 | 80 | MyPlayground (Playground) 1.xcscheme 81 | 82 | isShown 83 | 84 | orderHint 85 | 16 86 | 87 | MyPlayground (Playground) 2.xcscheme 88 | 89 | isShown 90 | 91 | orderHint 92 | 17 93 | 94 | MyPlayground (Playground) 3.xcscheme 95 | 96 | isShown 97 | 98 | orderHint 99 | 18 100 | 101 | MyPlayground (Playground) 4.xcscheme 102 | 103 | isShown 104 | 105 | orderHint 106 | 19 107 | 108 | MyPlayground (Playground) 5.xcscheme 109 | 110 | isShown 111 | 112 | orderHint 113 | 20 114 | 115 | MyPlayground (Playground).xcscheme 116 | 117 | isShown 118 | 119 | orderHint 120 | 15 121 | 122 | Tour (Playground) 1.xcscheme 123 | 124 | isShown 125 | 126 | orderHint 127 | 13 128 | 129 | Tour (Playground) 2.xcscheme 130 | 131 | isShown 132 | 133 | orderHint 134 | 14 135 | 136 | Tour (Playground) 3.xcscheme 137 | 138 | isShown 139 | 140 | orderHint 141 | 15 142 | 143 | Tour (Playground) 4.xcscheme 144 | 145 | isShown 146 | 147 | orderHint 148 | 16 149 | 150 | Tour (Playground) 5.xcscheme 151 | 152 | isShown 153 | 154 | orderHint 155 | 17 156 | 157 | Tour (Playground).xcscheme 158 | 159 | isShown 160 | 161 | orderHint 162 | 12 163 | 164 | TransactionObserver (Playground) 1.xcscheme 165 | 166 | isShown 167 | 168 | orderHint 169 | 10 170 | 171 | TransactionObserver (Playground) 2.xcscheme 172 | 173 | isShown 174 | 175 | orderHint 176 | 11 177 | 178 | TransactionObserver (Playground) 3.xcscheme 179 | 180 | isShown 181 | 182 | orderHint 183 | 21 184 | 185 | TransactionObserver (Playground) 4.xcscheme 186 | 187 | isShown 188 | 189 | orderHint 190 | 22 191 | 192 | TransactionObserver (Playground) 5.xcscheme 193 | 194 | isShown 195 | 196 | orderHint 197 | 23 198 | 199 | TransactionObserver (Playground).xcscheme 200 | 201 | isShown 202 | 203 | orderHint 204 | 9 205 | 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /InfiniteAppStore/Data/ProgramGen.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import ChatToys 3 | 4 | enum Prompts { 5 | private static var mobileTheming: String { 6 | #if os(iOS) 7 | return "This app should visually look like Windows 98 and use vintage design patterns, BUT be responsive to fit on a mobile device." 8 | #else 9 | return "" 10 | #endif 11 | } 12 | 13 | static let llmApiDoc = """ 14 | // use llmStream to stream results from a large-language AI model 15 | // `result` contains the full string up to this point; when done is true, result will be the full answer. 16 | function llmStream(prompt: string, callback: (result: string, done: bool) => void): void 17 | """ 18 | 19 | static let appleScriptApiDoc = """ 20 | // use appleScript to execute standard applescript to control a user's computer 21 | async function appleScript(prompt: string) => string | null 22 | """ 23 | 24 | static func generationPrompt(title: String, subtitle: String, llmEnabled: Bool, appleScriptEnabled: Bool) -> String { 25 | let apis: [String?] = [ 26 | llmEnabled ? llmApiDoc : nil, 27 | appleScriptEnabled ? appleScriptApiDoc : nil, 28 | ] 29 | let apiStr = apis.compactMap { $0 }.joined(separator: "\n\n") 30 | return """ 31 | You're a skilled software engineer developing simple local apps using HTML, CSS and JS. 32 | Your apps run within a webview with CORS disabled. 33 | 34 | I'll give you a prompt, and you'll write HTML, CSS and Javascript to make the app. 35 | Only call JS APIs that are available in a webview, or the special APIs described below. 36 | Store app state in localStorage and use utilities like prompt(), alert(), etc for convenience. 37 | 38 | You can call external APIs via AJAX if they don't need an API key. 39 | 40 | Write a complete program -- do not omit parts or leave things the user needs to fill in. 41 | There are no local resources provided on the domain. 42 | 43 | # Extra APIs 44 | \(apiStr.nilIfEmpty ?? "None") 45 | 46 | # Theming 47 | Make your app look like a retro Windows 98 ap. 48 | A base stylesheet has been applied that makes programs look like Windows 98. 49 | Use ordinary HTML elements (input, textarea, select, button, overflow: scroll, etc), and they'll automatically get this styling. 50 | Make sure it's responsive -- it should fill the viewport. You don't need a title h1. 51 | Do not reference external assets. 52 | You don't need to draw the window title bar; it will be drawn for you. 53 | \(mobileTheming) 54 | 55 | Here's an excerpt from the stylesheet, which is automatically included: 56 | 59 | 60 | # Icons 61 | There are a few built-in icons you can use. You should pick one for the app itself. You can also reference them in your code like this: `/icons/address_book.png`. 62 | Use only these icon identifiers: 63 | ``` 64 | \(Icons.iconNames.joined(separator: ", ")) 65 | ``` 66 | 67 | # App Prompt 68 | Here is the prompt: 69 | App Name: '\(title)' 70 | App Description: '\(subtitle)' 71 | 72 | Below, define the program as a JSON object containing the HTML/CSS/JS, and other app attributes: 73 | 74 | ``` 75 | interface App { 76 | icon: string // Use only icons from the list above. Name only (not path) like 'address_book' 77 | html: string // Include only, no . You don't need to link the CSS or JS files. 78 | css: string 79 | js: string 80 | } 81 | ``` 82 | 83 | Write your app below, in valid JSON syntax, with nothing else: 84 | """ 85 | } 86 | } 87 | 88 | enum ProgramGenError: Error { 89 | case noOutput 90 | } 91 | 92 | extension AppStore { 93 | func generateProgram(id: String, params: NewProgramParams) async throws { 94 | let prompt = Prompts.generationPrompt(title: params.title, subtitle: params.subtitle, llmEnabled: params.llmEnabled, appleScriptEnabled: params.applescript) 95 | 96 | struct Output: Codable { 97 | let icon: String? 98 | let html: String? 99 | let css: String? 100 | let js: String? 101 | } 102 | 103 | AppStore.shared.modify { $0.modifyProgram(id: id, { 104 | $0.installProgress = 0 105 | $0.title = params.title 106 | $0.subtitle = params.subtitle 107 | $0.applescriptEnabled = params.applescript 108 | $0.llmEnabled = params.llmEnabled 109 | }) } 110 | 111 | let llm = try await Credentials.getOrPromptForCreds().jsonLLM 112 | var last: Output? 113 | for try await partial in llm.completeStreamingWithJSONObject(prompt: [LLMMessage(role: .system, content: prompt)], type: Output.self) { 114 | last = partial 115 | AppStore.shared.modify { state in 116 | state.modifyProgram(id: id) { program in 117 | program.iconName = partial.icon ?? "executable" 118 | program.html = partial.html ?? "" 119 | program.css = partial.css ?? "" 120 | program.js = "" 121 | program.title = params.title 122 | program.subtitle = params.subtitle 123 | program.computeEstimatedInstallProgress(jsLen: partial.js?.count ?? 0) 124 | } 125 | } 126 | } 127 | guard let output = last else { 128 | throw ProgramGenError.noOutput 129 | } 130 | await AppStore.shared.modifyAsync { state in 131 | state.modifyProgram(id: id) { program in 132 | program.html = output.html ?? "" 133 | program.css = output.css ?? "" 134 | program.js = output.js ?? "" 135 | print("HTML: \(program.html.count); JS: \(program.js.count); CSS: \(program.css.count)") 136 | program.installProgress = nil 137 | } 138 | } 139 | } 140 | } 141 | 142 | extension Program { 143 | mutating func computeEstimatedInstallProgress(jsLen: Int) { 144 | let expectedHtmlLen: Int = 700 145 | let expectedCSSLen: Int = 700 146 | let expectedJSLen: Int = 700 147 | 148 | let finalHtmlLen: Int? = css.count > 0 ? html.count : nil 149 | let finalCSSLen: Int? = jsLen > 0 ? css.count : nil 150 | 151 | let totalLen = html.count + css.count + jsLen 152 | let expectedLen = (finalHtmlLen ?? expectedHtmlLen) + (finalCSSLen ?? expectedCSSLen) + max(expectedJSLen, jsLen) 153 | installProgress = 0.1 + Double(totalLen) / Double(expectedLen) * 0.9 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /InfiniteAppStore/Data/CSS.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum CSS { 4 | // @import url("http://localhost:50082/static/fonts.css"); 5 | static let baseCSS: String = """ 6 | @import url("http://localhost:50082/static/fonts.css"); 7 | 8 | :root { 9 | /* Color */ 10 | --text-color: #222222; 11 | --surface: #c0c0c0; 12 | --button-highlight: #ffffff; 13 | --button-face: #dfdfdf; 14 | --button-shadow: #808080; 15 | --window-frame: #0a0a0a; 16 | --dialog-blue: #000080; 17 | --dialog-blue-light: #1084d0; 18 | --dialog-gray: #808080; 19 | --dialog-gray-light: #b5b5b5; 20 | --link-blue: #0000ff; 21 | 22 | /* Spacing */ 23 | --element-spacing: 8px; 24 | --grouped-button-spacing: 4px; 25 | --grouped-element-spacing: 6px; 26 | --radio-width: 12px; 27 | --checkbox-width: 13px; 28 | --radio-label-spacing: 6px; 29 | --range-track-height: 4px; 30 | --range-spacing: 10px; 31 | 32 | /* Some detailed computations for radio buttons and checkboxes */ 33 | --radio-total-width-precalc: var(--radio-width) + var(--radio-label-spacing); 34 | --radio-total-width: calc(var(--radio-total-width-precalc)); 35 | --radio-left: calc(-1 * var(--radio-total-width-precalc)); 36 | --radio-dot-width: 4px; 37 | --radio-dot-top: calc(var(--radio-width) / 2 - var(--radio-dot-width) / 2); 38 | --radio-dot-left: calc( 39 | -1 * (var(--radio-total-width-precalc)) + var(--radio-width) / 2 - var( 40 | --radio-dot-width 41 | ) / 2 42 | ); 43 | 44 | --checkbox-total-width-precalc: var(--checkbox-width) + 45 | var(--radio-label-spacing); 46 | --checkbox-total-width: calc(var(--checkbox-total-width-precalc)); 47 | --checkbox-left: calc(-1 * var(--checkbox-total-width-precalc)); 48 | --checkmark-width: 7px; 49 | --checkmark-left: 3px; 50 | 51 | /* Borders */ 52 | --border-width: 1px; 53 | --border-raised-outer: inset -1px -1px var(--window-frame), 54 | inset 1px 1px var(--button-highlight); 55 | --border-raised-inner: inset -2px -2px var(--button-shadow), 56 | inset 2px 2px var(--button-face); 57 | --border-sunken-outer: inset -1px -1px var(--button-highlight), 58 | inset 1px 1px var(--window-frame); 59 | --border-sunken-inner: inset -2px -2px var(--button-face), 60 | inset 2px 2px var(--button-shadow); 61 | --default-button-border-raised-outer: inset -2px -2px var(--window-frame), inset 1px 1px var(--window-frame); 62 | --default-button-border-raised-inner: inset 2px 2px var(--button-highlight), inset -3px -3px var(--button-shadow), inset 3px 3px var(--button-face); 63 | --default-button-border-sunken-outer: inset 2px 2px var(--window-frame), inset -1px -1px var(--window-frame); 64 | --default-button-border-sunken-inner: inset -2px -2px var(--button-highlight), inset 3px 3px var(--button-shadow), inset -3px -3px var(--button-face); 65 | 66 | 67 | /* Window borders flip button-face and button-highlight */ 68 | --border-window-outer: inset -1px -1px var(--window-frame), 69 | inset 1px 1px var(--button-face); 70 | --border-window-inner: inset -2px -2px var(--button-shadow), 71 | inset 2px 2px var(--button-highlight); 72 | 73 | /* Field borders (checkbox, input, etc) flip window-frame and button-shadow */ 74 | --border-field: inset -1px -1px var(--button-highlight), 75 | inset 1px 1px var(--button-shadow), inset -2px -2px var(--button-face), 76 | inset 2px 2px var(--window-frame); 77 | } 78 | 79 | body { 80 | margin: 0; 81 | padding: 0; 82 | background-color: var(--surface); 83 | color: var(--text-color); 84 | } 85 | 86 | button, body, input, textarea { 87 | -webkit-font-smoothing: none; 88 | font-family: "RetroFont"; 89 | font-size: 12px; 90 | } 91 | 92 | button, 93 | input[type="submit"], 94 | input[type="reset"] { 95 | box-sizing: border-box; 96 | border: none; 97 | color: transparent; 98 | text-shadow: 0 0 var(--text-color); 99 | background: var(--surface); 100 | box-shadow: var(--border-raised-outer), var(--border-raised-inner); 101 | border-radius: 0; 102 | 103 | min-width: 75px; 104 | min-height: 23px; 105 | padding: 0 12px; 106 | 107 | outline: none; 108 | } 109 | 110 | button.default, 111 | input[type="submit"].default, 112 | input[type="reset"].default { 113 | box-shadow: var(--default-button-border-raised-outer), var(--default-button-border-raised-inner); 114 | } 115 | 116 | .vertical-bar { 117 | width: 4px; 118 | height: 20px; 119 | background: #c0c0c0; 120 | box-shadow: var(--border-raised-outer), var(--border-raised-inner); 121 | } 122 | 123 | button:not(:disabled):active, 124 | input[type="submit"]:not(:disabled):active, 125 | input[type="reset"]:not(:disabled):active { 126 | box-shadow: var(--border-sunken-outer), var(--border-sunken-inner); 127 | text-shadow: 1px 1px var(--text-color); 128 | } 129 | 130 | button.default:not(:disabled):active, 131 | input[type="submit"].default:not(:disabled):active, 132 | input[type="reset"].default:not(:disabled):active { 133 | box-shadow: var(--default-button-border-sunken-outer), var(--default-button-border-sunken-inner); 134 | } 135 | 136 | button:focus, 137 | input[type="submit"]:focus, 138 | input[type="reset"]:focus { 139 | outline: 1px dotted #000000; 140 | outline-offset: -4px; 141 | } 142 | 143 | button:disabled, 144 | input[type="submit"]:disabled, 145 | input[type="reset"]:disabled, 146 | :disabled + label { 147 | text-shadow: 1px 1px 0 var(--button-highlight); 148 | } 149 | 150 | .status-bar { 151 | margin: 0px 1px; 152 | display: flex; 153 | gap: 1px; 154 | } 155 | 156 | .status-bar-field { 157 | box-shadow: inset -1px -1px #dfdfdf, inset 1px 1px #808080; 158 | flex-grow: 1; 159 | padding: 2px 3px; 160 | margin: 0; 161 | } 162 | 163 | input[type="text"], 164 | input[type="password"], 165 | input[type="email"], 166 | input[type="tel"], 167 | input[type="number"], 168 | input[type="search"], 169 | select, 170 | textarea { 171 | padding: 3px 4px; 172 | border: none; 173 | box-shadow: var(--border-field); 174 | background-color: var(--button-highlight); 175 | box-sizing: border-box; 176 | -webkit-appearance: none; 177 | -moz-appearance: none; 178 | appearance: none; 179 | border-radius: 0; 180 | } 181 | 182 | input[type="text"], 183 | input[type="password"], 184 | input[type="email"], 185 | input[type="tel"], 186 | input[type="search"], 187 | select { 188 | height: 21px; 189 | } 190 | 191 | .sunken-panel { 192 | box-sizing: border-box; 193 | border: 2px groove transparent; 194 | border-image: svg-load("./icon/sunken-panel-border.svg") 2; 195 | overflow: auto; 196 | background-color: #fff; 197 | } 198 | 199 | table { 200 | border-collapse: collapse; 201 | position: relative; 202 | text-align: left; 203 | white-space: nowrap; 204 | background-color: #fff; 205 | } 206 | """ 207 | } 208 | -------------------------------------------------------------------------------- /InfiniteAppStore/Utils/DataStore.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Combine 3 | 4 | #if os(OSX) 5 | import AppKit 6 | #elseif os(iOS) 7 | import UIKit 8 | #endif 9 | 10 | // TODO: Save on deinit, not just app close? (for non-globals) 11 | open class DataStore: NSObject { 12 | public typealias ChangeHook = (Model, inout Model) -> Void 13 | 14 | private var _model: Model { 15 | didSet { 16 | if _model != oldValue { 17 | subject.send(_model) 18 | } 19 | } 20 | } 21 | private var subject: CurrentValueSubject 22 | 23 | public let persistenceKey: String? 24 | public let queue: Queue 25 | 26 | public var model: Model { 27 | get { 28 | var val: Model! 29 | queue.runSync { 30 | val = self._model 31 | } 32 | return val 33 | } 34 | set(newVal) { 35 | queue.runSync { 36 | var modifiedNewVal = newVal 37 | let oldVal = self._model 38 | for hook in changeHooks { 39 | hook(oldVal, &modifiedNewVal) 40 | } 41 | if modifiedNewVal != self._model { 42 | self._model = modifiedNewVal 43 | } 44 | } 45 | } 46 | } 47 | 48 | public var publisher: AnyPublisher { subject.eraseToAnyPublisher() } 49 | 50 | // private let _observableWithMetadata: MutableObservable<(Model, TransactionMetadata)> 51 | // public var observableWithMetadata: Observable<(Model, TransactionMetadata)> { return _observableWithMetadata } 52 | // private let observer = Observer() 53 | private var changeHooks = [ChangeHook]() 54 | 55 | public init(persistenceKey: String?, defaultModel: Model, queue: Queue) { 56 | _model = defaultModel 57 | self.queue = queue 58 | self.persistenceKey = persistenceKey 59 | // self._observableWithMetadata = MutableObservable(current: (defaultModel, TransactionMetadata(sender: nil)), queue: queue) 60 | // self.observable = self._observableWithMetadata.map({ $0.0 }) 61 | // self.uiObservable = self.observable.onQueue(.main).throttledToDisplayRefreshRate 62 | self.subject = .init(defaultModel) 63 | super.init() 64 | 65 | // Platform-specific notifications: 66 | #if os(OSX) 67 | let willResignActive = NSApplication.willResignActiveNotification 68 | let willTerminate = NSApplication.willTerminateNotification 69 | #elseif os(iOS) 70 | let willResignActive = UIApplication.willResignActiveNotification 71 | let willTerminate = UIApplication.willTerminateNotification 72 | #endif 73 | 74 | let startTime = CACurrentMediaTime() 75 | 76 | queue.run { 77 | var loadedBytes: Int = 0 78 | 79 | let initialState: Model? = { 80 | for url in [self.persistentURL, self.localPathForInitialState].compactMap({ $0 }) { 81 | do { 82 | let data = try Data(contentsOf: url) 83 | loadedBytes += data.count 84 | guard var state = (try? self.decode(data: data)) ?? self.migrate(previousData: data) else { 85 | throw DataStoreErrors.couldNotDecodeModel 86 | } 87 | print("Loaded \(url)") 88 | self.processModelAfterLoad(model: &state) 89 | return state 90 | } catch { 91 | let name = self.persistenceKey ?? "" 92 | print("Failed to load \(name) datastore: \(error)") 93 | } 94 | } 95 | return nil 96 | }() 97 | 98 | if let state = initialState { 99 | self._model = state 100 | } 101 | NotificationCenter.default.addObserver(self, selector: #selector(self.save), name: willResignActive, object: nil) 102 | NotificationCenter.default.addObserver(self, selector: #selector(self.saveSync), name: willTerminate, object: nil) 103 | 104 | // self.observer.observe(self.observable) { [weak self] value in 105 | // self?.subject.value = value 106 | // } 107 | 108 | self.setup() 109 | 110 | let loadTime = CACurrentMediaTime() - startTime 111 | self.didCompleteInitialLoad(duration: loadTime, bytesRead: loadedBytes) 112 | } 113 | } 114 | 115 | // Override to provide custom encode/decode 116 | open func decode(data: Data) throws -> Model { 117 | try JSONDecoder().decode(Model.self, from: data) 118 | } 119 | 120 | open func encode(model: Model) throws -> Data { 121 | try JSONEncoder().encode(model) 122 | } 123 | 124 | open func setup() { 125 | // for subclasses 126 | } 127 | 128 | public func modify(_ block: @escaping (inout Model) -> ()) { 129 | queue.run { 130 | var newModel = self.model 131 | block(&newModel) 132 | self.model = newModel 133 | } 134 | } 135 | 136 | @objc public func save() { 137 | queue.run { 138 | self.cleanup(model: &self._model) 139 | if let key = self.persistenceKey { 140 | var processed = self._model 141 | self.processModelBeforePersist(model: &processed) 142 | try! (try! self.encode(model: processed)).write(to: DataStore.persistentURL(key)) 143 | } 144 | } 145 | } 146 | 147 | @objc func saveSync() { 148 | queue.runSync { 149 | self.save() 150 | } 151 | } 152 | 153 | public var persistentURL: URL? { 154 | if let key = self.persistenceKey { 155 | return Self.persistentURL(key) 156 | } 157 | return nil 158 | } 159 | 160 | static func persistentURL(_ key: String) -> URL { 161 | let appDir = "DataStores" 162 | let dir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent(appDir) 163 | if !FileManager.default.fileExists(atPath: dir.path) { 164 | try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true, attributes: nil) 165 | } 166 | return dir.appendingPathComponent(key + ".json") 167 | } 168 | 169 | public func addChangeHook(_ changeHook: @escaping ChangeHook) { 170 | changeHooks.append(changeHook) 171 | } 172 | 173 | /// override this method to provide 'cleanup logic' to ensure that this data structure does not grow to an unbounded size. 174 | open func cleanup(model: inout Model) { 175 | } 176 | 177 | open func processModelBeforePersist(model: inout Model) { 178 | } 179 | 180 | open func processModelAfterLoad(model: inout Model) { 181 | } 182 | 183 | open func didCompleteInitialLoad(duration: TimeInterval, bytesRead: Int) { 184 | // override to do metrics 185 | } 186 | 187 | open var localPathForInitialState: URL? { 188 | return nil 189 | } 190 | 191 | open func migrate(previousData data: Data) -> Model? { 192 | return nil 193 | } 194 | 195 | // MARK: - Concurrency 196 | 197 | public func readAsync() async -> Model { 198 | return await withCheckedContinuation { continuation in 199 | self.queue.run { 200 | continuation.resume(returning: self.model) 201 | } 202 | } 203 | } 204 | 205 | @discardableResult 206 | public func modifyAsync(_ block: @escaping (inout Model) -> T) async -> T { 207 | return await withCheckedContinuation({ cont in 208 | self.queue.run { 209 | self.modify { state in 210 | let result = block(&state) 211 | cont.resume(returning: result) 212 | } 213 | } 214 | }) 215 | } 216 | } 217 | 218 | private enum DataStoreErrors: Error { 219 | case couldNotDecodeModel 220 | } 221 | -------------------------------------------------------------------------------- /InfiniteAppStore/Data/InfiniteAppStoreStore.json: -------------------------------------------------------------------------------- 1 | {"programs":{"EFECD4AA-8E9F-4403-9E54-3683EB02A169":{"llmEnabled":true,"html":"