Compare commits

..

44 Commits

Author SHA1 Message Date
HorizonCode 2e0ef81bcb feat: implement skin management 2026-02-22 17:05:52 +01:00
HorizonCode 96680ff8b5 fix: update button styles to use ring for better accessibility 2026-02-22 14:12:32 +01:00
HorizonCode 0b9d6373bf fix: add fade to bg and audio 2026-02-22 14:03:44 +01:00
HorizonCode 295c1424c9 chore: add audio 2026-02-22 12:35:08 +01:00
HorizonCode 0e12a801d2 chore: add secret :^) 2026-02-22 00:21:42 +01:00
HorizonCode cf86dba5c4 chore: rework titlebar buttons 2026-02-21 23:31:43 +01:00
HorizonCode 540c653399 fix: reconnect presence after pipe was closed 2026-02-21 23:18:01 +01:00
HorizonCode c9b8d9f599 fix: respect reducedAnimations 2026-02-21 22:59:19 +01:00
HorizonCode 49665d94bc fix: Setup WIzard styling 2026-02-19 19:38:21 +01:00
HorizonCode 1a19c19399 chore: bump version 2026-02-19 15:48:26 +01:00
HorizonCode cbfe5ec868 fix: Setup Wizard not working 2026-02-19 15:41:29 +01:00
HorizonCode c5b18dacbb chore: update lucide-svelte 2026-02-19 15:41:10 +01:00
HorizonCode ef01984875 fix: osu installation detection 2026-02-19 15:40:56 +01:00
HorizonCode 46ee36ce81 fix: disable all buttons and dropdowns on launch 2026-02-17 20:34:57 +01:00
HorizonCode c4658ae4cc fix: sort streaks by depth 2026-02-17 20:13:43 +01:00
HorizonCode ca15ff566f chore: bump version 2026-02-17 19:20:19 +01:00
HorizonCode a6fdda4487 fix: also use color-primary for pp stat 2026-02-17 19:19:09 +01:00
HorizonCode a30946eb60 chore: update animatedbg gradient background colors 2026-02-17 19:18:10 +01:00
HorizonCode 9106ad89b3 chore: replace all bg-pink and border-pink with bg-primary and border-primary 2026-02-17 19:15:38 +01:00
HorizonCode 194709df0c style: adjust primary color 2026-02-17 19:13:23 +01:00
HorizonCode 20f625be60 chore: improve line spreading alogrythm 2026-02-17 19:04:04 +01:00
HorizonCode 66a5fd84ae chore: set AnimatedBg globally 2026-02-17 19:02:37 +01:00
HorizonCode 44d7401947 chore: set AnimatedBg globally 2026-02-17 19:02:28 +01:00
HorizonCode f973e48561 chore: add border to view buttons 2026-02-17 18:58:16 +01:00
HorizonCode de186b7ff9 fix: change beatmapSets count icon 2026-02-17 18:55:45 +01:00
HorizonCode 244d526933 chore: make page switch transitions smoother 2026-02-17 18:52:41 +01:00
HorizonCode c7a990e3fe fix: show login result after redirect to home view 2026-02-17 18:46:12 +01:00
HorizonCode ed1f741a9e chore: remove all console.log 2026-02-17 17:43:05 +01:00
HorizonCode b6616a37f4 fix: isNet8Installed condition 2026-02-17 17:41:04 +01:00
HorizonCode 63433ac626 chore: update sileo 2026-02-17 17:40:46 +01:00
HorizonCode 149b4715d7 fix: updateUrl 2026-02-17 17:40:35 +01:00
HorizonCode ae3443d0b9 fix: osu running check 2026-02-16 21:53:53 +01:00
HorizonCode abc7b9d9d9 chore: replace sonner with sileo 2026-02-16 21:53:07 +01:00
HorizonCode d9dd21d3be chore: cleanup commands, fix osu folder check 2026-02-16 20:46:58 +01:00
HorizonCode 0df31178f4 fix: make count functions async to prevent frontend lock 2026-02-16 20:20:29 +01:00
HorizonCode 6825f9e99b fix: login box width, update releasestream and version 2026-02-15 19:08:04 +01:00
HorizonCode 08a8e34781 chore: add launch button states 2026-02-15 19:02:07 +01:00
HorizonCode 1aa973f6bb chore: bump version 2026-02-15 18:57:38 +01:00
HorizonCode 0999832985 fix: titlebar margin 2026-02-15 18:57:30 +01:00
HorizonCode 11798318f4 chore: only display user stats when logged in 2026-02-15 18:57:17 +01:00
HorizonCode f1b909e248 chore: make pp value better visible 2026-02-15 18:43:51 +01:00
HorizonCode 1f845f1282 chore: rework launcher design 2026-02-15 18:42:01 +01:00
HorizonCode f9b38ec9eb fix: improve error handling for network connection launch failures 2026-01-04 20:20:19 +01:00
HorizonCode 623559cf8e chore: update syn and other dependencies to latest versions 2026-01-04 20:08:26 +01:00
30 changed files with 2012 additions and 1088 deletions
+29 -2
View File
@@ -19,7 +19,11 @@
"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.562.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.562.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.9.6",
"@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.562.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-wDMULwtTFN2Sc/TFBm6gfuVCNb4Y5P9LDrwxNnUbV52+IEU7NXZmvxwXoz+vrrpad6Xupq+Hw5eUlqIHEGhouw=="],
"@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=="],
@@ -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=="],
@@ -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=="],
+8 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "ezpplauncher", "name": "ezpplauncher",
"version": "3.0.10", "version": "3.1.2",
"description": "", "description": "",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -32,8 +32,12 @@
"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.9.6",
"@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",
+57 -56
View File
@@ -161,7 +161,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -196,7 +196,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -672,7 +672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -682,7 +682,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -706,7 +706,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -717,7 +717,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -740,7 +740,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc_version", "rustc_version",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -815,7 +815,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -847,7 +847,7 @@ checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -954,7 +954,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -1007,10 +1007,11 @@ dependencies = [
[[package]] [[package]]
name = "ezpplauncher" name = "ezpplauncher"
version = "3.0.10" version = "3.1.2"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"discord-rich-presence", "discord-rich-presence",
"futures-util",
"hardware-id", "hardware-id",
"md5", "md5",
"once_cell", "once_cell",
@@ -1099,7 +1100,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -1186,7 +1187,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -1440,7 +1441,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -1519,7 +1520,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -2060,9 +2061,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.178" version = "0.2.179"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
[[package]] [[package]]
name = "libloading" name = "libloading"
@@ -2161,7 +2162,7 @@ checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -2337,7 +2338,7 @@ dependencies = [
"proc-macro-crate 3.4.0", "proc-macro-crate 3.4.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -2782,7 +2783,7 @@ dependencies = [
"phf_shared 0.11.3", "phf_shared 0.11.3",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -3228,7 +3229,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -3571,7 +3572,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_derive_internals", "serde_derive_internals",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -3676,7 +3677,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -3687,7 +3688,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -3711,7 +3712,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -3772,7 +3773,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -3794,7 +3795,7 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4027,9 +4028,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.112" version = "2.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4" checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -4053,7 +4054,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4152,7 +4153,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4252,7 +4253,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
"syn 2.0.112", "syn 2.0.113",
"tauri-utils", "tauri-utils",
"thiserror 2.0.17", "thiserror 2.0.17",
"time", "time",
@@ -4270,7 +4271,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
"tauri-codegen", "tauri-codegen",
"tauri-utils", "tauri-utils",
] ]
@@ -4537,7 +4538,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4548,7 +4549,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4609,9 +4610,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.48.0" version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@@ -4633,7 +4634,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4648,9 +4649,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.17" version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -4819,7 +4820,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4833,9 +4834,9 @@ dependencies = [
[[package]] [[package]]
name = "tray-icon" name = "tray-icon"
version = "0.21.2" version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d5572781bee8e3f994d7467084e1b1fd7a93ce66bd480f8156ba89dee55a2b" checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dirs", "dirs",
@@ -5115,7 +5116,7 @@ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -5305,7 +5306,7 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -5438,7 +5439,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -5449,7 +5450,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -5904,7 +5905,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
"synstructure", "synstructure",
] ]
@@ -5952,7 +5953,7 @@ dependencies = [
"proc-macro-crate 3.4.0", "proc-macro-crate 3.4.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
"zbus_names", "zbus_names",
"zvariant", "zvariant",
"zvariant_utils", "zvariant_utils",
@@ -5987,7 +5988,7 @@ checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -6007,7 +6008,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
"synstructure", "synstructure",
] ]
@@ -6047,14 +6048,14 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.8" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317f17ff091ac4515f17cc7a190d2769a8c9a96d227de5d64b500b01cda8f2cd" checksum = "30e0d8dffbae3d840f64bda38e28391faef673a7b5a6017840f2a106c8145868"
[[package]] [[package]]
name = "zvariant" name = "zvariant"
@@ -6080,7 +6081,7 @@ dependencies = [
"proc-macro-crate 3.4.0", "proc-macro-crate 3.4.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
"zvariant_utils", "zvariant_utils",
] ]
@@ -6093,6 +6094,6 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn 2.0.112", "syn 2.0.113",
"winnow 0.7.14", "winnow 0.7.14",
] ]
+3 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "ezpplauncher" name = "ezpplauncher"
version = "3.0.10" version = "3.1.2"
description = "EZPPLauncher redefined." description = "EZPPLauncher redefined."
authors = ["HorizonCode"] authors = ["HorizonCode"]
edition = "2024" edition = "2024"
@@ -32,15 +32,16 @@ reqwest = { version = "0.13.1", 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.0.0" discord-rich-presence = "1.0.0"
once_cell = "1.21.3" once_cell = "1.21.3"
futures-util = "0.3"
[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.3.6"
+110 -64
View File
@@ -56,17 +56,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 +77,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 +129,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,7 +154,7 @@ 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_user_config = get_osu_user_config(path.clone());
let songs_path = osu_user_config let songs_path = osu_user_config
@@ -173,12 +167,12 @@ pub fn get_beatmapsets_count(folder: String) -> Option<u64> {
} }
let mut count = 0; let mut count = 0;
if let Ok(entries) = std::fs::read_dir(songs_folder) { if let Ok(mut entries) = fs::read_dir(songs_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 == "osu") { if file.path().extension().map_or(false, |ext| ext == "osu") {
count += 1; count += 1;
break; break;
@@ -191,8 +185,77 @@ pub fn get_beatmapsets_count(folder: String) -> Option<u64> {
return Some(count); return Some(count);
} }
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SkinInfo {
pub name: String,
pub author: Option<String>,
pub modified: u64,
}
#[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 +264,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 +285,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 +324,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 +336,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 +422,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 +436,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 +445,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 +644,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 +653,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 +783,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 +819,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,
}, },
) )
+3 -2
View File
@@ -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,
+30 -12
View File
@@ -2,6 +2,7 @@ use discord_rich_presence::{
DiscordIpc, DiscordIpcClient, DiscordIpc, DiscordIpcClient,
activity::{Activity, Assets, Button, Timestamps}, activity::{Activity, Assets, Button, Timestamps},
}; };
use futures_util::future::BoxFuture;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::sync::Mutex as StdMutex; use std::sync::Mutex as StdMutex;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
@@ -24,6 +25,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 +60,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 +76,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>) -> BoxFuture<'_, ()> {
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,10 +107,20 @@ 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<()>) {
@@ -143,12 +159,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.");
} }
} }
} }
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "EZPPLauncher", "productName": "EZPPLauncher",
"version": "3.0.10", "version": "3.1.2",
"identifier": "farm.EZPPFarm.Launcher", "identifier": "farm.EZPPFarm.Launcher",
"mainBinaryName": "ezpplauncher", "mainBinaryName": "ezpplauncher",
"build": { "build": {
@@ -15,7 +15,7 @@
{ {
"title": "EZPPLauncher", "title": "EZPPLauncher",
"width": 1000, "width": 1000,
"height": 737, "height": 650,
"decorations": false, "decorations": false,
"resizable": false, "resizable": false,
"maximizable": false "maximizable": false
+16 -18
View File
@@ -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);
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.
@@ -0,0 +1,88 @@
<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?
const COUNT = 60;
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: rand(300, 320) * scale,
height: 32 * 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;
"
></div>
{/each}
</div>
<style>
.bg {
position: fixed;
inset: 0;
overflow: hidden;
background: linear-gradient(
180deg,
var(--color-primary-300) 0%,
var(--color-primary-600) 40%,
var(--color-primary-900) 100%
);
}
.streak {
position: absolute;
background: #d6f3ff;
border-radius: 999px;
transform: rotate(-35deg);
animation: move linear infinite;
will-change: transform;
}
@keyframes move {
from {
transform: translate(-150vw, 150vh) rotate(-35deg);
}
to {
transform: translate(150vw, -150vh) rotate(-35deg);
}
}
</style>
@@ -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>
@@ -0,0 +1,91 @@
<script lang="ts">
import Ellipsis from 'lucide-svelte/icons/ellipsis';
import Play from 'lucide-svelte/icons/play';
const prop: {
downloading: boolean;
progress: number;
text: string;
subtext?: string;
disabled?: boolean;
onClick?: () => void;
} = $props();
const radius = 16;
const circumference = 2 * Math.PI * radius;
const strokeDashoffset = $derived(circumference - (prop.progress / 100) * circumference);
</script>
<button
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}
onclick={prop.onClick}
>
<div class="relative size-12 shrink-0 flex items-center justify-center">
{#if prop.downloading}
{#if prop.progress === -1}
<svg class="absolute inset-0 animate-spin" viewBox="0 0 48 48">
<circle class="stroke-black/10" cx="24" cy="24" r={radius} fill="none" stroke-width="3" />
<circle
class="stroke-pink-950"
cx="24"
cy="24"
r={radius}
fill="none"
stroke-width="3"
stroke-linecap="round"
stroke-dasharray={circumference}
stroke-dashoffset={circumference * 0.7}
/>
</svg>
{:else}
<svg class="absolute inset-0 -rotate-90" viewBox="0 0 48 48">
<circle
cx="24"
cy="24"
r={radius}
fill="none"
stroke="rgba(0,0,0,0.2)"
stroke-width="3"
/>
<circle
cx="24"
cy="24"
r={radius}
fill="none"
stroke="rgba(0,0,0,0.7)"
stroke-width="3"
stroke-linecap="round"
stroke-dasharray={circumference}
stroke-dashoffset={strokeDashoffset}
class="transition-[stroke-dashoffset] duration-100"
/>
</svg>
{/if}
{/if}
<div class="size-9 rounded-full bg-black/30 flex items-center justify-center relative z-10">
{#if prop.downloading}
{#if prop.progress === -1}
<Ellipsis class="size-4 text-primary-foreground fill-current animate-pulse" />
{:else}
<span class="text-[10px] font-bold text-primary-foreground">
{prop.progress.toFixed()}
</span>
{/if}
{:else}
<Play class="size-4 text-primary-foreground fill-current ml-0.5" />
{/if}
</div>
</div>
<div class="pr-6 pl-1 text-center w-52">
<p class="text-sm font-bold text-primary-foreground leading-tight">
{prop.text}
</p>
{#if prop.subtext}
<p class="text-[10px] text-primary-foreground/70 leading-tight">
{prop.subtext}
</p>
{/if}
</div>
</button>
@@ -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
)} )}
@@ -85,7 +85,7 @@
animate(cursor, { animate(cursor, {
duration: smoothCursor ? $cursorSmoothness : 0, duration: smoothCursor ? $cursorSmoothness : 0,
translateX: mouseX, translateX: mouseX,
translateY: mouseY - 50, translateY: mouseY,
ease: (t: number) => (t - 1) ** 5 + 1, ease: (t: number) => (t - 1) ** 5 + 1,
}); });
@@ -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>
+27 -33
View File
@@ -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();
@@ -19,32 +20,40 @@
}); });
</script> </script>
<div data-tauri-drag-region class="titlebar z-[60] border-b border-theme-800/90"> <div data-tauri-drag-region class="titlebar z-[100]">
<div <div class="relative flex items-center justify-center p-1 group" id="titlebar-minimize">
class="mr-auto ms-2 flex flex-row gap-2 items-center text-[1.05rem] font-semibold pointer-events-none" <div
> class="absolute inset-0 bg-background rounded
<img src={Logo} alt="EZPP Launcher Logo" class="h-11 w-11 inline-block" /> scale-50 opacity-0
<span>EZPPLauncher</span> group-hover:opacity-100
<Badge class="!text-[0.6rem] py-[0.5px] bg-primary-500 hover:bg-primary-500" group-hover:scale-100
>{$launcherVersion}</Badge {$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 rounded-lg transition-colors duration-75" id="titlebar-minimize"> <div class="relative flex items-center justify-center p-1 group" id="titlebar-close">
<Minimize size={18} /> <div
</div> class="absolute inset-0 bg-red-500 rounded
<div class="titlebar-button close rounded-lg transition-colors duration-75" id="titlebar-close"> scale-50 opacity-0
<Close size={18} /> 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: 50px; 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;
@@ -52,21 +61,6 @@
right: 0; right: 0;
margin-bottom: 10px; margin-bottom: 10px;
padding-right: 5px; padding-right: 5px;
pointer-events: all !important; pointer-events: auto;
}
.titlebar-button {
display: inline-flex;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
user-select: none;
-webkit-user-select: none;
}
.titlebar-button:hover {
&.close {
background: #c22e2e;
}
background: #2d3049;
} }
</style> </style>
+1 -4
View File
@@ -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 => {
-1
View File
@@ -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);
+3 -1
View File
@@ -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);
+18 -9
View File
@@ -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 }
);
-1
View File
@@ -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';
}; };
+134 -2
View File
@@ -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,107 @@
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 fadeInDuration = 2000; // 2 seconds
const fadeInSteps = 20;
const fadeInStepTime = fadeInDuration / fadeInSteps;
const volumeStep = (1 - audio.volume) / fadeInSteps;
const fadeInInterval = setInterval(() => {
if (audio.volume < 0.3) {
audio.volume = Math.min(0.3, 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 +271,7 @@
<OsuCursor smoothCursor={$cursorSmoothening} /> <OsuCursor smoothCursor={$cursorSmoothening} />
{/if} {/if}
<Toaster richColors closeButton theme="dark" /> <SileoToast position="top-center" fill="black" />
<Titlebar /> <Titlebar />
@@ -178,5 +299,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>
+936 -639
View File
File diff suppressed because it is too large Load Diff
+62 -17
View File
@@ -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);
}; };
@@ -202,7 +247,7 @@
}); });
</script> </script>
<div class="flex flex-col items-center justify-center mt-[50px] h-[calc(100vh-50px)] w-full"> <div class="flex flex-col items-center justify-center h-[100vh] w-full">
<div class="relative w-80 h-80 flex items-center justify-center"> <div class="relative w-80 h-80 flex items-center justify-center">
<svg <svg
class="absolute top-0 left-0 w-full h-full animate-spin" class="absolute top-0 left-0 w-full h-full animate-spin"
-127
View File
@@ -1,127 +0,0 @@
<script lang="ts">
import Logo from '$assets/logo.png';
import { ezppfarm } from '@/api/ezpp';
import Button from '@/components/ui/button/button.svelte';
import Input from '@/components/ui/input/input.svelte';
import Label from '@/components/ui/label/label.svelte';
import { currentView } from '@/global';
import { currentUser, userAuth } from '@/userAuthentication';
import { animate } from 'animejs';
import { LoaderCircle } from 'lucide-svelte';
import { toast } from 'svelte-sonner';
import Launch from './Launch.svelte';
import { currentUserInfo } from '@/data';
import { preferredMode, preferredType } from '@/userSettings';
let username = $state('');
let password = $state('');
let isLoading = $state(false);
let ezppLogo: HTMLImageElement | undefined = $state(undefined);
const logo_mouseenter = () => {
if (ezppLogo) {
animate(ezppLogo, {
duration: 700,
scale: 1.2,
ease: (t: number) => Math.pow(2, -5 * t) * Math.sin((t - 0.075) * 20.94) + 1 - 0.0005 * t,
});
}
};
const logo_mouseleave = () => {
if (ezppLogo) {
animate(ezppLogo, {
duration: 700,
scale: 1,
ease: (t: number) => (t - 1) ** 7 + 1,
});
}
};
const performLogin = async () => {
isLoading = true;
try {
const loginResult = await ezppfarm.login(username, password);
if (loginResult && loginResult.user) {
toast.success('Login successful!', {
description: `Welcome back, ${loginResult.user.name}!`,
});
$userAuth.value('username').set(username);
$userAuth.value('password').set(password);
await $userAuth.save();
currentUser.set(loginResult.user);
currentView.set(Launch);
} else {
toast.error('Login failed!', {
description: 'Please check your username and password.',
});
isLoading = false;
}
} catch {
toast.error('Server error occurred during login.', {
description: 'There was an issue connecting to the server. Please try again later.',
});
isLoading = false;
}
if ($currentUser) {
const userInfo = await ezppfarm.getUserInfo($currentUser.id);
if (userInfo) {
currentUserInfo.set(userInfo.player);
preferredMode.set(userInfo.player.info.preferred_mode);
preferredType.set(userInfo.player.info.preferred_type);
}
}
};
</script>
<div class="mt-[50px] h-[calc(100vh-50px)] w-full">
<div class="w-full h-full flex flex-col items-center justify-center">
<img
src={Logo}
alt="EZPPLauncher Logo"
class="w-52 h-52 mb-2"
bind:this={ezppLogo}
onmouseenter={logo_mouseenter}
onmouseleave={logo_mouseleave}
/>
<form onsubmit={performLogin} class="w-full max-w-sm">
<div class="mb-4">
<Label for="username" class="block text-sm font-medium">Username</Label>
<Input
class="mt-4 w-full bg-theme-900 border-theme-800"
type="text"
id="username"
bind:value={username}
disabled={isLoading}
autocomplete="off"
autocorrect="off"
/>
</div>
<div class="mb-4">
<Label for="password" class="block text-sm font-medium">Password</Label>
<Input
class="mt-4 w-full bg-theme-900 border-theme-800"
type="password"
id="password"
bind:value={password}
disabled={isLoading}
autocomplete="off"
autocorrect="off"
/>
</div>
<Button class="w-full" type="submit" disabled={isLoading}>
{#if isLoading}
<LoaderCircle class="animate-spin" />
{:else}
Login
{/if}
</Button>
</form>
</div>
</div>
+135 -90
View File
@@ -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 mt-[50px] h-[calc(100vh-50px)]"> <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] mt-[50px] h-[calc(100vh-50px)]"> <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 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 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 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}