Compare commits
3 Commits
1834d8dfb3
...
50f6812054
Author | SHA1 | Date | |
---|---|---|---|
50f6812054 | |||
ba211641a6 | |||
26ea2aeab9 |
3
bun.lock
3
bun.lock
@@ -21,6 +21,7 @@
|
|||||||
"osu-classes": "3.1.0",
|
"osu-classes": "3.1.0",
|
||||||
"osu-parsers": "4.1.7",
|
"osu-parsers": "4.1.7",
|
||||||
"radix-icons-svelte": "1.2.1",
|
"radix-icons-svelte": "1.2.1",
|
||||||
|
"svelte-confetti": "^2.0.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lucide/svelte": "^0.482.0",
|
"@lucide/svelte": "^0.482.0",
|
||||||
@@ -557,6 +558,8 @@
|
|||||||
|
|
||||||
"svelte-check": ["svelte-check@4.2.2", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-1+31EOYZ7NKN0YDMKusav2hhEoA51GD9Ws6o//0SphMT0ve9mBTsTUEX7OmDMadUP3KjNHsSKtJrqdSaD8CrGQ=="],
|
"svelte-check": ["svelte-check@4.2.2", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-1+31EOYZ7NKN0YDMKusav2hhEoA51GD9Ws6o//0SphMT0ve9mBTsTUEX7OmDMadUP3KjNHsSKtJrqdSaD8CrGQ=="],
|
||||||
|
|
||||||
|
"svelte-confetti": ["svelte-confetti@2.3.1", "", { "peerDependencies": { "svelte": ">=5.0.0" } }, "sha512-bKd8etTOeBQyeS9LDPuSd7Oqy5msf0xvxItzsHPajKaarr/LWFzqPq7rp6QQO5rGTzLgM0fmjovOvLkRbrd2gg=="],
|
||||||
|
|
||||||
"svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
"svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
||||||
|
|
||||||
"sync-child-process": ["sync-child-process@1.0.2", "", { "dependencies": { "sync-message-port": "^1.0.0" } }, "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA=="],
|
"sync-child-process": ["sync-child-process@1.0.2", "", { "dependencies": { "sync-message-port": "^1.0.0" } }, "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA=="],
|
||||||
|
@@ -32,7 +32,8 @@
|
|||||||
"lucide-svelte": "0.523.0",
|
"lucide-svelte": "0.523.0",
|
||||||
"osu-classes": "3.1.0",
|
"osu-classes": "3.1.0",
|
||||||
"osu-parsers": "4.1.7",
|
"osu-parsers": "4.1.7",
|
||||||
"radix-icons-svelte": "1.2.1"
|
"radix-icons-svelte": "1.2.1",
|
||||||
|
"svelte-confetti": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lucide/svelte": "^0.482.0",
|
"@lucide/svelte": "^0.482.0",
|
||||||
|
@@ -3,16 +3,19 @@
|
|||||||
import cursor_additive from '$assets/cursor-additive.png';
|
import cursor_additive from '$assets/cursor-additive.png';
|
||||||
import { animate } from 'animejs';
|
import { animate } from 'animejs';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { currentMonitor } from '@tauri-apps/api/window';
|
||||||
|
import { estimateRefreshRate } from '@/displayUtils';
|
||||||
|
import { cursorSmoothness } from '@/userSettings';
|
||||||
|
|
||||||
let { smoothCursor = true }: { smoothCursor?: boolean } =
|
let { smoothCursor = true }: { smoothCursor?: boolean } = $props();
|
||||||
$props();
|
|
||||||
|
|
||||||
let mouseX = $state(0);
|
let mouseX = $state(0);
|
||||||
let mouseY = $state(0);
|
let mouseY = $state(0);
|
||||||
|
let lastMouseX = $state(0);
|
||||||
|
let lastMouseY = $state(0);
|
||||||
let rotation = $state(0);
|
let rotation = $state(0);
|
||||||
let isMouseDown = $state(false);
|
let isMouseDown = $state(false);
|
||||||
let isHoveringInteractive = $state(false);
|
let isHoveringInteractive = $state(false);
|
||||||
|
|
||||||
let dragStartX = $state(0);
|
let dragStartX = $state(0);
|
||||||
let dragStartY = $state(0);
|
let dragStartY = $state(0);
|
||||||
let degrees = $state(0);
|
let degrees = $state(0);
|
||||||
@@ -43,7 +46,10 @@
|
|||||||
const deltaX = e.pageX - window.pageXOffset - dragStartX;
|
const deltaX = e.pageX - window.pageXOffset - dragStartX;
|
||||||
const deltaY = e.pageY - window.pageYOffset - dragStartY;
|
const deltaY = e.pageY - window.pageYOffset - dragStartY;
|
||||||
|
|
||||||
if (!applyRotation && isMouseDown && deltaX * deltaX + deltaY * deltaY > 30 * 100) {
|
const velocityX = Math.abs(mouseX - lastMouseX);
|
||||||
|
const velocityY = Math.abs(mouseY - lastMouseY);
|
||||||
|
|
||||||
|
if (!applyRotation && isMouseDown && velocityX * velocityX + velocityY * velocityY > 800) {
|
||||||
applyRotation = true;
|
applyRotation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,10 +84,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
animate(cursor, {
|
animate(cursor, {
|
||||||
duration: smoothCursor ? 180 : 0,
|
duration: smoothCursor ? $cursorSmoothness : 0,
|
||||||
translateX: mouseX,
|
translateX: mouseX,
|
||||||
translateY: mouseY - 50,
|
translateY: mouseY - 50,
|
||||||
ease: (t: number) => (t - 1) ** 3 + 1,
|
ease: (t: number) => (t - 1) ** 5 + 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
animate(cursor, {
|
animate(cursor, {
|
||||||
@@ -90,6 +96,8 @@
|
|||||||
transformOrigin: '0px 0px 0',
|
transformOrigin: '0px 0px 0',
|
||||||
ease: (t: number) => Math.pow(2, -10 * t) * Math.sin((t - 0.075) * 20.94) + 1 - 0.0005 * t,
|
ease: (t: number) => Math.pow(2, -10 * t) * Math.sin((t - 0.075) * 20.94) + 1 - 0.0005 * t,
|
||||||
});
|
});
|
||||||
|
lastMouseX = mouseX;
|
||||||
|
lastMouseY = mouseY;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseDown = (event: MouseEvent) => {
|
const handleMouseDown = (event: MouseEvent) => {
|
||||||
@@ -144,10 +152,8 @@
|
|||||||
onmousedown={handleMouseDown}
|
onmousedown={handleMouseDown}
|
||||||
onmouseup={handleMouseUp}
|
onmouseup={handleMouseUp}
|
||||||
/>
|
/>
|
||||||
<div
|
|
||||||
class="h-7 w-7 fixed pointer-events-none z-[99999]"
|
<div class="h-7 w-7 fixed pointer-events-none z-[99999]" bind:this={cursor}>
|
||||||
bind:this={cursor}
|
|
||||||
>
|
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<img class="absolute top-0 left-0" src={cursor_default} bind:this={cursorInner} alt="cursor" />
|
<img class="absolute top-0 left-0" src={cursor_default} bind:this={cursorInner} alt="cursor" />
|
||||||
<img
|
<img
|
||||||
|
@@ -1,20 +1,17 @@
|
|||||||
import {
|
import { exists, mkdir, readTextFile, writeFile } from '@tauri-apps/plugin-fs';
|
||||||
BaseDirectory,
|
|
||||||
exists,
|
|
||||||
mkdir,
|
|
||||||
readFile,
|
|
||||||
readTextFile,
|
|
||||||
writeFile,
|
|
||||||
} from '@tauri-apps/plugin-fs';
|
|
||||||
import * as path from '@tauri-apps/api/path';
|
import * as path from '@tauri-apps/api/path';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { Crypto } from './crypto';
|
import { Crypto } from './crypto';
|
||||||
import { enc } from 'crypto-js';
|
|
||||||
|
|
||||||
export class Config {
|
export class Config {
|
||||||
private config: Record<string, unknown> = {};
|
private config: Record<string, unknown> = {};
|
||||||
private crypto: Crypto | undefined;
|
private crypto: Crypto | undefined;
|
||||||
private configFilePath: string | undefined;
|
private configFilePath: string | undefined;
|
||||||
|
private encrypt: boolean;
|
||||||
|
|
||||||
|
constructor(encrypt?: boolean) {
|
||||||
|
this.encrypt = encrypt ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
async init(): Promise<boolean> {
|
async init(): Promise<boolean> {
|
||||||
const hwid: string = (await invoke('get_hwid')) ?? 'recorderinsandybridge';
|
const hwid: string = (await invoke('get_hwid')) ?? 'recorderinsandybridge';
|
||||||
@@ -37,16 +34,15 @@ export class Config {
|
|||||||
|
|
||||||
private async load() {
|
private async load() {
|
||||||
if (!this.configFilePath) throw Error('configFilePath not set');
|
if (!this.configFilePath) throw Error('configFilePath not set');
|
||||||
if (!this.crypto) throw Error('crypto not initialized');
|
if (this.encrypt && !this.crypto) throw Error('crypto not initialized');
|
||||||
|
|
||||||
const fileStream = await readTextFile(this.configFilePath);
|
const fileStream = await readTextFile(this.configFilePath);
|
||||||
try {
|
try {
|
||||||
const decryptedJSON = JSON.parse(this.crypto.decrypt(fileStream)) as Record<string, unknown>;
|
const decryptedJSON = JSON.parse(
|
||||||
|
this.encrypt && this.crypto ? this.crypto.decrypt(fileStream) : fileStream
|
||||||
|
) as Record<string, unknown>;
|
||||||
this.config = decryptedJSON;
|
this.config = decryptedJSON;
|
||||||
console.log('config file loaded');
|
|
||||||
console.log(JSON.stringify(this.config));
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('failed to read file');
|
|
||||||
this.config = {};
|
this.config = {};
|
||||||
await this.save();
|
await this.save();
|
||||||
}
|
}
|
||||||
@@ -54,19 +50,18 @@ export class Config {
|
|||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
if (!this.configFilePath) throw Error('configFilePath not set');
|
if (!this.configFilePath) throw Error('configFilePath not set');
|
||||||
if (!this.crypto) throw Error('crypto not initialized');
|
if (this.encrypt && !this.crypto) throw Error('crypto not initialized');
|
||||||
const encryptedJSON = this.crypto.encrypt(JSON.stringify(this.config));
|
const encryptedJSON =
|
||||||
|
this.encrypt && this.crypto
|
||||||
|
? this.crypto.encrypt(JSON.stringify(this.config))
|
||||||
|
: JSON.stringify(this.config);
|
||||||
|
|
||||||
console.log(this.config);
|
|
||||||
console.log('saving file...');
|
|
||||||
console.log(encryptedJSON);
|
|
||||||
await writeFile(this.configFilePath, Buffer.from(encryptedJSON), {
|
await writeFile(this.configFilePath, Buffer.from(encryptedJSON), {
|
||||||
append: false,
|
append: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
value(key: string) {
|
value(key: string) {
|
||||||
console.log(this.config);
|
|
||||||
return {
|
return {
|
||||||
set: <T>(val: T) => {
|
set: <T>(val: T) => {
|
||||||
this.config[key] = val;
|
this.config[key] = val;
|
||||||
|
20
src/lib/displayUtils.ts
Normal file
20
src/lib/displayUtils.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export function estimateRefreshRate(): Promise<number> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let last = performance.now();
|
||||||
|
let frames = 0;
|
||||||
|
|
||||||
|
function loop() {
|
||||||
|
const now = performance.now();
|
||||||
|
frames++;
|
||||||
|
|
||||||
|
if (now - last >= 1000) {
|
||||||
|
console.log(`Estimated Refresh Rate: ${frames} FPS`);
|
||||||
|
resolve(frames); // estimated Hz
|
||||||
|
} else {
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
});
|
||||||
|
}
|
@@ -4,6 +4,7 @@ import type { Component } from 'svelte';
|
|||||||
import Loading from '../pages/Loading.svelte';
|
import Loading from '../pages/Loading.svelte';
|
||||||
|
|
||||||
export const current_view = writable<Component>(Loading);
|
export const current_view = writable<Component>(Loading);
|
||||||
|
export const first_startup = writable<boolean>(false);
|
||||||
|
|
||||||
export const server_ping = writable<number | undefined>(undefined);
|
export const server_ping = writable<number | undefined>(undefined);
|
||||||
export const server_connection_fails = writable(0);
|
export const server_connection_fails = writable(0);
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import { Config } from './config';
|
import { Config } from './config';
|
||||||
|
|
||||||
export const userSettings = writable<Config>(new Config());
|
export const userSettings = writable<Config>(new Config(false));
|
||||||
|
|
||||||
export const customCursor = writable<boolean>(true);
|
export const customCursor = writable<boolean>(true);
|
||||||
export const cursorSmoothening = writable<boolean>(true);
|
export const cursorSmoothening = writable<boolean>(true);
|
||||||
|
export const cursorSmoothness = writable<number>(180);
|
||||||
export const reduceAnimations = writable<boolean>(false);
|
export const reduceAnimations = writable<boolean>(false);
|
||||||
|
|
||||||
|
export const osuInstallationPath = writable<string>('');
|
||||||
|
@@ -27,7 +27,7 @@
|
|||||||
import Label from '@/components/ui/label/label.svelte';
|
import Label from '@/components/ui/label/label.svelte';
|
||||||
import { cursorSmoothening, customCursor, reduceAnimations, userSettings } from '@/userSettings';
|
import { cursorSmoothening, customCursor, reduceAnimations, userSettings } from '@/userSettings';
|
||||||
|
|
||||||
let selectedTab = $state('settings');
|
let selectedTab = $state('home');
|
||||||
let launching = $state(false);
|
let launching = $state(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -264,7 +264,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mt-auto bg-theme-900/90 border border-theme-800/90 rounded-lg px-6 py-3"
|
class="mt-auto bg-theme-900/90 border border-theme-800/90 rounded-lg px-6 py-3"
|
||||||
in:scale={{ duration: $reduceAnimations ? 0 : 400, start: 0.98 }}
|
in:scale={{
|
||||||
|
duration: $reduceAnimations ? 0 : 400,
|
||||||
|
delay: $reduceAnimations ? 0 : 50,
|
||||||
|
start: 0.98,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-2">
|
||||||
<Gamepad2 class="text-muted-foreground" size="24" />
|
<Gamepad2 class="text-muted-foreground" size="24" />
|
||||||
@@ -336,6 +340,76 @@
|
|||||||
class="flex items-center justify-center w-5 h-5"
|
class="flex items-center justify-center w-5 h-5"
|
||||||
></Checkbox>
|
></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);
|
||||||
|
$userSettings.save();
|
||||||
|
}}
|
||||||
|
disabled={!$customCursor}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bg-theme-900/90 flex flex-col justify-center gap-3 border border-theme-800/90 rounded-lg"
|
||||||
|
in:scale={{
|
||||||
|
duration: $reduceAnimations ? 0 : 400,
|
||||||
|
delay: $reduceAnimations ? 0 : 50,
|
||||||
|
start: 0.98,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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">Lazer-Style Cursor</Label>
|
||||||
|
<div class="text-muted-foreground text-xs">
|
||||||
|
Enable a custom cursor in the Launcher like in the lazer build of osu!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
id="setting-custom-cursor"
|
||||||
|
checked={$customCursor}
|
||||||
|
onCheckedChange={async (e) => {
|
||||||
|
if (!e) {
|
||||||
|
cursorSmoothening.set(false);
|
||||||
|
}
|
||||||
|
customCursor.set(e);
|
||||||
|
|
||||||
|
$userSettings.save();
|
||||||
|
}}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<Label class="text-sm" for="setting-cursor-smoothening">Cursor Smoothening</Label>
|
||||||
|
<div class="text-muted-foreground text-xs">
|
||||||
|
Makes the custom cursor movement smoother.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
id="setting-cursor-smoothening"
|
||||||
|
checked={$cursorSmoothening}
|
||||||
|
onCheckedChange={async (e) => {
|
||||||
|
if (!$customCursor) return;
|
||||||
|
cursorSmoothening.set(e);
|
||||||
|
$userSettings.save();
|
||||||
|
}}
|
||||||
|
disabled={!$customCursor}
|
||||||
|
class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<Label class="text-sm" for="setting-cursor-smoothening">Reduce Animations</Label>
|
<Label class="text-sm" for="setting-cursor-smoothening">Reduce Animations</Label>
|
||||||
<div class="text-muted-foreground text-xs">
|
<div class="text-muted-foreground text-xs">
|
||||||
|
@@ -1,7 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Logo from '$assets/logo.png';
|
import Logo from '$assets/logo.png';
|
||||||
import { animate } from 'animejs';
|
import { estimateRefreshRate } from '@/displayUtils';
|
||||||
|
import { current_view, first_startup } from '@/global';
|
||||||
|
import { cursorSmoothness } from '@/userSettings';
|
||||||
|
import { animate, utils } from 'animejs';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import SetupWizard from './SetupWizard.svelte';
|
||||||
|
import Launch from './Launch.svelte';
|
||||||
|
|
||||||
let ezppLogo: HTMLImageElement;
|
let ezppLogo: HTMLImageElement;
|
||||||
let spinnerCircle: SVGCircleElement;
|
let spinnerCircle: SVGCircleElement;
|
||||||
@@ -26,25 +31,61 @@
|
|||||||
}, 450);
|
}, 450);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Animate logo on mount: pop-in, then pulse
|
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();
|
||||||
|
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 ($first_startup) current_view.set(SetupWizard);
|
||||||
|
else current_view.set(Launch);
|
||||||
|
}, 250);
|
||||||
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Logo pop-in and pulse
|
|
||||||
animate(ezppLogo, {
|
animate(ezppLogo, {
|
||||||
opacity: [0, 1],
|
opacity: [0, 1],
|
||||||
scale: [0, 1],
|
scale: [0.95, 1],
|
||||||
duration: 900,
|
duration: 900,
|
||||||
ease: (t: number) => Math.pow(2, -5 * t) * Math.sin((t - 0.075) * 20.94) + 1 - 0.0005 * t,
|
ease: (t: number) => (t - 1) ** 7 + 1,
|
||||||
onComplete: doBPMAnimation,
|
onComplete: doBPMAnimation,
|
||||||
});
|
});
|
||||||
// Spinner animation (seamless, starts at 12 o'clock)
|
|
||||||
if (spinnerCircle) {
|
|
||||||
animate(spinnerCircle, {
|
animate(spinnerCircle, {
|
||||||
strokeDashoffset: [0, -565],
|
strokeDashoffset: [0, -565],
|
||||||
duration: 1800,
|
duration: 1800,
|
||||||
easing: 'linear',
|
easing: 'linear',
|
||||||
loop: true,
|
loop: true,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
prepare();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
utils.remove(ezppLogo);
|
||||||
|
utils.remove(spinnerCircle);
|
||||||
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@@ -4,10 +4,16 @@
|
|||||||
import Input from '@/components/ui/input/input.svelte';
|
import Input from '@/components/ui/input/input.svelte';
|
||||||
import { animate } from 'animejs';
|
import { animate } from 'animejs';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade, scale } from 'svelte/transition';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { Check, CheckCircle, CircleOff } from 'lucide-svelte';
|
import { Check, CheckCircle, CircleOff, Settings2 } 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 { cursorSmoothening, customCursor, reduceAnimations, userSettings } from '@/userSettings';
|
||||||
|
import Label from '@/components/ui/label/label.svelte';
|
||||||
|
import { current_view } from '@/global';
|
||||||
|
import Launch from './Launch.svelte';
|
||||||
|
import Confetti from 'svelte-confetti';
|
||||||
|
|
||||||
let selectedStep = $state(1);
|
let selectedStep = $state(1);
|
||||||
const steps = ['Welcome', 'Locate your osu! Installation', 'Appearance Settings'];
|
const steps = ['Welcome', 'Locate your osu! Installation', 'Appearance Settings'];
|
||||||
@@ -16,6 +22,7 @@
|
|||||||
let manualSelect = $state(false);
|
let manualSelect = $state(false);
|
||||||
let manualSelectValid = $state(false);
|
let manualSelectValid = $state(false);
|
||||||
let autoDetectedOsuPath = $state(false);
|
let autoDetectedOsuPath = $state(false);
|
||||||
|
let wizardFinished = $state(false);
|
||||||
|
|
||||||
let ezppLogo: HTMLImageElement | undefined = $state(undefined);
|
let ezppLogo: HTMLImageElement | undefined = $state(undefined);
|
||||||
|
|
||||||
@@ -57,6 +64,7 @@
|
|||||||
osuInstallationPath = selectedPath;
|
osuInstallationPath = selectedPath;
|
||||||
autoDetectedOsuPath = false;
|
autoDetectedOsuPath = false;
|
||||||
manualSelectValid = true;
|
manualSelectValid = true;
|
||||||
|
$userSettings.value('osu_installation_path').set(osuInstallationPath);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,10 +74,29 @@
|
|||||||
if (osuInstallPath) {
|
if (osuInstallPath) {
|
||||||
osuInstallationPath = osuInstallPath;
|
osuInstallationPath = osuInstallPath;
|
||||||
autoDetectedOsuPath = true;
|
autoDetectedOsuPath = true;
|
||||||
|
$userSettings.value('osu_installation_path').set(osuInstallationPath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</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={async () => {
|
||||||
|
await $userSettings.save();
|
||||||
|
current_view.set(Launch);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Finish
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
<div class="grid grid-cols-[0.41fr_1fr] mt-[50px] h-[calc(100vh-50px)]">
|
<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">
|
<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)}
|
||||||
@@ -81,7 +108,10 @@
|
|||||||
: 'border-theme-800 bg-theme-900'} rounded-lg p-2 transition-all"
|
: 'border-theme-800 bg-theme-900'} rounded-lg p-2 transition-all"
|
||||||
>
|
>
|
||||||
<div
|
<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"
|
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}
|
{#if selectedStep > i + 1}
|
||||||
<Check class="mt-0.5 text-green-400" />
|
<Check class="mt-0.5 text-green-400" />
|
||||||
@@ -103,7 +133,7 @@
|
|||||||
{#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 flex flex-col items-center justify-center"
|
||||||
in:fade={{ duration: 100 }}
|
in:fade={{ duration: $reduceAnimations ? 0 : 200 }}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={Logo}
|
src={Logo}
|
||||||
@@ -124,7 +154,7 @@
|
|||||||
{: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 flex flex-col items-center justify-center"
|
||||||
in:fade={{ duration: 100 }}
|
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>
|
||||||
<p class="text-muted-foreground mt-2">
|
<p class="text-muted-foreground mt-2">
|
||||||
@@ -156,7 +186,8 @@
|
|||||||
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/20 border border-red-900/20 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
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else if manualSelectValid}
|
{:else if manualSelectValid}
|
||||||
@@ -172,11 +203,75 @@
|
|||||||
>
|
>
|
||||||
<CircleOff />
|
<CircleOff />
|
||||||
<span
|
<span
|
||||||
>Selected osu! installation path is invalid! Please select a valid osu! installation.</span
|
>Selected osu! installation path is invalid! Please select a valid osu!
|
||||||
|
installation.</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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">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}
|
{/if}
|
||||||
|
|
||||||
<div class="mt-auto flex flex-row items-center justify-between">
|
<div class="mt-auto flex flex-row items-center justify-between">
|
||||||
@@ -187,10 +282,15 @@
|
|||||||
disabled={selectedStep <= 1}>Previous</Button
|
disabled={selectedStep <= 1}>Previous</Button
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onclick={() => (selectedStep = Math.min(selectedStep + 1, steps.length))}
|
onclick={() => {
|
||||||
disabled={selectedStep >= steps.length ||
|
if (selectedStep >= steps.length) wizardFinished = true;
|
||||||
(selectedStep === 2 && osuInstallationPath.length <= 0)}>Next</Button
|
else selectedStep = Math.min(selectedStep + 1, steps.length);
|
||||||
|
}}
|
||||||
|
disabled={selectedStep > steps.length ||
|
||||||
|
(selectedStep === 2 && osuInstallationPath.length <= 0)}
|
||||||
|
>{selectedStep >= steps.length ? 'Finish' : 'Next'}</Button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Titlebar from '@/components/ui/titlebar/titlebar.svelte';
|
import Titlebar from '@/components/ui/titlebar/titlebar.svelte';
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
import { current_view, setupValues } from '@/global';
|
import { current_view, first_startup, setupValues } from '@/global';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import OsuCursor from '@/components/ui/osu-cursor/OsuCursor.svelte';
|
import OsuCursor from '@/components/ui/osu-cursor/OsuCursor.svelte';
|
||||||
import { cursorSmoothening, customCursor, reduceAnimations, userSettings } from '@/userSettings';
|
import { cursorSmoothening, customCursor, reduceAnimations, userSettings } from '@/userSettings';
|
||||||
@@ -27,11 +27,7 @@
|
|||||||
cursorSmoothening.subscribe((val) => config_cursor_smoothening.set(val));
|
cursorSmoothening.subscribe((val) => config_cursor_smoothening.set(val));
|
||||||
reduceAnimations.subscribe((val) => config_reduce_animations.set(val));
|
reduceAnimations.subscribe((val) => config_reduce_animations.set(val));
|
||||||
|
|
||||||
if (!firstStartup) {
|
first_startup.set(firstStartup);
|
||||||
current_view.set(Launch);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* current_view.set(SetupWizard); */
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@@ -1,7 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { current_view } from '@/global';
|
import { current_view } from '@/global';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
const View = $derived($current_view);
|
const View = $derived($current_view);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#key View}
|
||||||
|
<div in:fade={{ duration: 300 }}>
|
||||||
<View />
|
<View />
|
||||||
|
</div>
|
||||||
|
{/key}
|
||||||
|
Reference in New Issue
Block a user