diff options
| author | 2026-01-19 11:06:38 +0800 | |
|---|---|---|
| committer | 2026-01-19 11:06:38 +0800 | |
| commit | f5560d7e8abe4a41c5f959cb6eb888f6aef6ca65 (patch) | |
| tree | f3675bdb552a79ddb4601ccf2f5ddd81eb47c9fb /ui | |
| parent | ee767338d6db510ef15d6b8cc11f6fb9a6215a43 (diff) | |
| parent | bdff2175a8470accdab030b3931406495c56074d (diff) | |
| download | DropOut-f5560d7e8abe4a41c5f959cb6eb888f6aef6ca65.tar.gz DropOut-f5560d7e8abe4a41c5f959cb6eb888f6aef6ca65.zip | |
Merge branch 'main' into chore/migrate-repository
Diffstat (limited to 'ui')
| -rw-r--r-- | ui/CHANGELOG.md | 7 | ||||
| -rw-r--r-- | ui/package-lock.json | 1872 | ||||
| -rw-r--r-- | ui/package.json | 7 | ||||
| -rw-r--r-- | ui/src/App.svelte | 9 | ||||
| -rw-r--r-- | ui/src/components/InstanceCreationModal.svelte | 485 | ||||
| -rw-r--r-- | ui/src/components/InstanceEditorModal.svelte | 439 | ||||
| -rw-r--r-- | ui/src/components/InstancesView.svelte | 110 | ||||
| -rw-r--r-- | ui/src/components/SettingsView.svelte | 146 | ||||
| -rw-r--r-- | ui/src/components/VersionsView.svelte | 6 | ||||
| -rw-r--r-- | ui/src/stores/game.svelte.ts | 17 | ||||
| -rw-r--r-- | ui/src/stores/settings.svelte.ts | 9 | ||||
| -rw-r--r-- | ui/src/types/index.ts | 28 |
12 files changed, 1163 insertions, 1972 deletions
diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md new file mode 100644 index 0000000..4b2d22b --- /dev/null +++ b/ui/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## v0.2.0-alpha.1 + +### New Features + +- [`e7ac28c`](https://github.com/HydroRoll-Team/DropOut/commit/e7ac28c6b8467a8fca0a3b61ba498e4742d3a718): Prepare for alpha mode pre-release. ([#62](https://github.com/HydroRoll-Team/DropOut/pull/62) by @fu050409) diff --git a/ui/package-lock.json b/ui/package-lock.json deleted file mode 100644 index 073c76d..0000000 --- a/ui/package-lock.json +++ /dev/null @@ -1,1872 +0,0 @@ -{ - "name": "ui", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "ui", - "version": "0.0.0", - "dependencies": { - "@tauri-apps/api": "^2.9.1", - "@tauri-apps/plugin-dialog": "^2.5.0", - "@tauri-apps/plugin-fs": "^2.4.5", - "@tauri-apps/plugin-shell": "^2.3.4", - "lucide-svelte": "^0.562.0", - "marked": "^17.0.1", - "node-emoji": "^2.2.0" - }, - "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^6.2.1", - "@tailwindcss/vite": "^4.1.18", - "@tsconfig/svelte": "^5.0.6", - "@types/node": "^24.10.1", - "autoprefixer": "^10.4.23", - "postcss": "^8.5.6", - "svelte": "^5.46.4", - "svelte-check": "^4.3.4", - "tailwindcss": "^4.1.18", - "typescript": "~5.9.3", - "vite": "npm:rolldown-vite@7.2.5" - } - }, - "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", - "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@oxc-project/runtime": { - "version": "0.97.0", - "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.97.0.tgz", - "integrity": "sha512-yH0zw7z+jEws4dZ4IUKoix5Lh3yhqIJWF9Dc8PWvhpo7U7O+lJrv7ZZL4BeRO0la8LBQFwcCewtLBnVV7hPe/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.97.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.97.0.tgz", - "integrity": "sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.50.tgz", - "integrity": "sha512-XlEkrOIHLyGT3avOgzfTFSjG+f+dZMw+/qd+Y3HLN86wlndrB/gSimrJCk4gOhr1XtRtEKfszpadI3Md4Z4/Ag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.50.tgz", - "integrity": "sha512-+JRqKJhoFlt5r9q+DecAGPLZ5PxeLva+wCMtAuoFMWPoZzgcYrr599KQ+Ix0jwll4B4HGP43avu9My8KtSOR+w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.50.tgz", - "integrity": "sha512-fFXDjXnuX7/gQZQm/1FoivVtRcyAzdjSik7Eo+9iwPQ9EgtA5/nB2+jmbzaKtMGG3q+BnZbdKHCtOacmNrkIDA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.50.tgz", - "integrity": "sha512-F1b6vARy49tjmT/hbloplzgJS7GIvwWZqt+tAHEstCh0JIh9sa8FAMVqEmYxDviqKBaAI8iVvUREm/Kh/PD26Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.50.tgz", - "integrity": "sha512-U6cR76N8T8M6lHj7EZrQ3xunLPxSvYYxA8vJsBKZiFZkT8YV4kjgCO3KwMJL0NOjQCPGKyiXO07U+KmJzdPGRw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.50.tgz", - "integrity": "sha512-ONgyjofCrrE3bnh5GZb8EINSFyR/hmwTzZ7oVuyUB170lboza1VMCnb8jgE6MsyyRgHYmN8Lb59i3NKGrxrYjw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.50.tgz", - "integrity": "sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.50.tgz", - "integrity": "sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.50.tgz", - "integrity": "sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.50.tgz", - "integrity": "sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.50.tgz", - "integrity": "sha512-nmCN0nIdeUnmgeDXiQ+2HU6FT162o+rxnF7WMkBm4M5Ds8qTU7Dzv2Wrf22bo4ftnlrb2hKK6FSwAJSAe2FWLg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.50.tgz", - "integrity": "sha512-7kcNLi7Ua59JTTLvbe1dYb028QEPaJPJQHqkmSZ5q3tJueUeb6yjRtx8mw4uIqgWZcnQHAR3PrLN4XRJxvgIkA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-ia32-msvc": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.50.tgz", - "integrity": "sha512-lL70VTNvSCdSZkDPPVMwWn/M2yQiYvSoXw9hTLgdIWdUfC3g72UaruezusR6ceRuwHCY1Ayu2LtKqXkBO5LIwg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.50.tgz", - "integrity": "sha512-4qU4x5DXWB4JPjyTne/wBNPqkbQU8J45bl21geERBKtEittleonioACBL1R0PsBu0Aq21SwMK5a9zdBkWSlQtQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz", - "integrity": "sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.8.tgz", - "integrity": "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", - "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", - "deepmerge": "^4.3.1", - "magic-string": "^0.30.21", - "obug": "^2.1.0", - "vitefu": "^1.1.1" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "peerDependencies": { - "svelte": "^5.0.0", - "vite": "^6.3.0 || ^7.0.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz", - "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "obug": "^2.1.0" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", - "svelte": "^5.0.0", - "vite": "^6.3.0 || ^7.0.0" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", - "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.1", - "lightningcss": "1.30.2", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.18" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", - "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-x64": "4.1.18", - "@tailwindcss/oxide-freebsd-x64": "4.1.18", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-x64-musl": "4.1.18", - "@tailwindcss/oxide-wasm32-wasi": "4.1.18", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", - "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", - "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", - "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", - "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", - "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", - "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", - "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", - "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", - "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", - "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.0", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", - "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", - "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", - "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.18", - "@tailwindcss/oxide": "4.1.18", - "tailwindcss": "4.1.18" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@tauri-apps/api": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.9.1.tgz", - "integrity": "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw==", - "license": "Apache-2.0 OR MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - } - }, - "node_modules/@tauri-apps/plugin-dialog": { - "version": "2.6.0", - "resolved": "https://registry.npmmirror.com/@tauri-apps/plugin-dialog/-/plugin-dialog-2.6.0.tgz", - "integrity": "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@tauri-apps/api": "^2.8.0" - } - }, - "node_modules/@tauri-apps/plugin-fs": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.4.5.tgz", - "integrity": "sha512-dVxWWGE6VrOxC7/jlhyE+ON/Cc2REJlM35R3PJX3UvFw2XwYhLGQVAIyrehenDdKjotipjYEVc4YjOl3qq90fA==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@tauri-apps/api": "^2.8.0" - } - }, - "node_modules/@tauri-apps/plugin-shell": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.4.tgz", - "integrity": "sha512-ktsRWf8wHLD17aZEyqE8c5x98eNAuTizR1FSX475zQ4TxaiJnhwksLygQz+AGwckJL5bfEP13nWrlTNQJUpKpA==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@tauri-apps/api": "^2.8.0" - } - }, - "node_modules/@tsconfig/svelte": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.6.tgz", - "integrity": "sha512-yGxYL0I9eETH1/DR9qVJey4DAsCdeau4a9wYPKuXfEhm8lFO8wg+LLYJjIpAm6Fw7HSlhepPhYPDop75485yWQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.10.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.8.tgz", - "integrity": "sha512-r0bBaXu5Swb05doFYO2kTWHMovJnNVbCsII0fhesM8bNRlLhXIuckley4a2DaD+vOdmm5G+zGkQZAPZsF80+YQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.23", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", - "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.14", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", - "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001764", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", - "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/devalue": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz", - "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.267", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", - "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", - "dev": true, - "license": "ISC" - }, - "node_modules/emojilib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", - "license": "MIT" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.4", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", - "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "license": "MIT" - }, - "node_modules/esrap": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.1.tgz", - "integrity": "sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "license": "MIT" - }, - "node_modules/lucide-svelte": { - "version": "0.562.0", - "resolved": "https://registry.npmmirror.com/lucide-svelte/-/lucide-svelte-0.562.0.tgz", - "integrity": "sha512-kSJDH/55lf0mun/o4nqWBXOcq0fWYzPeIjbTD97ywoeumAB9kWxtM06gC7oynqjtK3XhAljWSz5RafIzPEYIQA==", - "license": "ISC", - "peerDependencies": { - "svelte": "^3 || ^4 || ^5.0.0-next.42" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/marked": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", - "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-emoji": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", - "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.6.0", - "char-regex": "^1.0.2", - "emojilib": "^2.4.0", - "skin-tone": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/rolldown": { - "version": "1.0.0-beta.50", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.50.tgz", - "integrity": "sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.97.0", - "@rolldown/pluginutils": "1.0.0-beta.50" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-beta.50", - "@rolldown/binding-darwin-arm64": "1.0.0-beta.50", - "@rolldown/binding-darwin-x64": "1.0.0-beta.50", - "@rolldown/binding-freebsd-x64": "1.0.0-beta.50", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.50", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.50", - "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.50", - "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.50", - "@rolldown/binding-linux-x64-musl": "1.0.0-beta.50", - "@rolldown/binding-openharmony-arm64": "1.0.0-beta.50", - "@rolldown/binding-wasm32-wasi": "1.0.0-beta.50", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.50", - "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.50", - "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.50" - } - }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/skin-tone": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", - "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", - "license": "MIT", - "dependencies": { - "unicode-emoji-modifier-base": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/svelte": { - "version": "5.46.4", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.46.4.tgz", - "integrity": "sha512-VJwdXrmv9L8L7ZasJeWcCjoIuMRVbhuxbss0fpVnR8yorMmjNDwcjIH08vS6wmSzzzgAG5CADQ1JuXPS2nwt9w==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/estree": "^1.0.5", - "acorn": "^8.12.1", - "aria-query": "^5.3.1", - "axobject-query": "^4.1.0", - "clsx": "^2.1.1", - "devalue": "^5.6.2", - "esm-env": "^1.2.1", - "esrap": "^2.2.1", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/svelte-check": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.5.tgz", - "integrity": "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "chokidar": "^4.0.1", - "fdir": "^6.2.0", - "picocolors": "^1.0.0", - "sade": "^1.7.4" - }, - "bin": { - "svelte-check": "bin/svelte-check" - }, - "engines": { - "node": ">= 18.0.0" - }, - "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": ">=5.0.0" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", - "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/unicode-emoji-modifier-base": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", - "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "name": "rolldown-vite", - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.2.5.tgz", - "integrity": "sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/runtime": "0.97.0", - "fdir": "^6.5.0", - "lightningcss": "^1.30.2", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rolldown": "1.0.0-beta.50", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "esbuild": "^0.25.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitefu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", - "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", - "dev": true, - "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*", - "tests/projects/workspace/packages/*" - ], - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/zimmerframe": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", - "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "license": "MIT" - } - } -} diff --git a/ui/package.json b/ui/package.json index 008fcfb..943fddc 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "@dropout/ui", - "version": "0.0.0", + "version": "0.2.0-alpha.1", "private": true, "type": "module", "scripts": { @@ -37,10 +37,5 @@ "tailwindcss": "^4.1.18", "typescript": "~5.9.3", "vite": "npm:rolldown-vite@7.2.5" - }, - "pnpm": { - "overrides": { - "vite": "npm:rolldown-vite@7.2.5" - } } } diff --git a/ui/src/App.svelte b/ui/src/App.svelte index 127bbea..f73e0a2 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -48,6 +48,15 @@ window.addEventListener("mousemove", handleMouseMove); }); + // Refresh versions when active instance changes + $effect(() => { + if (instancesState.activeInstanceId) { + gameState.loadVersions(); + } else { + gameState.versions = []; + } + }); + onDestroy(() => { if (typeof window !== 'undefined') window.removeEventListener("mousemove", handleMouseMove); diff --git a/ui/src/components/InstanceCreationModal.svelte b/ui/src/components/InstanceCreationModal.svelte new file mode 100644 index 0000000..c54cb98 --- /dev/null +++ b/ui/src/components/InstanceCreationModal.svelte @@ -0,0 +1,485 @@ +<script lang="ts"> + import { invoke } from "@tauri-apps/api/core"; + import { X, ChevronLeft, ChevronRight, Loader2, Search } from "lucide-svelte"; + import { instancesState } from "../stores/instances.svelte"; + import { gameState } from "../stores/game.svelte"; + import type { Version, Instance, FabricLoaderEntry, ForgeVersion } from "../types"; + + interface Props { + isOpen: boolean; + onClose: () => void; + } + + let { isOpen, onClose }: Props = $props(); + + // Wizard steps: 1 = Name, 2 = Version, 3 = Mod Loader + let currentStep = $state(1); + let instanceName = $state(""); + let selectedVersion = $state<Version | null>(null); + let modLoaderType = $state<"vanilla" | "fabric" | "forge">("vanilla"); + let selectedFabricLoader = $state(""); + let selectedForgeLoader = $state(""); + let creating = $state(false); + let errorMessage = $state(""); + + // Mod loader lists + let fabricLoaders = $state<FabricLoaderEntry[]>([]); + let forgeVersions = $state<ForgeVersion[]>([]); + let loadingLoaders = $state(false); + + // Version list filtering + let versionSearch = $state(""); + let versionFilter = $state<"all" | "release" | "snapshot">("release"); + + // Filtered versions + let filteredVersions = $derived(() => { + let versions = gameState.versions || []; + + // Filter by type + if (versionFilter !== "all") { + versions = versions.filter((v) => v.type === versionFilter); + } + + // Search filter + if (versionSearch) { + versions = versions.filter((v) => + v.id.toLowerCase().includes(versionSearch.toLowerCase()) + ); + } + + return versions; + }); + + // Fetch mod loaders when entering step 3 + async function loadModLoaders() { + if (!selectedVersion) return; + + loadingLoaders = true; + try { + if (modLoaderType === "fabric") { + const loaders = await invoke<FabricLoaderEntry[]>("get_fabric_loaders_for_version", { + gameVersion: selectedVersion.id, + }); + fabricLoaders = loaders; + if (loaders.length > 0) { + selectedFabricLoader = loaders[0].loader.version; + } + } else if (modLoaderType === "forge") { + const versions = await invoke<ForgeVersion[]>("get_forge_versions_for_game", { + gameVersion: selectedVersion.id, + }); + forgeVersions = versions; + if (versions.length > 0) { + selectedForgeLoader = versions[0].version; + } + } + } catch (err) { + errorMessage = `Failed to load ${modLoaderType} versions: ${err}`; + } finally { + loadingLoaders = false; + } + } + + // Watch for mod loader type changes and load loaders + $effect(() => { + if (currentStep === 3 && modLoaderType !== "vanilla") { + loadModLoaders(); + } + }); + + // Reset modal state + function resetModal() { + currentStep = 1; + instanceName = ""; + selectedVersion = null; + modLoaderType = "vanilla"; + selectedFabricLoader = ""; + selectedForgeLoader = ""; + creating = false; + errorMessage = ""; + versionSearch = ""; + versionFilter = "release"; + } + + function handleClose() { + if (!creating) { + resetModal(); + onClose(); + } + } + + function goToStep(step: number) { + errorMessage = ""; + currentStep = step; + } + + function validateStep1() { + if (!instanceName.trim()) { + errorMessage = "Please enter an instance name"; + return false; + } + return true; + } + + function validateStep2() { + if (!selectedVersion) { + errorMessage = "Please select a Minecraft version"; + return false; + } + return true; + } + + async function handleNext() { + errorMessage = ""; + + if (currentStep === 1) { + if (validateStep1()) { + goToStep(2); + } + } else if (currentStep === 2) { + if (validateStep2()) { + goToStep(3); + } + } + } + + async function handleCreate() { + if (!validateStep1() || !validateStep2()) return; + + creating = true; + errorMessage = ""; + + try { + // Step 1: Create instance + const instance: Instance = await invoke("create_instance", { + name: instanceName.trim(), + }); + + // Step 2: Install vanilla version + await invoke("install_version", { + instanceId: instance.id, + versionId: selectedVersion!.id, + }); + + // Step 3: Install mod loader if selected + if (modLoaderType === "fabric" && selectedFabricLoader) { + await invoke("install_fabric", { + instanceId: instance.id, + gameVersion: selectedVersion!.id, + loaderVersion: selectedFabricLoader, + }); + } else if (modLoaderType === "forge" && selectedForgeLoader) { + await invoke("install_forge", { + instanceId: instance.id, + gameVersion: selectedVersion!.id, + forgeVersion: selectedForgeLoader, + }); + } else { + // Update instance with vanilla version_id + await invoke("update_instance", { + instance: { ...instance, version_id: selectedVersion!.id }, + }); + } + + // Reload instances + await instancesState.loadInstances(); + + // Success! Close modal + resetModal(); + onClose(); + } catch (error) { + errorMessage = String(error); + creating = false; + } + } +</script> + +{#if isOpen} + <div + class="fixed inset-0 z-[100] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4" + role="dialog" + aria-modal="true" + > + <div + class="bg-zinc-900 border border-zinc-700 rounded-xl shadow-2xl w-full max-w-3xl max-h-[90vh] overflow-hidden flex flex-col" + > + <!-- Header --> + <div + class="flex items-center justify-between p-6 border-b border-zinc-700" + > + <div> + <h2 class="text-xl font-bold text-white">Create New Instance</h2> + <p class="text-sm text-zinc-400 mt-1"> + Step {currentStep} of 3 + </p> + </div> + <button + onclick={handleClose} + disabled={creating} + class="p-2 rounded-lg hover:bg-zinc-800 text-zinc-400 hover:text-white transition-colors disabled:opacity-50" + > + <X size={20} /> + </button> + </div> + + <!-- Progress indicator --> + <div class="flex gap-2 px-6 pt-4"> + <div + class="flex-1 h-1 rounded-full transition-colors {currentStep >= 1 + ? 'bg-indigo-500' + : 'bg-zinc-700'}" + ></div> + <div + class="flex-1 h-1 rounded-full transition-colors {currentStep >= 2 + ? 'bg-indigo-500' + : 'bg-zinc-700'}" + ></div> + <div + class="flex-1 h-1 rounded-full transition-colors {currentStep >= 3 + ? 'bg-indigo-500' + : 'bg-zinc-700'}" + ></div> + </div> + + <!-- Content --> + <div class="flex-1 overflow-y-auto p-6"> + {#if currentStep === 1} + <!-- Step 1: Name --> + <div class="space-y-4"> + <div> + <label + for="instance-name" + class="block text-sm font-medium text-white/90 mb-2" + >Instance Name</label + > + <input + id="instance-name" + type="text" + bind:value={instanceName} + placeholder="My Minecraft Instance" + class="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-indigo-500" + disabled={creating} + /> + </div> + <p class="text-xs text-zinc-400"> + Give your instance a memorable name + </p> + </div> + {:else if currentStep === 2} + <!-- Step 2: Version Selection --> + <div class="space-y-4"> + <div class="flex gap-4"> + <div class="flex-1 relative"> + <Search + size={16} + class="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500" + /> + <input + type="text" + bind:value={versionSearch} + placeholder="Search versions..." + class="w-full pl-10 pr-4 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-indigo-500" + /> + </div> + <div class="flex gap-2"> + {#each [ + { value: "all", label: "All" }, + { value: "release", label: "Release" }, + { value: "snapshot", label: "Snapshot" }, + ] as filter} + <button + onclick={() => { + versionFilter = filter.value as "all" | "release" | "snapshot"; + }} + class="px-4 py-2 rounded-lg text-sm font-medium transition-colors {versionFilter === + filter.value + ? 'bg-indigo-600 text-white' + : 'bg-zinc-800 text-zinc-400 hover:text-white'}" + > + {filter.label} + </button> + {/each} + </div> + </div> + + <div class="max-h-96 overflow-y-auto space-y-2"> + {#each filteredVersions() as version} + <button + onclick={() => (selectedVersion = version)} + class="w-full p-3 rounded-lg border transition-colors text-left {selectedVersion?.id === + version.id + ? 'bg-indigo-600/20 border-indigo-500 text-white' + : 'bg-zinc-800 border-zinc-700 text-zinc-300 hover:border-zinc-600'}" + > + <div class="flex items-center justify-between"> + <span class="font-medium">{version.id}</span> + <span + class="text-xs px-2 py-1 rounded-full {version.type === + 'release' + ? 'bg-green-500/20 text-green-400' + : 'bg-yellow-500/20 text-yellow-400'}" + > + {version.type} + </span> + </div> + </button> + {/each} + + {#if filteredVersions().length === 0} + <div class="text-center py-8 text-zinc-500"> + No versions found + </div> + {/if} + </div> + </div> + {:else if currentStep === 3} + <!-- Step 3: Mod Loader --> + <div class="space-y-4"> + <div> + <div class="text-sm font-medium text-white/90 mb-3"> + Mod Loader Type + </div> + <div class="flex gap-3"> + {#each [ + { value: "vanilla", label: "Vanilla" }, + { value: "fabric", label: "Fabric" }, + { value: "forge", label: "Forge" }, + ] as loader} + <button + onclick={() => { + modLoaderType = loader.value as "vanilla" | "fabric" | "forge"; + }} + class="flex-1 px-4 py-3 rounded-lg text-sm font-medium transition-colors {modLoaderType === + loader.value + ? 'bg-indigo-600 text-white' + : 'bg-zinc-800 text-zinc-400 hover:text-white'}" + > + {loader.label} + </button> + {/each} + </div> + </div> + + {#if modLoaderType === "fabric"} + <div> + <label for="fabric-loader" class="block text-sm font-medium text-white/90 mb-2"> + Fabric Loader Version + </label> + {#if loadingLoaders} + <div class="flex items-center gap-2 text-zinc-400"> + <Loader2 size={16} class="animate-spin" /> + Loading Fabric versions... + </div> + {:else if fabricLoaders.length > 0} + <select + id="fabric-loader" + bind:value={selectedFabricLoader} + class="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500" + > + {#each fabricLoaders as loader} + <option value={loader.loader.version}> + {loader.loader.version} {loader.loader.stable ? "(Stable)" : "(Beta)"} + </option> + {/each} + </select> + {:else} + <p class="text-sm text-red-400">No Fabric loaders available for this version</p> + {/if} + </div> + {:else if modLoaderType === "forge"} + <div> + <label for="forge-version" class="block text-sm font-medium text-white/90 mb-2"> + Forge Version + </label> + {#if loadingLoaders} + <div class="flex items-center gap-2 text-zinc-400"> + <Loader2 size={16} class="animate-spin" /> + Loading Forge versions... + </div> + {:else if forgeVersions.length > 0} + <select + id="forge-version" + bind:value={selectedForgeLoader} + class="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500" + > + {#each forgeVersions as version} + <option value={version.version}> + {version.version} + </option> + {/each} + </select> + {:else} + <p class="text-sm text-red-400">No Forge versions available for this version</p> + {/if} + </div> + {:else if modLoaderType === "vanilla"} + <p class="text-sm text-zinc-400"> + Create a vanilla Minecraft instance without any mod loaders + </p> + {/if} + </div> + {/if} + + {#if errorMessage} + <div + class="mt-4 p-3 bg-red-500/10 border border-red-500/30 rounded-lg text-red-400 text-sm" + > + {errorMessage} + </div> + {/if} + </div> + + <!-- Footer --> + <div + class="flex items-center justify-between gap-3 p-6 border-t border-zinc-700" + > + <button + onclick={() => goToStep(currentStep - 1)} + disabled={currentStep === 1 || creating} + class="px-4 py-2 rounded-lg bg-zinc-800 hover:bg-zinc-700 text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2" + > + <ChevronLeft size={16} /> + Back + </button> + + <div class="flex gap-3"> + <button + onclick={handleClose} + disabled={creating} + class="px-4 py-2 rounded-lg bg-zinc-800 hover:bg-zinc-700 text-white transition-colors disabled:opacity-50" + > + Cancel + </button> + + {#if currentStep < 3} + <button + onclick={handleNext} + disabled={creating} + class="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-white transition-colors disabled:opacity-50 flex items-center gap-2" + > + Next + <ChevronRight size={16} /> + </button> + {:else} + <button + onclick={handleCreate} + disabled={creating || + !instanceName.trim() || + !selectedVersion || + (modLoaderType === "fabric" && !selectedFabricLoader) || + (modLoaderType === "forge" && !selectedForgeLoader)} + class="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-white transition-colors disabled:opacity-50 flex items-center gap-2" + > + {#if creating} + <Loader2 size={16} class="animate-spin" /> + Creating... + {:else} + Create Instance + {/if} + </button> + {/if} + </div> + </div> + </div> + </div> +{/if} diff --git a/ui/src/components/InstanceEditorModal.svelte b/ui/src/components/InstanceEditorModal.svelte new file mode 100644 index 0000000..0856d93 --- /dev/null +++ b/ui/src/components/InstanceEditorModal.svelte @@ -0,0 +1,439 @@ +<script lang="ts"> + import { invoke } from "@tauri-apps/api/core"; + import { X, Save, Loader2, Trash2, FolderOpen } from "lucide-svelte"; + import { instancesState } from "../stores/instances.svelte"; + import { gameState } from "../stores/game.svelte"; + import { settingsState } from "../stores/settings.svelte"; + import type { Instance, FileInfo, FabricLoaderEntry, ForgeVersion } from "../types"; + import ModLoaderSelector from "./ModLoaderSelector.svelte"; + + interface Props { + isOpen: boolean; + instance: Instance | null; + onClose: () => void; + } + + let { isOpen, instance, onClose }: Props = $props(); + + // Tabs: "info" | "version" | "files" | "settings" + let activeTab = $state<"info" | "version" | "files" | "settings">("info"); + let saving = $state(false); + let errorMessage = $state(""); + + // Info tab state + let editName = $state(""); + let editNotes = $state(""); + + // Version tab state + let fabricLoaders = $state<FabricLoaderEntry[]>([]); + let forgeVersions = $state<ForgeVersion[]>([]); + let loadingVersions = $state(false); + + // Files tab state + let selectedFileFolder = $state<"mods" | "resourcepacks" | "shaderpacks" | "saves" | "screenshots">("mods"); + let fileList = $state<FileInfo[]>([]); + let loadingFiles = $state(false); + let deletingPath = $state<string | null>(null); + + // Settings tab state + let editMemoryMin = $state(0); + let editMemoryMax = $state(0); + let editJavaArgs = $state(""); + + // Initialize form when instance changes + $effect(() => { + if (isOpen && instance) { + editName = instance.name; + editNotes = instance.notes || ""; + editMemoryMin = instance.memory_override?.min || settingsState.settings.min_memory || 512; + editMemoryMax = instance.memory_override?.max || settingsState.settings.max_memory || 2048; + editJavaArgs = instance.jvm_args_override || ""; + errorMessage = ""; + } + }); + + // Load files when switching to files tab + $effect(() => { + if (isOpen && instance && activeTab === "files") { + loadFileList(); + } + }); + + // Load file list for selected folder + async function loadFileList() { + if (!instance) return; + + loadingFiles = true; + try { + const files = await invoke<FileInfo[]>("list_instance_directory", { + instanceId: instance.id, + folder: selectedFileFolder, + }); + fileList = files; + } catch (err) { + errorMessage = `Failed to load files: ${err}`; + fileList = []; + } finally { + loadingFiles = false; + } + } + + // Change selected folder and reload + async function changeFolder(folder: "mods" | "resourcepacks" | "shaderpacks" | "saves" | "screenshots") { + selectedFileFolder = folder; + await loadFileList(); + } + + // Delete a file or directory + async function deleteFile(filePath: string) { + if (!confirm(`Are you sure you want to delete "${filePath.split("/").pop()}"?`)) { + return; + } + + deletingPath = filePath; + try { + await invoke("delete_instance_file", { path: filePath }); + // Reload file list + await loadFileList(); + } catch (err) { + errorMessage = `Failed to delete file: ${err}`; + } finally { + deletingPath = null; + } + } + + // Open file in system explorer + async function openInExplorer(filePath: string) { + try { + await invoke("open_file_explorer", { path: filePath }); + } catch (err) { + errorMessage = `Failed to open file explorer: ${err}`; + } + } + + // Save instance changes + async function saveChanges() { + if (!instance) return; + if (!editName.trim()) { + errorMessage = "Instance name cannot be empty"; + return; + } + + saving = true; + errorMessage = ""; + + try { + const updatedInstance: Instance = { + ...instance, + name: editName.trim(), + notes: editNotes.trim() || undefined, + memory_override: { + min: editMemoryMin, + max: editMemoryMax, + }, + jvm_args_override: editJavaArgs.trim() || undefined, + }; + + await instancesState.updateInstance(updatedInstance); + onClose(); + } catch (err) { + errorMessage = `Failed to save instance: ${err}`; + } finally { + saving = false; + } + } + + function formatFileSize(bytes: number): string { + if (bytes === 0) return "0 B"; + const k = 1024; + const sizes = ["B", "KB", "MB", "GB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i]; + } + + function formatDate(timestamp: number): string { + return new Date(timestamp * 1000).toLocaleDateString(); + } +</script> + +{#if isOpen && instance} + <div + class="fixed inset-0 z-[100] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4" + role="dialog" + aria-modal="true" + > + <div + class="bg-zinc-900 border border-zinc-700 rounded-xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col" + > + <!-- Header --> + <div class="flex items-center justify-between p-6 border-b border-zinc-700"> + <div> + <h2 class="text-xl font-bold text-white">Edit Instance</h2> + <p class="text-sm text-zinc-400 mt-1">{instance.name}</p> + </div> + <button + onclick={onClose} + disabled={saving} + class="p-2 rounded-lg hover:bg-zinc-800 text-zinc-400 hover:text-white transition-colors disabled:opacity-50" + > + <X size={20} /> + </button> + </div> + + <!-- Tab Navigation --> + <div class="flex gap-1 px-6 pt-4 border-b border-zinc-700"> + {#each [ + { id: "info", label: "Info" }, + { id: "version", label: "Version" }, + { id: "files", label: "Files" }, + { id: "settings", label: "Settings" }, + ] as tab} + <button + onclick={() => (activeTab = tab.id as any)} + class="px-4 py-2 text-sm font-medium transition-colors rounded-t-lg {activeTab === tab.id + ? 'bg-indigo-600 text-white' + : 'bg-zinc-800 text-zinc-400 hover:text-white'}" + > + {tab.label} + </button> + {/each} + </div> + + <!-- Content Area --> + <div class="flex-1 overflow-y-auto p-6"> + {#if activeTab === "info"} + <!-- Info Tab --> + <div class="space-y-4"> + <div> + <label for="instance-name" class="block text-sm font-medium text-white/90 mb-2"> + Instance Name + </label> + <input + id="instance-name" + type="text" + bind:value={editName} + class="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-indigo-500" + disabled={saving} + /> + </div> + + <div> + <label for="instance-notes" class="block text-sm font-medium text-white/90 mb-2"> + Notes + </label> + <textarea + id="instance-notes" + bind:value={editNotes} + rows="4" + placeholder="Add notes about this instance..." + class="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 resize-none" + disabled={saving} + ></textarea> + </div> + + <div class="grid grid-cols-2 gap-4 text-sm"> + <div class="p-3 bg-zinc-800 rounded-lg"> + <p class="text-zinc-400">Created</p> + <p class="text-white font-medium">{formatDate(instance.created_at)}</p> + </div> + <div class="p-3 bg-zinc-800 rounded-lg"> + <p class="text-zinc-400">Last Played</p> + <p class="text-white font-medium"> + {instance.last_played ? formatDate(instance.last_played) : "Never"} + </p> + </div> + <div class="p-3 bg-zinc-800 rounded-lg"> + <p class="text-zinc-400">Game Directory</p> + <p class="text-white font-medium text-xs truncate" title={instance.game_dir}> + {instance.game_dir.split("/").pop()} + </p> + </div> + <div class="p-3 bg-zinc-800 rounded-lg"> + <p class="text-zinc-400">Current Version</p> + <p class="text-white font-medium">{instance.version_id || "None"}</p> + </div> + </div> + </div> + {:else if activeTab === "version"} + <!-- Version Tab --> + <div class="space-y-4"> + {#if instance.version_id} + <div class="p-4 bg-indigo-500/10 border border-indigo-500/30 rounded-lg"> + <p class="text-sm text-indigo-400"> + Currently playing: <span class="font-medium">{instance.version_id}</span> + {#if instance.mod_loader} + with <span class="capitalize">{instance.mod_loader}</span> + {instance.mod_loader_version && `${instance.mod_loader_version}`} + {/if} + </p> + </div> + {/if} + + <div> + <h3 class="text-sm font-medium text-white/90 mb-4">Change Version or Mod Loader</h3> + <ModLoaderSelector + selectedGameVersion={instance.version_id || ""} + onInstall={(versionId) => { + // Version installed, update instance version_id + instance.version_id = versionId; + saveChanges(); + }} + /> + </div> + </div> + {:else if activeTab === "files"} + <!-- Files Tab --> + <div class="space-y-4"> + <div class="flex gap-2 flex-wrap"> + {#each ["mods", "resourcepacks", "shaderpacks", "saves", "screenshots"] as folder} + <button + onclick={() => changeFolder(folder as any)} + class="px-3 py-1.5 rounded-lg text-sm font-medium transition-colors {selectedFileFolder === + folder + ? "bg-indigo-600 text-white" + : "bg-zinc-800 text-zinc-400 hover:text-white"}" + > + {folder} + </button> + {/each} + </div> + + {#if loadingFiles} + <div class="flex items-center gap-2 text-zinc-400 py-8 justify-center"> + <Loader2 size={16} class="animate-spin" /> + Loading files... + </div> + {:else if fileList.length === 0} + <div class="text-center py-8 text-zinc-500"> + No files in this folder + </div> + {:else} + <div class="space-y-2"> + {#each fileList as file} + <div + class="flex items-center justify-between p-3 bg-zinc-800 rounded-lg hover:bg-zinc-700 transition-colors" + > + <div class="flex-1 min-w-0"> + <p class="font-medium text-white truncate">{file.name}</p> + <p class="text-xs text-zinc-400"> + {file.is_directory ? "Folder" : formatFileSize(file.size)} + • {formatDate(file.modified)} + </p> + </div> + <div class="flex gap-2 ml-4"> + <button + onclick={() => openInExplorer(file.path)} + title="Open in explorer" + class="p-2 rounded-lg hover:bg-zinc-600 text-zinc-400 hover:text-white transition-colors" + > + <FolderOpen size={16} /> + </button> + <button + onclick={() => deleteFile(file.path)} + disabled={deletingPath === file.path} + title="Delete" + class="p-2 rounded-lg hover:bg-red-600/20 text-red-400 hover:text-red-300 transition-colors disabled:opacity-50" + > + {#if deletingPath === file.path} + <Loader2 size={16} class="animate-spin" /> + {:else} + <Trash2 size={16} /> + {/if} + </button> + </div> + </div> + {/each} + </div> + {/if} + </div> + {:else if activeTab === "settings"} + <!-- Settings Tab --> + <div class="space-y-4"> + <div> + <label for="min-memory" class="block text-sm font-medium text-white/90 mb-2"> + Minimum Memory (MB) + </label> + <input + id="min-memory" + type="number" + bind:value={editMemoryMin} + min="256" + max={editMemoryMax} + class="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500" + disabled={saving} + /> + <p class="text-xs text-zinc-400 mt-1"> + Default: {settingsState.settings.min_memory}MB + </p> + </div> + + <div> + <label for="max-memory" class="block text-sm font-medium text-white/90 mb-2"> + Maximum Memory (MB) + </label> + <input + id="max-memory" + type="number" + bind:value={editMemoryMax} + min={editMemoryMin} + max="16384" + class="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500" + disabled={saving} + /> + <p class="text-xs text-zinc-400 mt-1"> + Default: {settingsState.settings.max_memory}MB + </p> + </div> + + <div> + <label for="java-args" class="block text-sm font-medium text-white/90 mb-2"> + JVM Arguments (Advanced) + </label> + <textarea + id="java-args" + bind:value={editJavaArgs} + rows="4" + placeholder="-XX:+UnlockExperimentalVMOptions -XX:G1NewCollectionPercentage=20..." + class="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 font-mono text-sm resize-none" + disabled={saving} + ></textarea> + <p class="text-xs text-zinc-400 mt-1"> + Leave empty to use global Java arguments + </p> + </div> + </div> + {/if} + + {#if errorMessage} + <div class="mt-4 p-3 bg-red-500/10 border border-red-500/30 rounded-lg text-red-400 text-sm"> + {errorMessage} + </div> + {/if} + </div> + + <!-- Footer --> + <div class="flex items-center justify-end gap-3 p-6 border-t border-zinc-700"> + <button + onclick={onClose} + disabled={saving} + class="px-4 py-2 rounded-lg bg-zinc-800 hover:bg-zinc-700 text-white transition-colors disabled:opacity-50" + > + Cancel + </button> + <button + onclick={saveChanges} + disabled={saving || !editName.trim()} + class="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-white transition-colors disabled:opacity-50 flex items-center gap-2" + > + {#if saving} + <Loader2 size={16} class="animate-spin" /> + Saving... + {:else} + <Save size={16} /> + Save Changes + {/if} + </button> + </div> + </div> + </div> +{/if} diff --git a/ui/src/components/InstancesView.svelte b/ui/src/components/InstancesView.svelte index a4881e6..5334f9e 100644 --- a/ui/src/components/InstancesView.svelte +++ b/ui/src/components/InstancesView.svelte @@ -3,12 +3,15 @@ import { instancesState } from "../stores/instances.svelte"; import { Plus, Trash2, Edit2, Copy, Check, X } from "lucide-svelte"; import type { Instance } from "../types"; + import InstanceCreationModal from "./InstanceCreationModal.svelte"; + import InstanceEditorModal from "./InstanceEditorModal.svelte"; let showCreateModal = $state(false); let showEditModal = $state(false); let showDeleteConfirm = $state(false); let showDuplicateModal = $state(false); let selectedInstance: Instance | null = $state(null); + let editingInstance: Instance | null = $state(null); let newInstanceName = $state(""); let duplicateName = $state(""); @@ -17,14 +20,11 @@ }); function handleCreate() { - newInstanceName = ""; showCreateModal = true; } function handleEdit(instance: Instance) { - selectedInstance = instance; - newInstanceName = instance.name; - showEditModal = true; + editingInstance = instance; } function handleDelete(instance: Instance) { @@ -38,24 +38,6 @@ showDuplicateModal = true; } - async function confirmCreate() { - if (!newInstanceName.trim()) return; - await instancesState.createInstance(newInstanceName.trim()); - showCreateModal = false; - newInstanceName = ""; - } - - async function confirmEdit() { - if (!selectedInstance || !newInstanceName.trim()) return; - await instancesState.updateInstance({ - ...selectedInstance, - name: newInstanceName.trim(), - }); - showEditModal = false; - selectedInstance = null; - newInstanceName = ""; - } - async function confirmDelete() { if (!selectedInstance) return; await instancesState.deleteInstance(selectedInstance.id); @@ -111,10 +93,13 @@ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> {#each instancesState.instances as instance (instance.id)} <div + role="button" + tabindex="0" class="relative p-4 bg-gray-100 dark:bg-gray-800 rounded-lg border-2 transition-all cursor-pointer hover:border-blue-500 {instancesState.activeInstanceId === instance.id ? 'border-blue-500' : 'border-transparent'}" onclick={() => instancesState.setActiveInstance(instance.id)} + onkeydown={(e) => e.key === "Enter" && instancesState.setActiveInstance(instance.id)} > {#if instancesState.activeInstanceId === instance.id} <div class="absolute top-2 right-2"> @@ -128,6 +113,7 @@ </h3> <div class="flex gap-1"> <button + type="button" onclick={(e) => { e.stopPropagation(); handleEdit(instance); @@ -138,6 +124,7 @@ <Edit2 size={16} class="text-gray-600 dark:text-gray-400" /> </button> <button + type="button" onclick={(e) => { e.stopPropagation(); handleDuplicate(instance); @@ -148,6 +135,7 @@ <Copy size={16} class="text-gray-600 dark:text-gray-400" /> </button> <button + type="button" onclick={(e) => { e.stopPropagation(); handleDelete(instance); @@ -195,75 +183,16 @@ </div> <!-- Create Modal --> -{#if showCreateModal} - <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> - <div class="bg-white dark:bg-gray-800 rounded-lg p-6 w-96"> - <h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-white">Create Instance</h2> - <input - type="text" - bind:value={newInstanceName} - placeholder="Instance name" - class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white mb-4" - onkeydown={(e) => e.key === "Enter" && confirmCreate()} - autofocus - /> - <div class="flex gap-2 justify-end"> - <button - onclick={() => { - showCreateModal = false; - newInstanceName = ""; - }} - class="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-white rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors" - > - Cancel - </button> - <button - onclick={confirmCreate} - disabled={!newInstanceName.trim()} - class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" - > - Create - </button> - </div> - </div> - </div> -{/if} +<InstanceCreationModal isOpen={showCreateModal} onClose={() => (showCreateModal = false)} /> -<!-- Edit Modal --> -{#if showEditModal && selectedInstance} - <div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> - <div class="bg-white dark:bg-gray-800 rounded-lg p-6 w-96"> - <h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-white">Edit Instance</h2> - <input - type="text" - bind:value={newInstanceName} - placeholder="Instance name" - class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white mb-4" - onkeydown={(e) => e.key === "Enter" && confirmEdit()} - autofocus - /> - <div class="flex gap-2 justify-end"> - <button - onclick={() => { - showEditModal = false; - selectedInstance = null; - newInstanceName = ""; - }} - class="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-white rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors" - > - Cancel - </button> - <button - onclick={confirmEdit} - disabled={!newInstanceName.trim()} - class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" - > - Save - </button> - </div> - </div> - </div> -{/if} +<!-- Instance Editor Modal --> +<InstanceEditorModal + isOpen={editingInstance !== null} + instance={editingInstance} + onClose={() => { + editingInstance = null; + }} +/> <!-- Delete Confirmation --> {#if showDeleteConfirm && selectedInstance} @@ -305,7 +234,6 @@ placeholder="New instance name" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white mb-4" onkeydown={(e) => e.key === "Enter" && confirmDuplicate()} - autofocus /> <div class="flex gap-2 justify-end"> <button diff --git a/ui/src/components/SettingsView.svelte b/ui/src/components/SettingsView.svelte index 4de18b3..0020506 100644 --- a/ui/src/components/SettingsView.svelte +++ b/ui/src/components/SettingsView.svelte @@ -123,6 +123,34 @@ settingsState.settings.custom_background_path = undefined; settingsState.saveSettings(); } + + let migrating = $state(false); + async function runMigrationToSharedCaches() { + if (migrating) return; + migrating = true; + try { + const { invoke } = await import("@tauri-apps/api/core"); + const result = await invoke<{ + moved_files: number; + hardlinks: number; + copies: number; + saved_mb: number; + }>("migrate_shared_caches"); + + // Reload settings to reflect changes + await settingsState.loadSettings(); + + // Show success message + const msg = `Migration complete! ${result.moved_files} files (${result.hardlinks} hardlinks, ${result.copies} copies), ${result.saved_mb.toFixed(2)} MB saved.`; + console.log(msg); + alert(msg); + } catch (e) { + console.error("Migration failed:", e); + alert(`Migration failed: ${e}`); + } finally { + migrating = false; + } + } </script> <div class="h-full flex flex-col p-6 overflow-hidden"> @@ -398,6 +426,124 @@ </div> </div> + <!-- Storage & Caches --> + <div class="dark:bg-[#09090b] bg-white p-6 rounded-sm border dark:border-white/10 border-gray-200 shadow-sm"> + <h3 class="text-xs font-bold uppercase tracking-widest text-white/40 mb-6 flex items-center gap-2">Storage & Version Caches</h3> + <div class="space-y-4"> + <div class="flex items-center justify-between"> + <div> + <h4 class="text-sm font-medium text-white/90" id="shared-caches-label">Use Shared Caches</h4> + <p class="text-xs text-white/40 mt-1">Store versions/libraries/assets in a global cache shared by all instances.</p> + </div> + <button + aria-labelledby="shared-caches-label" + onclick={() => { settingsState.settings.use_shared_caches = !settingsState.settings.use_shared_caches; settingsState.saveSettings(); }} + class="w-11 h-6 rounded-full transition-colors duration-200 ease-in-out relative focus:outline-none {settingsState.settings.use_shared_caches ? 'bg-indigo-500' : 'bg-white/10'}" + > + <div class="absolute top-1 left-1 bg-white w-4 h-4 rounded-full shadow-sm transition-transform duration-200 ease-in-out {settingsState.settings.use_shared_caches ? 'translate-x-5' : 'translate-x-0'}"></div> + </button> + </div> + + <div class="flex items-center justify-between"> + <div> + <h4 class="text-sm font-medium text-white/90" id="legacy-storage-label">Keep Legacy Per-Instance Storage</h4> + <p class="text-xs text-white/40 mt-1">Do not migrate existing instance caches; keep current layout.</p> + </div> + <button + aria-labelledby="legacy-storage-label" + onclick={() => { settingsState.settings.keep_legacy_per_instance_storage = !settingsState.settings.keep_legacy_per_instance_storage; settingsState.saveSettings(); }} + class="w-11 h-6 rounded-full transition-colors duration-200 ease-in-out relative focus:outline-none {settingsState.settings.keep_legacy_per_instance_storage ? 'bg-indigo-500' : 'bg-white/10'}" + > + <div class="absolute top-1 left-1 bg-white w-4 h-4 rounded-full shadow-sm transition-transform duration-200 ease-in-out {settingsState.settings.keep_legacy_per_instance_storage ? 'translate-x-5' : 'translate-x-0'}"></div> + </button> + </div> + + <div class="flex items-center justify-between pt-2 border-t border-white/10"> + <div> + <h4 class="text-sm font-medium text-white/90">Run Migration</h4> + <p class="text-xs text-white/40 mt-1">Hard-link or copy existing per-instance caches into the shared cache.</p> + </div> + <button + onclick={runMigrationToSharedCaches} + disabled={migrating} + class="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-white text-sm disabled:opacity-50 disabled:cursor-not-allowed" + > + {migrating ? "Migrating..." : "Migrate Now"} + </button> + </div> + </div> + </div> + + <!-- Feature Flags --> + <div class="dark:bg-[#09090b] bg-white p-6 rounded-sm border dark:border-white/10 border-gray-200 shadow-sm"> + <h3 class="text-xs font-bold uppercase tracking-widest text-white/40 mb-6 flex items-center gap-2">Feature Flags (Launcher Arguments)</h3> + <div class="space-y-4"> + <div class="flex items-center justify-between"> + <div> + <h4 class="text-sm font-medium text-white/90" id="demo-user-label">Demo User</h4> + <p class="text-xs text-white/40 mt-1">Enable demo-related arguments when rules require them.</p> + </div> + <button + aria-labelledby="demo-user-label" + onclick={() => { settingsState.settings.feature_flags.demo_user = !settingsState.settings.feature_flags.demo_user; settingsState.saveSettings(); }} + class="w-11 h-6 rounded-full transition-colors duration-200 ease-in-out relative focus:outline-none {settingsState.settings.feature_flags.demo_user ? 'bg-indigo-500' : 'bg-white/10'}" + > + <div class="absolute top-1 left-1 bg-white w-4 h-4 rounded-full shadow-sm transition-transform duration-200 ease-in-out {settingsState.settings.feature_flags.demo_user ? 'translate-x-5' : 'translate-x-0'}"></div> + </button> + </div> + + <div class="flex items-center justify-between"> + <div> + <h4 class="text-sm font-medium text-white/90" id="quick-play-label">Quick Play</h4> + <p class="text-xs text-white/40 mt-1">Enable quick play singleplayer/multiplayer arguments.</p> + </div> + <button + aria-labelledby="quick-play-label" + onclick={() => { settingsState.settings.feature_flags.quick_play_enabled = !settingsState.settings.feature_flags.quick_play_enabled; settingsState.saveSettings(); }} + class="w-11 h-6 rounded-full transition-colors duration-200 ease-in-out relative focus:outline-none {settingsState.settings.feature_flags.quick_play_enabled ? 'bg-indigo-500' : 'bg-white/10'}" + > + <div class="absolute top-1 left-1 bg-white w-4 h-4 rounded-full shadow-sm transition-transform duration-200 ease-in-out {settingsState.settings.feature_flags.quick_play_enabled ? 'translate-x-5' : 'translate-x-0'}"></div> + </button> + </div> + + {#if settingsState.settings.feature_flags.quick_play_enabled} + <div class="grid grid-cols-1 md:grid-cols-2 gap-4 pl-2 border-l-2 border-white/10"> + <div> + <label class="block text-sm font-medium text-white/70 mb-2">Singleplayer World Path</label> + <input + type="text" + bind:value={settingsState.settings.feature_flags.quick_play_path} + placeholder="/path/to/saves/MyWorld" + class="bg-black/40 text-white w-full px-4 py-3 rounded-xl border border-white/10 focus:border-indigo-500/50 outline-none font-mono text-xs transition-colors" + /> + </div> + <div class="flex items-center justify-between"> + <div> + <h4 class="text-sm font-medium text-white/90" id="qp-singleplayer-label">Prefer Singleplayer</h4> + <p class="text-xs text-white/40 mt-1">If enabled, use singleplayer quick play path.</p> + </div> + <button + aria-labelledby="qp-singleplayer-label" + onclick={() => { settingsState.settings.feature_flags.quick_play_singleplayer = !settingsState.settings.feature_flags.quick_play_singleplayer; settingsState.saveSettings(); }} + class="w-11 h-6 rounded-full transition-colors duration-200 ease-in-out relative focus:outline-none {settingsState.settings.feature_flags.quick_play_singleplayer ? 'bg-indigo-500' : 'bg-white/10'}" + > + <div class="absolute top-1 left-1 bg-white w-4 h-4 rounded-full shadow-sm transition-transform duration-200 ease-in-out {settingsState.settings.feature_flags.quick_play_singleplayer ? 'translate-x-5' : 'translate-x-0'}"></div> + </button> + </div> + <div> + <label class="block text-sm font-medium text-white/70 mb-2">Multiplayer Server Address</label> + <input + type="text" + bind:value={settingsState.settings.feature_flags.quick_play_multiplayer_server} + placeholder="example.org:25565" + class="bg-black/40 text-white w-full px-4 py-3 rounded-xl border border-white/10 focus:border-indigo-500/50 outline-none font-mono text-xs transition-colors" + /> + </div> + </div> + {/if} + </div> + </div> + <!-- Debug / Logs --> <div class="dark:bg-[#09090b] bg-white p-6 rounded-sm border dark:border-white/10 border-gray-200 shadow-sm"> <h3 class="text-xs font-bold uppercase tracking-widest text-white/40 mb-6 flex items-center gap-2"> diff --git a/ui/src/components/VersionsView.svelte b/ui/src/components/VersionsView.svelte index d4d36d5..f1474d9 100644 --- a/ui/src/components/VersionsView.svelte +++ b/ui/src/components/VersionsView.svelte @@ -217,7 +217,10 @@ if (!versionToDelete) return; try { - await invoke("delete_version", { versionId: versionToDelete }); + await invoke("delete_version", { + instanceId: instancesState.activeInstanceId, + versionId: versionToDelete + }); // Clear selection if deleted version was selected if (gameState.selectedVersion === versionToDelete) { gameState.selectedVersion = ""; @@ -253,6 +256,7 @@ isLoadingMetadata = true; try { const metadata = await invoke<VersionMetadata>("get_version_metadata", { + instanceId: instancesState.activeInstanceId, versionId, }); selectedVersionMetadata = metadata; diff --git a/ui/src/stores/game.svelte.ts b/ui/src/stores/game.svelte.ts index 1e4119f..504d108 100644 --- a/ui/src/stores/game.svelte.ts +++ b/ui/src/stores/game.svelte.ts @@ -8,13 +8,26 @@ export class GameState { versions = $state<Version[]>([]); selectedVersion = $state(""); + constructor() { + // Constructor intentionally empty + // Instance switching handled in App.svelte with $effect + } + get latestRelease() { return this.versions.find((v) => v.type === "release"); } - async loadVersions() { + async loadVersions(instanceId?: string) { + const id = instanceId || instancesState.activeInstanceId; + if (!id) { + this.versions = []; + return; + } + try { - this.versions = await invoke<Version[]>("get_versions"); + this.versions = await invoke<Version[]>("get_versions", { + instanceId: id, + }); // Don't auto-select version here - let BottomBar handle version selection // based on installed versions only } catch (e) { diff --git a/ui/src/stores/settings.svelte.ts b/ui/src/stores/settings.svelte.ts index 8a90736..5d20050 100644 --- a/ui/src/stores/settings.svelte.ts +++ b/ui/src/stores/settings.svelte.ts @@ -42,6 +42,15 @@ export class SettingsState { tts_enabled: false, tts_provider: "disabled", }, + use_shared_caches: false, + keep_legacy_per_instance_storage: true, + feature_flags: { + demo_user: false, + quick_play_enabled: false, + quick_play_path: undefined, + quick_play_singleplayer: true, + quick_play_multiplayer_server: undefined, + }, }); // Convert background path to proper asset URL diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index a5b336e..b4412b8 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -68,6 +68,19 @@ export interface LauncherConfig { log_upload_service: "paste.rs" | "pastebin.com"; pastebin_api_key?: string; assistant: AssistantConfig; + // Storage management + use_shared_caches: boolean; + keep_legacy_per_instance_storage: boolean; + // Feature-gated argument flags + feature_flags: FeatureFlags; +} + +export interface FeatureFlags { + demo_user: boolean; + quick_play_enabled: boolean; + quick_play_path?: string; + quick_play_singleplayer: boolean; + quick_play_multiplayer_server?: string; } export interface JavaInstallation { @@ -201,4 +214,19 @@ export interface Instance { notes?: string; mod_loader?: string; mod_loader_version?: string; + jvm_args_override?: string; + memory_override?: MemoryOverride; +} + +export interface MemoryOverride { + min: number; // MB + max: number; // MB +} + +export interface FileInfo { + name: string; + path: string; + is_directory: boolean; + size: number; + modified: number; } |