Compare commits

...

8 Commits

13 changed files with 870 additions and 813 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ezpplauncher", "name": "ezpplauncher",
"version": "3.0.10", "version": "3.1.0",
"description": "", "description": "",
"type": "module", "type": "module",
"scripts": { "scripts": {
+56 -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,7 +1007,7 @@ dependencies = [
[[package]] [[package]]
name = "ezpplauncher" name = "ezpplauncher"
version = "3.0.10" version = "3.1.0"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"discord-rich-presence", "discord-rich-presence",
@@ -1099,7 +1099,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -1186,7 +1186,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -1440,7 +1440,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 +1519,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 +2060,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 +2161,7 @@ checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -2337,7 +2337,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 +2782,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 +3228,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -3571,7 +3571,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 +3676,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -3687,7 +3687,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -3711,7 +3711,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -3772,7 +3772,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -3794,7 +3794,7 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4027,9 +4027,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 +4053,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4152,7 +4152,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4252,7 +4252,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 +4270,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 +4537,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4548,7 +4548,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4609,9 +4609,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 +4633,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4648,9 +4648,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 +4819,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -4833,9 +4833,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 +5115,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 +5305,7 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -5438,7 +5438,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -5449,7 +5449,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -5904,7 +5904,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
"synstructure", "synstructure",
] ]
@@ -5952,7 +5952,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 +5987,7 @@ checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
] ]
[[package]] [[package]]
@@ -6007,7 +6007,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.112", "syn 2.0.113",
"synstructure", "synstructure",
] ]
@@ -6047,14 +6047,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 +6080,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 +6093,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",
] ]
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "ezpplauncher" name = "ezpplauncher"
version = "3.0.10" version = "3.1.0"
description = "EZPPLauncher redefined." description = "EZPPLauncher redefined."
authors = ["HorizonCode"] authors = ["HorizonCode"]
edition = "2024" edition = "2024"
+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.0",
"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
@@ -0,0 +1,77 @@
<script lang="ts">
//TODO: maybe dynamic background images fetched from ezpp?
const COUNT = 120;
function rand(min: number, max: number) {
return Math.random() * (max - min) + min;
}
const streaks = Array.from({ length: COUNT }).map(() => {
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(180, 320) * scale,
height: rand(16, 32) * scale,
x: rand(-80, 100),
y: rand(-80, 100),
opacity: 0.25 + depth * 0.65,
duration,
delay: rand(0, duration),
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}
<div
class="streak drop-shadow-lg"
style="
width:{s.width}px;
height:{s.height}px;
left:{s.x}%;
top:{s.y}%;
opacity:{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, #8f2f64 0%, #963f75 40%, #9a6c7f 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,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-pink-400 disabled:bg-pink-400/70 shadow-lg shadow-primary/25 transition-all not-disabled:hover:shadow-primary/40 not-disabled:hover:brightness-110 not-disabled:active:scale-[0.98] cursor-pointer w-fit"
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>
@@ -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,
}); });
+9 -18
View File
@@ -19,27 +19,18 @@
}); });
</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="titlebar-button rounded transition-colors duration-75" id="titlebar-minimize">
class="mr-auto ms-2 flex flex-row gap-2 items-center text-[1.05rem] font-semibold pointer-events-none" <Minimize size={14} />
>
<img src={Logo} alt="EZPP Launcher Logo" class="h-11 w-11 inline-block" />
<span>EZPPLauncher</span>
<Badge class="!text-[0.6rem] py-[0.5px] bg-primary-500 hover:bg-primary-500"
>{$launcherVersion}</Badge
>
</div> </div>
<div class="titlebar-button rounded-lg transition-colors duration-75" id="titlebar-minimize"> <div class="titlebar-button close rounded transition-colors duration-75" id="titlebar-close">
<Minimize size={18} /> <Close size={14} />
</div>
<div class="titlebar-button close rounded-lg transition-colors duration-75" id="titlebar-close">
<Close size={18} />
</div> </div>
</div> </div>
<style lang="scss"> <style lang="scss">
.titlebar { .titlebar {
height: 50px; height: 35px;
/* background: #040612; */ /* background: #040612; */
user-select: none; user-select: none;
display: flex; display: flex;
@@ -52,14 +43,14 @@
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 { .titlebar-button {
display: inline-flex; display: inline-flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 40px; width: 25px;
height: 40px; height: 25px;
user-select: none; user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
} }
+1 -1
View File
@@ -150,7 +150,7 @@
<OsuCursor smoothCursor={$cursorSmoothening} /> <OsuCursor smoothCursor={$cursorSmoothening} />
{/if} {/if}
<Toaster richColors closeButton theme="dark" /> <Toaster richColors closeButton theme="dark" position="top-right" />
<Titlebar /> <Titlebar />
+342 -317
View File
@@ -7,7 +7,6 @@
import { import {
beatmapSets, beatmapSets,
currentSkin, currentSkin,
currentView,
discordPresence, discordPresence,
launcherStream, launcherStream,
launcherStreams, launcherStreams,
@@ -25,13 +24,9 @@
} from '@/global'; } from '@/global';
import { import {
LoaderCircle, LoaderCircle,
Logs,
Music2, Music2,
Play,
Wifi, Wifi,
Gamepad2,
WifiOff, WifiOff,
Settings2,
Drum, Drum,
Cherry, Cherry,
Piano, Piano,
@@ -39,10 +34,9 @@
LogOut, LogOut,
LogIn, LogIn,
Brush, Brush,
Heart,
ArrowRight, ArrowRight,
Settings, Settings,
ArrowLeft, House,
} from 'lucide-svelte'; } from 'lucide-svelte';
import NumberFlow from '@number-flow/svelte'; import NumberFlow from '@number-flow/svelte';
import * as AlertDialog from '@/components/ui/alert-dialog'; import * as AlertDialog from '@/components/ui/alert-dialog';
@@ -50,13 +44,12 @@
import { import {
compareBuildNumbers, compareBuildNumbers,
formatBytes, formatBytes,
formatTimeReadable,
numberHumanReadable, numberHumanReadable,
openURL, openURL,
releaseStreamToReadable, releaseStreamToReadable,
urlIsValidImage, urlIsValidImage,
} from '@/utils'; } from '@/utils';
import { fade, scale } from 'svelte/transition'; import { fade, fly, scale } from 'svelte/transition';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import Label from '@/components/ui/label/label.svelte'; import Label from '@/components/ui/label/label.svelte';
import { import {
@@ -72,7 +65,6 @@
import Input from '@/components/ui/input/input.svelte'; import Input from '@/components/ui/input/input.svelte';
import { open } from '@tauri-apps/plugin-dialog'; import { open } from '@tauri-apps/plugin-dialog';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import Login from './Login.svelte';
import { currentUser, userAuth } from '@/userAuthentication'; import { currentUser, userAuth } from '@/userAuthentication';
import { import {
getGamemodeInt, getGamemodeInt,
@@ -110,22 +102,27 @@
} from '@/osuUtil'; } from '@/osuUtil';
import { getCurrentWindow } from '@tauri-apps/api/window'; import { getCurrentWindow } from '@tauri-apps/api/window';
import { ezppfarm } from '@/api/ezpp'; import { ezppfarm } from '@/api/ezpp';
import Hearts from '@/components/ui/effects/Hearts.svelte';
import { EZPPActionStatus } from '@/types'; import { EZPPActionStatus } from '@/types';
import * as presence from '@/presence'; import * as presence from '@/presence';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import * as DropdownMenu from '@/components/ui/dropdown-menu'; import * as DropdownMenu from '@/components/ui/dropdown-menu';
import ChevronDown from '@lucide/svelte/icons/chevron-down'; import DownloadButton from '@/components/ui/download-button/DownloadButton.svelte';
import { animate } from 'animejs';
import AnimatedBg from '@/components/ui/animated-bg/AnimatedBg.svelte';
let selectedView = $state('home'); let selectedView = $state('home');
let progress = $state(-1); let progress = $state(-1);
let launchInfo = $state(''); let launchInfo = $state('');
let launchError = $state<Error | undefined>(undefined); let launchError = $state<Error | undefined>(undefined);
let ezppLogo: HTMLImageElement;
let askForTrackingPermission = $state(false); let askForTrackingPermission = $state(false);
let downloadingUpdate = $state(false); let downloadingUpdate = $state(false);
let downloadingEZPPFiles = $state(false);
let cleanup = $state(false);
let selectedGamemode = $derived( let selectedGamemode = $derived(
getGamemodeInt(modeIntToStr($preferredMode), typeIntToStr($preferredType)) getGamemodeInt(modeIntToStr($preferredMode), typeIntToStr($preferredType))
); );
@@ -172,6 +169,7 @@
}; };
const launch = async () => { const launch = async () => {
if ($launching) return;
if ($trackingEnabled) umami.track('app_launch_osu'); if ($trackingEnabled) umami.track('app_launch_osu');
const osuRunning = await isOsuRunning(); const osuRunning = await isOsuRunning();
if (osuRunning) { if (osuRunning) {
@@ -219,12 +217,13 @@
} }
try { try {
launchInfo = 'Looking for EZPPLauncher File updates...'; launchInfo = 'Looking for file updates...';
const updateResult = await getEZPPLauncherUpdateFiles(osuPath, $launcherStream); const updateResult = await getEZPPLauncherUpdateFiles(osuPath, $launcherStream);
if (updateResult) { if (updateResult) {
if (updateResult.filesToDownload.length > 0) { if (updateResult.filesToDownload.length > 0) {
launchInfo = 'Found EZPPLauncher File updates!'; downloadingEZPPFiles = true;
launchInfo = 'Found file updates!';
await new Promise((res) => setTimeout(res, 1000)); await new Promise((res) => setTimeout(res, 1000));
await downloadEZPPLauncherUpdateFiles( await downloadEZPPLauncherUpdateFiles(
osuPath, osuPath,
@@ -232,12 +231,13 @@
updateResult.updateFiles, updateResult.updateFiles,
(file) => { (file) => {
progress = file.progress; progress = file.progress;
launchInfo = `Downloading ${file.fileName}(${formatBytes( launchInfo = `${file.fileName}(${formatBytes(
file.downloaded file.downloaded
)}/${formatBytes(file.size)})...`; )}/${formatBytes(file.size)})...`;
} }
); );
progress = -1; progress = -1;
downloadingEZPPFiles = false;
} else { } else {
launchInfo = 'EZPPLauncher Files are up to date!'; launchInfo = 'EZPPLauncher Files are up to date!';
await new Promise((res) => setTimeout(res, 1500)); await new Promise((res) => setTimeout(res, 1500));
@@ -477,6 +477,7 @@
await runOsu(osuPath, true); await runOsu(osuPath, true);
if ($trackingEnabled) umami.track('app_exit_osu'); if ($trackingEnabled) umami.track('app_exit_osu');
cleanup = true;
launchInfo = 'Cleaning up...'; launchInfo = 'Cleaning up...';
await getCurrentWindow().show(); await getCurrentWindow().show();
if (presenceUpdater) { if (presenceUpdater) {
@@ -520,15 +521,92 @@
} }
launching.set(false); launching.set(false);
cleanup = false;
} catch (err) { } catch (err) {
launchError = err as Error; cleanup = false;
console.log(err); const error = err as Error;
if (error.name === 'AbortError') {
toast.error('Hmmm...', {
description: 'Failed to launch.',
});
launching.set(false);
launchError = {
name: error.name,
message: 'Network request connection timed out.',
};
} else {
launchError = error;
toast.error('Hmmm...', { toast.error('Hmmm...', {
description: 'Failed to launch.', description: 'Failed to launch.',
}); });
launching.set(false); launching.set(false);
if ($trackingEnabled) umami.track('app_launch_fail', { error: err }); if ($trackingEnabled) umami.track('app_launch_fail', { error: err });
} }
}
};
let username = $state('');
let password = $state('');
let loginIsLoading = $state(false);
const performLogin = async () => {
loginIsLoading = 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);
selectedView = 'home';
} else {
toast.error('Login failed!', {
description: 'Please check your username and password.',
});
loginIsLoading = false;
}
} catch {
toast.error('Server error occurred during login.', {
description: 'There was an issue connecting to the server. Please try again later.',
});
loginIsLoading = 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);
}
}
};
let animateInterval: number | undefined;
const doBPMAnimation = () => {
if (animateInterval) return;
animateInterval = window.setInterval(async () => {
animate(ezppLogo, {
scale: 1.1,
duration: 900,
ease: (t: number) => Math.pow(2, -5 * t) * Math.sin((t - 0.075) * 20.94) + 1 - 0.0005 * t,
onComplete: () => {},
});
await new Promise((resolve) => setTimeout(resolve, 200));
animate(ezppLogo, {
scale: 1,
duration: 900,
ease: (t: number) => (t - 1) ** 7 + 1,
onComplete: () => {},
});
}, 450);
}; };
onMount(() => { onMount(() => {
@@ -539,6 +617,17 @@
} else { } else {
askForTrackingPermission = true; askForTrackingPermission = true;
} }
animate(ezppLogo, {
opacity: [0, 1],
scale: [0.95, 1],
duration: 900,
ease: (t: number) => (t - 1) ** 7 + 1,
onComplete: doBPMAnimation,
});
return () => {
window.clearInterval(animateInterval);
};
}); });
</script> </script>
@@ -697,65 +786,47 @@
</AlertDialog.Content> </AlertDialog.Content>
</AlertDialog.Root> </AlertDialog.Root>
<AlertDialog.Root bind:open={$launching}> <div class="grid grid-cols-[0.085fr_1fr] h-[100vh] relative">
<AlertDialog.Content <div class="absolute top-0 left-0 w-full h-[100vh] opacity-30 z-0">
class="bg-theme-950 border-theme-800 p-0" <AnimatedBg />
escapeKeydownBehavior="ignore" </div>
interactOutsideBehavior="ignore"
>
<div <div
class="flex flex-col items-center justify-center border-b border-theme-800 bg-black/40 rounded-t-lg p-3" class="p-3 border-r border-r-theme-900 flex flex-col items-center gap-2 z-10 bg-black/40 backdrop-blur-sm"
> >
<img class="h-20 w-20" src={Logo} alt="logo" /> <div class="bg-pink-950/80 border border-pink-900/50 rounded-[1.1rem] p-1">
<span class="font-semibold text-xl">Launching...</span> <img class="pointer-events-none" src={Logo} alt="logo" bind:this={ezppLogo} />
</div> </div>
<div class="flex flex-col items-center justify-center gap-2 p-3 rounded-lg"> <Badge class="text-[0.5rem] py-0 px-2">{$launcherVersion || 'dev'}</Badge>
<Progress indeterminate={progress === -1} value={progress} />
<span class="text-muted-foreground text-sm mt-4">{launchInfo}</span>
</div>
</AlertDialog.Content>
</AlertDialog.Root>
<div class="grid grid-cols-[0.38fr_1fr] mt-[50px] h-[calc(100vh-50px)]">
<div class="w-full h-full border-r border-theme-800/90 flex flex-col gap-6 py-3">
<div class="flex flex-col items-center gap-2 border-b pb-3">
<DropdownMenu.Root>
<DropdownMenu.Trigger class="w-full px-3">
<Button <Button
class="w-full justify-start py-6 ps-1 pr-3 border-theme-800/90 bg-theme-900/90 hover:bg-theme-800/90 border" class="flex size-12 items-center gap-2 {selectedView === 'home'
? 'bg-pink-300/50'
: 'bg-black/20'} hover:bg-pink-300/50 rounded-[0.85rem] p-3 mt-3"
onclick={() => (selectedView = 'home')}
> >
<div class="flex flex-row items-center gap-2 relative"> <House class="text-theme-200 !size-5" />
{#if $currentUser?.donor} </Button>
<Hearts className="-top-1 -left-1 z-50 h-[35px] w-[250px] opacity-80 absolute" <Button
></Hearts> class="flex size-12 items-center gap-2 {selectedView === 'settings'
{/if} ? 'bg-pink-300/50'
<Avatar.Root class="size-10 border-2 border-theme-800"> : 'bg-black/20'} hover:bg-pink-300/50 rounded-[0.85rem] p-3 mt-3"
onclick={() => (selectedView = 'settings')}
>
<Settings class="text-theme-200 !size-5" />
</Button>
<div class="mt-auto">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Avatar.Root class="size-10">
<Avatar.Image src="https://a.ez-pp.farm/{$currentUser?.id ?? 0}" /> <Avatar.Image src="https://a.ez-pp.farm/{$currentUser?.id ?? 0}" />
<Avatar.Fallback class="bg-theme-900"> <Avatar.Fallback class="bg-theme-900">
<LoaderCircle class="animate-spin" size={32} /> <LoaderCircle class="animate-spin" size={32} />
</Avatar.Fallback> </Avatar.Fallback>
</Avatar.Root> </Avatar.Root>
<span class="font-semibold text-sm text-muted-foreground truncate max-w-40">
{$currentUser?.name ?? 'Guest'}
</span>
</div>
<ChevronDown class="ms-auto text-[#5d6581]" />
</Button>
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DropdownMenu.Content <DropdownMenu.Content align="end" side="right" class="w-44">
class="w-[calc(var(--bits-dropdown-menu-anchor-width)_-_1.5rem)] bg-theme-950 border border-theme-900 rounded-lg"
align="center"
>
<DropdownMenu.Item
class={selectedView === 'settings' ? 'bg-accent text-accent-foreground' : ''}
onclick={() => (selectedView = 'settings')}
>
<Settings class="!size-4" />
Settings
</DropdownMenu.Item>
<DropdownMenu.Separator class="bg-theme-800/45" />
{#if $currentUser} {#if $currentUser}
<DropdownMenu.Item <DropdownMenu.Item
class="text-destructive focus:text-destructive text-xs"
onclick={async () => { onclick={async () => {
$userAuth.value('username').del(); $userAuth.value('username').del();
$userAuth.value('password').del(); $userAuth.value('password').del();
@@ -765,25 +836,146 @@
}); });
currentUser.set(undefined); currentUser.set(undefined);
currentUserInfo.set(undefined); currentUserInfo.set(undefined);
selectedView = 'home';
}} }}
> >
<LogOut class="!size-4" /> <LogOut class="size-3.5 mr-2" />
Logout Logout
</DropdownMenu.Item> </DropdownMenu.Item>
{:else} {:else}
<DropdownMenu.Item onclick={() => currentView.set(Login)}> <DropdownMenu.Item class="text-xs" onclick={() => (selectedView = 'login')}>
<LogIn class="!size-4" /> <LogIn class="size-3.5 mr-2" />
Login Login
</DropdownMenu.Item> </DropdownMenu.Item>
{/if} {/if}
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
</div> </div>
{#if $currentUser} </div>
<div class="flex flex-col gap-6 h-full px-3"> <div class="z-10 h-full overflow-hidden">
{#if selectedView === 'home'}
<div <div
in:scale={{ duration: $reduceAnimations ? 0 : 400, start: 0.98 }} class="flex flex-col-reverse h-full"
out:scale={{ duration: $reduceAnimations ? 0 : 400, start: 0.98 }} in:fly={{ duration: 400, delay: 400, y: 10, opacity: 0 }}
out:fly={{ duration: 400, y: -10, opacity: 0 }}
>
<div class="relative z-10 px-8 py-4 flex items-center justify-between">
<div class="flex items-center gap-4">
<div class="flex items-center gap-2">
<Music2 class="size-3.5 text-blue-400" />
<span class="text-[11px] text-white/70 font-medium">
{#if !$beatmapSets && $beatmapSets !== 0}
<LoaderCircle class="animate-spin" size={12} />
{:else}
{numberHumanReadable($beatmapSets ?? 0)}
{/if}
</span>
</div>
<div class="flex items-center gap-2">
<Brush class="size-3.5 text-amber-400" />
<span class="text-[11px] text-white/70 font-medium">
{#if !$skins && $skins !== 0}
<LoaderCircle class="animate-spin" size={12} />
{:else}
{numberHumanReadable($skins ?? 0)}
{/if}
</span>
</div>
<div class="flex items-center gap-2">
{#if $serverConnectionFails > 1}
<WifiOff class="size-3.5 text-red-400" />
{:else}
<Wifi class="size-3.5 text-emerald-400" />
{/if}
<span class="relative text-[11px] text-white/70 font-medium">
<div
class="absolute top-0.5 left-1/2 -translate-x-1/2 {!$serverPing ||
$serverConnectionFails > 1
? 'opacity-100'
: 'opacity-0'} transition-opacity duration-1000"
>
<LoaderCircle class="size-3.5 animate-spin" />
</div>
<div
class="{!$serverPing || $serverConnectionFails > 1
? 'opacity-0'
: 'opacity-100'} transition-opacity duration-1000"
>
{#if $reduceAnimations}
<span>{$serverPing}ms</span>
{:else}
<NumberFlow value={$serverPing ?? 0} trend={0} suffix="ms" />
{/if}
</div>
</span>
</div>
<div class="w-px h-4 bg-white/10"></div>
<span
class="text-[10px] px-2 py-0.5 rounded-full bg-white/10 text-white/50 border border-white/[0.06]"
>
{#if $osuStream}
{releaseStreamToReadable($osuStream)}
{:else}
<LoaderCircle class="animate-spin" size={10} />
{/if}
</span>
<span
class="text-[10px] px-2 py-0.5 rounded-full bg-white/10 text-white/50 font-mono border border-white/[0.06]"
>
{#if $osuBuild}
{$osuBuild}
{:else}
<LoaderCircle class="animate-spin" size={10} />
{/if}
</span>
<span
class="text-[10px] px-2 py-0.5 rounded-full bg-primary/20 text-primary border border-primary/20"
>
{#if $currentSkin}
{#if $currentSkin.length > 23}
{$currentSkin.slice(0, 23) + '...'}
{:else}
{$currentSkin}
{/if}
{:else}
<LoaderCircle class="animate-spin" size={10} />
{/if}
</span>
</div>
<DownloadButton
downloading={$launching}
{progress}
text={$launching
? downloadingEZPPFiles
? 'Downloading...'
: cleanup
? 'Cleaning up...'
: 'Launching...'
: $serverConnectionFails > 1
? 'No connection'
: $osuInstallationPath === ''
? 'Hmmm..'
: 'Launch'}
subtext={$launching && !cleanup
? launchInfo
: $osuInstallationPath === ''
? 'osu! path is not set!'
: $serverConnectionFails > 1
? 'No connection...'
: undefined}
disabled={$osuInstallationPath === '' || $serverConnectionFails > 1}
onClick={launch}
/>
</div>
{#if $currentUserInfo}
<div
class="m-1 w-72 h-48 bg-black/40 backdrop-blur-sm rounded-md border border-black/40 flex flex-col items-center p-3"
in:fly={{ duration: 400, y: 10, opacity: 0 }}
out:fly={{ duration: 400, y: -10, opacity: 0 }}
> >
<Select.Root <Select.Root
type="single" type="single"
@@ -826,28 +1018,10 @@
{/each} {/each}
</Select.Content> </Select.Content>
</Select.Root> </Select.Root>
</div> <div class="grid grid-cols-2 gap-2 w-full m-3">
<div <div class="flex flex-col">
class="bg-theme-900/90 border border-theme-800/90 rounded-lg p-2" <span class="text-xs text-muted-foreground font-semibold">Rank</span>
in:scale={{ <div class="flex items-center h-full font-semibold text-theme-50">
duration: $reduceAnimations ? 0 : 400,
delay: $reduceAnimations ? 0 : 50,
start: 0.98,
}}
out:scale={{
duration: $reduceAnimations ? 0 : 400,
delay: $reduceAnimations ? 0 : 50,
start: 0.98,
}}
>
<div class="flex flex-row items-center gap-2">
<Logs class="text-muted-foreground" size="16" />
<span class="font-semibold text-muted-foreground text-sm">Mode Stats</span>
</div>
<div class="grid grid-cols-2 mt-2 border-t border-theme-800 pt-2 pb-2">
<div class="flex flex-col gap-0.5">
<span class="text-sm text-muted-foreground font-semibold">Rank</span>
<div class="flex items-center h-full text-lg font-semibold text-theme-50">
{#if $currentUserInfo} {#if $currentUserInfo}
<div in:fade> <div in:fade>
{#if $reduceAnimations} {#if $reduceAnimations}
@@ -869,18 +1043,19 @@
{/if} {/if}
</div> </div>
</div> </div>
<div class="flex flex-col gap-0.5"> <div class="flex flex-col">
<span class="text-sm text-muted-foreground font-semibold">PP</span> <span class="text-xs text-muted-foreground font-semibold">PP</span>
<div class="flex items-center h-full text-lg font-semibold text-theme-50"> <div class="flex items-center h-full font-semibold text-pink-300">
{#if $currentUserInfo} {#if $currentUserInfo}
<div in:fade> <div in:fade>
{#if $reduceAnimations} {#if $reduceAnimations}
<span> <span>
{numberHumanReadable($currentUserInfo.stats[selectedGamemode].pp ?? 0)} {numberHumanReadable($currentUserInfo.stats[selectedGamemode].pp ?? 0)}pp
</span> </span>
{:else} {:else}
<NumberFlow <NumberFlow
trend={0} trend={0}
suffix="pp"
value={$currentUserInfo.stats[selectedGamemode].pp ?? 0} value={$currentUserInfo.stats[selectedGamemode].pp ?? 0}
/> />
{/if} {/if}
@@ -892,22 +1067,20 @@
{/if} {/if}
</div> </div>
</div> </div>
</div> <div class="flex flex-col">
<div class="grid grid-cols-[1fr_auto] border-t border-theme-800 pt-2">
<span class="text-xs text-muted-foreground font-semibold">Accuracy</span> <span class="text-xs text-muted-foreground font-semibold">Accuracy</span>
<div <div class="flex items-center h-full font-semibold text-theme-50">
class="flex items-center flex-row-reverse h-full text-xs text-end font-semibold text-theme-50"
>
{#if $currentUserInfo} {#if $currentUserInfo}
<div in:fade> <div in:fade>
{#if $reduceAnimations} {#if $reduceAnimations}
<span>{($currentUserInfo.stats[selectedGamemode].acc ?? 0).toFixed(2)}%</span> <span>
{($currentUserInfo.stats[selectedGamemode].acc ?? 0).toFixed(2)}%
</span>
{:else} {:else}
<NumberFlow <NumberFlow
class="leading-none"
trend={0} trend={0}
suffix="%" suffix="%"
value={($currentUserInfo.stats[selectedGamemode].acc ?? 0).toFixed(2)} value={$currentUserInfo.stats[selectedGamemode].acc.toFixed(2) ?? 0}
/> />
{/if} {/if}
</div> </div>
@@ -917,22 +1090,18 @@
</div> </div>
{/if} {/if}
</div> </div>
</div>
<span class="text-xs text-muted-foreground font-semibold">Play Count</span> <div class="flex flex-col">
<div <span class="text-xs text-muted-foreground font-semibold">Playcount</span>
class="flex items-center flex-row-reverse h-full text-xs text-end font-semibold text-theme-50" <div class="flex items-center h-full font-semibold text-theme-50">
>
{#if $currentUserInfo} {#if $currentUserInfo}
<div in:fade> <div in:fade>
{#if $reduceAnimations} {#if $reduceAnimations}
<span <span>
>{numberHumanReadable( {numberHumanReadable($currentUserInfo.stats[selectedGamemode].plays ?? 0)}
$currentUserInfo.stats[selectedGamemode].plays ?? 0 </span>
)}</span
>
{:else} {:else}
<NumberFlow <NumberFlow
class="leading-none"
trend={0} trend={0}
value={$currentUserInfo.stats[selectedGamemode].plays ?? 0} value={$currentUserInfo.stats[selectedGamemode].plays ?? 0}
/> />
@@ -944,207 +1113,31 @@
</div> </div>
{/if} {/if}
</div> </div>
<span class="text-xs text-muted-foreground font-semibold">Play Time</span>
<div
class="flex items-center flex-row-reverse h-full text-xs text-end font-semibold text-theme-50"
>
{#if $currentUserInfo}
<div in:fade>
{formatTimeReadable($currentUserInfo.stats[selectedGamemode].playtime ?? 0)}
</div>
{:else}
<div in:fade>
<LoaderCircle class="animate-spin" size={21} />
</div>
{/if}
</div>
</div> </div>
</div> </div>
</div> </div>
{/if} {/if}
</div> <div class="flex flex-col mb-auto mt-12 px-6">
<div class="flex flex-col gap-6 w-full h-full p-6"> <span class="text-4xl font-bold drop-shadow-lg">EZPPLauncher</span>
{#if selectedView === 'home'} <span class="text-muted-foreground font-semibold drop-shadow-lg"
<div >Hello {$currentUser?.name ?? 'Guest'}!</span
class="my-auto bg-theme-900/90 flex flex-col items-center justify-center gap-6 border border-theme-800/90 rounded-lg p-6"
in:scale={{ duration: $reduceAnimations ? 0 : 400, start: 0.98 }}
> >
<div class="grid grid-cols-3 w-full gap-3">
<div
class="bg-theme-800/90 border border-theme-700/90 rounded-lg px-2 py-4 w-full flex flex-col gap-1 items-center justify-center"
>
<div class="flex items-center justify-center p-2 rounded-lg bg-blue-500/20">
<Music2 class="text-blue-500" size="26" />
</div>
<div class="relative font-bold text-xl text-blue-400">
<div
class="absolute top-1 left-1/2 -translate-x-1/2 {!$beatmapSets
? 'opacity-100'
: 'opacity-0'} transition-opacity duration-1000"
>
<LoaderCircle class="animate-spin" />
</div>
<div
class="{!$beatmapSets
? 'opacity-0'
: 'opacity-100'} transition-opacity duration-1000"
>
{#if $reduceAnimations}
<span>{numberHumanReadable($beatmapSets ?? 0)}</span>
{:else}
<NumberFlow value={$beatmapSets ?? 0} trend={0} />
{/if}
</div>
</div>
<div class="text-muted-foreground text-[12px] leading-4">Beatmap Sets imported</div>
</div>
<div
class="bg-theme-800/90 border border-theme-700/90 rounded-lg px-2 py-4 w-full flex flex-col gap-1 items-center justify-center"
>
<div class="flex items-center justify-center p-2 rounded-lg bg-yellow-500/20">
<Brush class="text-yellow-500" size="26" />
</div>
<div class="relative font-bold text-xl text-yellow-400">
<div
class="absolute top-1 left-1/2 -translate-x-1/2 {!$skins && $skins !== 0
? 'opacity-100'
: 'opacity-0'} transition-opacity duration-1000"
>
<LoaderCircle class="animate-spin" />
</div>
<div
class="{!$skins && $skins !== 0
? 'opacity-0'
: 'opacity-100'} transition-opacity duration-1000"
>
{#if $reduceAnimations}
<span>{numberHumanReadable($skins ?? 0)}</span>
{:else}
<NumberFlow value={$skins ?? 0} trend={0} />
{/if}
</div>
</div>
<div class="text-muted-foreground text-[12px] leading-4">Skins</div>
</div>
<div
class="bg-theme-800/90 border border-theme-700/90 rounded-lg px-2 py-4 w-full flex flex-col gap-1 items-center justify-center"
>
<div
class="flex items-center justify-center p-2 rounded-lg {$serverConnectionFails > 1
? 'bg-red-500/20'
: 'bg-green-500/20'}"
>
{#if $serverConnectionFails > 1}
<WifiOff class="text-red-500" size="26" />
{:else}
<Wifi class="text-green-500" size="26" />
{/if}
</div>
<div
class="relative font-bold text-xl {$serverConnectionFails > 1
? 'text-red-400'
: 'text-green-400'}"
>
<div
class="absolute top-1 left-1/2 -translate-x-1/2 {!$serverPing ||
$serverConnectionFails > 1
? 'opacity-100'
: 'opacity-0'} transition-opacity duration-1000"
>
<LoaderCircle class="animate-spin" />
</div>
<div
class="{!$serverPing || $serverConnectionFails > 1
? 'opacity-0'
: 'opacity-100'} transition-opacity duration-1000"
>
{#if $reduceAnimations}
<span>{$serverPing}ms</span>
{:else}
<NumberFlow value={$serverPing ?? 0} trend={0} suffix="ms" />
{/if}
</div>
</div>
<div class="text-muted-foreground text-[12px] leading-4">Ping to Server</div>
</div>
</div>
<Button
size="lg"
class="text-xl h-16 px-16 border-2 border-violet-500/40"
disabled={$launching || $osuInstallationPath === '' || $serverConnectionFails > 1}
onclick={launch}
>
<Play class="!size-5" />
{$launching ? 'Launching...' : $serverConnectionFails > 1 ? 'No connection' : 'Launch'}
</Button>
</div>
<div
class="mt-auto bg-theme-900/90 border border-theme-800/90 rounded-lg px-6 py-3"
in:scale={{
duration: $reduceAnimations ? 0 : 400,
delay: $reduceAnimations ? 0 : 50,
start: 0.98,
}}
>
<div class="flex flex-row items-center gap-2">
<Gamepad2 class="text-muted-foreground" size="24" />
<span class="font-semibold text-muted-foreground text-sm">Client Info</span>
</div>
<div class="grid grid-cols-[1fr_auto] gap-1 mt-2 border-t border-theme-800 pt-2 px-2 pb-0">
<span class="text-xs text-muted-foreground font-semibold">osu! Release Stream</span>
<span class="text-xs font-semibold text-end text-theme-50">
<Badge class="text-[0.65rem] py-0.5 px-2 leading-none">
{#if $osuStream}
{releaseStreamToReadable($osuStream)}
{:else}
<LoaderCircle class="animate-spin" size={12} />
{/if}
</Badge>
</span>
<span class="text-xs text-muted-foreground font-semibold">osu! Version</span>
<span class="text-xs font-semibold text-end text-theme-50">
<Badge class="text-[0.65rem] py-0.5 px-2 leading-none">
{#if $osuBuild}
{$osuBuild}
{:else}
<LoaderCircle class="animate-spin" size={12} />
{/if}
</Badge>
</span>
<span class="text-xs text-muted-foreground font-semibold">Skin</span>
<span class="text-xs font-semibold text-end text-theme-50">
<Badge class="text-[0.65rem] py-0.5 px-2 leading-none">
{#if $currentSkin}
{$currentSkin}
{:else}
<LoaderCircle class="animate-spin" size={12} />
{/if}
</Badge>
</span>
</div> </div>
</div> </div>
{:else if selectedView === 'settings'} {:else if selectedView === 'settings'}
<Button onclick={() => (selectedView = 'home')}>
<ArrowLeft />
Back
</Button>
<div <div
class="bg-theme-900/90 flex flex-col justify-center gap-3 border border-theme-800/90 rounded-lg" class="h-[100vh] w-full flex flex-col items-center justify-center"
in:scale={{ duration: $reduceAnimations ? 0 : 400, start: 0.98 }} in:fly={{ duration: 400, delay: 400, y: 10, opacity: 0 }}
> out:fly={{ duration: 400, y: -10, opacity: 0 }}
<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 pt-4 pb-1 px-6"
> >
<div class="p-8 w-full">
<div class="bg-black/40 backdrop-blur-sm py-8 px-6 rounded-lg">
<div class="grid grid-cols-[1fr_auto] gap-y-5 items-center px-6">
<div class="flex flex-col"> <div class="flex flex-col">
<Label class="text-sm" for="setting-patch">Patching</Label> <Label class="text-sm" for="setting-patch">Patching</Label>
<div class="text-muted-foreground text-xs"> <div class="text-muted-foreground text-xs">
Shows misses in Relax and Autopilot {#if $platform !== 'windows'}<span Shows misses in Relax and Autopilot {#if $platform !== 'windows'}<span
class="text-red-500 bg-red-800/20 border border-red-600/20 p-0.5 mx-1 px-2 rounded-lg" class="text-red-500 bg-red-800/20 border border-red-600/20 p-0.5 mx-1 px-2 rounded-lg !text-[0.55rem]"
>currently only on windows!</span >currently only on windows!</span
> >
{/if} {/if}
@@ -1202,7 +1195,8 @@
<div class="flex flex-col"> <div class="flex flex-col">
<Label class="text-sm" for="setting-reduce-animations">Reduce Animations</Label> <Label class="text-sm" for="setting-reduce-animations">Reduce Animations</Label>
<div class="text-muted-foreground text-xs"> <div class="text-muted-foreground text-xs">
Disables some animations in the Launcher to improve performance on low-end devices. Disables some animations in the Launcher to improve performance on low-end
devices.
</div> </div>
</div> </div>
<Checkbox <Checkbox
@@ -1325,24 +1319,55 @@
</div> </div>
</div> </div>
</div> </div>
{/if} </div>
{:else if selectedView === 'login'}
<div <div
class="mt-auto mx-auto flex flex-row items-center gap-2" class="h-[100vh] w-full flex flex-col items-center justify-center"
in:scale={{ in:fly={{ duration: 400, delay: 400, y: 10, opacity: 0 }}
duration: $reduceAnimations ? 0 : 400, out:fly={{ duration: 400, y: -10, opacity: 0 }}
delay: $reduceAnimations ? 0 : 50,
start: 0.97,
}}
> >
<Button <div class="p-8 w-full">
variant="link" <form onsubmit={performLogin} class="bg-black/40 backdrop-blur-sm rounded-lg p-8">
class="font-light font-mono text-sm text-theme-500" <div class="flex flex-col items-center justify-center mb-4">
onclick={() => openURL('https://ez-pp.farm/u/1001')} <span class="text-xl font-semibold">Login to EZPPFarm</span>
<span class="text-xs text-muted-foreground"
>Use your EZPPFarm account to login to EZPPLauncher</span
> >
made with </div>
<Heart class="text-red-600 fill-red-600 animate-pulse" /> <div class="mb-4">
by horizoncode <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={loginIsLoading}
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={loginIsLoading}
autocomplete="off"
autocorrect="off"
/>
</div>
<Button class="w-full" type="submit" disabled={loginIsLoading}>
{#if loginIsLoading}
<LoaderCircle class="animate-spin" />
{:else}
Login
{/if}
</Button> </Button>
</form>
</div> </div>
</div> </div>
{/if}
</div>
</div> </div>
+1 -1
View File
@@ -202,7 +202,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>
+5 -5
View File
@@ -119,7 +119,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]">
<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,7 +128,7 @@
<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]">
<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-theme-800/90 flex flex-col gap-6 p-3">
{#each steps as step, i (step)} {#each steps as step, i (step)}
<div <div
@@ -163,7 +163,7 @@
<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 bg-theme-900/40 p-6">
{#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-theme-800/15 rounded-lg border border-900/60 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
@@ -184,7 +184,7 @@
</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-theme-800/15 rounded-lg border border-900/60 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>
@@ -242,7 +242,7 @@
</div> </div>
{:else if selectedStep === 3} {:else if selectedStep === 3}
<div <div
class="bg-theme-900/90 flex flex-col justify-center gap-3 border border-theme-800/90 rounded-lg" class="bg-theme-900/90 flex flex-col justify-center gap-3 mt-5 border border-theme-800/90 rounded-lg"
in:fade={{ duration: $reduceAnimations ? 0 : 200 }} in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
> >
<div class="flex flex-row items-center gap-3 font-semibold text-xl px-3 pt-3"> <div class="flex flex-row items-center gap-3 font-semibold text-xl px-3 pt-3">