Compare commits
45 Commits
3.1.0
..
24802b5132
| Author | SHA1 | Date | |
|---|---|---|---|
| 24802b5132 | |||
| f09405ed58 | |||
| eeda85f48f | |||
| 9770e81d6f | |||
| 31b7d17b09 | |||
| 1fc0391113 | |||
| 62f03cf7c0 | |||
| 94f6e850cf | |||
| d6fdb2a152 | |||
| 9cd990cf96 | |||
| 2e0ef81bcb | |||
| 96680ff8b5 | |||
| 0b9d6373bf | |||
| 295c1424c9 | |||
| 0e12a801d2 | |||
| cf86dba5c4 | |||
| 540c653399 | |||
| c9b8d9f599 | |||
| 49665d94bc | |||
| 1a19c19399 | |||
| cbfe5ec868 | |||
| c5b18dacbb | |||
| ef01984875 | |||
| 46ee36ce81 | |||
| c4658ae4cc | |||
| ca15ff566f | |||
| a6fdda4487 | |||
| a30946eb60 | |||
| 9106ad89b3 | |||
| 194709df0c | |||
| 20f625be60 | |||
| 66a5fd84ae | |||
| 44d7401947 | |||
| f973e48561 | |||
| de186b7ff9 | |||
| 244d526933 | |||
| c7a990e3fe | |||
| ed1f741a9e | |||
| b6616a37f4 | |||
| 63433ac626 | |||
| 149b4715d7 | |||
| ae3443d0b9 | |||
| abc7b9d9d9 | |||
| d9dd21d3be | |||
| 0df31178f4 |
@@ -10,16 +10,20 @@
|
|||||||
"@number-flow/svelte": "^0.3.9",
|
"@number-flow/svelte": "^0.3.9",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"@tauri-apps/api": "^2.9.1",
|
"@tauri-apps/api": "^2.10.1",
|
||||||
"@tauri-apps/plugin-dialog": "^2.4.2",
|
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||||
"@tauri-apps/plugin-fs": "^2.4.4",
|
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||||
"@tauri-apps/plugin-shell": "^2.3.3",
|
"@tauri-apps/plugin-shell": "^2.3.5",
|
||||||
"@types/umami": "^2.10.1",
|
"@types/umami": "^2.10.1",
|
||||||
"animejs": "^4.1.3",
|
"animejs": "^4.1.3",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"lucide-svelte": "^0.562.0",
|
"lucide-svelte": "^0.575.0",
|
||||||
|
"react": "^19.2.4",
|
||||||
|
"react-dom": "^19.2.4",
|
||||||
|
"react-to-webcomponent": "^2.0.1",
|
||||||
"semver": "^7.7.2",
|
"semver": "^7.7.2",
|
||||||
|
"sileo": "^0.1.3",
|
||||||
"svelte-confetti": "^2.3.2",
|
"svelte-confetti": "^2.3.2",
|
||||||
"tw-animate-css": "^1.3.8",
|
"tw-animate-css": "^1.3.8",
|
||||||
},
|
},
|
||||||
@@ -27,13 +31,14 @@
|
|||||||
"@eslint/compat": "^2.0.0",
|
"@eslint/compat": "^2.0.0",
|
||||||
"@eslint/js": "^9.35.0",
|
"@eslint/js": "^9.35.0",
|
||||||
"@iarna/toml": "2.2.5",
|
"@iarna/toml": "2.2.5",
|
||||||
"@lucide/svelte": "^0.482.0",
|
"@lucide/svelte": "^0.575.0",
|
||||||
"@sveltejs/adapter-static": "^3.0.9",
|
"@sveltejs/adapter-static": "^3.0.9",
|
||||||
"@sveltejs/kit": "^2.37.1",
|
"@sveltejs/kit": "^2.37.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.1.4",
|
"@sveltejs/vite-plugin-svelte": "^6.1.4",
|
||||||
"@tauri-apps/cli": "^2.9.6",
|
"@tauri-apps/cli": "^2.10.0",
|
||||||
"@types/bun": "^1.2.21",
|
"@types/bun": "^1.2.21",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@types/crypto-js": "^4.2.2",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/semver": "^7.7.1",
|
"@types/semver": "^7.7.1",
|
||||||
"autoprefixer": "10.4.21",
|
"autoprefixer": "10.4.21",
|
||||||
"bits-ui": "^1.4.7",
|
"bits-ui": "^1.4.7",
|
||||||
@@ -174,7 +179,7 @@
|
|||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||||
|
|
||||||
"@lucide/svelte": ["@lucide/svelte@0.482.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-n2ycHU9cNcleRDwwpEHBJ6pYzVhHIaL3a+9dQa8kns9hB2g05bY+v2p2KP8v0pZwtNhYTHk/F2o2uZ1bVtQGhw=="],
|
"@lucide/svelte": ["@lucide/svelte@0.575.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-FEFp/0McZwsjBqh1Dn8H+UBm1yHFQYk+utuVMFDw57155+wz2XMoc1pw027ylCPzs+bi14UEXYKbekFhuJKtnw=="],
|
||||||
|
|
||||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||||
|
|
||||||
@@ -214,6 +219,8 @@
|
|||||||
|
|
||||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||||
|
|
||||||
|
"@r2wc/core": ["@r2wc/core@1.3.0", "", {}, "sha512-aPBnND92Itl+SWWbWyyxdFFF0+RqKB6dptGHEdiPB8ZvnHWHlVzOfEvbEcyUYGtB6HBdsfkVuBiaGYyBFVTzVQ=="],
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.44.1", "", { "os": "android", "cpu": "arm" }, "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w=="],
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.44.1", "", { "os": "android", "cpu": "arm" }, "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w=="],
|
||||||
|
|
||||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.44.1", "", { "os": "android", "cpu": "arm64" }, "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ=="],
|
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.44.1", "", { "os": "android", "cpu": "arm64" }, "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ=="],
|
||||||
@@ -300,37 +307,37 @@
|
|||||||
|
|
||||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.11", "", { "dependencies": { "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw=="],
|
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.11", "", { "dependencies": { "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "tailwindcss": "4.1.11" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw=="],
|
||||||
|
|
||||||
"@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="],
|
"@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
|
||||||
|
|
||||||
"@tauri-apps/cli": ["@tauri-apps/cli@2.9.6", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.9.6", "@tauri-apps/cli-darwin-x64": "2.9.6", "@tauri-apps/cli-linux-arm-gnueabihf": "2.9.6", "@tauri-apps/cli-linux-arm64-gnu": "2.9.6", "@tauri-apps/cli-linux-arm64-musl": "2.9.6", "@tauri-apps/cli-linux-riscv64-gnu": "2.9.6", "@tauri-apps/cli-linux-x64-gnu": "2.9.6", "@tauri-apps/cli-linux-x64-musl": "2.9.6", "@tauri-apps/cli-win32-arm64-msvc": "2.9.6", "@tauri-apps/cli-win32-ia32-msvc": "2.9.6", "@tauri-apps/cli-win32-x64-msvc": "2.9.6" }, "bin": { "tauri": "tauri.js" } }, "sha512-3xDdXL5omQ3sPfBfdC8fCtDKcnyV7OqyzQgfyT5P3+zY6lcPqIYKQBvUasNvppi21RSdfhy44ttvJmftb0PCDw=="],
|
"@tauri-apps/cli": ["@tauri-apps/cli@2.10.0", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.10.0", "@tauri-apps/cli-darwin-x64": "2.10.0", "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", "@tauri-apps/cli-linux-arm64-musl": "2.10.0", "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", "@tauri-apps/cli-linux-x64-gnu": "2.10.0", "@tauri-apps/cli-linux-x64-musl": "2.10.0", "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", "@tauri-apps/cli-win32-x64-msvc": "2.10.0" }, "bin": { "tauri": "tauri.js" } }, "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.9.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gf5no6N9FCk1qMrti4lfwP77JHP5haASZgVbBgpZG7BUepB3fhiLCXGUK8LvuOjP36HivXewjg72LTnPDScnQQ=="],
|
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.10.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.9.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-oWh74WmqbERwwrwcueJyY6HYhgCksUc6NT7WKeXyrlY/FPmNgdyQAgcLuTSkhRFuQ6zh4Np1HZpOqCTpeZBDcw=="],
|
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.10.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.9.6", "", { "os": "linux", "cpu": "arm" }, "sha512-/zde3bFroFsNXOHN204DC2qUxAcAanUjVXXSdEGmhwMUZeAQalNj5cz2Qli2elsRjKN/hVbZOJj0gQ5zaYUjSg=="],
|
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.10.0", "", { "os": "linux", "cpu": "arm" }, "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.9.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-pvbljdhp9VOo4RnID5ywSxgBs7qiylTPlK56cTk7InR3kYSTJKYMqv/4Q/4rGo/mG8cVppesKIeBMH42fw6wjg=="],
|
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.9.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-02TKUndpodXBCR0oP//6dZWGYcc22Upf2eP27NvC6z0DIqvkBBFziQUcvi2n6SrwTRL0yGgQjkm9K5NIn8s6jw=="],
|
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.9.6", "", { "os": "linux", "cpu": "none" }, "sha512-fmp1hnulbqzl1GkXl4aTX9fV+ubHw2LqlLH1PE3BxZ11EQk+l/TmiEongjnxF0ie4kV8DQfDNJ1KGiIdWe1GvQ=="],
|
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.10.0", "", { "os": "linux", "cpu": "none" }, "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.9.6", "", { "os": "linux", "cpu": "x64" }, "sha512-vY0le8ad2KaV1PJr+jCd8fUF9VOjwwQP/uBuTJvhvKTloEwxYA/kAjKK9OpIslGA9m/zcnSo74czI6bBrm2sYA=="],
|
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.9.6", "", { "os": "linux", "cpu": "x64" }, "sha512-TOEuB8YCFZTWVDzsO2yW0+zGcoMiPPwcUgdnW1ODnmgfwccpnihDRoks+ABT1e3fHb1ol8QQWsHSCovb3o2ENQ=="],
|
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.9.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-ujmDGMRc4qRLAnj8nNG26Rlz9klJ0I0jmZs2BPpmNNf0gM/rcVHhqbEkAaHPTBVIrtUdf7bGvQAD2pyIiUrBHQ=="],
|
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.10.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.9.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-S4pT0yAJgFX8QRCyKA1iKjZ9Q/oPjCZf66A/VlG5Yw54Nnr88J1uBpmenINbXxzyhduWrIXBaUbEY1K80ZbpMg=="],
|
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.10.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.9.6", "", { "os": "win32", "cpu": "x64" }, "sha512-ldWuWSSkWbKOPjQMJoYVj9wLHcOniv7diyI5UAJ4XsBdtaFB0pKHQsqw/ItUma0VXGC7vB4E9fZjivmxur60aw=="],
|
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.0", "", { "os": "win32", "cpu": "x64" }, "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.4.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-lNIn5CZuw8WZOn8zHzmFmDSzg5zfohWoa3mdULP0YFh/VogVdMVWZPcWSHlydsiJhRQYaTNSYKN7RmZKE2lCYQ=="],
|
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.6.0", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.4", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-MTorXxIRmOnOPT1jZ3w96vjSuScER38ryXY88vl5F0uiKdnvTKKTtaEjTEo8uPbl4e3gnUtfsDVwC7h77GQLvQ=="],
|
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.5", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-dVxWWGE6VrOxC7/jlhyE+ON/Cc2REJlM35R3PJX3UvFw2XwYhLGQVAIyrehenDdKjotipjYEVc4YjOl3qq90fA=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Xod+pRcFxmOWFWEnqH5yZcA7qwAMuaaDkMR1Sply+F8VfBj++CGnj2xf5UoialmjZ2Cvd8qrvSCbU+7GgNVsKQ=="],
|
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.3.5", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
|
"@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
|
||||||
|
|
||||||
@@ -346,6 +353,8 @@
|
|||||||
|
|
||||||
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
||||||
|
|
||||||
|
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||||
|
|
||||||
"@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
|
"@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
|
||||||
|
|
||||||
"@types/umami": ["@types/umami@2.10.1", "", {}, "sha512-CvOEMq+BFOUXj8yciq4hPsdOt0o9KBg5jQXnF1FrdOMKZMxJiwryyZNLa9Ud2NVO6nXSwU22YMKR3E+Xhbo+DA=="],
|
"@types/umami": ["@types/umami@2.10.1", "", {}, "sha512-CvOEMq+BFOUXj8yciq4hPsdOt0o9KBg5jQXnF1FrdOMKZMxJiwryyZNLa9Ud2NVO6nXSwU22YMKR3E+Xhbo+DA=="],
|
||||||
@@ -506,6 +515,8 @@
|
|||||||
|
|
||||||
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
|
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
|
||||||
|
|
||||||
|
"framer-motion": ["framer-motion@12.34.1", "", { "dependencies": { "motion-dom": "^12.34.1", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-kcZyNaYQfvE2LlH6+AyOaJAQV4rGp5XbzfhsZpiSZcwDMfZUHhuxLWeyRzf5I7jip3qKRpuimPA9pXXfr111kQ=="],
|
||||||
|
|
||||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||||
@@ -592,7 +603,7 @@
|
|||||||
|
|
||||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||||
|
|
||||||
"lucide-svelte": ["lucide-svelte@0.562.0", "", { "peerDependencies": { "svelte": "^3 || ^4 || ^5.0.0-next.42" } }, "sha512-kSJDH/55lf0mun/o4nqWBXOcq0fWYzPeIjbTD97ywoeumAB9kWxtM06gC7oynqjtK3XhAljWSz5RafIzPEYIQA=="],
|
"lucide-svelte": ["lucide-svelte@0.575.0", "", { "peerDependencies": { "svelte": "^3 || ^4 || ^5.0.0-next.42" } }, "sha512-Tu15tJfbmRNPaU61yeNFf3jfRHs8ABA+NwTt7TWmwVbhlSA3H7sW65tX6RttcP7HGV4aHUlYhXixZOlntoFBdw=="],
|
||||||
|
|
||||||
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
||||||
|
|
||||||
@@ -610,6 +621,12 @@
|
|||||||
|
|
||||||
"mode-watcher": ["mode-watcher@1.1.0", "", { "dependencies": { "runed": "^0.25.0", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.27.0" } }, "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g=="],
|
"mode-watcher": ["mode-watcher@1.1.0", "", { "dependencies": { "runed": "^0.25.0", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.27.0" } }, "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g=="],
|
||||||
|
|
||||||
|
"motion": ["motion@12.34.1", "", { "dependencies": { "framer-motion": "^12.34.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-N9RVNGn/NSo85OgHX1wGaUWHvReuQ7dZUwuQRhHyzY2wfVOvY3cEgn0Mw4NXOsXMHL/y7EYuzA+b59PYI6EejA=="],
|
||||||
|
|
||||||
|
"motion-dom": ["motion-dom@12.34.1", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-SC7ZC5dRcGwku2g7EsPvI4q/EzHumUbqsDNumBmZTLFg+goBO5LTJvDu9MAxx+0mtX4IA78B2be/A3aRjY0jnw=="],
|
||||||
|
|
||||||
|
"motion-utils": ["motion-utils@12.29.2", "", {}, "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A=="],
|
||||||
|
|
||||||
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
||||||
|
|
||||||
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
||||||
@@ -666,6 +683,12 @@
|
|||||||
|
|
||||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||||
|
|
||||||
|
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
|
||||||
|
|
||||||
|
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
|
||||||
|
|
||||||
|
"react-to-webcomponent": ["react-to-webcomponent@2.0.1", "", { "dependencies": { "@r2wc/core": "^1.0.0" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-jpVztcA0SbV6TnBRV6EFuU+hmuuSIxgEww3wRcbneT0ytqn57y9ygeRfUmhzz8+bYg53u1h4COKk8iAextHE+Q=="],
|
||||||
|
|
||||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||||
|
|
||||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||||
@@ -722,6 +745,8 @@
|
|||||||
|
|
||||||
"sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.92.1", "", { "os": "win32", "cpu": "x64" }, "sha512-B6Nz/GbH7Vkpb2TkQHsGcczWM5t+70VWopWF1x5V5yxLpA8ZzVQ7NTKKi+jDoVY2Efu6ZyzgT9n5KgG2kWliXA=="],
|
"sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.92.1", "", { "os": "win32", "cpu": "x64" }, "sha512-B6Nz/GbH7Vkpb2TkQHsGcczWM5t+70VWopWF1x5V5yxLpA8ZzVQ7NTKKi+jDoVY2Efu6ZyzgT9n5KgG2kWliXA=="],
|
||||||
|
|
||||||
|
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||||
|
|
||||||
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||||
|
|
||||||
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
||||||
@@ -730,6 +755,8 @@
|
|||||||
|
|
||||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||||
|
|
||||||
|
"sileo": ["sileo@0.1.3", "", { "dependencies": { "motion": "^12.34.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-kmGjM2ts2TpuPzOaD7llxcMpU37Pyj5dO8s4wGXvDlR143zV2sB6sJ76PCVP4kY2+hfkrLJhiSm/wXGdUHDFjQ=="],
|
||||||
|
|
||||||
"sirv": ["sirv@3.0.1", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A=="],
|
"sirv": ["sirv@3.0.1", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A=="],
|
||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|||||||
@@ -36,5 +36,14 @@ export default ts.config(
|
|||||||
svelteConfig,
|
svelteConfig,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'no-empty': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^\\$\\$(Props|Events|Slots|Generic)$' },
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ezpplauncher",
|
"name": "ezpplauncher",
|
||||||
"version": "3.1.0",
|
"version": "3.1.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -24,16 +24,20 @@
|
|||||||
"@number-flow/svelte": "^0.3.9",
|
"@number-flow/svelte": "^0.3.9",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"@tauri-apps/api": "^2.9.1",
|
"@tauri-apps/api": "^2.10.1",
|
||||||
"@tauri-apps/plugin-dialog": "^2.4.2",
|
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||||
"@tauri-apps/plugin-fs": "^2.4.4",
|
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||||
"@tauri-apps/plugin-shell": "^2.3.3",
|
"@tauri-apps/plugin-shell": "^2.3.5",
|
||||||
"@types/umami": "^2.10.1",
|
"@types/umami": "^2.10.1",
|
||||||
"animejs": "^4.1.3",
|
"animejs": "^4.1.3",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"lucide-svelte": "^0.562.0",
|
"lucide-svelte": "^0.575.0",
|
||||||
|
"react": "^19.2.4",
|
||||||
|
"react-dom": "^19.2.4",
|
||||||
|
"react-to-webcomponent": "^2.0.1",
|
||||||
"semver": "^7.7.2",
|
"semver": "^7.7.2",
|
||||||
|
"sileo": "^0.1.3",
|
||||||
"svelte-confetti": "^2.3.2",
|
"svelte-confetti": "^2.3.2",
|
||||||
"tw-animate-css": "^1.3.8"
|
"tw-animate-css": "^1.3.8"
|
||||||
},
|
},
|
||||||
@@ -41,13 +45,14 @@
|
|||||||
"@eslint/compat": "^2.0.0",
|
"@eslint/compat": "^2.0.0",
|
||||||
"@eslint/js": "^9.35.0",
|
"@eslint/js": "^9.35.0",
|
||||||
"@iarna/toml": "2.2.5",
|
"@iarna/toml": "2.2.5",
|
||||||
"@lucide/svelte": "^0.562.0",
|
"@lucide/svelte": "^0.575.0",
|
||||||
"@sveltejs/adapter-static": "^3.0.9",
|
"@sveltejs/adapter-static": "^3.0.9",
|
||||||
"@sveltejs/kit": "^2.37.1",
|
"@sveltejs/kit": "^2.37.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.1.4",
|
"@sveltejs/vite-plugin-svelte": "^6.1.4",
|
||||||
"@tauri-apps/cli": "^2.9.6",
|
"@tauri-apps/cli": "^2.10.0",
|
||||||
"@types/bun": "^1.2.21",
|
"@types/bun": "^1.2.21",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@types/crypto-js": "^4.2.2",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/semver": "^7.7.1",
|
"@types/semver": "^7.7.1",
|
||||||
"autoprefixer": "10.4.21",
|
"autoprefixer": "10.4.21",
|
||||||
"bits-ui": "^1.4.7",
|
"bits-ui": "^1.4.7",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ezpplauncher"
|
name = "ezpplauncher"
|
||||||
version = "3.1.0"
|
version = "3.1.2"
|
||||||
description = "EZPPLauncher redefined."
|
description = "EZPPLauncher redefined."
|
||||||
authors = ["HorizonCode"]
|
authors = ["HorizonCode"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
@@ -15,33 +15,35 @@ name = "ezpplauncher_lib"
|
|||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.5.3", features = [] }
|
tauri-build = { version = "2.5.5", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2.9.5", features = [] }
|
tauri = { version = "2.10.2", features = [] }
|
||||||
tauri-plugin-shell = "2.3.3"
|
tauri-plugin-shell = "2.3.5"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.148"
|
serde_json = "1.0.149"
|
||||||
serde_repr = "0.1.20"
|
serde_repr = "0.1.20"
|
||||||
tauri-plugin-dialog = "2.4.2"
|
tauri-plugin-dialog = "2.6.0"
|
||||||
tauri-plugin-fs = "2.4.4"
|
tauri-plugin-fs = "2.4.5"
|
||||||
hardware-id = "0.3.0"
|
hardware-id = "0.3.0"
|
||||||
tauri-plugin-cors-fetch = "4.1.0"
|
tauri-plugin-cors-fetch = "5.0.0"
|
||||||
sysinfo = "0.37.2"
|
sysinfo = "0.38.2"
|
||||||
reqwest = { version = "0.13.1", features = ["json", "stream", "query"] }
|
reqwest = { version = "0.13.2", features = ["json", "stream", "query"] }
|
||||||
md5 = "0.8.0"
|
md5 = "0.8.0"
|
||||||
tokio = { version = "1.48.0", features = ["full"] }
|
tokio = { version = "1.48.0", features = ["full"] }
|
||||||
open = "5.3.3"
|
open = "5.3.3"
|
||||||
windows-sys = "0.61.2"
|
discord-rich-presence = "1.1.0"
|
||||||
discord-rich-presence = "1.0.0"
|
|
||||||
once_cell = "1.21.3"
|
once_cell = "1.21.3"
|
||||||
|
futures-util = "0.3.32"
|
||||||
|
osynic_osudb = "0.1.4"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.55.0"
|
winreg = "0.55.0"
|
||||||
winapi = { version = "0.3", features = ["winuser", "wincrypt", "memoryapi", "winbase", "dpapi"] }
|
winapi = { version = "0.3", features = ["winuser", "wincrypt", "memoryapi", "winbase", "dpapi"] }
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
widestring = "1.2.1"
|
widestring = "1.2.1"
|
||||||
|
windows-sys = "0.61.2"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-single-instance = "2.3.6"
|
tauri-plugin-single-instance = "2.4.0"
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#fff</color>
|
||||||
|
</resources>
|
||||||
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 357 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 24 KiB |
@@ -1,4 +1,5 @@
|
|||||||
use hardware_id::get_id;
|
use hardware_id::get_id;
|
||||||
|
use osynic_osudb::entity::osu::osudb::OsuDB;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@@ -56,17 +57,11 @@ pub fn valid_osu_folder(folder: String) -> bool {
|
|||||||
"scores.db",
|
"scores.db",
|
||||||
];
|
];
|
||||||
|
|
||||||
let folder_files: Vec<&str> = osu_folder_files.iter().map(|&s| s).collect();
|
let path = PathBuf::from(folder);
|
||||||
if folder_files.iter().any(|&file| file == "osu!.exe") == false {
|
if !path.join("osu!.exe").exists() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
check_folder_completeness(path, &osu_folder_files) >= 70.0
|
||||||
let path = PathBuf::from(folder);
|
|
||||||
let match_percentage = check_folder_completeness(path, &osu_folder_files) >= 70.0;
|
|
||||||
if match_percentage {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
@@ -83,10 +78,10 @@ pub fn find_osu_installation() -> Option<String> {
|
|||||||
|
|
||||||
let hklm_registry_paths = ["SOFTWARE\\Classes\\osu\\DefaultIcon"];
|
let hklm_registry_paths = ["SOFTWARE\\Classes\\osu\\DefaultIcon"];
|
||||||
|
|
||||||
let hkcr_registry_paths = [
|
let hkcu_registry_paths = [
|
||||||
"osustable.File.osk\\DefaultIcon",
|
"Software\\Classes\\osustable.File.osk\\DefaultIcon",
|
||||||
"osustable.File.osr\\DefaultIcon",
|
"Software\\Classes\\osustable.File.osr\\DefaultIcon",
|
||||||
"osustable.File.osz\\DefaultIcon",
|
"Software\\Classes\\osustable.File.osz\\DefaultIcon",
|
||||||
];
|
];
|
||||||
|
|
||||||
let osu_folder_files = [
|
let osu_folder_files = [
|
||||||
@@ -135,10 +130,10 @@ pub fn find_osu_installation() -> Option<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let hkcr = RegKey::predef(HKEY_CLASSES_ROOT);
|
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||||
|
|
||||||
for reg_path in &hkcr_registry_paths {
|
for reg_path in &hkcu_registry_paths {
|
||||||
if let Ok(subkey) = hkcr.open_subkey_with_flags(reg_path, KEY_READ | KEY_WOW64_32KEY) {
|
if let Ok(subkey) = hkcu.open_subkey_with_flags(reg_path, KEY_READ | KEY_WOW64_32KEY) {
|
||||||
let value_names = [""];
|
let value_names = [""];
|
||||||
for value_name in &value_names {
|
for value_name in &value_names {
|
||||||
if let Ok(value) = subkey.get_value::<String, _>(value_name) {
|
if let Ok(value) = subkey.get_value::<String, _>(value_name) {
|
||||||
@@ -160,39 +155,89 @@ pub fn find_osu_installation() -> Option<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_beatmapsets_count(folder: String) -> Option<u64> {
|
pub async fn get_beatmapsets_count(folder: String) -> Option<u64> {
|
||||||
let path = PathBuf::from(folder);
|
let path = PathBuf::from(folder);
|
||||||
let osu_user_config = get_osu_user_config(path.clone());
|
let osu_db_path = path.join("osu!.db");
|
||||||
let songs_path = osu_user_config
|
if !osu_db_path.exists() {
|
||||||
.and_then(|config| config.get("Songs").cloned())
|
return Some(0);
|
||||||
.unwrap_or_else(|| path.join("Songs").to_string_lossy().into_owned());
|
|
||||||
let songs_folder = PathBuf::from(songs_path);
|
|
||||||
|
|
||||||
if !songs_folder.exists() {
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
let mut osudb = OsuDB::from_file(&osu_db_path).unwrap();
|
||||||
|
let beatmaps = osudb.beatmaps.iter_mut();
|
||||||
|
let beatmap_sets = beatmaps.map(|b| b.beatmapset_id).collect::<HashSet<_>>();
|
||||||
|
return Some(beatmap_sets.len() as u64);
|
||||||
|
}
|
||||||
|
|
||||||
let mut count = 0;
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
if let Ok(entries) = std::fs::read_dir(songs_folder) {
|
#[serde(rename_all = "camelCase")]
|
||||||
for entry in entries.flatten() {
|
pub struct SkinInfo {
|
||||||
if entry.file_type().map_or(false, |ft| ft.is_dir()) {
|
pub name: String,
|
||||||
let dir_path = entry.path();
|
pub author: Option<String>,
|
||||||
if let Ok(files) = std::fs::read_dir(&dir_path) {
|
pub modified: u64,
|
||||||
for file in files.flatten() {
|
|
||||||
if file.path().extension().map_or(false, |ext| ext == "osu") {
|
|
||||||
count += 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Some(count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_skins_count(folder: String) -> Option<u64> {
|
pub async fn get_skins(folder: String) -> Option<Vec<SkinInfo>> {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::UNIX_EPOCH;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
let skins_folder = PathBuf::from(folder).join("Skins");
|
||||||
|
|
||||||
|
if !skins_folder.exists() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut entries = fs::read_dir(skins_folder).await.ok()?;
|
||||||
|
|
||||||
|
let mut skins = Vec::new();
|
||||||
|
|
||||||
|
while let Some(entry) = entries.next_entry().await.ok()? {
|
||||||
|
if !entry.file_type().await.ok()?.is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir_path = entry.path();
|
||||||
|
let skin_ini = dir_path.join("skin.ini");
|
||||||
|
|
||||||
|
if !skin_ini.exists() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = entry.file_name().to_string_lossy().to_string();
|
||||||
|
|
||||||
|
let author = fs::read_to_string(&skin_ini)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.and_then(|content| {
|
||||||
|
content.lines().find_map(|line| {
|
||||||
|
let (key, value) = line.split_once(':')?;
|
||||||
|
key.trim()
|
||||||
|
.eq_ignore_ascii_case("Author")
|
||||||
|
.then(|| value.trim().to_string())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let modified = entry
|
||||||
|
.metadata()
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.and_then(|m| m.modified().ok())
|
||||||
|
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
|
||||||
|
.map(|d| d.as_secs())
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
skins.push(SkinInfo {
|
||||||
|
name,
|
||||||
|
author,
|
||||||
|
modified,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(skins)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn get_skins_count(folder: String) -> Option<u64> {
|
||||||
let path = PathBuf::from(folder);
|
let path = PathBuf::from(folder);
|
||||||
let skins_folder = path.join("Skins");
|
let skins_folder = path.join("Skins");
|
||||||
|
|
||||||
@@ -201,12 +246,12 @@ pub fn get_skins_count(folder: String) -> Option<u64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
if let Ok(entries) = std::fs::read_dir(skins_folder) {
|
if let Ok(mut entries) = fs::read_dir(skins_folder).await {
|
||||||
for entry in entries.flatten() {
|
while let Ok(Some(entry)) = entries.next_entry().await {
|
||||||
if entry.file_type().map_or(false, |ft| ft.is_dir()) {
|
if entry.file_type().await.map_or(false, |ft| ft.is_dir()) {
|
||||||
let dir_path = entry.path();
|
let dir_path = entry.path();
|
||||||
if let Ok(files) = std::fs::read_dir(&dir_path) {
|
if let Ok(mut files) = fs::read_dir(&dir_path).await {
|
||||||
for file in files.flatten() {
|
while let Ok(Some(file)) = files.next_entry().await {
|
||||||
if file.path().extension().map_or(false, |ext| ext == "ini") {
|
if file.path().extension().map_or(false, |ext| ext == "ini") {
|
||||||
count += 1;
|
count += 1;
|
||||||
break;
|
break;
|
||||||
@@ -222,28 +267,28 @@ pub fn get_skins_count(folder: String) -> Option<u64> {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_osu_skin(folder: String) -> String {
|
pub fn get_osu_skin(folder: String) -> String {
|
||||||
let path = PathBuf::from(folder);
|
let path = PathBuf::from(folder);
|
||||||
let osu_user_config = get_osu_user_config(path.clone());
|
let osu_user_config = get_osu_user_config(path);
|
||||||
return osu_user_config
|
osu_user_config
|
||||||
.and_then(|config| config.get("Skin").cloned())
|
.and_then(|config| config.get("Skin").cloned())
|
||||||
.unwrap_or_else(|| "Default".to_string());
|
.unwrap_or_else(|| "Default".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_osu_version(folder: String) -> String {
|
pub fn get_osu_version(folder: String) -> String {
|
||||||
let path = PathBuf::from(folder);
|
let path = PathBuf::from(folder);
|
||||||
let osu_user_config = get_osu_user_config(path.clone());
|
let osu_user_config = get_osu_user_config(path);
|
||||||
return osu_user_config
|
osu_user_config
|
||||||
.and_then(|config| config.get("LastVersion").cloned())
|
.and_then(|config| config.get("LastVersion").cloned())
|
||||||
.unwrap_or_else(|| "failed".to_string());
|
.unwrap_or_else(|| "failed".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_osu_release_stream(folder: String) -> String {
|
pub fn get_osu_release_stream(folder: String) -> String {
|
||||||
let path = PathBuf::from(folder);
|
let path = PathBuf::from(folder);
|
||||||
let osu_config = get_osu_config(path.clone());
|
let osu_config = get_osu_config(path);
|
||||||
return osu_config
|
osu_config
|
||||||
.and_then(|config| config.get("_ReleaseStream").cloned())
|
.and_then(|config| config.get("_ReleaseStream").cloned())
|
||||||
.unwrap_or_else(|| "Stable40".to_string());
|
.unwrap_or_else(|| "Stable40".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
@@ -261,10 +306,7 @@ pub fn set_osu_user_config_values(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|entry| (entry.key.as_str(), Some(entry.value.as_str())))
|
.map(|entry| (entry.key.as_str(), Some(entry.value.as_str())))
|
||||||
.collect();
|
.collect();
|
||||||
match set_osu_user_config_vals(&osu_folder_path, &converted) {
|
Ok(set_osu_user_config_vals(&osu_folder_path, &converted).is_ok())
|
||||||
Ok(_) => Ok(true),
|
|
||||||
Err(_) => Ok(false),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -276,22 +318,16 @@ pub fn set_osu_config_values(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|entry| (entry.key.as_str(), Some(entry.value.as_str())))
|
.map(|entry| (entry.key.as_str(), Some(entry.value.as_str())))
|
||||||
.collect();
|
.collect();
|
||||||
match set_osu_config_vals(&osu_folder_path, &converted) {
|
Ok(set_osu_config_vals(&osu_folder_path, &converted).is_ok())
|
||||||
Ok(_) => Ok(true),
|
|
||||||
Err(_) => Ok(false),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn run_osu_updater(folder: String) -> Result<(), String> {
|
pub async fn run_osu_updater(folder: String) -> Result<(), String> {
|
||||||
#[cfg(windows)]
|
|
||||||
const DETACHED_PROCESS: u32 = 0x00000008;
|
|
||||||
#[cfg(windows)]
|
|
||||||
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
|
|
||||||
|
|
||||||
let mut updater_process = {
|
let mut updater_process = {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
|
const DETACHED_PROCESS: u32 = 0x00000008;
|
||||||
|
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
|
||||||
let osu_exe_path = PathBuf::from(&folder).join("osu!.exe");
|
let osu_exe_path = PathBuf::from(&folder).join("osu!.exe");
|
||||||
Command::new(&osu_exe_path)
|
Command::new(&osu_exe_path)
|
||||||
.arg("-repair")
|
.arg("-repair")
|
||||||
@@ -368,9 +404,6 @@ pub async fn run_osu_updater(folder: String) -> Result<(), String> {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> {
|
pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> {
|
||||||
/* #[cfg(windows)]
|
|
||||||
use std::os::windows::process::CommandExt; */
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
const DETACHED_PROCESS: u32 = 0x00000008;
|
const DETACHED_PROCESS: u32 = 0x00000008;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@@ -385,7 +418,7 @@ pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> {
|
|||||||
.arg("ez-pp.farm")
|
.arg("ez-pp.farm")
|
||||||
.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP)
|
.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP)
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| format!("Failed to spawn updater: {}", e))?
|
.map_err(|e| format!("Failed to spawn osu: {}", e))?
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
@@ -394,7 +427,7 @@ pub async fn run_osu(folder: String, patch: bool) -> Result<(), String> {
|
|||||||
.arg("--devserver")
|
.arg("--devserver")
|
||||||
.arg("ez-pp.farm")
|
.arg("ez-pp.farm")
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| format!("Failed to spawn updater: {}", e))?
|
.map_err(|e| format!("Failed to spawn osu: {}", e))?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -593,7 +626,7 @@ pub fn replace_ui_files(folder: String, revert: bool) -> Result<(), ReplaceUIErr
|
|||||||
if !revert {
|
if !revert {
|
||||||
try_rename(&osu_ui, &osu_ui_bak)?;
|
try_rename(&osu_ui, &osu_ui_bak)?;
|
||||||
try_rename(&ezpp_ui, &osu_ui)?;
|
try_rename(&ezpp_ui, &osu_ui)?;
|
||||||
|
|
||||||
try_rename(&osu_seasonal, &osu_seasonal_bak)?;
|
try_rename(&osu_seasonal, &osu_seasonal_bak)?;
|
||||||
try_rename(&ezpp_seasonal, &osu_seasonal)?;
|
try_rename(&ezpp_seasonal, &osu_seasonal)?;
|
||||||
|
|
||||||
@@ -602,7 +635,7 @@ pub fn replace_ui_files(folder: String, revert: bool) -> Result<(), ReplaceUIErr
|
|||||||
} else {
|
} else {
|
||||||
try_rename(&osu_ui, &ezpp_ui)?;
|
try_rename(&osu_ui, &ezpp_ui)?;
|
||||||
try_rename(&osu_ui_bak, &osu_ui)?;
|
try_rename(&osu_ui_bak, &osu_ui)?;
|
||||||
|
|
||||||
try_rename(&osu_seasonal, &ezpp_seasonal)?;
|
try_rename(&osu_seasonal, &ezpp_seasonal)?;
|
||||||
try_rename(&osu_seasonal_bak, &osu_seasonal)?;
|
try_rename(&osu_seasonal_bak, &osu_seasonal)?;
|
||||||
|
|
||||||
@@ -732,12 +765,7 @@ pub async fn has_net8() -> bool {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn encrypt_string(string: String, entropy: String) -> String {
|
pub fn encrypt_string(string: String, entropy: String) -> String {
|
||||||
let encrypted = encrypt_password(&string, &entropy);
|
encrypt_password(&string, &entropy).unwrap_or(string)
|
||||||
|
|
||||||
match encrypted {
|
|
||||||
Ok(encrypted_vec) => encrypted_vec,
|
|
||||||
Err(_) => string,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -773,7 +801,7 @@ pub async fn download_ezpp_launcher_update(app: AppHandle, url: String) -> Resul
|
|||||||
UpdateStatus {
|
UpdateStatus {
|
||||||
file_name: "Update".to_string(),
|
file_name: "Update".to_string(),
|
||||||
downloaded,
|
downloaded,
|
||||||
size: size,
|
size,
|
||||||
progress: ((downloaded as f64 / size as f64 * 100.0) * 100.0).trunc() / 100.0,
|
progress: ((downloaded as f64 / size as f64 * 100.0) * 100.0).trunc() / 100.0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ use crate::commands::{
|
|||||||
check_for_corruption, download_ezpp_launcher_update, download_ezpp_launcher_update_files,
|
check_for_corruption, download_ezpp_launcher_update, download_ezpp_launcher_update_files,
|
||||||
encrypt_string, exit, find_osu_installation, get_beatmapsets_count,
|
encrypt_string, exit, find_osu_installation, get_beatmapsets_count,
|
||||||
get_ezpp_launcher_update_files, get_hwid, get_launcher_version, get_osu_release_stream,
|
get_ezpp_launcher_update_files, get_hwid, get_launcher_version, get_osu_release_stream,
|
||||||
get_osu_skin, get_osu_version, get_platform, get_skins_count, has_net8, has_osuwinello,
|
get_osu_skin, get_osu_version, get_platform, get_skins, get_skins_count, has_net8,
|
||||||
has_wmctrl, install_ezpp_launcher_update, is_osu_running, open_url_in_browser,
|
has_osuwinello, has_wmctrl, install_ezpp_launcher_update, is_osu_running, open_url_in_browser,
|
||||||
presence_connect, presence_disconnect, presence_is_connected, presence_update_status,
|
presence_connect, presence_disconnect, presence_is_connected, presence_update_status,
|
||||||
presence_update_user, replace_ui_files, run_osu, run_osu_updater, set_osu_config_values,
|
presence_update_user, replace_ui_files, run_osu, run_osu_updater, set_osu_config_values,
|
||||||
set_osu_user_config_values, valid_osu_folder,
|
set_osu_user_config_values, valid_osu_folder,
|
||||||
@@ -37,6 +37,7 @@ pub fn run() {
|
|||||||
find_osu_installation,
|
find_osu_installation,
|
||||||
valid_osu_folder,
|
valid_osu_folder,
|
||||||
get_beatmapsets_count,
|
get_beatmapsets_count,
|
||||||
|
get_skins,
|
||||||
get_skins_count,
|
get_skins_count,
|
||||||
get_osu_version,
|
get_osu_version,
|
||||||
get_osu_release_stream,
|
get_osu_release_stream,
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use discord_rich_presence::{
|
use discord_rich_presence::{
|
||||||
DiscordIpc, DiscordIpcClient,
|
|
||||||
activity::{Activity, Assets, Button, Timestamps},
|
activity::{Activity, Assets, Button, Timestamps},
|
||||||
|
DiscordIpc, DiscordIpcClient,
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
use std::sync::Mutex as StdMutex;
|
use std::sync::Mutex as StdMutex;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
@@ -24,6 +26,7 @@ enum PresenceCommand {
|
|||||||
Disconnect(oneshot::Sender<()>),
|
Disconnect(oneshot::Sender<()>),
|
||||||
UpdateData(PresenceData),
|
UpdateData(PresenceData),
|
||||||
IsConnected(oneshot::Sender<bool>),
|
IsConnected(oneshot::Sender<bool>),
|
||||||
|
Tick,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PresenceActor {
|
struct PresenceActor {
|
||||||
@@ -58,7 +61,7 @@ impl PresenceActor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn run(&mut self) {
|
async fn run(&mut self) {
|
||||||
let mut update_interval = interval(Duration::from_millis(2500));
|
let mut update_interval = interval(Duration::from_secs(5));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
@@ -74,18 +77,22 @@ impl PresenceActor {
|
|||||||
PresenceCommand::IsConnected(responder) => {
|
PresenceCommand::IsConnected(responder) => {
|
||||||
let _ = responder.send(self.client.is_some());
|
let _ = responder.send(self.client.is_some());
|
||||||
}
|
}
|
||||||
|
PresenceCommand::Tick => {
|
||||||
|
if self.client.is_some() {
|
||||||
|
self.handle_update().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = update_interval.tick() => {
|
_ = update_interval.tick() => {
|
||||||
if self.client.is_some() {
|
let _ = PRESENCE_TX.send(PresenceCommand::Tick).await;
|
||||||
self.handle_update().await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_connect(&mut self, responder: oneshot::Sender<bool>) {
|
fn handle_connect(&mut self, responder: oneshot::Sender<bool>) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||||
|
Box::pin(async move {
|
||||||
if self.client.is_some() {
|
if self.client.is_some() {
|
||||||
let _ = responder.send(true);
|
let _ = responder.send(true);
|
||||||
return;
|
return;
|
||||||
@@ -101,17 +108,27 @@ impl PresenceActor {
|
|||||||
let _ = responder.send(true);
|
let _ = responder.send(true);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Failed to create Discord client: {:?}", e);
|
eprintln!("Failed to connect to Discord: {:?}. Retrying in 5s...", e);
|
||||||
let _ = responder.send(false);
|
let tx = PRESENCE_TX.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||||
|
let (resp_tx, _) = oneshot::channel();
|
||||||
|
let _ = tx.send(PresenceCommand::Connect(resp_tx)).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
if !responder.is_closed() {
|
||||||
|
let _ = responder.send(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_disconnect(&mut self, responder: oneshot::Sender<()>) {
|
async fn handle_disconnect(&mut self, responder: oneshot::Sender<()>) {
|
||||||
if let Some(mut client) = self.client.take() {
|
if let Some(mut client) = self.client.take() {
|
||||||
println!("Actor: Disconnecting...");
|
println!("Actor: Disconnecting from Discord...");
|
||||||
let _ = client.clear_activity().map_err(|e| e.to_string());
|
let _ = client.clear_activity();
|
||||||
let _ = client.close().map_err(|e| e.to_string());
|
let _ = client.close();
|
||||||
println!("Actor: Disconnected successfully.");
|
println!("Actor: Disconnected successfully.");
|
||||||
}
|
}
|
||||||
let _ = responder.send(());
|
let _ = responder.send(());
|
||||||
@@ -143,12 +160,14 @@ impl PresenceActor {
|
|||||||
Button::new("Join EZPPFarm", "https://ez-pp.farm/discord"),
|
Button::new("Join EZPPFarm", "https://ez-pp.farm/discord"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if let Err(e) = client.set_activity(activity).map_err(|e| e.to_string()) {
|
if let Err(e) = client.set_activity(activity) {
|
||||||
eprintln!("Failed to set activity, disconnecting: {:?}", e);
|
eprintln!("Failed to set activity, disconnecting: {:?}", e);
|
||||||
if let Some(mut client) = self.client.take() {
|
self.client = None;
|
||||||
let _ = client.clear_activity();
|
|
||||||
let _ = client.close();
|
let (tx, _) = oneshot::channel();
|
||||||
}
|
self.handle_connect(tx).await;
|
||||||
|
} else {
|
||||||
|
println!("Actor: Presence updated successfully.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,14 +201,9 @@ pub async fn connect() -> bool {
|
|||||||
|
|
||||||
pub async fn disconnect() {
|
pub async fn disconnect() {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
if PRESENCE_TX
|
// Use a timeout to ensure we don't hang the exit process if Discord is unresponsive
|
||||||
.send(PresenceCommand::Disconnect(tx))
|
if let Ok(_) = PRESENCE_TX.send(PresenceCommand::Disconnect(tx)).await {
|
||||||
.await
|
let _ = tokio::time::timeout(Duration::from_secs(2), rx).await;
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
let _ = rx.await;
|
|
||||||
} else {
|
|
||||||
println!("Could not send disconnect command; actor may not be running.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "EZPPLauncher",
|
"productName": "EZPPLauncher",
|
||||||
"version": "3.1.0",
|
"version": "3.1.2",
|
||||||
"identifier": "farm.EZPPFarm.Launcher",
|
"identifier": "farm.EZPPFarm.Launcher",
|
||||||
"mainBinaryName": "ezpplauncher",
|
"mainBinaryName": "ezpplauncher",
|
||||||
"build": {
|
"build": {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
--card-foreground: oklch(89.24% 0.079 263.24);
|
--card-foreground: oklch(89.24% 0.079 263.24);
|
||||||
--popover: oklch(20.3% 0.05 282.86);
|
--popover: oklch(20.3% 0.05 282.86);
|
||||||
--popover-foreground: oklch(89.24% 0.079 263.24);
|
--popover-foreground: oklch(89.24% 0.079 263.24);
|
||||||
--primary: oklch(81.45% 0.159 300.53);
|
--primary: oklch(72.08% 0.196 356.34);
|
||||||
--primary-foreground: oklch(20.22% 0.048 282.86);
|
--primary-foreground: oklch(20.22% 0.048 282.86);
|
||||||
--secondary: oklch(42.99% 0.031 270.49);
|
--secondary: oklch(42.99% 0.031 270.49);
|
||||||
--secondary-foreground: oklch(89.24% 0.079 263.24);
|
--secondary-foreground: oklch(89.24% 0.079 263.24);
|
||||||
@@ -67,8 +67,8 @@
|
|||||||
--destructive-foreground: oklch(20.22% 0.048 282.86);
|
--destructive-foreground: oklch(20.22% 0.048 282.86);
|
||||||
--border: oklch(27.7% 0.041 276.87);
|
--border: oklch(27.7% 0.041 276.87);
|
||||||
--input: oklch(27.7% 0.041 276.87);
|
--input: oklch(27.7% 0.041 276.87);
|
||||||
--ring: oklch(81.45% 0.159 300.53);
|
--ring: oklch(72.08% 0.196 356.34);
|
||||||
--chart-1: oklch(81.45% 0.159 300.53);
|
--chart-1: oklch(72.08% 0.196 356.34);
|
||||||
--chart-2: oklch(77.19% 0.121 223.01);
|
--chart-2: oklch(77.19% 0.121 223.01);
|
||||||
--chart-3: oklch(79.1% 0.119 150.21);
|
--chart-3: oklch(79.1% 0.119 150.21);
|
||||||
--chart-4: oklch(79.74% 0.167 57.97);
|
--chart-4: oklch(79.74% 0.167 57.97);
|
||||||
@@ -76,12 +76,12 @@
|
|||||||
--sidebar: oklch(13.29% 0.036 282.86);
|
--sidebar: oklch(13.29% 0.036 282.86);
|
||||||
--sidebar-background: oklch(13.29% 0.036 282.86);
|
--sidebar-background: oklch(13.29% 0.036 282.86);
|
||||||
--sidebar-foreground: oklch(89.24% 0.079 263.24);
|
--sidebar-foreground: oklch(89.24% 0.079 263.24);
|
||||||
--sidebar-primary: oklch(81.45% 0.159 300.53);
|
--sidebar-primary: oklch(72.08% 0.196 356.34);
|
||||||
--sidebar-primary-foreground: oklch(20.22% 0.048 282.86);
|
--sidebar-primary-foreground: oklch(20.22% 0.048 282.86);
|
||||||
--sidebar-accent: oklch(75.21% 0.121 293.01);
|
--sidebar-accent: oklch(75.21% 0.121 293.01);
|
||||||
--sidebar-accent-foreground: oklch(20.22% 0.048 282.86);
|
--sidebar-accent-foreground: oklch(20.22% 0.048 282.86);
|
||||||
--sidebar-border: oklch(35.13% 0.03 273.31);
|
--sidebar-border: oklch(35.13% 0.03 273.31);
|
||||||
--sidebar-ring: oklch(81.45% 0.159 300.53);
|
--sidebar-ring: oklch(72.08% 0.196 356.34);
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
@@ -108,20 +108,18 @@
|
|||||||
--color-popover: var(--popover);
|
--color-popover: var(--popover);
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
--color-primary: var(--primary);
|
--color-primary: var(--primary);
|
||||||
--color-primary-50: #fefdff;
|
--color-primary-50: #ffe5f1;
|
||||||
--color-primary-100: #faf6fe;
|
--color-primary-100: #ffcce2;
|
||||||
--color-primary-200: #f2e9fc;
|
--color-primary-200: #ff99c5;
|
||||||
--color-primary-300: #e9dbfa;
|
--color-primary-300: #ff66a8;
|
||||||
--color-primary-400: #e1cef9;
|
--color-primary-400: #ff338b;
|
||||||
--color-primary-500: #d9c0f7;
|
--color-primary-500: #ff006e;
|
||||||
--color-primary-600: #c6a0f3;
|
--color-primary-600: #cc0058;
|
||||||
--color-primary-700: #b381ef;
|
--color-primary-700: #990042;
|
||||||
--color-primary-800: #a061eb;
|
--color-primary-800: #66002c;
|
||||||
--color-primary-900: #8d41e7;
|
--color-primary-900: #330016;
|
||||||
--color-primary-950: #8331e5;
|
--color-primary-950: #24000f;
|
||||||
--color-primary-foreground: var(--primary-foreground);
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
--color-primary-50: #fefdff;
|
|
||||||
--color-primary-100: #faf6fe;
|
|
||||||
--color-secondary: var(--secondary);
|
--color-secondary: var(--secondary);
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
--color-muted: var(--muted);
|
--color-muted: var(--muted);
|
||||||
|
|||||||
|
After Width: | Height: | Size: 70 KiB |
@@ -1,47 +1,53 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Logo from '$assets/logo.png';
|
||||||
|
import { reduceAnimations } from '@/userSettings';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
//TODO: maybe dynamic background images fetched from ezpp?
|
//TODO: maybe dynamic background images fetched from ezpp?
|
||||||
|
|
||||||
const COUNT = 120;
|
const COUNT = 60;
|
||||||
|
|
||||||
function rand(min: number, max: number) {
|
function rand(min: number, max: number) {
|
||||||
return Math.random() * (max - min) + min;
|
return Math.random() * (max - min) + min;
|
||||||
}
|
}
|
||||||
|
|
||||||
const streaks = Array.from({ length: COUNT }).map(() => {
|
const streaks = Array.from({ length: COUNT })
|
||||||
const depth = rand(0, 2);
|
.map((_, i) => {
|
||||||
|
const depth = rand(0, 2);
|
||||||
|
|
||||||
const scale = 0.6 + depth * 0.8;
|
const scale = 0.6 + depth * 0.8;
|
||||||
const speed = 0.4 + depth * 1.35;
|
const speed = 0.4 + depth * 1.35;
|
||||||
|
|
||||||
const distance = 150;
|
const distance = 150;
|
||||||
const duration = distance / speed;
|
const duration = distance / speed;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width: rand(180, 320) * scale,
|
width: rand(300, 320) * scale,
|
||||||
height: rand(16, 32) * scale,
|
height: 32 * scale,
|
||||||
x: rand(-80, 100),
|
x: (i / COUNT) * 200 - 50,
|
||||||
y: rand(-80, 100),
|
y: ((i * 137.5) % 100) - 20,
|
||||||
opacity: 0.25 + depth * 0.65,
|
opacity: 0.25 + depth * 0.65,
|
||||||
duration,
|
duration,
|
||||||
delay: rand(0, duration),
|
delay: rand(0, duration),
|
||||||
scale,
|
scale,
|
||||||
};
|
};
|
||||||
});
|
})
|
||||||
|
.sort((a, b) => a.scale - b.scale);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="absolute bg blur-[2px]">
|
<div class="absolute bg blur-[2px]">
|
||||||
<div
|
<div
|
||||||
class="relative h-full w-full from-0% to-90% bg-gradient-to-b from-transparent to-black z-50"
|
class="relative h-full w-full from-0% to-90% bg-gradient-to-b from-transparent to-black z-50"
|
||||||
></div>
|
></div>
|
||||||
{#each streaks as s}
|
{#each streaks as s, index (index)}
|
||||||
<div
|
<div
|
||||||
class="streak drop-shadow-lg"
|
class="streak drop-shadow-lg transition-opacity duration-1000"
|
||||||
style="
|
style="
|
||||||
width:{s.width}px;
|
width:{s.width}px;
|
||||||
height:{s.height}px;
|
height:{s.height}px;
|
||||||
left:{s.x}%;
|
left:{s.x}%;
|
||||||
top:{s.y}%;
|
top:{s.y}%;
|
||||||
opacity:{s.opacity};
|
opacity:{$reduceAnimations ? 0 : s.opacity};
|
||||||
animation-duration:{s.duration}s;
|
animation-duration:{s.duration}s;
|
||||||
animation-delay:-{s.delay}s;
|
animation-delay:-{s.delay}s;
|
||||||
"
|
"
|
||||||
@@ -54,7 +60,12 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: linear-gradient(180deg, #8f2f64 0%, #963f75 40%, #9a6c7f 100%);
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
var(--color-primary-300) 0%,
|
||||||
|
var(--color-primary-600) 40%,
|
||||||
|
var(--color-primary-900) 100%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.streak {
|
.streak {
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import NyanCat from '$assets/nyan-cat.gif';
|
||||||
|
import { reduceAnimations } from '@/userSettings';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
|
//TODO: maybe dynamic background images fetched from ezpp?
|
||||||
|
|
||||||
|
const COUNT = 18;
|
||||||
|
|
||||||
|
function rand(min: number, max: number) {
|
||||||
|
return Math.random() * (max - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
const streaks = Array.from({ length: COUNT })
|
||||||
|
.map((_, i) => {
|
||||||
|
const depth = rand(0, 2);
|
||||||
|
|
||||||
|
const scale = 0.6 + depth * 0.8;
|
||||||
|
const speed = 0.4 + depth * 1.35;
|
||||||
|
|
||||||
|
const distance = 150;
|
||||||
|
const duration = distance / speed;
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: 290 * scale,
|
||||||
|
height: 140 * scale,
|
||||||
|
x: (i / COUNT) * 200 - 50,
|
||||||
|
y: ((i * 137.5) % 100) - 20,
|
||||||
|
opacity: 0.25 + depth * 0.65,
|
||||||
|
duration,
|
||||||
|
delay: rand(0, duration),
|
||||||
|
scale,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.scale - b.scale);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="absolute bg blur-[2px]">
|
||||||
|
<div
|
||||||
|
class="relative h-full w-full from-0% to-90% bg-gradient-to-b from-transparent to-black z-50"
|
||||||
|
></div>
|
||||||
|
{#each streaks as s, index (index)}
|
||||||
|
<div
|
||||||
|
class="streak drop-shadow-lg transition-opacity duration-1000"
|
||||||
|
style="
|
||||||
|
width:{s.width}px;
|
||||||
|
height:{s.height}px;
|
||||||
|
left:{s.x}%;
|
||||||
|
top:{s.y}%;
|
||||||
|
opacity:{$reduceAnimations ? 0 : s.opacity};
|
||||||
|
animation-duration:{s.duration}s;
|
||||||
|
animation-delay:-{s.delay}s;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={NyanCat}
|
||||||
|
alt="nyan cat"
|
||||||
|
class="w-full h-full"
|
||||||
|
style="opacity:{$reduceAnimations ? 0 : s.opacity};"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bg {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
var(--color-blue-800) 0%,
|
||||||
|
var(--color-blue-600) 40%,
|
||||||
|
var(--color-blue-900) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.streak {
|
||||||
|
position: absolute;
|
||||||
|
animation: move linear infinite;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes move {
|
||||||
|
from {
|
||||||
|
transform: translate(-250vw, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translate(250vw, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="group flex items-center h-12 rounded-full bg-pink-400 disabled:bg-pink-400/70 shadow-lg shadow-primary/25 transition-all not-disabled:hover:shadow-primary/40 not-disabled:hover:brightness-110 not-disabled:active:scale-[0.98] cursor-pointer w-fit"
|
class="group flex items-center h-12 rounded-full bg-primary-300 disabled:bg-primary-300/70 shadow-lg shadow-primary/25 transition-all not-disabled:hover:shadow-primary/40 not-disabled:hover:brightness-110 not-disabled:active:scale-[0.98] cursor-pointer w-fit"
|
||||||
disabled={prop.disabled}
|
disabled={prop.disabled}
|
||||||
onclick={prop.onClick}
|
onclick={prop.onClick}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<DropdownMenuPrimitive.Item
|
<DropdownMenuPrimitive.Item
|
||||||
bind:ref
|
bind:ref
|
||||||
class={cn(
|
class={cn(
|
||||||
'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
'data-[highlighted]:bg-primary data-[highlighted]:text-primary-foreground relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||||
inset && 'pl-8',
|
inset && 'pl-8',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
// Props
|
||||||
|
let { children, class: className = '', topOffset = 0 } = $props();
|
||||||
|
|
||||||
|
let wrapper: HTMLDivElement;
|
||||||
|
let container: HTMLDivElement;
|
||||||
|
let thumb: HTMLDivElement;
|
||||||
|
|
||||||
|
// Thumb state
|
||||||
|
let thumbHeight = $state(20);
|
||||||
|
let thumbTop = $state(0);
|
||||||
|
|
||||||
|
// Drag state
|
||||||
|
let dragging = false;
|
||||||
|
let startY = 0;
|
||||||
|
let startScroll = 0;
|
||||||
|
|
||||||
|
/** Update thumb height & position */
|
||||||
|
function updateScrollbar() {
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = container;
|
||||||
|
const scrollable = scrollHeight - clientHeight;
|
||||||
|
|
||||||
|
if (scrollable <= 0) {
|
||||||
|
thumbHeight = 0;
|
||||||
|
thumbTop = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbHeight = Math.max((clientHeight / scrollHeight) * clientHeight, 24);
|
||||||
|
|
||||||
|
const trackHeight = clientHeight - thumbHeight - topOffset;
|
||||||
|
thumbTop = (scrollTop / scrollable) * trackHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Start dragging the thumb */
|
||||||
|
function startDrag(e: PointerEvent) {
|
||||||
|
dragging = true;
|
||||||
|
startY = e.clientY;
|
||||||
|
startScroll = container.scrollTop;
|
||||||
|
thumb.setPointerCapture(e.pointerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Drag the thumb */
|
||||||
|
function drag(e: PointerEvent) {
|
||||||
|
if (!dragging) return;
|
||||||
|
|
||||||
|
const delta = e.clientY - startY;
|
||||||
|
const { scrollHeight, clientHeight } = container;
|
||||||
|
const scrollable = scrollHeight - clientHeight;
|
||||||
|
const trackHeight = clientHeight - thumbHeight - topOffset;
|
||||||
|
|
||||||
|
container.scrollTop = startScroll + (delta / trackHeight) * scrollable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** End dragging */
|
||||||
|
function endDrag(e: PointerEvent) {
|
||||||
|
dragging = false;
|
||||||
|
thumb.releasePointerCapture(e.pointerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reactive effect to update scrollbar
|
||||||
|
$effect(() => updateScrollbar());
|
||||||
|
|
||||||
|
// ResizeObserver for dynamic content
|
||||||
|
$effect(() => {
|
||||||
|
if (!container) return;
|
||||||
|
const observer = new ResizeObserver(updateScrollbar);
|
||||||
|
observer.observe(container);
|
||||||
|
return () => observer.disconnect();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="wrapper" bind:this={wrapper}>
|
||||||
|
<!-- Scrollable content -->
|
||||||
|
<div class={`content ${className}`} bind:this={container} onscroll={updateScrollbar}>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Custom scrollbar -->
|
||||||
|
<div class="scrollbar" style="top: {topOffset}px;">
|
||||||
|
<div
|
||||||
|
class="thumb"
|
||||||
|
bind:this={thumb}
|
||||||
|
onpointerdown={startDrag}
|
||||||
|
onpointermove={drag}
|
||||||
|
onpointerup={endDrag}
|
||||||
|
onpointercancel={endDrag}
|
||||||
|
style="height:{thumbHeight}px; transform:translateY({thumbTop}px);"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollable content */
|
||||||
|
.content {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.content::-webkit-scrollbar {
|
||||||
|
display: none; /* Chrome, Safari, Edge */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar track */
|
||||||
|
.scrollbar {
|
||||||
|
position: absolute;
|
||||||
|
right: 2px;
|
||||||
|
bottom: 0;
|
||||||
|
width: 12px;
|
||||||
|
background: var(--color-theme-950);
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thumb */
|
||||||
|
.thumb {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--color-theme-800);
|
||||||
|
border-radius: 999px;
|
||||||
|
cursor: grab;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
background: var(--color-theme-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb:hover {
|
||||||
|
background: var(--color-theme-700);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
bind:ref
|
bind:ref
|
||||||
{value}
|
{value}
|
||||||
class={cn(
|
class={cn(
|
||||||
'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
'data-[highlighted]:bg-primary-300 data-[highlighted]:text-primary-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createElement } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import type { SileoOptions } from 'sileo';
|
||||||
|
|
||||||
|
let props: SileoOptions = $props();
|
||||||
|
let rootEl: HTMLElement;
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const root = createRoot(rootEl);
|
||||||
|
|
||||||
|
import('sileo').then(({ Toaster }) => {
|
||||||
|
const sileo = createElement(Toaster, { ...props });
|
||||||
|
root.render(sileo);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => root.unmount();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={rootEl}></div>
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { launcherVersion } from '@/global';
|
import { launcherVersion } from '@/global';
|
||||||
import Badge from '../badge/badge.svelte';
|
import Badge from '../badge/badge.svelte';
|
||||||
|
import { reduceAnimations } from '@/userSettings';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const appWindow = getCurrentWindow();
|
const appWindow = getCurrentWindow();
|
||||||
@@ -20,22 +21,39 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div data-tauri-drag-region class="titlebar z-[100]">
|
<div data-tauri-drag-region class="titlebar z-[100]">
|
||||||
<div class="titlebar-button rounded transition-colors duration-75" id="titlebar-minimize">
|
<div class="relative flex items-center justify-center p-1 group" id="titlebar-minimize">
|
||||||
<Minimize size={14} />
|
<div
|
||||||
|
class="absolute inset-0 bg-background rounded
|
||||||
|
scale-80 opacity-0
|
||||||
|
group-hover:opacity-100
|
||||||
|
group-hover:scale-100
|
||||||
|
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
|
||||||
|
origin-center"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<Minimize class="z-10 pointer-events-none" size={14} />
|
||||||
</div>
|
</div>
|
||||||
<div class="titlebar-button close rounded transition-colors duration-75" id="titlebar-close">
|
<div class="relative flex items-center justify-center p-1 group" id="titlebar-close">
|
||||||
<Close size={14} />
|
<div
|
||||||
|
class="absolute inset-0 bg-red-500 rounded
|
||||||
|
scale-80 opacity-0
|
||||||
|
group-hover:opacity-100
|
||||||
|
group-hover:scale-100
|
||||||
|
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
|
||||||
|
origin-center"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<Close class="z-10 pointer-events-none" size={14} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.titlebar {
|
.titlebar {
|
||||||
height: 35px;
|
height: 35px;
|
||||||
/* background: #040612; */
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 10px;
|
gap: 5px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -45,19 +63,4 @@
|
|||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
.titlebar-button {
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 25px;
|
|
||||||
height: 25px;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
}
|
|
||||||
.titlebar-button:hover {
|
|
||||||
&.close {
|
|
||||||
background: #c22e2e;
|
|
||||||
}
|
|
||||||
background: #2d3049;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -44,8 +44,7 @@ export class Config {
|
|||||||
this.encrypt && this.crypto ? this.crypto.decrypt(fileStream) : fileStream
|
this.encrypt && this.crypto ? this.crypto.decrypt(fileStream) : fileStream
|
||||||
) as Record<string, unknown>;
|
) as Record<string, unknown>;
|
||||||
this.config = decryptedJSON;
|
this.config = decryptedJSON;
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.log(err);
|
|
||||||
this.config = {};
|
this.config = {};
|
||||||
await this.save();
|
await this.save();
|
||||||
}
|
}
|
||||||
@@ -65,13 +64,11 @@ export class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
value(key: string) {
|
value(key: string) {
|
||||||
console.log('Accessing config key:', key, this.config[key]);
|
|
||||||
return {
|
return {
|
||||||
set: <T>(val: T) => {
|
set: <T>(val: T) => {
|
||||||
this.config[key] = val;
|
this.config[key] = val;
|
||||||
},
|
},
|
||||||
exists: () => {
|
exists: () => {
|
||||||
console.log('Checking existence of key:', key, this.config[key] !== undefined);
|
|
||||||
return this.config[key] !== undefined;
|
return this.config[key] !== undefined;
|
||||||
},
|
},
|
||||||
get: <T>(fallback: T): T => {
|
get: <T>(fallback: T): T => {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export function estimateRefreshRate(): Promise<number> {
|
|||||||
frames++;
|
frames++;
|
||||||
|
|
||||||
if (now - last >= 1000) {
|
if (now - last >= 1000) {
|
||||||
console.log(`Estimated Refresh Rate: ${frames - 2} FPS`);
|
|
||||||
resolve(frames - 2);
|
resolve(frames - 2);
|
||||||
} else {
|
} else {
|
||||||
requestAnimationFrame(loop);
|
requestAnimationFrame(loop);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export const currentLoadingInfo = writable<string>('Initializing...');
|
|||||||
export const firstStartup = writable<boolean>(false);
|
export const firstStartup = writable<boolean>(false);
|
||||||
|
|
||||||
export const launching = writable<boolean>(false);
|
export const launching = writable<boolean>(false);
|
||||||
|
export const nyanCatSong = writable<HTMLAudioElement | undefined>(undefined);
|
||||||
|
|
||||||
export const serverPing = writable<number | undefined>(undefined);
|
export const serverPing = writable<number | undefined>(undefined);
|
||||||
export const serverConnectionFails = writable(0);
|
export const serverConnectionFails = writable(0);
|
||||||
@@ -31,7 +32,8 @@ export const serverConnectionFails = writable(0);
|
|||||||
export const onlineFriends = writable<number | undefined>(undefined);
|
export const onlineFriends = writable<number | undefined>(undefined);
|
||||||
|
|
||||||
export const beatmapSets = writable<number | undefined>(undefined);
|
export const beatmapSets = writable<number | undefined>(undefined);
|
||||||
export const skins = writable<number | undefined>(undefined);
|
export const skinsCount = writable<number | undefined>(undefined);
|
||||||
|
export const skins = writable<{ name: string; author: string | undefined; modified: number }[]>([]);
|
||||||
|
|
||||||
export const osuStream = writable<string | undefined>(undefined);
|
export const osuStream = writable<string | undefined>(undefined);
|
||||||
export const osuBuild = writable<string | undefined>(undefined);
|
export const osuBuild = writable<string | undefined>(undefined);
|
||||||
|
|||||||
@@ -3,7 +3,19 @@ import type { UpdateFile, UpdateStatus } from './types';
|
|||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import { betterFetch } from '@better-fetch/fetch';
|
import { betterFetch } from '@better-fetch/fetch';
|
||||||
|
|
||||||
const updateUrl = 'https://next.ez-pp.farm/api/ezpplauncher';
|
const updateUrl = 'https://ez-pp.farm/api/ezpplauncher';
|
||||||
|
|
||||||
|
export function normalizeOsuName(name: string): string {
|
||||||
|
return (
|
||||||
|
name
|
||||||
|
.trim()
|
||||||
|
// Remove leading symbols/spaces
|
||||||
|
.replace(/^[^a-zA-Z0-9]+/, '')
|
||||||
|
// Normalize spacing
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.toLowerCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const getHWID = async () => {
|
export const getHWID = async () => {
|
||||||
const hwid = await invoke('get_hwid');
|
const hwid = await invoke('get_hwid');
|
||||||
@@ -55,13 +67,6 @@ export const getBeatmapSetsCount = async (folder: string) => {
|
|||||||
return typeof result === 'number' ? result : 0;
|
return typeof result === 'number' ? result : 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSkinsCount = async (folder: string) => {
|
|
||||||
const result = await invoke('get_skins_count', {
|
|
||||||
folder,
|
|
||||||
});
|
|
||||||
return typeof result === 'number' ? result : 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSkin = async (folder: string) => {
|
export const getSkin = async (folder: string) => {
|
||||||
const result = await invoke('get_osu_skin', {
|
const result = await invoke('get_osu_skin', {
|
||||||
folder,
|
folder,
|
||||||
@@ -82,7 +87,6 @@ export const getEZPPLauncherStreams = async () => {
|
|||||||
if (!resp.error) {
|
if (!resp.error) {
|
||||||
return resp.data.streams;
|
return resp.data.streams;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -152,3 +156,8 @@ export const downloadUpdate = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const installUpdate = async () => await invoke('install_ezpp_launcher_update');
|
export const installUpdate = async () => await invoke('install_ezpp_launcher_update');
|
||||||
|
export const getSkins = async (folder: string) =>
|
||||||
|
await invoke<{ name: string; author: string | undefined; modified: number }[] | undefined>(
|
||||||
|
'get_skins',
|
||||||
|
{ folder }
|
||||||
|
);
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
const specialOrder = "'-!#$%&(),;@[]^_`{}~+=";
|
||||||
|
|
||||||
|
const priority = Object.fromEntries([...specialOrder].map((c, i) => [c, i]));
|
||||||
|
|
||||||
|
const osuSkinsCollator = new Intl.Collator(undefined, {
|
||||||
|
numeric: true,
|
||||||
|
sensitivity: 'base',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sortSkins = (skins: { name: string, author: string | undefined, modified: number}[]) =>
|
||||||
|
skins.sort((a, b) => {
|
||||||
|
const pa = priority[a.name[0]];
|
||||||
|
const pb = priority[b.name[0]];
|
||||||
|
|
||||||
|
if (pa !== undefined || pb !== undefined) {
|
||||||
|
if (pa === undefined) return 1;
|
||||||
|
if (pb === undefined) return -1;
|
||||||
|
return pa - pb;
|
||||||
|
}
|
||||||
|
|
||||||
|
return osuSkinsCollator.compare(a.name, b.name);
|
||||||
|
});
|
||||||
@@ -44,7 +44,6 @@ export const formatTimeReadable = (initialSeconds: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const releaseStreamToReadable = (releaseStream: string) => {
|
export const releaseStreamToReadable = (releaseStream: string) => {
|
||||||
console.log(releaseStream.replaceAll(' ', '').toLowerCase());
|
|
||||||
if (releaseStream.replaceAll(' ', '').toLowerCase() === 'cuttingedge') return 'Cutting Edge';
|
if (releaseStream.replaceAll(' ', '').toLowerCase() === 'cuttingedge') return 'Cutting Edge';
|
||||||
return 'Stable';
|
return 'Stable';
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Logo from '$assets/logo.png';
|
import Logo from '$assets/logo.png';
|
||||||
|
import NyanCatSong from '$assets/nyan-cat.mp3';
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
|
|
||||||
import Titlebar from '@/components/ui/titlebar/titlebar.svelte';
|
import Titlebar from '@/components/ui/titlebar/titlebar.svelte';
|
||||||
@@ -9,6 +10,7 @@
|
|||||||
discordPresence,
|
discordPresence,
|
||||||
firstStartup,
|
firstStartup,
|
||||||
launcherVersion,
|
launcherVersion,
|
||||||
|
nyanCatSong,
|
||||||
platform,
|
platform,
|
||||||
presenceLoading,
|
presenceLoading,
|
||||||
setupValues,
|
setupValues,
|
||||||
@@ -25,18 +27,23 @@
|
|||||||
userSettings,
|
userSettings,
|
||||||
} from '@/userSettings';
|
} from '@/userSettings';
|
||||||
import { Buffer } from 'buffer';
|
import { Buffer } from 'buffer';
|
||||||
import { Toaster } from '@/components/ui/sonner';
|
import SileoToast from '@/components/ui/sileo/sileo-toast.svelte';
|
||||||
import { userAuth } from '@/userAuthentication';
|
import { userAuth } from '@/userAuthentication';
|
||||||
import { encryptString, exit, getLauncherVersion, getPlatform } from '@/osuUtil';
|
import { encryptString, exit, getLauncherVersion, getPlatform } from '@/osuUtil';
|
||||||
import Button from '@/components/ui/button/button.svelte';
|
import Button from '@/components/ui/button/button.svelte';
|
||||||
import * as presence from '@/presence';
|
import * as presence from '@/presence';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
import '@fontsource/sora';
|
import '@fontsource/sora';
|
||||||
import '@fontsource/space-mono';
|
import '@fontsource/space-mono';
|
||||||
|
import AnimatedBg from '@/components/ui/animated-bg/AnimatedBg.svelte';
|
||||||
|
import NyanCatBg from '@/components/ui/animated-bg/NyanCatBg.svelte';
|
||||||
|
import { sileo } from 'sileo';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|
||||||
let unsupported_platform = $state<boolean>(false);
|
let unsupported_platform = $state<boolean>(false);
|
||||||
|
let secret_enabled = $state<boolean>(false);
|
||||||
|
|
||||||
function disableReload() {
|
function disableReload() {
|
||||||
if (window.location.hostname !== 'tauri.localhost') {
|
if (window.location.hostname !== 'tauri.localhost') {
|
||||||
@@ -86,6 +93,19 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let konami_code = [
|
||||||
|
'ArrowUp',
|
||||||
|
'ArrowUp',
|
||||||
|
'ArrowDown',
|
||||||
|
'ArrowDown',
|
||||||
|
'ArrowLeft',
|
||||||
|
'ArrowRight',
|
||||||
|
'ArrowLeft',
|
||||||
|
'ArrowRight',
|
||||||
|
'b',
|
||||||
|
'a',
|
||||||
|
];
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
window.Buffer = Buffer;
|
window.Buffer = Buffer;
|
||||||
|
|
||||||
@@ -97,6 +117,108 @@
|
|||||||
const isFirstStartup = await $userSettings.init();
|
const isFirstStartup = await $userSettings.init();
|
||||||
$userAuth.init();
|
$userAuth.init();
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key.toLowerCase() === konami_code[0].toLowerCase()) {
|
||||||
|
konami_code.shift();
|
||||||
|
if (konami_code.length === 0) {
|
||||||
|
if (secret_enabled) {
|
||||||
|
sileo.success({
|
||||||
|
title: 'Cheatcode deactivated!',
|
||||||
|
duration: 3000,
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const audio = $nyanCatSong;
|
||||||
|
if (audio) {
|
||||||
|
const fadeOutDuration = 2000; // 2 seconds
|
||||||
|
const fadeOutSteps = 20;
|
||||||
|
const fadeOutStepTime = fadeOutDuration / fadeOutSteps;
|
||||||
|
const volumeStep = audio.volume / fadeOutSteps;
|
||||||
|
|
||||||
|
const fadeOutInterval = setInterval(() => {
|
||||||
|
if (audio.volume > 0) {
|
||||||
|
audio.volume = Math.max(0, audio.volume - volumeStep);
|
||||||
|
} else {
|
||||||
|
audio.pause();
|
||||||
|
clearInterval(fadeOutInterval);
|
||||||
|
}
|
||||||
|
}, fadeOutStepTime);
|
||||||
|
}
|
||||||
|
nyanCatSong.set(undefined);
|
||||||
|
secret_enabled = false;
|
||||||
|
konami_code = [
|
||||||
|
'ArrowUp',
|
||||||
|
'ArrowUp',
|
||||||
|
'ArrowDown',
|
||||||
|
'ArrowDown',
|
||||||
|
'ArrowLeft',
|
||||||
|
'ArrowRight',
|
||||||
|
'ArrowLeft',
|
||||||
|
'ArrowRight',
|
||||||
|
'b',
|
||||||
|
'a',
|
||||||
|
];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sileo.success({
|
||||||
|
title: 'Cheatcode activated!',
|
||||||
|
duration: 3000,
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const audio = new Audio(NyanCatSong);
|
||||||
|
audio.loop = true;
|
||||||
|
audio.volume = 0;
|
||||||
|
audio.play();
|
||||||
|
const fadeInVolume = 0.15;
|
||||||
|
const fadeInDuration = 2000; // 2 seconds
|
||||||
|
const fadeInSteps = 20;
|
||||||
|
const fadeInStepTime = fadeInDuration / fadeInSteps;
|
||||||
|
const volumeStep = (fadeInVolume - audio.volume) / fadeInSteps;
|
||||||
|
|
||||||
|
const fadeInInterval = setInterval(() => {
|
||||||
|
if (audio.volume < fadeInVolume) {
|
||||||
|
audio.volume = Math.min(fadeInVolume, audio.volume + volumeStep);
|
||||||
|
} else {
|
||||||
|
clearInterval(fadeInInterval);
|
||||||
|
}
|
||||||
|
}, fadeInStepTime);
|
||||||
|
nyanCatSong.set(audio);
|
||||||
|
|
||||||
|
secret_enabled = true;
|
||||||
|
konami_code = [
|
||||||
|
'ArrowUp',
|
||||||
|
'ArrowUp',
|
||||||
|
'ArrowDown',
|
||||||
|
'ArrowDown',
|
||||||
|
'ArrowLeft',
|
||||||
|
'ArrowRight',
|
||||||
|
'ArrowLeft',
|
||||||
|
'ArrowRight',
|
||||||
|
'b',
|
||||||
|
'a',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
konami_code = [
|
||||||
|
'ArrowUp',
|
||||||
|
'ArrowUp',
|
||||||
|
'ArrowDown',
|
||||||
|
'ArrowDown',
|
||||||
|
'ArrowLeft',
|
||||||
|
'ArrowRight',
|
||||||
|
'ArrowLeft',
|
||||||
|
'ArrowRight',
|
||||||
|
'b',
|
||||||
|
'a',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
currentLoadingInfo.set('Loading config...');
|
currentLoadingInfo.set('Loading config...');
|
||||||
const config_patching = $userSettings.value('patching');
|
const config_patching = $userSettings.value('patching');
|
||||||
const config_custom_cursor = $userSettings.value('custom_cursor');
|
const config_custom_cursor = $userSettings.value('custom_cursor');
|
||||||
@@ -150,7 +272,7 @@
|
|||||||
<OsuCursor smoothCursor={$cursorSmoothening} />
|
<OsuCursor smoothCursor={$cursorSmoothening} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Toaster richColors closeButton theme="dark" position="top-right" />
|
<SileoToast position="top-center" fill="black" />
|
||||||
|
|
||||||
<Titlebar />
|
<Titlebar />
|
||||||
|
|
||||||
@@ -178,5 +300,16 @@
|
|||||||
</AlertDialog.Root>
|
</AlertDialog.Root>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
<div class="opacity-30">
|
||||||
|
{#if secret_enabled}
|
||||||
|
<div transition:fade={{ duration: $reduceAnimations ? 0 : 500 }}>
|
||||||
|
<NyanCatBg />
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div transition:fade={{ duration: $reduceAnimations ? 0 : 500 }}>
|
||||||
|
<AnimatedBg />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
launcherVersion,
|
launcherVersion,
|
||||||
launching,
|
launching,
|
||||||
newVersion,
|
newVersion,
|
||||||
|
nyanCatSong,
|
||||||
osuBuild,
|
osuBuild,
|
||||||
osuStream,
|
osuStream,
|
||||||
platform,
|
platform,
|
||||||
@@ -20,11 +21,12 @@
|
|||||||
serverConnectionFails,
|
serverConnectionFails,
|
||||||
serverPing,
|
serverPing,
|
||||||
skins,
|
skins,
|
||||||
|
skinsCount,
|
||||||
trackingEnabled,
|
trackingEnabled,
|
||||||
} from '@/global';
|
} from '@/global';
|
||||||
import {
|
import {
|
||||||
LoaderCircle,
|
LoaderCircle,
|
||||||
Music2,
|
Music,
|
||||||
Wifi,
|
Wifi,
|
||||||
WifiOff,
|
WifiOff,
|
||||||
Drum,
|
Drum,
|
||||||
@@ -37,6 +39,8 @@
|
|||||||
ArrowRight,
|
ArrowRight,
|
||||||
Settings,
|
Settings,
|
||||||
House,
|
House,
|
||||||
|
Paintbrush,
|
||||||
|
FileQuestionMark,
|
||||||
} from 'lucide-svelte';
|
} from 'lucide-svelte';
|
||||||
import NumberFlow from '@number-flow/svelte';
|
import NumberFlow from '@number-flow/svelte';
|
||||||
import * as AlertDialog from '@/components/ui/alert-dialog';
|
import * as AlertDialog from '@/components/ui/alert-dialog';
|
||||||
@@ -49,7 +53,7 @@
|
|||||||
releaseStreamToReadable,
|
releaseStreamToReadable,
|
||||||
urlIsValidImage,
|
urlIsValidImage,
|
||||||
} from '@/utils';
|
} from '@/utils';
|
||||||
import { fade, fly, scale } from 'svelte/transition';
|
import { fade, fly } from 'svelte/transition';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import Label from '@/components/ui/label/label.svelte';
|
import Label from '@/components/ui/label/label.svelte';
|
||||||
import {
|
import {
|
||||||
@@ -64,7 +68,6 @@
|
|||||||
} from '@/userSettings';
|
} from '@/userSettings';
|
||||||
import Input from '@/components/ui/input/input.svelte';
|
import Input from '@/components/ui/input/input.svelte';
|
||||||
import { open } from '@tauri-apps/plugin-dialog';
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
import { toast } from 'svelte-sonner';
|
|
||||||
import { currentUser, userAuth } from '@/userAuthentication';
|
import { currentUser, userAuth } from '@/userAuthentication';
|
||||||
import {
|
import {
|
||||||
getGamemodeInt,
|
getGamemodeInt,
|
||||||
@@ -85,7 +88,7 @@
|
|||||||
getEZPPLauncherUpdateFiles,
|
getEZPPLauncherUpdateFiles,
|
||||||
getReleaseStream,
|
getReleaseStream,
|
||||||
getSkin,
|
getSkin,
|
||||||
getSkinsCount,
|
getSkins,
|
||||||
getVersion,
|
getVersion,
|
||||||
hasNet8,
|
hasNet8,
|
||||||
hasOsuWinello,
|
hasOsuWinello,
|
||||||
@@ -108,7 +111,7 @@
|
|||||||
import * as DropdownMenu from '@/components/ui/dropdown-menu';
|
import * as DropdownMenu from '@/components/ui/dropdown-menu';
|
||||||
import DownloadButton from '@/components/ui/download-button/DownloadButton.svelte';
|
import DownloadButton from '@/components/ui/download-button/DownloadButton.svelte';
|
||||||
import { animate } from 'animejs';
|
import { animate } from 'animejs';
|
||||||
import AnimatedBg from '@/components/ui/animated-bg/AnimatedBg.svelte';
|
import { sileo } from 'sileo';
|
||||||
|
|
||||||
let selectedView = $state('home');
|
let selectedView = $state('home');
|
||||||
let progress = $state(-1);
|
let progress = $state(-1);
|
||||||
@@ -146,22 +149,37 @@
|
|||||||
}
|
}
|
||||||
const validFolder = await isValidOsuFolder(selectedPath);
|
const validFolder = await isValidOsuFolder(selectedPath);
|
||||||
if (!validFolder) {
|
if (!validFolder) {
|
||||||
toast.error('Oops...', {
|
sileo.error({
|
||||||
|
title: 'Hmm...',
|
||||||
description:
|
description:
|
||||||
'The selected folder is not a valid osu! installation folder. Please select the correct folder.',
|
'The selected folder is not a valid osu! installation folder. Please select the correct folder.',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
osuInstallationPath.set(selectedPath);
|
osuInstallationPath.set(selectedPath);
|
||||||
$userSettings.value('osu_installation_path').set(selectedPath);
|
$userSettings.value('osu_installation_path').set(selectedPath);
|
||||||
$userSettings.save();
|
$userSettings.save();
|
||||||
toast.success('osu! installation path set successfully.');
|
sileo.success({
|
||||||
|
title: 'Yay!',
|
||||||
|
description: 'osu! installation path set successfully.',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const beatmapSetCount: number | null = await getBeatmapSetsCount(selectedPath);
|
const beatmapSetCount: number | null = await getBeatmapSetsCount(selectedPath);
|
||||||
if (beatmapSetCount) beatmapSets.set(beatmapSetCount);
|
if (beatmapSetCount) beatmapSets.set(beatmapSetCount);
|
||||||
|
|
||||||
const skinsCount: number | null = await getSkinsCount(selectedPath);
|
const skins_list = await getSkins(selectedPath);
|
||||||
if (skinsCount !== null) skins.set(skinsCount);
|
if (skins_list) {
|
||||||
|
skins.set(skins_list);
|
||||||
|
skinsCount.set(skins_list.length);
|
||||||
|
}
|
||||||
|
|
||||||
const skin: string = await getSkin(selectedPath);
|
const skin: string = await getSkin(selectedPath);
|
||||||
currentSkin.set(skin);
|
currentSkin.set(skin);
|
||||||
@@ -175,30 +193,49 @@
|
|||||||
|
|
||||||
const launch = async () => {
|
const launch = async () => {
|
||||||
if ($launching) return;
|
if ($launching) return;
|
||||||
|
launchInfo = 'Checking if osu! is already running...';
|
||||||
|
launching.set(true);
|
||||||
if ($trackingEnabled) umami.track('app_launch_osu');
|
if ($trackingEnabled) umami.track('app_launch_osu');
|
||||||
const osuRunning = await isOsuRunning();
|
const osuRunning = await isOsuRunning();
|
||||||
if (osuRunning) {
|
if (osuRunning) {
|
||||||
toast.error('Hold on a second!', {
|
sileo.error({
|
||||||
|
title: 'Hold on a second!',
|
||||||
description:
|
description:
|
||||||
'osu! is currently running, please exit osu! before launching via EZPPLauncher!',
|
'osu! is currently running, please exit osu! before launching via EZPPLauncher!',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
launching.set(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
launchInfo = 'Checking osu! installation...';
|
||||||
if (!$osuBuild) {
|
if (!$osuBuild) {
|
||||||
toast.error('Hmmm...', {
|
sileo.error({
|
||||||
|
title: 'Hmmm...',
|
||||||
description: 'There was an issue detecting your installed osu! version',
|
description: 'There was an issue detecting your installed osu! version',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
launching.set(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const osuPath = $osuInstallationPath;
|
const osuPath = $osuInstallationPath;
|
||||||
|
|
||||||
launchInfo = 'Validating osu! installation...';
|
launchInfo = 'Validating osu! installation...';
|
||||||
launching.set(true);
|
|
||||||
|
|
||||||
const validFolder = await isValidOsuFolder(osuPath);
|
const validFolder = await isValidOsuFolder(osuPath);
|
||||||
if (!validFolder) {
|
if (!validFolder) {
|
||||||
toast.error('Hmmm...', {
|
sileo.error({
|
||||||
|
title: 'Hmmm...',
|
||||||
description: 'Your selected osu! installation folder is not valid.',
|
description: 'Your selected osu! installation folder is not valid.',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
launching.set(false);
|
launching.set(false);
|
||||||
return;
|
return;
|
||||||
@@ -206,15 +243,25 @@
|
|||||||
|
|
||||||
if ($platform === 'linux') {
|
if ($platform === 'linux') {
|
||||||
if (!(await hasWMCTRL())) {
|
if (!(await hasWMCTRL())) {
|
||||||
toast.error('Hmmm...', {
|
sileo.error({
|
||||||
|
title: 'Hmmm...',
|
||||||
description: 'wmctrl seems to be missing, please install via AUR.',
|
description: 'wmctrl seems to be missing, please install via AUR.',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
launching.set(false);
|
launching.set(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(await hasOsuWinello())) {
|
if (!(await hasOsuWinello())) {
|
||||||
toast.error('Hmmm...', {
|
sileo.error({
|
||||||
|
title: 'Hmmm...',
|
||||||
description: 'osu-winello seems to be missing, please install it.',
|
description: 'osu-winello seems to be missing, please install it.',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
launching.set(false);
|
launching.set(false);
|
||||||
return;
|
return;
|
||||||
@@ -249,7 +296,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
|
||||||
launchError = err as Error;
|
launchError = err as Error;
|
||||||
launching.set(false);
|
launching.set(false);
|
||||||
return;
|
return;
|
||||||
@@ -258,8 +304,13 @@
|
|||||||
try {
|
try {
|
||||||
const streamInfo = await osuapi.latestBuildVersion('stable40');
|
const streamInfo = await osuapi.latestBuildVersion('stable40');
|
||||||
if (!streamInfo) {
|
if (!streamInfo) {
|
||||||
toast.error('Hmmm...', {
|
sileo.error({
|
||||||
|
title: 'Hmmm...',
|
||||||
description: 'Failed to check for updates, maybe osu! is down?',
|
description: 'Failed to check for updates, maybe osu! is down?',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
launching.set(false);
|
launching.set(false);
|
||||||
return;
|
return;
|
||||||
@@ -268,8 +319,13 @@
|
|||||||
const releaseStream = await getReleaseStream(osuPath);
|
const releaseStream = await getReleaseStream(osuPath);
|
||||||
|
|
||||||
if (releaseStream === undefined) {
|
if (releaseStream === undefined) {
|
||||||
toast.error('Hmmm...', {
|
sileo.error({
|
||||||
|
title: 'Hmmm...',
|
||||||
description: 'Failed to get osu! release stream.',
|
description: 'Failed to get osu! release stream.',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
launching.set(false);
|
launching.set(false);
|
||||||
return;
|
return;
|
||||||
@@ -277,8 +333,13 @@
|
|||||||
|
|
||||||
// only stable osu! release streams are supported for now
|
// only stable osu! release streams are supported for now
|
||||||
if (!releaseStream.toLowerCase().includes('stable')) {
|
if (!releaseStream.toLowerCase().includes('stable')) {
|
||||||
toast.error('Hmmm...', {
|
sileo.error({
|
||||||
|
title: 'Hmmm...',
|
||||||
description: 'You are not on the stable release stream, please switch to it.',
|
description: 'You are not on the stable release stream, please switch to it.',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
launching.set(false);
|
launching.set(false);
|
||||||
return;
|
return;
|
||||||
@@ -375,6 +436,25 @@
|
|||||||
}
|
}
|
||||||
await new Promise((res) => setTimeout(res, 1500));
|
await new Promise((res) => setTimeout(res, 1500));
|
||||||
launchInfo = 'Launching osu!...';
|
launchInfo = 'Launching osu!...';
|
||||||
|
|
||||||
|
if ($nyanCatSong) {
|
||||||
|
const audio = $nyanCatSong;
|
||||||
|
const fadeOutDuration = 2000; // 2 seconds
|
||||||
|
const fadeOutSteps = 20;
|
||||||
|
const fadeOutStepTime = fadeOutDuration / fadeOutSteps;
|
||||||
|
const volumeStep = audio.volume / fadeOutSteps;
|
||||||
|
|
||||||
|
const fadeOutInterval = setInterval(() => {
|
||||||
|
if (audio.volume > 0) {
|
||||||
|
audio.volume = Math.max(0, audio.volume - volumeStep);
|
||||||
|
} else {
|
||||||
|
clearInterval(fadeOutInterval);
|
||||||
|
audio.pause();
|
||||||
|
audio.currentTime = 0;
|
||||||
|
}
|
||||||
|
}, fadeOutStepTime);
|
||||||
|
}
|
||||||
|
|
||||||
await replaceUIFiles(osuPath, false);
|
await replaceUIFiles(osuPath, false);
|
||||||
await new Promise((res) => setTimeout(res, 1000));
|
await new Promise((res) => setTimeout(res, 1000));
|
||||||
await getCurrentWindow().hide();
|
await getCurrentWindow().hide();
|
||||||
@@ -484,10 +564,30 @@
|
|||||||
if ($trackingEnabled) umami.track('app_exit_osu');
|
if ($trackingEnabled) umami.track('app_exit_osu');
|
||||||
cleanup = true;
|
cleanup = true;
|
||||||
launchInfo = 'Cleaning up...';
|
launchInfo = 'Cleaning up...';
|
||||||
|
|
||||||
|
if ($nyanCatSong) {
|
||||||
|
const audio = $nyanCatSong;
|
||||||
|
audio.play();
|
||||||
|
audio.volume = 0;
|
||||||
|
|
||||||
|
const fadeInVolume = 0.15;
|
||||||
|
const fadeInDuration = 2000; // 2 seconds
|
||||||
|
const fadeInSteps = 20;
|
||||||
|
const fadeInStepTime = fadeInDuration / fadeInSteps;
|
||||||
|
const volumeStep = (fadeInVolume - audio.volume) / fadeInSteps;
|
||||||
|
|
||||||
|
const fadeInInterval = setInterval(() => {
|
||||||
|
if (audio.volume < fadeInVolume) {
|
||||||
|
audio.volume = Math.min(fadeInVolume, audio.volume + volumeStep);
|
||||||
|
} else {
|
||||||
|
clearInterval(fadeInInterval);
|
||||||
|
}
|
||||||
|
}, fadeInStepTime);
|
||||||
|
}
|
||||||
|
|
||||||
await getCurrentWindow().show();
|
await getCurrentWindow().show();
|
||||||
if (presenceUpdater) {
|
if (presenceUpdater) {
|
||||||
window.clearInterval(presenceUpdater);
|
window.clearInterval(presenceUpdater);
|
||||||
console.log('clearing discord presence...');
|
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
presence.updateUser({
|
presence.updateUser({
|
||||||
@@ -501,7 +601,6 @@
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
} catch {}
|
} catch {}
|
||||||
console.log('discord presence cleared...');
|
|
||||||
}
|
}
|
||||||
await new Promise((res) => setTimeout(res, 1000));
|
await new Promise((res) => setTimeout(res, 1000));
|
||||||
await replaceUIFiles(osuPath, true);
|
await replaceUIFiles(osuPath, true);
|
||||||
@@ -514,8 +613,11 @@
|
|||||||
const beatmapSetCount = await getBeatmapSetsCount(osuPath);
|
const beatmapSetCount = await getBeatmapSetsCount(osuPath);
|
||||||
if (beatmapSetCount) beatmapSets.set(beatmapSetCount);
|
if (beatmapSetCount) beatmapSets.set(beatmapSetCount);
|
||||||
|
|
||||||
const skinCount = await getSkinsCount(osuPath);
|
const skinsList = await getSkins(osuPath);
|
||||||
if (skinCount !== null) skins.set(skinCount);
|
if (skinsList) {
|
||||||
|
skins.set(skinsList);
|
||||||
|
skinsCount.set(skinsList.length);
|
||||||
|
}
|
||||||
|
|
||||||
const skin = await getSkin(osuPath);
|
const skin = await getSkin(osuPath);
|
||||||
currentSkin.set(skin);
|
currentSkin.set(skin);
|
||||||
@@ -531,8 +633,13 @@
|
|||||||
cleanup = false;
|
cleanup = false;
|
||||||
const error = err as Error;
|
const error = err as Error;
|
||||||
if (error.name === 'AbortError') {
|
if (error.name === 'AbortError') {
|
||||||
toast.error('Hmmm...', {
|
sileo.error({
|
||||||
|
title: 'Hmmm...',
|
||||||
description: 'Failed to launch.',
|
description: 'Failed to launch.',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
launching.set(false);
|
launching.set(false);
|
||||||
launchError = {
|
launchError = {
|
||||||
@@ -541,8 +648,13 @@
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
launchError = error;
|
launchError = error;
|
||||||
toast.error('Hmmm...', {
|
sileo.error({
|
||||||
|
title: 'Hmmm...',
|
||||||
description: 'Failed to launch.',
|
description: 'Failed to launch.',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
launching.set(false);
|
launching.set(false);
|
||||||
if ($trackingEnabled) umami.track('app_launch_fail', { error: err });
|
if ($trackingEnabled) umami.track('app_launch_fail', { error: err });
|
||||||
@@ -560,8 +672,13 @@
|
|||||||
try {
|
try {
|
||||||
const loginResult = await ezppfarm.login(username, password);
|
const loginResult = await ezppfarm.login(username, password);
|
||||||
if (loginResult && loginResult.user) {
|
if (loginResult && loginResult.user) {
|
||||||
toast.success('Login successful!', {
|
sileo.success({
|
||||||
|
title: 'Login successful!',
|
||||||
description: `Welcome back, ${loginResult.user.name}!`,
|
description: `Welcome back, ${loginResult.user.name}!`,
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
$userAuth.value('username').set(username);
|
$userAuth.value('username').set(username);
|
||||||
@@ -571,14 +688,24 @@
|
|||||||
currentUser.set(loginResult.user);
|
currentUser.set(loginResult.user);
|
||||||
selectedView = 'home';
|
selectedView = 'home';
|
||||||
} else {
|
} else {
|
||||||
toast.error('Login failed!', {
|
sileo.error({
|
||||||
|
title: 'Login failed!',
|
||||||
description: 'Please check your username and password.',
|
description: 'Please check your username and password.',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
loginIsLoading = false;
|
loginIsLoading = false;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Server error occurred during login.', {
|
sileo.error({
|
||||||
|
title: 'Login failed!',
|
||||||
description: 'There was an issue connecting to the server. Please try again later.',
|
description: 'There was an issue connecting to the server. Please try again later.',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
loginIsLoading = false;
|
loginIsLoading = false;
|
||||||
}
|
}
|
||||||
@@ -706,8 +833,13 @@
|
|||||||
onclick={async () => {
|
onclick={async () => {
|
||||||
const updateFile = $newVersion?.assets.find((asset) => asset.name.endsWith('.exe'));
|
const updateFile = $newVersion?.assets.find((asset) => asset.name.endsWith('.exe'));
|
||||||
if (!updateFile) {
|
if (!updateFile) {
|
||||||
toast.error('Hmmm...', {
|
sileo.error({
|
||||||
|
title: 'Hmmm...',
|
||||||
description: 'No update file found.',
|
description: 'No update file found.',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
$newVersion = undefined;
|
$newVersion = undefined;
|
||||||
return;
|
return;
|
||||||
@@ -792,35 +924,40 @@
|
|||||||
</AlertDialog.Root>
|
</AlertDialog.Root>
|
||||||
|
|
||||||
<div class="grid grid-cols-[0.085fr_1fr] h-[100vh] relative">
|
<div class="grid grid-cols-[0.085fr_1fr] h-[100vh] relative">
|
||||||
<div class="absolute top-0 left-0 w-full h-[100vh] opacity-30 z-0">
|
|
||||||
<AnimatedBg />
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="p-3 border-r border-r-theme-900 flex flex-col items-center gap-2 z-10 bg-black/40 backdrop-blur-sm"
|
class="p-3 border-r border-r-theme-900 flex flex-col items-center gap-2 z-10 bg-black/40 backdrop-blur-sm"
|
||||||
>
|
>
|
||||||
<div class="bg-pink-950/80 border border-pink-900/50 rounded-[1.1rem] p-1">
|
<div class="bg-primary-800/80 ring-1 ring-inset ring-white/15 rounded-[1.1rem] p-1.5">
|
||||||
<img class="pointer-events-none" src={Logo} alt="logo" bind:this={ezppLogo} />
|
<img class="pointer-events-none" src={Logo} alt="logo" bind:this={ezppLogo} />
|
||||||
</div>
|
</div>
|
||||||
<Badge class="text-[0.5rem] py-0 px-2">{$launcherVersion || 'dev'}</Badge>
|
<Badge class="text-[0.5rem] py-0 px-2">{$launcherVersion || 'dev'}</Badge>
|
||||||
<Button
|
<Button
|
||||||
class="flex size-12 items-center gap-2 {selectedView === 'home'
|
class="flex size-12 items-center gap-2 ring-1 ring-inset ring-white/15 {selectedView ===
|
||||||
? 'bg-pink-300/50'
|
'home'
|
||||||
: 'bg-black/20'} hover:bg-pink-300/50 rounded-[0.85rem] p-3 mt-3"
|
? 'bg-primary-300/50'
|
||||||
onclick={() => (selectedView = 'home')}
|
: 'bg-black/20 border-black/20'} hover:bg-primary-300/50 rounded-[0.85rem] p-3 mt-3"
|
||||||
|
disabled={$launching}
|
||||||
|
onclick={() => {
|
||||||
|
if (!$launching) selectedView = 'home';
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<House class="text-theme-200 !size-5" />
|
<House class="text-theme-200 !size-5" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
class="flex size-12 items-center gap-2 {selectedView === 'settings'
|
class="flex size-12 items-center gap-2 ring-1 ring-inset ring-white/15 {selectedView ===
|
||||||
? 'bg-pink-300/50'
|
'settings'
|
||||||
: 'bg-black/20'} hover:bg-pink-300/50 rounded-[0.85rem] p-3 mt-3"
|
? 'bg-primary-300/50'
|
||||||
onclick={() => (selectedView = 'settings')}
|
: 'bg-black/20 border-black/20'} hover:bg-primary-300/50 rounded-[0.85rem] p-3 mt-3"
|
||||||
|
disabled={$launching}
|
||||||
|
onclick={() => {
|
||||||
|
if (!$launching) selectedView = 'settings';
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Settings class="text-theme-200 !size-5" />
|
<Settings class="text-theme-200 !size-5" />
|
||||||
</Button>
|
</Button>
|
||||||
<div class="mt-auto">
|
<div class="mt-auto">
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
<DropdownMenu.Trigger>
|
<DropdownMenu.Trigger disabled={$launching}>
|
||||||
<Avatar.Root class="size-10">
|
<Avatar.Root class="size-10">
|
||||||
<Avatar.Image src="https://a.ez-pp.farm/{$currentUser?.id ?? 0}" />
|
<Avatar.Image src="https://a.ez-pp.farm/{$currentUser?.id ?? 0}" />
|
||||||
<Avatar.Fallback class="bg-theme-900">
|
<Avatar.Fallback class="bg-theme-900">
|
||||||
@@ -836,8 +973,13 @@
|
|||||||
$userAuth.value('username').del();
|
$userAuth.value('username').del();
|
||||||
$userAuth.value('password').del();
|
$userAuth.value('password').del();
|
||||||
await $userAuth.save();
|
await $userAuth.save();
|
||||||
toast.success('Logout successful!', {
|
sileo.success({
|
||||||
|
title: 'Logout successful!',
|
||||||
description: 'See you soon!',
|
description: 'See you soon!',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
currentUser.set(undefined);
|
currentUser.set(undefined);
|
||||||
currentUserInfo.set(undefined);
|
currentUserInfo.set(undefined);
|
||||||
@@ -861,14 +1003,19 @@
|
|||||||
{#if selectedView === 'home'}
|
{#if selectedView === 'home'}
|
||||||
<div
|
<div
|
||||||
class="flex flex-col-reverse h-full"
|
class="flex flex-col-reverse h-full"
|
||||||
in:fly={{ duration: 400, delay: 400, y: 10, opacity: 0 }}
|
in:fly={{
|
||||||
out:fly={{ duration: 400, y: -10, opacity: 0 }}
|
duration: $reduceAnimations ? 0 : 400,
|
||||||
|
delay: $reduceAnimations ? 0 : 400,
|
||||||
|
y: 5,
|
||||||
|
opacity: 0,
|
||||||
|
}}
|
||||||
|
out:fly={{ duration: $reduceAnimations ? 0 : 400, y: -5, opacity: 0 }}
|
||||||
>
|
>
|
||||||
<div class="relative z-10 px-8 py-4 flex items-center justify-between">
|
<div class="relative z-10 px-8 py-4 flex items-center justify-between">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
{#if $osuInstallationPath !== ''}
|
{#if $osuInstallationPath !== ''}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Music2 class="size-3.5 text-blue-400" />
|
<Music class="size-3.5 text-blue-400" />
|
||||||
<span class="text-[11px] text-white/70 font-medium">
|
<span class="text-[11px] text-white/70 font-medium">
|
||||||
{#if !$beatmapSets && $beatmapSets !== 0}
|
{#if !$beatmapSets && $beatmapSets !== 0}
|
||||||
<LoaderCircle class="animate-spin" size={12} />
|
<LoaderCircle class="animate-spin" size={12} />
|
||||||
@@ -880,10 +1027,10 @@
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Brush class="size-3.5 text-amber-400" />
|
<Brush class="size-3.5 text-amber-400" />
|
||||||
<span class="text-[11px] text-white/70 font-medium">
|
<span class="text-[11px] text-white/70 font-medium">
|
||||||
{#if !$skins && $skins !== 0}
|
{#if !$skinsCount && $skinsCount !== 0}
|
||||||
<LoaderCircle class="animate-spin" size={12} />
|
<LoaderCircle class="animate-spin" size={12} />
|
||||||
{:else}
|
{:else}
|
||||||
{numberHumanReadable($skins ?? 0)}
|
{numberHumanReadable($skinsCount ?? 0)}
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -982,9 +1129,9 @@
|
|||||||
|
|
||||||
{#if $currentUserInfo}
|
{#if $currentUserInfo}
|
||||||
<div
|
<div
|
||||||
class="m-1 w-72 h-48 bg-black/40 backdrop-blur-sm rounded-md border border-black/40 flex flex-col items-center p-3"
|
class="m-1 w-72 h-48 bg-black/40 backdrop-blur-sm rounded-md ring-1 ring-inset ring-white/5 flex flex-col items-center p-3"
|
||||||
in:fly={{ duration: 400, y: 10, opacity: 0 }}
|
in:fly={{ duration: $reduceAnimations ? 0 : 400, y: 5, opacity: 0 }}
|
||||||
out:fly={{ duration: 400, y: -10, opacity: 0 }}
|
out:fly={{ duration: $reduceAnimations ? 0 : 400, y: -5, opacity: 0 }}
|
||||||
>
|
>
|
||||||
<Select.Root
|
<Select.Root
|
||||||
type="single"
|
type="single"
|
||||||
@@ -1054,7 +1201,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-xs text-muted-foreground font-semibold">PP</span>
|
<span class="text-xs text-muted-foreground font-semibold">PP</span>
|
||||||
<div class="flex items-center h-full font-semibold text-pink-300">
|
<div class="flex items-center h-full font-semibold text-primary-200">
|
||||||
{#if $currentUserInfo}
|
{#if $currentUserInfo}
|
||||||
<div in:fade>
|
<div in:fade>
|
||||||
{#if $reduceAnimations}
|
{#if $reduceAnimations}
|
||||||
@@ -1135,247 +1282,254 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else if selectedView === 'settings'}
|
{:else if selectedView === 'settings'}
|
||||||
<div
|
<div
|
||||||
class="h-[100vh] w-full flex flex-col items-center justify-center"
|
class="h-[100vh] w-full flex flex-col items-center justify-center bg-black/20 backdrop-blur-sm"
|
||||||
in:fly={{ duration: 400, delay: 400, y: 10, opacity: 0 }}
|
in:fly={{
|
||||||
out:fly={{ duration: 400, y: -10, opacity: 0 }}
|
duration: $reduceAnimations ? 0 : 400,
|
||||||
|
delay: $reduceAnimations ? 0 : 400,
|
||||||
|
y: 5,
|
||||||
|
opacity: 0,
|
||||||
|
}}
|
||||||
|
out:fly={{ duration: $reduceAnimations ? 0 : 400, y: -5, opacity: 0 }}
|
||||||
>
|
>
|
||||||
<div class="p-8 w-full">
|
<div class="grid grid-cols-[1fr_auto] w-full gap-y-5 items-center px-6">
|
||||||
<div class="bg-black/40 backdrop-blur-sm py-8 px-6 rounded-lg">
|
<div class="flex flex-col">
|
||||||
<div class="grid grid-cols-[1fr_auto] gap-y-5 items-center px-6">
|
<Label class="text-sm" for="setting-patch">Patching</Label>
|
||||||
<div class="flex flex-col">
|
<div class="text-muted-foreground text-xs">
|
||||||
<Label class="text-sm" for="setting-patch">Patching</Label>
|
Shows misses in Relax and Autopilot {#if $platform !== 'windows'}<span
|
||||||
<div class="text-muted-foreground text-xs">
|
class="text-red-500 bg-red-800/20 border border-red-600/20 p-0.5 mx-1 px-2 rounded-lg !text-[0.55rem]"
|
||||||
Shows misses in Relax and Autopilot {#if $platform !== 'windows'}<span
|
>currently only on windows!</span
|
||||||
class="text-red-500 bg-red-800/20 border border-red-600/20 p-0.5 mx-1 px-2 rounded-lg !text-[0.55rem]"
|
>
|
||||||
>currently only on windows!</span
|
{/if}
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Checkbox
|
|
||||||
id="setting-patch"
|
|
||||||
checked={$platform === 'windows' ? $patch : false}
|
|
||||||
disabled={$platform !== 'windows'}
|
|
||||||
onCheckedChange={async (e) => {
|
|
||||||
patch.set(e);
|
|
||||||
$userSettings.save();
|
|
||||||
}}
|
|
||||||
class="flex items-center justify-center w-5 h-5"
|
|
||||||
></Checkbox>
|
|
||||||
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<Label class="text-sm" for="setting-custom-cursor">Lazer-Style Cursor</Label>
|
|
||||||
<div class="text-muted-foreground text-xs">
|
|
||||||
Enable a custom cursor in the Launcher like in the lazer build of osu!
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Checkbox
|
|
||||||
id="setting-custom-cursor"
|
|
||||||
checked={$customCursor}
|
|
||||||
onCheckedChange={async (e) => {
|
|
||||||
if (!e) {
|
|
||||||
cursorSmoothening.set(false);
|
|
||||||
}
|
|
||||||
customCursor.set(e);
|
|
||||||
|
|
||||||
$userSettings.save();
|
|
||||||
}}
|
|
||||||
class="flex items-center justify-center w-5 h-5"
|
|
||||||
></Checkbox>
|
|
||||||
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<Label class="text-sm" for="setting-cursor-smoothening">Cursor Smoothening</Label>
|
|
||||||
<div class="text-muted-foreground text-xs">
|
|
||||||
Makes the custom cursor movement smoother.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Checkbox
|
|
||||||
id="setting-cursor-smoothening"
|
|
||||||
checked={$cursorSmoothening}
|
|
||||||
onCheckedChange={async (e) => {
|
|
||||||
if (!$customCursor) return;
|
|
||||||
cursorSmoothening.set(e);
|
|
||||||
$userSettings.save();
|
|
||||||
}}
|
|
||||||
disabled={!$customCursor}
|
|
||||||
class="flex items-center justify-center w-5 h-5"
|
|
||||||
></Checkbox>
|
|
||||||
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<Label class="text-sm" for="setting-reduce-animations">Reduce Animations</Label>
|
|
||||||
<div class="text-muted-foreground text-xs">
|
|
||||||
Disables some animations in the Launcher to improve performance on low-end
|
|
||||||
devices.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Checkbox
|
|
||||||
id="setting-reduce-animations"
|
|
||||||
checked={$reduceAnimations}
|
|
||||||
onCheckedChange={async (e) => {
|
|
||||||
reduceAnimations.set(e);
|
|
||||||
$userSettings.save();
|
|
||||||
}}
|
|
||||||
class="flex items-center justify-center w-5 h-5"
|
|
||||||
></Checkbox>
|
|
||||||
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<Label class="text-sm" for="setting-rich-presence">Discord Rich Presence</Label>
|
|
||||||
<div class="text-muted-foreground text-xs">
|
|
||||||
Let other discord users show what you are doing right now 👀
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="relative">
|
|
||||||
{#if $presenceLoading}
|
|
||||||
<div class="-left-8 absolute" transition:fade>
|
|
||||||
<LoaderCircle class="animate-spin" />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<Checkbox
|
|
||||||
id="setting-rich-presence"
|
|
||||||
bind:checked={$discordPresence}
|
|
||||||
disabled={$presenceLoading}
|
|
||||||
class="flex items-center justify-center w-5 h-5"
|
|
||||||
></Checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<Label class="text-sm" for="setting-tracking">App Tracking</Label>
|
|
||||||
<div class="text-muted-foreground text-xs">
|
|
||||||
Allow anonymous usage data to be collected to help improve the application.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Checkbox
|
|
||||||
id="setting-tracking"
|
|
||||||
checked={$trackingEnabled}
|
|
||||||
onCheckedChange={async (e) => {
|
|
||||||
trackingEnabled.set(e);
|
|
||||||
$userSettings.value('tracking_consent').set(e);
|
|
||||||
await $userSettings.save();
|
|
||||||
}}
|
|
||||||
class="flex items-center justify-center w-5 h-5"
|
|
||||||
></Checkbox>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
</div>
|
||||||
class="grid grid-cols-[0.7fr_auto] gap-y-1 items-center border-theme-800 pl-6 pr-5 pb-4"
|
<Checkbox
|
||||||
|
id="setting-patch"
|
||||||
|
checked={$platform === 'windows' ? $patch : false}
|
||||||
|
disabled={$platform !== 'windows'}
|
||||||
|
onCheckedChange={async (e) => {
|
||||||
|
patch.set(e);
|
||||||
|
$userSettings.save();
|
||||||
|
}}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<Label class="text-sm" for="setting-custom-cursor">Lazer-Style Cursor</Label>
|
||||||
|
<div class="text-muted-foreground text-xs">
|
||||||
|
Enable a custom cursor in the Launcher like in the lazer build of osu!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
id="setting-custom-cursor"
|
||||||
|
checked={$customCursor}
|
||||||
|
onCheckedChange={async (e) => {
|
||||||
|
if (!e) {
|
||||||
|
cursorSmoothening.set(false);
|
||||||
|
}
|
||||||
|
customCursor.set(e);
|
||||||
|
|
||||||
|
$userSettings.save();
|
||||||
|
}}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<Label class="text-sm" for="setting-cursor-smoothening">Cursor Smoothening</Label>
|
||||||
|
<div class="text-muted-foreground text-xs">
|
||||||
|
Makes the custom cursor movement smoother.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
id="setting-cursor-smoothening"
|
||||||
|
checked={$cursorSmoothening}
|
||||||
|
onCheckedChange={async (e) => {
|
||||||
|
if (!$customCursor) return;
|
||||||
|
cursorSmoothening.set(e);
|
||||||
|
$userSettings.save();
|
||||||
|
}}
|
||||||
|
disabled={!$customCursor}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<Label class="text-sm" for="setting-reduce-animations">Reduce Animations</Label>
|
||||||
|
<div class="text-muted-foreground text-xs">
|
||||||
|
Disables some animations in the Launcher to improve performance on low-end devices.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
id="setting-reduce-animations"
|
||||||
|
checked={$reduceAnimations}
|
||||||
|
onCheckedChange={async (e) => {
|
||||||
|
reduceAnimations.set(e);
|
||||||
|
$userSettings.save();
|
||||||
|
}}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<Label class="text-sm" for="setting-rich-presence">Discord Rich Presence</Label>
|
||||||
|
<div class="text-muted-foreground text-xs">
|
||||||
|
Let other discord users show what you are doing right now 👀
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="relative">
|
||||||
|
{#if $presenceLoading}
|
||||||
|
<div class="-left-8 absolute" transition:fade>
|
||||||
|
<LoaderCircle class="animate-spin" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<Checkbox
|
||||||
|
id="setting-rich-presence"
|
||||||
|
bind:checked={$discordPresence}
|
||||||
|
disabled={$presenceLoading}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<Label class="text-sm" for="setting-tracking">App Tracking</Label>
|
||||||
|
<div class="text-muted-foreground text-xs">
|
||||||
|
Allow anonymous usage data to be collected to help improve the application.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
id="setting-tracking"
|
||||||
|
checked={$trackingEnabled}
|
||||||
|
onCheckedChange={async (e) => {
|
||||||
|
trackingEnabled.set(e);
|
||||||
|
$userSettings.value('tracking_consent').set(e);
|
||||||
|
await $userSettings.save();
|
||||||
|
}}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="grid grid-cols-[0.7fr_auto] gap-y-1 mt-3 w-full items-center border-theme-800 pl-6 pr-5 pb-4"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<Label class="text-sm" for="setting-custom-cursor">osu! installation path</Label>
|
||||||
|
<div class="text-muted-foreground text-xs">The path to your osu! installation.</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row w-full">
|
||||||
|
<Input
|
||||||
|
class="mt-4 w-full bg-theme-950 border-theme-800 border-r-0 rounded-r-none"
|
||||||
|
type="text"
|
||||||
|
value={$osuInstallationPath}
|
||||||
|
placeholder="Path to osu! installation"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
class="mt-4 bg-theme-950 border-theme-800 rounded-l-none"
|
||||||
|
variant="outline"
|
||||||
|
onclick={browse_osu_installation}>Browse</Button
|
||||||
>
|
>
|
||||||
<div class="flex flex-col">
|
</div>
|
||||||
<Label class="text-sm" for="setting-custom-cursor">osu! installation path</Label>
|
|
||||||
<div class="text-muted-foreground text-xs">The path to your osu! installation.</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row w-full">
|
|
||||||
<Input
|
|
||||||
class="mt-4 w-full bg-theme-950 border-theme-800 border-r-0 rounded-r-none"
|
|
||||||
type="text"
|
|
||||||
value={$osuInstallationPath}
|
|
||||||
placeholder="Path to osu! installation"
|
|
||||||
readonly
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
class="mt-4 bg-theme-950 border-theme-800 rounded-l-none"
|
|
||||||
variant="outline"
|
|
||||||
onclick={browse_osu_installation}>Browse</Button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<Label class="text-sm" for="setting-custom-cursor">patcher release stream</Label>
|
<Label class="text-sm" for="setting-custom-cursor">patcher release stream</Label>
|
||||||
<div class="text-muted-foreground text-xs">
|
<div class="text-muted-foreground text-xs">test different versions of the patcher</div>
|
||||||
test different versions of the patcher
|
</div>
|
||||||
|
<div class="flex flex-row w-full">
|
||||||
|
<Select.Root
|
||||||
|
type="single"
|
||||||
|
bind:value={$launcherStream}
|
||||||
|
onValueChange={async (newStream) => {
|
||||||
|
const isNet8Installed = await hasNet8();
|
||||||
|
if (!isNet8Installed) {
|
||||||
|
launcherStream.set('stable');
|
||||||
|
sileo.error({
|
||||||
|
title: 'Hmm...',
|
||||||
|
description: '.NET 8.0 Desktop Runtime not found!',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
title: 'Download .NET 8.0',
|
||||||
|
onClick: async () =>
|
||||||
|
await openURL(
|
||||||
|
'https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.22-windows-x64-installer'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$userSettings.value('patcherStream').set(newStream);
|
||||||
|
launcherStream.set(newStream);
|
||||||
|
await $userSettings.save();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Trigger
|
||||||
|
class="border-theme-800 bg-theme-950 !text-muted-foreground font-semibold"
|
||||||
|
>
|
||||||
|
<div class="flex flex-row items-center gap-2 font-normal text-foreground">
|
||||||
|
{$launcherStream}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Select.Trigger>
|
||||||
<div class="flex flex-row w-full">
|
<Select.Content class="bg-theme-950 border border-theme-950 rounded-lg">
|
||||||
<Select.Root
|
{#each $launcherStreams as stream (stream)}
|
||||||
type="single"
|
<Select.Item value={stream}>
|
||||||
bind:value={$launcherStream}
|
<div class="flex flex-row gap-2 items-center">
|
||||||
onValueChange={async (newStream) => {
|
{stream}
|
||||||
const isNet8Installed = await hasNet8();
|
|
||||||
if (newStream === 'experimental' && !isNet8Installed) {
|
|
||||||
launcherStream.set('stable');
|
|
||||||
toast.error('.NET 8.0 Desktop Runtime not found!', {
|
|
||||||
action: {
|
|
||||||
label: 'Download .NET 8.0',
|
|
||||||
onClick: async () =>
|
|
||||||
await openURL(
|
|
||||||
'https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.22-windows-x64-installer'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$userSettings.value('patcherStream').set(newStream);
|
|
||||||
launcherStream.set(newStream);
|
|
||||||
await $userSettings.save();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Select.Trigger
|
|
||||||
class="border-theme-800 bg-theme-950 !text-muted-foreground font-semibold"
|
|
||||||
>
|
|
||||||
<div class="flex flex-row items-center gap-2 font-normal text-foreground">
|
|
||||||
{$launcherStream}
|
|
||||||
</div>
|
</div>
|
||||||
</Select.Trigger>
|
</Select.Item>
|
||||||
<Select.Content class="bg-theme-950 border border-theme-950 rounded-lg">
|
{/each}
|
||||||
{#each $launcherStreams as stream (stream)}
|
</Select.Content>
|
||||||
<Select.Item value={stream}>
|
</Select.Root>
|
||||||
<div class="flex flex-row gap-2 items-center">
|
|
||||||
{stream}
|
|
||||||
</div>
|
|
||||||
</Select.Item>
|
|
||||||
{/each}
|
|
||||||
</Select.Content>
|
|
||||||
</Select.Root>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if selectedView === 'login'}
|
{:else if selectedView === 'login'}
|
||||||
<div
|
<div
|
||||||
class="h-[100vh] w-full flex flex-col items-center justify-center"
|
class="h-[100vh] w-full flex flex-col items-center justify-center bg-black/20 backdrop-blur-sm"
|
||||||
in:fly={{ duration: 400, delay: 400, y: 10, opacity: 0 }}
|
in:fly={{
|
||||||
out:fly={{ duration: 400, y: -10, opacity: 0 }}
|
duration: $reduceAnimations ? 0 : 400,
|
||||||
|
delay: $reduceAnimations ? 0 : 400,
|
||||||
|
y: 5,
|
||||||
|
opacity: 0,
|
||||||
|
}}
|
||||||
|
out:fly={{ duration: $reduceAnimations ? 0 : 400, y: -5, opacity: 0 }}
|
||||||
>
|
>
|
||||||
<div class="p-8 px-52 w-full">
|
<form onsubmit={performLogin} class="w-96">
|
||||||
<form onsubmit={performLogin} class="bg-black/40 backdrop-blur-sm rounded-lg p-8">
|
<div class="flex flex-col items-center justify-center mb-8">
|
||||||
<div class="flex flex-col items-center justify-center mb-4">
|
<span class="text-xl font-semibold">Login to EZPPFarm</span>
|
||||||
<span class="text-xl font-semibold">Login to EZPPFarm</span>
|
<span class="text-xs text-muted-foreground"
|
||||||
<span class="text-xs text-muted-foreground"
|
>Use your EZPPFarm account to login to EZPPLauncher</span
|
||||||
>Use your EZPPFarm account to login to EZPPLauncher</span
|
>
|
||||||
>
|
</div>
|
||||||
</div>
|
<div class="mb-4">
|
||||||
<div class="mb-4">
|
<Label for="username" class="block text-sm font-medium">Username</Label>
|
||||||
<Label for="username" class="block text-sm font-medium">Username</Label>
|
<Input
|
||||||
<Input
|
class="mt-4 w-full bg-theme-900 border-theme-800"
|
||||||
class="mt-4 w-full bg-theme-900 border-theme-800"
|
type="text"
|
||||||
type="text"
|
id="username"
|
||||||
id="username"
|
bind:value={username}
|
||||||
bind:value={username}
|
disabled={loginIsLoading}
|
||||||
disabled={loginIsLoading}
|
autocomplete="off"
|
||||||
autocomplete="off"
|
autocorrect="off"
|
||||||
autocorrect="off"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div class="mb-4">
|
||||||
<div class="mb-4">
|
<Label for="password" class="block text-sm font-medium">Password</Label>
|
||||||
<Label for="password" class="block text-sm font-medium">Password</Label>
|
<Input
|
||||||
<Input
|
class="mt-4 w-full bg-theme-900 border-theme-800"
|
||||||
class="mt-4 w-full bg-theme-900 border-theme-800"
|
type="password"
|
||||||
type="password"
|
id="password"
|
||||||
id="password"
|
bind:value={password}
|
||||||
bind:value={password}
|
disabled={loginIsLoading}
|
||||||
disabled={loginIsLoading}
|
autocomplete="off"
|
||||||
autocomplete="off"
|
autocorrect="off"
|
||||||
autocorrect="off"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<Button class="w-full" type="submit" disabled={loginIsLoading}>
|
||||||
<Button class="w-full" type="submit" disabled={loginIsLoading}>
|
{#if loginIsLoading}
|
||||||
{#if loginIsLoading}
|
<LoaderCircle class="animate-spin" />
|
||||||
<LoaderCircle class="animate-spin" />
|
{:else}
|
||||||
{:else}
|
Login
|
||||||
Login
|
{/if}
|
||||||
{/if}
|
</Button>
|
||||||
</Button>
|
</form>
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
osuBuild,
|
osuBuild,
|
||||||
osuStream,
|
osuStream,
|
||||||
skins,
|
skins,
|
||||||
|
skinsCount,
|
||||||
} from '@/global';
|
} from '@/global';
|
||||||
import {
|
import {
|
||||||
cursorSmoothness,
|
cursorSmoothness,
|
||||||
@@ -28,18 +29,19 @@
|
|||||||
import Launch from './Launch.svelte';
|
import Launch from './Launch.svelte';
|
||||||
import { currentUser, userAuth } from '@/userAuthentication';
|
import { currentUser, userAuth } from '@/userAuthentication';
|
||||||
import { ezppfarm } from '@/api/ezpp';
|
import { ezppfarm } from '@/api/ezpp';
|
||||||
import { toast } from 'svelte-sonner';
|
|
||||||
import { currentUserInfo } from '@/data';
|
import { currentUserInfo } from '@/data';
|
||||||
import {
|
import {
|
||||||
getBeatmapSetsCount,
|
getBeatmapSetsCount,
|
||||||
getEZPPLauncherStreams,
|
getEZPPLauncherStreams,
|
||||||
getReleaseStream,
|
getReleaseStream,
|
||||||
getSkin,
|
getSkin,
|
||||||
getSkinsCount,
|
getSkins,
|
||||||
getVersion,
|
getVersion,
|
||||||
isValidOsuFolder,
|
isValidOsuFolder,
|
||||||
} from '@/osuUtil';
|
} from '@/osuUtil';
|
||||||
import { git } from '@/api/git';
|
import { git } from '@/api/git';
|
||||||
|
import { sileo } from 'sileo';
|
||||||
|
import type { EZPPUser } from '@/types';
|
||||||
|
|
||||||
let ezppLogo: HTMLImageElement;
|
let ezppLogo: HTMLImageElement;
|
||||||
let spinnerCircle: SVGCircleElement;
|
let spinnerCircle: SVGCircleElement;
|
||||||
@@ -84,25 +86,32 @@
|
|||||||
|
|
||||||
const username = $userAuth.value('username').get('');
|
const username = $userAuth.value('username').get('');
|
||||||
const password = $userAuth.value('password').get('');
|
const password = $userAuth.value('password').get('');
|
||||||
if (username.length > 0 && password.length > 0) {
|
|
||||||
|
const shouldLogin = username.length > 0 && password.length > 0;
|
||||||
|
let userLoginResult: { message: string; user?: EZPPUser } = {
|
||||||
|
message: 'An unknown error occured!',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (shouldLogin) {
|
||||||
currentLoadingInfo.set('Logging in...');
|
currentLoadingInfo.set('Logging in...');
|
||||||
try {
|
try {
|
||||||
const loginResult = await ezppfarm.login(username, password);
|
const loginResult = await ezppfarm.login(username, password);
|
||||||
if (loginResult && loginResult.user) {
|
if (loginResult && loginResult.user) {
|
||||||
toast.success('Login successful!', {
|
userLoginResult = {
|
||||||
description: `Welcome back, ${loginResult.user.name}!`,
|
message: `Welcome back, ${loginResult.user.name}!`,
|
||||||
});
|
user: loginResult.user,
|
||||||
|
};
|
||||||
|
|
||||||
currentUser.set(loginResult.user);
|
currentUser.set(loginResult.user);
|
||||||
} else {
|
} else {
|
||||||
toast.error('Login failed!', {
|
userLoginResult = {
|
||||||
description: 'Please check your username and password.',
|
message: 'Please check your username and password.',
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Server error occurred during login.', {
|
userLoginResult = {
|
||||||
description: 'There was an issue connecting to the server. Please try again later.',
|
message: 'There was an issue connecting to the server. Please try again later.',
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($currentUser) {
|
if ($currentUser) {
|
||||||
@@ -123,8 +132,13 @@
|
|||||||
osuInstallationPath.set('');
|
osuInstallationPath.set('');
|
||||||
$userSettings.value('osu_installation_path').del();
|
$userSettings.value('osu_installation_path').del();
|
||||||
await $userSettings.save();
|
await $userSettings.save();
|
||||||
toast.error('Oops...', {
|
sileo.error({
|
||||||
|
title: 'Hmm...',
|
||||||
description: 'Your previously set osu! installation path seems to be invalid.',
|
description: 'Your previously set osu! installation path seems to be invalid.',
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
currentLoadingInfo.set('Getting osu version...');
|
currentLoadingInfo.set('Getting osu version...');
|
||||||
@@ -138,8 +152,15 @@
|
|||||||
if (beatmapSetCount !== null) beatmapSets.set(beatmapSetCount);
|
if (beatmapSetCount !== null) beatmapSets.set(beatmapSetCount);
|
||||||
|
|
||||||
currentLoadingInfo.set('Counting skins...');
|
currentLoadingInfo.set('Counting skins...');
|
||||||
const skinCount = await getSkinsCount($osuInstallationPath);
|
const skins_list = await getSkins($osuInstallationPath);
|
||||||
if (skinCount !== null) skins.set(skinCount);
|
console.log(skins_list);
|
||||||
|
if (skins_list) {
|
||||||
|
skins.set(skins_list);
|
||||||
|
skinsCount.set(skins_list.length);
|
||||||
|
} else {
|
||||||
|
skinsCount.set(0);
|
||||||
|
}
|
||||||
|
|
||||||
const skin: string = await getSkin($osuInstallationPath);
|
const skin: string = await getSkin($osuInstallationPath);
|
||||||
currentSkin.set(skin);
|
currentSkin.set(skin);
|
||||||
}
|
}
|
||||||
@@ -174,7 +195,31 @@
|
|||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if ($firstStartup) currentView.set(SetupWizard);
|
if ($firstStartup) currentView.set(SetupWizard);
|
||||||
else currentView.set(Launch);
|
else {
|
||||||
|
currentView.set(Launch);
|
||||||
|
if (shouldLogin) {
|
||||||
|
if (userLoginResult.user) {
|
||||||
|
sileo.success({
|
||||||
|
title: 'Login successful!',
|
||||||
|
description: userLoginResult.message,
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
|
duration: 2500,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sileo.error({
|
||||||
|
title: 'Login failed!',
|
||||||
|
description: userLoginResult.message,
|
||||||
|
fill: '#181825',
|
||||||
|
styles: {
|
||||||
|
description: 'text-center!',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}, 250);
|
}, 250);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import { animate } from 'animejs';
|
import { animate } from 'animejs';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { Check, CheckCircle, CircleOff, Settings2 } from 'lucide-svelte';
|
import { Check, CircleCheckBig, CircleOff, LoaderCircle } from 'lucide-svelte';
|
||||||
import { open } from '@tauri-apps/plugin-dialog';
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
import Checkbox from '@/components/ui/checkbox/checkbox.svelte';
|
import Checkbox from '@/components/ui/checkbox/checkbox.svelte';
|
||||||
import {
|
import {
|
||||||
@@ -17,7 +17,18 @@
|
|||||||
userSettings,
|
userSettings,
|
||||||
} from '@/userSettings';
|
} from '@/userSettings';
|
||||||
import Label from '@/components/ui/label/label.svelte';
|
import Label from '@/components/ui/label/label.svelte';
|
||||||
import { beatmapSets, currentSkin, currentView, osuBuild, osuStream, skins } from '@/global';
|
import {
|
||||||
|
beatmapSets,
|
||||||
|
currentSkin,
|
||||||
|
currentView,
|
||||||
|
discordPresence,
|
||||||
|
osuBuild,
|
||||||
|
osuStream,
|
||||||
|
platform,
|
||||||
|
presenceLoading,
|
||||||
|
skins,
|
||||||
|
skinsCount,
|
||||||
|
} from '@/global';
|
||||||
import Launch from './Launch.svelte';
|
import Launch from './Launch.svelte';
|
||||||
import Confetti from 'svelte-confetti';
|
import Confetti from 'svelte-confetti';
|
||||||
import {
|
import {
|
||||||
@@ -25,7 +36,7 @@
|
|||||||
getBeatmapSetsCount,
|
getBeatmapSetsCount,
|
||||||
getReleaseStream,
|
getReleaseStream,
|
||||||
getSkin,
|
getSkin,
|
||||||
getSkinsCount,
|
getSkins,
|
||||||
getVersion,
|
getVersion,
|
||||||
isValidOsuFolder,
|
isValidOsuFolder,
|
||||||
} from '@/osuUtil';
|
} from '@/osuUtil';
|
||||||
@@ -92,9 +103,12 @@
|
|||||||
beatmapSets.set(beatmapSetCount);
|
beatmapSets.set(beatmapSetCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
const skinsCount: number | null = await getSkinsCount(osuInstallPath);
|
const skins_list = await getSkins(osuInstallPath);
|
||||||
if (skinsCount) {
|
if (skins_list) {
|
||||||
skins.set(skinsCount);
|
skins.set(skins_list);
|
||||||
|
skinsCount.set(skins_list.length);
|
||||||
|
} else {
|
||||||
|
skinsCount.set(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const skin: string = await getSkin(osuInstallPath);
|
const skin: string = await getSkin(osuInstallPath);
|
||||||
@@ -110,6 +124,7 @@
|
|||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const osuPath = await autoDetectOsuInstallFolder();
|
const osuPath = await autoDetectOsuInstallFolder();
|
||||||
|
console.log(osuPath);
|
||||||
if (osuPath) {
|
if (osuPath) {
|
||||||
osuInstallPath = osuPath;
|
osuInstallPath = osuPath;
|
||||||
autoDetectedOsuPath = true;
|
autoDetectedOsuPath = true;
|
||||||
@@ -119,7 +134,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if wizardFinished}
|
{#if wizardFinished}
|
||||||
<div class="relative flex flex-col items-center justify-center h-[100vh]">
|
<div class="relative flex flex-col items-center justify-center h-[100vh] z-50">
|
||||||
<div class="absolute h-fit w-fit top-1/2 left-1/2">
|
<div class="absolute h-fit w-fit top-1/2 left-1/2">
|
||||||
<Confetti amount={200} y={[-1, 1.5]} x={[-2.3, 2.3]} colorArray={['#C6A0F3']} />
|
<Confetti amount={200} y={[-1, 1.5]} x={[-2.3, 2.3]} colorArray={['#C6A0F3']} />
|
||||||
</div>
|
</div>
|
||||||
@@ -128,15 +143,17 @@
|
|||||||
<Button class="mt-4" onclick={saveConfig}>Finish</Button>
|
<Button class="mt-4" onclick={saveConfig}>Finish</Button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="grid grid-cols-[0.41fr_1fr] h-[100vh]">
|
<div class="grid grid-cols-[0.41fr_1fr] h-[100vh] z-50">
|
||||||
<div class="w-full h-full border-r border-theme-800/90 flex flex-col gap-6 p-3">
|
<div
|
||||||
|
class="w-full h-full border-r border-black/40 bg-black/40 backdrop-blur-md flex flex-col gap-6 p-3 z-50"
|
||||||
|
>
|
||||||
{#each steps as step, i (step)}
|
{#each steps as step, i (step)}
|
||||||
<div
|
<div
|
||||||
class="flex flex-row items-center gap-2 border {selectedStep === i + 1
|
class="flex flex-row items-center gap-2 border {selectedStep === i + 1
|
||||||
? 'border-primary-800/30 bg-primary-900/30'
|
? 'border-primary-600/30 bg-primary-700/30'
|
||||||
: selectedStep > i
|
: selectedStep > i
|
||||||
? 'border-green-800/30 bg-green-900/30'
|
? 'border-green-800/30 bg-green-900/30'
|
||||||
: 'border-theme-800 bg-theme-900'} rounded-lg p-2 transition-all"
|
: 'border-black/30 bg-black/30'} rounded-lg p-2 transition-all"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-center justify-center h-8 w-8 border-[2px] {selectedStep >
|
class="flex flex-col items-center justify-center h-8 w-8 border-[2px] {selectedStep >
|
||||||
@@ -160,10 +177,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-6 w-full h-full bg-theme-900/40 p-6">
|
<div class="flex flex-col gap-6 w-full h-full backdrop-blur-md p-6 z-50">
|
||||||
{#if selectedStep === 1}
|
{#if selectedStep === 1}
|
||||||
<div
|
<div
|
||||||
class="my-auto h-full w-full bg-theme-800/15 rounded-lg border border-900/60 p-6 mt-5 flex flex-col items-center justify-center"
|
class="my-auto h-full w-full bg-black/40 rounded-lg border border-black/40 p-6 mt-5 flex flex-col items-center justify-center"
|
||||||
in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
|
in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -178,13 +195,13 @@
|
|||||||
<p class="text-muted-foreground mt-2">
|
<p class="text-muted-foreground mt-2">
|
||||||
This setup wizard will guide you through the initial setup of EZPPLauncher.
|
This setup wizard will guide you through the initial setup of EZPPLauncher.
|
||||||
</p>
|
</p>
|
||||||
<div class="bg-red-800/20 border border-red-900/20 text-red-500 p-4 rounded-lg mt-4">
|
<div class="bg-red-800 border border-red-900 text-red-200 p-4 rounded-lg mt-4">
|
||||||
Please make sure you have osu! installed on your system before proceeding.
|
Please make sure you have osu! installed on your system before proceeding.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if selectedStep === 2}
|
{:else if selectedStep === 2}
|
||||||
<div
|
<div
|
||||||
class="my-auto h-full w-full bg-theme-800/15 rounded-lg border border-900/60 p-6 mt-5 flex flex-col items-center justify-center"
|
class="my-auto h-full w-full bg-black/40 rounded-lg border border-black/40 p-6 mt-5 flex flex-col items-center justify-center"
|
||||||
in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
|
in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
|
||||||
>
|
>
|
||||||
<h1 class="text-3xl font-semibold">Locate your osu! Installation</h1>
|
<h1 class="text-3xl font-semibold">Locate your osu! Installation</h1>
|
||||||
@@ -207,14 +224,14 @@
|
|||||||
{#if !manualSelect}
|
{#if !manualSelect}
|
||||||
{#if autoDetectedOsuPath}
|
{#if autoDetectedOsuPath}
|
||||||
<div
|
<div
|
||||||
class="flex flex-row gap-3 bg-green-800/20 border border-green-900/20 text-green-500 p-4 rounded-lg mt-4"
|
class="flex flex-row gap-3 bg-green-800 border border-green-900 text-green-500 p-4 rounded-lg mt-4"
|
||||||
>
|
>
|
||||||
<CheckCircle />
|
<CircleCheckBig />
|
||||||
<span>Auto-detected osu! installation path! Please check if its correct!</span>
|
<span>Auto-detected osu! installation path! Please check if its correct!</span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="flex flex-row gap-3 bg-red-800/20 border border-red-900/20 text-red-500 p-4 rounded-lg mt-4"
|
class="flex flex-row gap-3 bg-red-800 border border-red-900 text-red-500 p-4 rounded-lg mt-4"
|
||||||
>
|
>
|
||||||
<CircleOff />
|
<CircleOff />
|
||||||
<span>Could not auto-detect osu! installation path. Please select it manually.</span
|
<span>Could not auto-detect osu! installation path. Please select it manually.</span
|
||||||
@@ -223,14 +240,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{:else if manualSelectValid}
|
{:else if manualSelectValid}
|
||||||
<div
|
<div
|
||||||
class="flex flex-row gap-3 bg-green-800/20 border border-green-900/20 text-green-500 p-4 rounded-lg mt-4"
|
class="flex flex-row gap-3 bg-green-800 border border-green-900 text-green-500 p-4 rounded-lg mt-4"
|
||||||
>
|
>
|
||||||
<CheckCircle />
|
<CircleCheckBig />
|
||||||
<span>Selected osu! installation path is valid!</span>
|
<span>Selected osu! installation path is valid!</span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="flex flex-row gap-3 bg-red-800/20 border border-red-900/20 text-red-500 p-4 rounded-lg mt-4"
|
class="flex flex-row gap-3 bg-red-800 border border-red-900 text-red-500 p-4 rounded-lg mt-4"
|
||||||
>
|
>
|
||||||
<CircleOff />
|
<CircleOff />
|
||||||
<span
|
<span
|
||||||
@@ -241,80 +258,108 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else if selectedStep === 3}
|
{:else if selectedStep === 3}
|
||||||
<div
|
<div class="h-full w-full flex items-center justify-center">
|
||||||
class="bg-theme-900/90 flex flex-col justify-center gap-3 mt-5 border border-theme-800/90 rounded-lg"
|
|
||||||
in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
|
|
||||||
>
|
|
||||||
<div class="flex flex-row items-center gap-3 font-semibold text-xl px-3 pt-3">
|
|
||||||
<Settings2 /> EZPPLauncher Settings
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-[1fr_auto] gap-y-5 items-center border-t border-theme-800 py-3 px-6"
|
class="bg-black/40 backdrop-blur-sm py-8 px-8 rounded-lg"
|
||||||
|
in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
|
||||||
>
|
>
|
||||||
<div class="flex flex-col">
|
<div class="grid grid-cols-[1fr_auto] gap-y-5 items-center px-6">
|
||||||
<Label class="text-sm" for="setting-custom-cursor">Patching</Label>
|
<div class="flex flex-col">
|
||||||
<div class="text-muted-foreground text-xs">Shows misses in Relax and Autopilot</div>
|
<Label class="text-sm" for="setting-patch">Patching</Label>
|
||||||
</div>
|
<div class="text-muted-foreground text-xs">
|
||||||
<Checkbox
|
Shows misses in Relax and Autopilot {#if $platform !== 'windows'}<span
|
||||||
id="setting-custom-cursor"
|
class="text-red-500 bg-red-800/20 border border-red-600/20 p-0.5 mx-1 px-2 rounded-lg !text-[0.55rem]"
|
||||||
checked={$patch}
|
>currently only on windows!</span
|
||||||
onCheckedChange={async (e) => {
|
>
|
||||||
patch.set(e);
|
{/if}
|
||||||
$userSettings.save();
|
</div>
|
||||||
}}
|
</div>
|
||||||
class="flex items-center justify-center w-5 h-5"
|
<Checkbox
|
||||||
></Checkbox>
|
id="setting-patch"
|
||||||
|
checked={$platform === 'windows' ? $patch : false}
|
||||||
|
disabled={$platform !== 'windows'}
|
||||||
|
onCheckedChange={async (e) => {
|
||||||
|
patch.set(e);
|
||||||
|
$userSettings.save();
|
||||||
|
}}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<Label class="text-sm" for="setting-custom-cursor">Lazer-Style Cursor</Label>
|
<Label class="text-sm" for="setting-custom-cursor">Lazer-Style Cursor</Label>
|
||||||
<div class="text-muted-foreground text-xs">
|
<div class="text-muted-foreground text-xs">
|
||||||
Enable a custom cursor in the Launcher like in the lazer build of osu!
|
Enable a custom cursor in the Launcher like in the lazer build of osu!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
id="setting-custom-cursor"
|
||||||
|
checked={$customCursor}
|
||||||
|
onCheckedChange={async (e) => {
|
||||||
|
if (!e) {
|
||||||
|
cursorSmoothening.set(false);
|
||||||
|
}
|
||||||
|
customCursor.set(e);
|
||||||
|
|
||||||
|
$userSettings.save();
|
||||||
|
}}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<Label class="text-sm" for="setting-cursor-smoothening">Cursor Smoothening</Label>
|
||||||
|
<div class="text-muted-foreground text-xs">
|
||||||
|
Makes the custom cursor movement smoother.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
id="setting-cursor-smoothening"
|
||||||
|
checked={$cursorSmoothening}
|
||||||
|
onCheckedChange={async (e) => {
|
||||||
|
if (!$customCursor) return;
|
||||||
|
cursorSmoothening.set(e);
|
||||||
|
$userSettings.save();
|
||||||
|
}}
|
||||||
|
disabled={!$customCursor}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<Label class="text-sm" for="setting-reduce-animations">Reduce Animations</Label>
|
||||||
|
<div class="text-muted-foreground text-xs">
|
||||||
|
Disables some animations in the Launcher to improve performance on low-end
|
||||||
|
devices.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
id="setting-reduce-animations"
|
||||||
|
checked={$reduceAnimations}
|
||||||
|
onCheckedChange={async (e) => {
|
||||||
|
reduceAnimations.set(e);
|
||||||
|
$userSettings.save();
|
||||||
|
}}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<Label class="text-sm" for="setting-rich-presence">Discord Rich Presence</Label>
|
||||||
|
<div class="text-muted-foreground text-xs">
|
||||||
|
Let other discord users show what you are doing right now 👀
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="relative">
|
||||||
|
{#if $presenceLoading}
|
||||||
|
<div class="-left-8 absolute" transition:fade>
|
||||||
|
<LoaderCircle class="animate-spin" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<Checkbox
|
||||||
|
id="setting-rich-presence"
|
||||||
|
bind:checked={$discordPresence}
|
||||||
|
disabled={$presenceLoading}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Checkbox
|
|
||||||
id="setting-custom-cursor"
|
|
||||||
checked={$customCursor}
|
|
||||||
onCheckedChange={async (e) => {
|
|
||||||
if (!e) {
|
|
||||||
cursorSmoothening.set(false);
|
|
||||||
}
|
|
||||||
customCursor.set(e);
|
|
||||||
}}
|
|
||||||
class="flex items-center justify-center w-5 h-5"
|
|
||||||
></Checkbox>
|
|
||||||
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<Label class="text-sm" for="setting-cursor-smoothening">Cursor Smoothening</Label>
|
|
||||||
<div class="text-muted-foreground text-xs">
|
|
||||||
Makes the custom cursor movement smoother.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Checkbox
|
|
||||||
id="setting-cursor-smoothening"
|
|
||||||
checked={$cursorSmoothening}
|
|
||||||
onCheckedChange={async (e) => {
|
|
||||||
if (!$customCursor) return;
|
|
||||||
cursorSmoothening.set(e);
|
|
||||||
}}
|
|
||||||
disabled={!$customCursor}
|
|
||||||
class="flex items-center justify-center w-5 h-5"
|
|
||||||
></Checkbox>
|
|
||||||
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<Label class="text-sm" for="setting-cursor-smoothening">Reduce Animations</Label>
|
|
||||||
<div class="text-muted-foreground text-xs">
|
|
||||||
Disables some animations in the Launcher to improve performance on low-end devices.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Checkbox
|
|
||||||
id="setting-cursor-smoothening"
|
|
||||||
checked={$reduceAnimations}
|
|
||||||
onCheckedChange={async (e) => {
|
|
||||||
reduceAnimations.set(e);
|
|
||||||
}}
|
|
||||||
disabled={!$customCursor}
|
|
||||||
class="flex items-center justify-center w-5 h-5"
|
|
||||||
></Checkbox>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||