feat: implement osu! installation validation
This commit is contained in:
parent
c43fd4395d
commit
1834d8dfb3
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
@ -1084,6 +1084,7 @@ dependencies = [
|
|||||||
"tauri-plugin-shell",
|
"tauri-plugin-shell",
|
||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
"tauri-plugin-sql",
|
"tauri-plugin-sql",
|
||||||
|
"winreg 0.55.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -27,6 +27,7 @@ tauri-plugin-sql = "2.3.0"
|
|||||||
tauri-plugin-dialog = "2.3.0"
|
tauri-plugin-dialog = "2.3.0"
|
||||||
tauri-plugin-fs = "2.4.0"
|
tauri-plugin-fs = "2.4.0"
|
||||||
hardware-id = "0.3.0"
|
hardware-id = "0.3.0"
|
||||||
|
winreg = "0.55.0"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-single-instance = "2.3.0"
|
tauri-plugin-single-instance = "2.3.0"
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||||
use hardware_id::get_id;
|
use hardware_id::get_id;
|
||||||
|
use std::path::PathBuf;
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
use winreg::enums::*;
|
||||||
|
use winreg::RegKey;
|
||||||
|
|
||||||
|
use crate::utils::check_folder_completeness;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_hwid() -> String {
|
fn get_hwid() -> String {
|
||||||
@ -8,6 +14,122 @@ fn get_hwid() -> String {
|
|||||||
hwid.into()
|
hwid.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command(rename_all = "snake_case")]
|
||||||
|
fn valid_osu_folder(folder: String) -> bool {
|
||||||
|
// List of files that should be present in the osu! installation folder
|
||||||
|
let osu_folder_files = [
|
||||||
|
"avcodec-51.dll",
|
||||||
|
"avformat-52.dll",
|
||||||
|
"avutil-49.dll",
|
||||||
|
"bass.dll",
|
||||||
|
"bass_fx.dll",
|
||||||
|
"collection.db",
|
||||||
|
"d3dcompiler_47.dll",
|
||||||
|
"libEGL.dll",
|
||||||
|
"libGLESv2.dll",
|
||||||
|
"Microsoft.Ink.dll",
|
||||||
|
"OpenTK.dll",
|
||||||
|
"osu!.cfg",
|
||||||
|
"osu!.db",
|
||||||
|
"osu!.exe",
|
||||||
|
"osu!auth.dll",
|
||||||
|
"osu!gameplay.dll",
|
||||||
|
"osu!seasonal.dll",
|
||||||
|
"osu!ui.dll",
|
||||||
|
"presence.db",
|
||||||
|
"pthreadGC2.dll",
|
||||||
|
"scores.db",
|
||||||
|
];
|
||||||
|
|
||||||
|
let path = PathBuf::from(folder);
|
||||||
|
let match_percentage = check_folder_completeness(path, &osu_folder_files) >= 70.0;
|
||||||
|
if match_percentage {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn find_osu_installation() -> Option<String> {
|
||||||
|
// List of possible registry paths to check for osu! installation
|
||||||
|
let hklm_registry_paths = ["SOFTWARE\\Classes\\osu\\DefaultIcon"];
|
||||||
|
|
||||||
|
let hkcr_registry_paths = [
|
||||||
|
"osustable.File.osk\\DefaultIcon",
|
||||||
|
"osustable.File.osr\\DefaultIcon",
|
||||||
|
"osustable.File.osz\\DefaultIcon",
|
||||||
|
];
|
||||||
|
|
||||||
|
let osu_folder_files = [
|
||||||
|
"avcodec-51.dll",
|
||||||
|
"avformat-52.dll",
|
||||||
|
"avutil-49.dll",
|
||||||
|
"bass.dll",
|
||||||
|
"bass_fx.dll",
|
||||||
|
"collection.db",
|
||||||
|
"d3dcompiler_47.dll",
|
||||||
|
"libEGL.dll",
|
||||||
|
"libGLESv2.dll",
|
||||||
|
"Microsoft.Ink.dll",
|
||||||
|
"OpenTK.dll",
|
||||||
|
"osu!.cfg",
|
||||||
|
"osu!.db",
|
||||||
|
"osu!.exe",
|
||||||
|
"osu!auth.dll",
|
||||||
|
"osu!gameplay.dll",
|
||||||
|
"osu!seasonal.dll",
|
||||||
|
"osu!ui.dll",
|
||||||
|
"presence.db",
|
||||||
|
"pthreadGC2.dll",
|
||||||
|
"scores.db",
|
||||||
|
];
|
||||||
|
|
||||||
|
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||||
|
|
||||||
|
for reg_path in &hklm_registry_paths {
|
||||||
|
if let Ok(subkey) = hklm.open_subkey_with_flags(reg_path, KEY_READ | KEY_WOW64_32KEY) {
|
||||||
|
let value_names = [""];
|
||||||
|
for value_name in &value_names {
|
||||||
|
if let Ok(value) = subkey.get_value::<String, _>(value_name) {
|
||||||
|
let trimmed = value.trim_matches('"');
|
||||||
|
let stripped = trimmed.strip_suffix(",0").unwrap_or(trimmed);
|
||||||
|
let path = PathBuf::from(stripped.trim());
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
let match_percentage = check_folder_completeness(parent, &osu_folder_files);
|
||||||
|
|
||||||
|
if match_percentage >= 70.0 {
|
||||||
|
return Some(parent.to_string_lossy().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let hkcr = RegKey::predef(HKEY_CLASSES_ROOT);
|
||||||
|
|
||||||
|
for reg_path in &hkcr_registry_paths {
|
||||||
|
if let Ok(subkey) = hkcr.open_subkey_with_flags(reg_path, KEY_READ | KEY_WOW64_32KEY) {
|
||||||
|
let value_names = [""];
|
||||||
|
for value_name in &value_names {
|
||||||
|
if let Ok(value) = subkey.get_value::<String, _>(value_name) {
|
||||||
|
let trimmed = value.trim_matches('"');
|
||||||
|
let stripped = trimmed.strip_suffix(",1").unwrap_or(trimmed);
|
||||||
|
let path = PathBuf::from(stripped.trim());
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
let match_percentage = check_folder_completeness(parent, &osu_folder_files);
|
||||||
|
|
||||||
|
if match_percentage >= 70.0 {
|
||||||
|
return Some(parent.to_string_lossy().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
let mut builder = tauri::Builder::default().plugin(tauri_plugin_fs::init());
|
let mut builder = tauri::Builder::default().plugin(tauri_plugin_fs::init());
|
||||||
@ -23,7 +145,7 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.invoke_handler(tauri::generate_handler![get_hwid])
|
.invoke_handler(tauri::generate_handler![get_hwid, find_osu_installation, valid_osu_folder])
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.plugin(tauri_plugin_sql::Builder::default().build())
|
.plugin(tauri_plugin_sql::Builder::default().build())
|
||||||
|
24
src-tauri/src/utils.rs
Normal file
24
src-tauri/src/utils.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// Checks the presence of required files in a folder and returns the percentage found.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `folder_path` - The path to the folder to check.
|
||||||
|
/// * `required_files` - A slice of file names that should be present in the folder.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `f32` - The percentage (0.0 to 100.0) of required files found in the folder.
|
||||||
|
pub fn check_folder_completeness<P: AsRef<Path>>(folder_path: P, required_files: &[&str]) -> f32 {
|
||||||
|
let mut found = 0;
|
||||||
|
for file in required_files {
|
||||||
|
let file_path = folder_path.as_ref().join(file);
|
||||||
|
if file_path.exists() {
|
||||||
|
found += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if required_files.is_empty() {
|
||||||
|
100.0
|
||||||
|
} else {
|
||||||
|
(found as f32 / required_files.len() as f32) * 100.0
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import ky from 'ky';
|
import ky from 'ky';
|
||||||
|
|
||||||
const BANCHO_ENDPOINT = 'https://c.ez-pp.farm/';
|
const BANCHO_ENDPOINT = 'https://c.ez-pp.farm/';
|
||||||
|
const ENDPOINT = 'https://ez-pp.farm/';
|
||||||
|
|
||||||
export const ezppfarm = {
|
export const ezppfarm = {
|
||||||
ping: async (): Promise<number | undefined> => {
|
ping: async (): Promise<number | undefined> => {
|
||||||
@ -14,4 +15,44 @@ export const ezppfarm = {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
login: async (
|
||||||
|
username: string,
|
||||||
|
password: string
|
||||||
|
): Promise<
|
||||||
|
| {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
user?: {
|
||||||
|
id: number;
|
||||||
|
donor: boolean;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
> => {
|
||||||
|
try {
|
||||||
|
const request = await ky(`${ENDPOINT}login/check`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ username, password }),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'User-Agent': 'EZPPLauncher',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!request.ok) return undefined;
|
||||||
|
return await request.json<{
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
user?: {
|
||||||
|
id: number;
|
||||||
|
donor: boolean;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
}>();
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -16,7 +16,7 @@ export class Config {
|
|||||||
private crypto: Crypto | undefined;
|
private crypto: Crypto | undefined;
|
||||||
private configFilePath: string | undefined;
|
private configFilePath: string | undefined;
|
||||||
|
|
||||||
async init() {
|
async init(): Promise<boolean> {
|
||||||
const hwid: string = (await invoke('get_hwid')) ?? 'recorderinsandybridge';
|
const hwid: string = (await invoke('get_hwid')) ?? 'recorderinsandybridge';
|
||||||
|
|
||||||
this.crypto = new Crypto(hwid);
|
this.crypto = new Crypto(hwid);
|
||||||
@ -28,9 +28,11 @@ export class Config {
|
|||||||
const createFolder = !(await exists(folderPath));
|
const createFolder = !(await exists(folderPath));
|
||||||
if (createFolder) await mkdir(folderPath);
|
if (createFolder) await mkdir(folderPath);
|
||||||
|
|
||||||
const createConfig = !(await exists(this.configFilePath));
|
const configExists = await exists(this.configFilePath);
|
||||||
if (createConfig) await this.save();
|
if (!configExists) return true;
|
||||||
else await this.load();
|
|
||||||
|
await this.load();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async load() {
|
private async load() {
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import { ezppfarm } from './api/ezpp';
|
import { ezppfarm } from './api/ezpp';
|
||||||
|
import type { Component } from 'svelte';
|
||||||
|
import Loading from '../pages/Loading.svelte';
|
||||||
|
|
||||||
|
export const current_view = writable<Component>(Loading);
|
||||||
|
|
||||||
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,78 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { LoaderCircle } from "lucide-svelte";
|
import Logo from '$assets/logo.png';
|
||||||
|
import { animate } from 'animejs';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
let ezppLogo: HTMLImageElement;
|
||||||
|
let spinnerCircle: SVGCircleElement;
|
||||||
|
|
||||||
|
//TODO: use this to check for updates upon launch
|
||||||
|
|
||||||
|
const doBPMAnimation = () => {
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Animate logo on mount: pop-in, then pulse
|
||||||
|
onMount(() => {
|
||||||
|
// Logo pop-in and pulse
|
||||||
|
animate(ezppLogo, {
|
||||||
|
opacity: [0, 1],
|
||||||
|
scale: [0, 1],
|
||||||
|
duration: 900,
|
||||||
|
ease: (t: number) => Math.pow(2, -5 * t) * Math.sin((t - 0.075) * 20.94) + 1 - 0.0005 * t,
|
||||||
|
onComplete: doBPMAnimation,
|
||||||
|
});
|
||||||
|
// Spinner animation (seamless, starts at 12 o'clock)
|
||||||
|
if (spinnerCircle) {
|
||||||
|
animate(spinnerCircle, {
|
||||||
|
strokeDashoffset: [0, -565],
|
||||||
|
duration: 1800,
|
||||||
|
easing: 'linear',
|
||||||
|
loop: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
</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 mt-[50px] h-[calc(100vh-50px)] w-full">
|
||||||
<LoaderCircle class="animate-spin" size={80} />
|
<div class="relative w-80 h-80 flex items-center justify-center">
|
||||||
<span></span>
|
<svg
|
||||||
</div>
|
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>
|
||||||
|
</div>
|
||||||
|
28
src/pages/Login.svelte
Normal file
28
src/pages/Login.svelte
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ezppfarm } from '@/api/ezpp';
|
||||||
|
|
||||||
|
let username = $state('');
|
||||||
|
let password = $state('');
|
||||||
|
|
||||||
|
let errorMessage = $state('');
|
||||||
|
let isLoading = $state(false);
|
||||||
|
|
||||||
|
const performLogin = async () => {
|
||||||
|
isLoading = true;
|
||||||
|
errorMessage = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const loginResult = await ezppfarm.login(username, password);
|
||||||
|
if (loginResult) {
|
||||||
|
// Handle successful login, e.g., redirect to the main app or store the token
|
||||||
|
console.log('Login successful:', loginResult);
|
||||||
|
} else {
|
||||||
|
errorMessage = 'Invalid username or password.';
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
errorMessage = 'Login failed. Please check your credentials.';
|
||||||
|
} finally {
|
||||||
|
isLoading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -1,25 +1,73 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Logo from '$assets/logo.png';
|
import Logo from '$assets/logo.png';
|
||||||
import * as Avatar from '@/components/ui/avatar';
|
|
||||||
import Badge from '@/components/ui/badge/badge.svelte';
|
|
||||||
import Button from '@/components/ui/button/button.svelte';
|
import Button from '@/components/ui/button/button.svelte';
|
||||||
import * as Select from '@/components/ui/select';
|
import Input from '@/components/ui/input/input.svelte';
|
||||||
import { beatmap_sets, online_friends, server_connection_fails, server_ping } from '@/global';
|
import { animate } from 'animejs';
|
||||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
|
import { onMount } from 'svelte';
|
||||||
import { LoaderCircle, Logs, Music2, Play, Users, Wifi, Gamepad2, WifiOff } from 'lucide-svelte';
|
import { fade } from 'svelte/transition';
|
||||||
import { Circle } from 'radix-icons-svelte';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import NumberFlow from '@number-flow/svelte';
|
import { Check, CheckCircle, CircleOff } from 'lucide-svelte';
|
||||||
import * as AlertDialog from '@/components/ui/alert-dialog';
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
import Progress from '@/components/ui/progress/progress.svelte';
|
|
||||||
import { numberHumanReadable } from '@/utils';
|
|
||||||
|
|
||||||
let selectedStep = $state(1);
|
let selectedStep = $state(1);
|
||||||
const steps = [
|
const steps = ['Welcome', 'Locate your osu! Installation', 'Appearance Settings'];
|
||||||
'Welcome',
|
|
||||||
'Login/Register',
|
let osuInstallationPath = $state('');
|
||||||
'Locate your osu! Installation',
|
let manualSelect = $state(false);
|
||||||
'Select your default mode',
|
let manualSelectValid = $state(false);
|
||||||
];
|
let autoDetectedOsuPath = $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: boolean = await invoke('valid_osu_folder', { folder: selectedPath });
|
||||||
|
manualSelect = true;
|
||||||
|
if (!validFolder) {
|
||||||
|
manualSelectValid = false;
|
||||||
|
osuInstallationPath = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
osuInstallationPath = selectedPath;
|
||||||
|
autoDetectedOsuPath = false;
|
||||||
|
manualSelectValid = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const osuInstallPath: string | null = await invoke('find_osu_installation');
|
||||||
|
console.log('osu install path: ' + osuInstallPath);
|
||||||
|
if (osuInstallPath) {
|
||||||
|
osuInstallationPath = osuInstallPath;
|
||||||
|
autoDetectedOsuPath = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<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)]">
|
||||||
@ -28,27 +76,109 @@
|
|||||||
<div
|
<div
|
||||||
class="flex flex-row items-center gap-2 border {selectedStep === i + 1
|
class="flex flex-row items-center gap-2 border {selectedStep === i + 1
|
||||||
? 'border-primary-800/30 bg-primary-900/30'
|
? 'border-primary-800/30 bg-primary-900/30'
|
||||||
: 'border-theme-800 bg-theme-900'} rounded-lg p-2 transition-all"
|
: selectedStep > i
|
||||||
|
? 'border-green-800/30 bg-green-900/30'
|
||||||
|
: '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] 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"
|
||||||
>
|
>
|
||||||
<span class="text-lg font-semibold text-theme-100">{i + 1}</span>
|
{#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>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="{selectedStep === i + 1 ? 'text-white' : 'text-muted-foreground'} transition-all"
|
class="{selectedStep === i + 1
|
||||||
>{step}</span
|
? 'text-white'
|
||||||
|
: selectedStep > i
|
||||||
|
? 'text-green-500'
|
||||||
|
: "'text-muted-foreground'"} transition-all text-sm font-bold">{step}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-6 w-full h-full bg-theme-900/40 p-6">
|
<div class="flex flex-col gap-6 w-full h-full bg-theme-900/40 p-6">
|
||||||
<div class="my-auto flex flex-col items-center justify-center">
|
{#if selectedStep === 1}
|
||||||
{#if selectedStep === 1}
|
<div
|
||||||
<h1 class="text-3xl">Welcome to EZPPLauncher!</h1>
|
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 }}
|
||||||
{/if}
|
>
|
||||||
</div>
|
<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: 100 }}
|
||||||
|
>
|
||||||
|
<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={osuInstallationPath}
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="mt-auto flex flex-row items-center justify-between">
|
<div class="mt-auto flex flex-row items-center justify-between">
|
||||||
<Button
|
<Button
|
||||||
class="bg-theme-950 hover:bg-theme-800"
|
class="bg-theme-950 hover:bg-theme-800"
|
||||||
@ -58,7 +188,8 @@
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
onclick={() => (selectedStep = Math.min(selectedStep + 1, steps.length))}
|
onclick={() => (selectedStep = Math.min(selectedStep + 1, steps.length))}
|
||||||
disabled={selectedStep >= steps.length}>Next</Button
|
disabled={selectedStep >= steps.length ||
|
||||||
|
(selectedStep === 2 && osuInstallationPath.length <= 0)}>Next</Button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
<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 { setupValues } from '@/global';
|
import { current_view, 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';
|
||||||
import { Buffer } from 'buffer';
|
import { Buffer } from 'buffer';
|
||||||
|
import SetupWizard from '../pages/SetupWizard.svelte';
|
||||||
|
import Launch from '../pages/Launch.svelte';
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
window.Buffer = Buffer;
|
window.Buffer = Buffer;
|
||||||
setupValues();
|
setupValues();
|
||||||
await $userSettings.init();
|
const firstStartup = await $userSettings.init();
|
||||||
|
|
||||||
const config_custom_cursor = $userSettings.value('custom_cursor');
|
const config_custom_cursor = $userSettings.value('custom_cursor');
|
||||||
const config_cursor_smoothening = $userSettings.value('cursor_smoothening');
|
const config_cursor_smoothening = $userSettings.value('cursor_smoothening');
|
||||||
@ -24,6 +26,12 @@
|
|||||||
customCursor.subscribe((val) => config_custom_cursor.set(val));
|
customCursor.subscribe((val) => config_custom_cursor.set(val));
|
||||||
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) {
|
||||||
|
current_view.set(Launch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* current_view.set(SetupWizard); */
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Launch from "../pages/Launch.svelte";
|
import { current_view } from '@/global';
|
||||||
import Loading from "../pages/Loading.svelte";
|
|
||||||
import SetupWizard from "../pages/SetupWizard.svelte";
|
const View = $derived($current_view);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Launch />
|
<View />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user