chore: rename pages to screens

This commit is contained in:
2025-07-16 22:10:38 +02:00
parent f214aa454e
commit 13b90092b7
5 changed files with 0 additions and 7 deletions

1240
src/screens/Launch.svelte Normal file

File diff suppressed because it is too large Load Diff

233
src/screens/Loading.svelte Normal file
View File

@@ -0,0 +1,233 @@
<script lang="ts">
import Logo from '$assets/logo.png';
import { estimateRefreshRate } from '@/displayUtils';
import {
beatmapSets,
currentLoadingInfo,
currentSkin,
currentView,
firstStartup,
launcherStream,
launcherStreams,
launcherVersion,
newVersion,
osuBuild,
osuStream,
skins,
} from '@/global';
import {
cursorSmoothness,
osuInstallationPath,
preferredMode,
preferredType,
userSettings,
} from '@/userSettings';
import { animate, utils } from 'animejs';
import { onMount } from 'svelte';
import SetupWizard from './SetupWizard.svelte';
import Launch from './Launch.svelte';
import { currentUser, userAuth } from '@/userAuthentication';
import { ezppfarm } from '@/api/ezpp';
import { toast } from 'svelte-sonner';
import { currentUserInfo } from '@/data';
import {
getBeatmapSetsCount,
getEZPPLauncherStreams,
getReleaseStream,
getSkin,
getSkinsCount,
getVersion,
isValidOsuFolder,
} from '@/osuUtil';
import { git } from '@/api/git';
let ezppLogo: HTMLImageElement;
let spinnerCircle: SVGCircleElement;
let animateInterval: number | undefined;
//TODO: use this to check for updates upon launch
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);
};
const calculateCursorSmoothness = async () => {
const refreshRate = await estimateRefreshRate();
const hzMin = 60;
const hzMax = 144;
const durationMin = 70;
const durationMax = 180;
const duration =
durationMin + ((refreshRate - hzMin) / (hzMax - hzMin)) * (durationMax - durationMin);
cursorSmoothness.set(Math.round(duration));
};
const prepare = async () => {
await calculateCursorSmoothness();
const username = $userAuth.value('username').get('');
const password = $userAuth.value('password').get('');
if (username.length > 0 && password.length > 0) {
currentLoadingInfo.set('Logging in...');
try {
const loginResult = await ezppfarm.login(username, password);
if (loginResult && loginResult.user) {
toast.success('Login successful!', {
description: `Welcome back, ${loginResult.user.name}!`,
});
currentUser.set(loginResult.user);
} else {
toast.error('Login failed!', {
description: 'Please check your username and password.',
});
}
} catch {
toast.error('Server error occurred during login.', {
description: 'There was an issue connecting to the server. Please try again later.',
});
}
}
if ($currentUser) {
currentLoadingInfo.set('Loading user info...');
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);
}
}
if (!$firstStartup) {
currentLoadingInfo.set('Checking osu installation path...');
const validFolder = await isValidOsuFolder($osuInstallationPath);
if (!validFolder) {
osuInstallationPath.set('');
$userSettings.value('osu_installation_path').del();
await $userSettings.save();
toast.error('Oops...', {
description: 'Your previously set osu! installation path seems to be invalid.',
});
} else {
currentLoadingInfo.set('Getting osu version...');
const osuReleaseStream = await getReleaseStream($osuInstallationPath);
osuStream.set(osuReleaseStream);
const osuVersion = await getVersion($osuInstallationPath);
osuBuild.set(osuVersion);
currentLoadingInfo.set('Counting beatmapsets...');
const beatmapSetCount = await getBeatmapSetsCount($osuInstallationPath);
if (beatmapSetCount) beatmapSets.set(beatmapSetCount);
currentLoadingInfo.set('Counting skins...');
const skinCount = await getSkinsCount($osuInstallationPath);
if (skinCount) skins.set(skinCount);
const skin: string = await getSkin($osuInstallationPath);
currentSkin.set(skin);
}
}
currentLoadingInfo.set('Checking for EZPPLauncher updates...');
const launcherUpdate = await git.hasUpdate($launcherVersion);
if (launcherUpdate) {
newVersion.set(launcherUpdate);
}
const ezpplauncherStreams = await getEZPPLauncherStreams();
if (ezpplauncherStreams) launcherStreams.set(ezpplauncherStreams);
const selectedLauncherStream = $userSettings.value('patcherStream').get('stable');
if ($launcherStreams.includes(selectedLauncherStream)) {
launcherStream.set(selectedLauncherStream);
}
animate(ezppLogo, {
opacity: [1, 0],
scale: [1, 1.05],
duration: 1000,
ease: (t: number) => (t - 1) ** 7 + 1,
onComplete: () => {},
});
animate(spinnerCircle, {
opacity: 0,
duration: 1000,
ease: (t: number) => (t - 1) ** 7 + 1,
onComplete: () => {},
});
setTimeout(() => {
if ($firstStartup) currentView.set(SetupWizard);
else currentView.set(Launch);
}, 250);
};
onMount(() => {
animate(ezppLogo, {
opacity: [0, 1],
scale: [0.95, 1],
duration: 900,
ease: (t: number) => (t - 1) ** 7 + 1,
onComplete: doBPMAnimation,
});
animate(spinnerCircle, {
strokeDashoffset: [0, -565],
duration: 1800,
easing: 'linear',
loop: true,
});
prepare();
return () => {
window.clearInterval(animateInterval);
utils.remove(spinnerCircle);
};
});
</script>
<div class="flex flex-col items-center justify-center mt-[50px] h-[calc(100vh-50px)] w-full">
<div class="relative w-80 h-80 flex items-center justify-center">
<svg
class="absolute top-0 left-0 w-full h-full animate-spin"
style="animation-duration: 5s;"
viewBox="0 0 208 208"
>
<circle
cx="104"
cy="104"
r="90"
fill="none"
stroke="#ff0098"
stroke-width="8"
stroke-linecap="round"
stroke-dasharray="180 385"
stroke-dashoffset="0"
bind:this={spinnerCircle}
/>
</svg>
<img
src={Logo}
alt="EZPPLauncher Logo"
class="w-52 h-52 mb-2 relative z-10"
bind:this={ezppLogo}
/>
</div>
<span class="text-theme-200 text-sm mt-5">{$currentLoadingInfo}</span>
</div>

127
src/screens/Login.svelte Normal file
View File

@@ -0,0 +1,127 @@
<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>

View File

@@ -0,0 +1,341 @@
<script lang="ts">
import Logo from '$assets/logo.png';
import Button from '@/components/ui/button/button.svelte';
import Input from '@/components/ui/input/input.svelte';
import { animate } from 'animejs';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import { Check, CheckCircle, CircleOff, Settings2 } from 'lucide-svelte';
import { open } from '@tauri-apps/plugin-dialog';
import Checkbox from '@/components/ui/checkbox/checkbox.svelte';
import {
cursorSmoothening,
customCursor,
osuInstallationPath,
patch,
reduceAnimations,
userSettings,
} from '@/userSettings';
import Label from '@/components/ui/label/label.svelte';
import { beatmapSets, currentSkin, currentView, osuBuild, osuStream, skins } from '@/global';
import Launch from './Launch.svelte';
import Confetti from 'svelte-confetti';
import {
autoDetectOsuInstallFolder,
getBeatmapSetsCount,
getReleaseStream,
getSkin,
getSkinsCount,
getVersion,
isValidOsuFolder,
} from '@/osuUtil';
let selectedStep = $state(1);
const steps = ['Welcome', 'Locate your osu! Installation', 'Appearance Settings'];
let osuInstallPath = $state('');
let manualSelect = $state(false);
let manualSelectValid = $state(false);
let autoDetectedOsuPath = $state(false);
let wizardFinished = $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 browse_osu_installation = async () => {
const selectedPath = await open({
directory: true,
multiple: false,
title: 'Select osu! Installation Folder',
});
if (typeof selectedPath === 'string') {
const validFolder = await isValidOsuFolder(selectedPath);
manualSelect = true;
if (!validFolder) {
manualSelectValid = false;
osuInstallPath = '';
return;
}
osuInstallPath = selectedPath;
autoDetectedOsuPath = false;
manualSelectValid = true;
}
};
const saveConfig = async () => {
$userSettings.value('osu_installation_path').set(osuInstallPath);
await $userSettings.save();
osuInstallationPath.set(osuInstallPath);
const beatmapSetCount = await getBeatmapSetsCount(osuInstallPath);
if (beatmapSetCount) {
beatmapSets.set(beatmapSetCount);
}
const skinsCount: number | null = await getSkinsCount(osuInstallPath);
if (skinsCount) {
skins.set(skinsCount);
}
const skin: string = await getSkin(osuInstallPath);
currentSkin.set(skin);
const osuReleaseStream = await getReleaseStream(osuInstallPath);
osuStream.set(osuReleaseStream);
const osuVersion = await getVersion(osuInstallPath);
osuBuild.set(osuVersion);
currentView.set(Launch);
};
onMount(async () => {
const osuPath = await autoDetectOsuInstallFolder();
if (osuPath) {
osuInstallPath = osuPath;
autoDetectedOsuPath = true;
$userSettings.value('osu_installation_path').set(osuInstallPath);
}
});
</script>
{#if wizardFinished}
<div class="relative flex flex-col items-center justify-center mt-[50px] h-[calc(100vh-50px)]">
<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']} />
</div>
<h1 class="text-3xl font-semibold">EZPPLauncher Setup completed!</h1>
<p class="text-muted-foreground mt-2">You are now ready to farm some maps!</p>
<Button class="mt-4" onclick={saveConfig}>Finish</Button>
</div>
{:else}
<div class="grid grid-cols-[0.41fr_1fr] mt-[50px] h-[calc(100vh-50px)]">
<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)}
<div
class="flex flex-row items-center gap-2 border {selectedStep === i + 1
? 'border-primary-800/30 bg-primary-900/30'
: selectedStep > i
? 'border-green-800/30 bg-green-900/30'
: 'border-theme-800 bg-theme-900'} rounded-lg p-2 transition-all"
>
<div
class="flex flex-col items-center justify-center h-8 w-8 border-[2px] {selectedStep >
i + 1
? 'border-green-600'
: 'border-theme-600'} rounded-full"
>
{#if selectedStep > i + 1}
<Check class="mt-0.5 text-green-400" />
{:else}
<span class="text-lg font-semibold text-theme-100">{i + 1}</span>
{/if}
</div>
<span
class="{selectedStep === i + 1
? 'text-white'
: selectedStep > i
? 'text-green-500'
: "'text-muted-foreground'"} transition-all text-sm font-bold">{step}</span
>
</div>
{/each}
</div>
<div class="flex flex-col gap-6 w-full h-full bg-theme-900/40 p-6">
{#if selectedStep === 1}
<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"
in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
>
<img
src={Logo}
alt="EZPPLauncher Logo"
class="w-52 h-52 mb-2"
bind:this={ezppLogo}
onmouseenter={logo_mouseenter}
onmouseleave={logo_mouseleave}
/>
<h1 class="text-3xl font-semibold">Welcome to EZPPLauncher!</h1>
<p class="text-muted-foreground mt-2">
This setup wizard will guide you through the initial setup of EZPPLauncher.
</p>
<div class="bg-red-800/20 border border-red-900/20 text-red-500 p-4 rounded-lg mt-4">
Please make sure you have osu! installed on your system before proceeding.
</div>
</div>
{:else if selectedStep === 2}
<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"
in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
>
<h1 class="text-3xl font-semibold">Locate your osu! Installation</h1>
<p class="text-muted-foreground mt-2">
Please select the folder where your osu! installation is located.
</p>
<div class="flex flex-row w-full">
<Input
class="mt-4 w-full bg-theme-950 border-theme-800 border-r-0 rounded-r-none"
type="text"
placeholder="Path to osu! installation"
value={osuInstallPath}
/>
<Button
class="mt-4 bg-theme-950 border-theme-800 rounded-l-none"
variant="outline"
onclick={browse_osu_installation}>Browse</Button
>
</div>
{#if !manualSelect}
{#if autoDetectedOsuPath}
<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"
>
<CheckCircle />
<span>Auto-detected osu! installation path! Please check if its correct!</span>
</div>
{:else}
<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"
>
<CircleOff />
<span>Could not auto-detect osu! installation path. Please select it manually.</span
>
</div>
{/if}
{:else if manualSelectValid}
<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"
>
<CheckCircle />
<span>Selected osu! installation path is valid!</span>
</div>
{:else}
<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"
>
<CircleOff />
<span
>Selected osu! installation path is invalid! Please select a valid osu!
installation.</span
>
</div>
{/if}
</div>
{:else if selectedStep === 3}
<div
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
class="grid grid-cols-[1fr_auto] gap-y-5 items-center border-t border-theme-800 py-3 px-6"
>
<div class="flex flex-col">
<Label class="text-sm" for="setting-custom-cursor">Patching</Label>
<div class="text-muted-foreground text-xs">Shows misses in Relax and Autopilot</div>
</div>
<Checkbox
id="setting-custom-cursor"
checked={$patch}
onCheckedChange={async (e) => {
patch.set(e);
$userSettings.save();
}}
class="flex items-center justify-center w-5 h-5"
></Checkbox>
<div class="flex flex-col">
<Label class="text-sm" for="setting-custom-cursor">Lazer-Style Cursor</Label>
<div class="text-muted-foreground text-xs">
Enable a custom cursor in the Launcher like in the lazer build of osu!
</div>
</div>
<Checkbox
id="setting-custom-cursor"
checked={$customCursor}
onCheckedChange={async (e) => {
if (!e) {
cursorSmoothening.set(false);
}
customCursor.set(e);
}}
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>
{/if}
<div class="mt-auto flex flex-row items-center justify-between">
<Button
class="bg-theme-950 hover:bg-theme-800"
variant="outline"
onclick={() => (selectedStep = Math.max(selectedStep - 1, 1))}
disabled={selectedStep <= 1}>Previous</Button
>
<Button
onclick={() => {
if (selectedStep >= steps.length) wizardFinished = true;
else selectedStep = Math.min(selectedStep + 1, steps.length);
}}
disabled={selectedStep > steps.length ||
(selectedStep === 2 && osuInstallPath.length <= 0)}
>{selectedStep >= steps.length ? 'Finish' : 'Next'}</Button
>
</div>
</div>
</div>
{/if}