too many changes, forgot to commit smh
This commit is contained in:
parent
96a8604d4b
commit
60e3102257
3
bun.lock
3
bun.lock
@ -12,6 +12,7 @@
|
|||||||
"@tauri-apps/plugin-fs": "2.4.0",
|
"@tauri-apps/plugin-fs": "2.4.0",
|
||||||
"@tauri-apps/plugin-shell": "2.3.0",
|
"@tauri-apps/plugin-shell": "2.3.0",
|
||||||
"@tauri-apps/plugin-sql": "2.3.0",
|
"@tauri-apps/plugin-sql": "2.3.0",
|
||||||
|
"animejs": "^4.0.2",
|
||||||
"ky": "1.8.1",
|
"ky": "1.8.1",
|
||||||
"lucide-svelte": "0.523.0",
|
"lucide-svelte": "0.523.0",
|
||||||
"osu-classes": "3.1.0",
|
"osu-classes": "3.1.0",
|
||||||
@ -232,6 +233,8 @@
|
|||||||
|
|
||||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||||
|
|
||||||
|
"animejs": ["animejs@4.0.2", "", {}, "sha512-f0L/kSya2RF23iMSF/VO01pMmLwlAFoiQeNAvBXhEyLzIPd2/QTBRatwGUqkVCC6seaAJYzAkGir55N4SL+h3A=="],
|
||||||
|
|
||||||
"ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
"ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||||
|
|
||||||
"ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
"ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
"@tauri-apps/plugin-fs": "2.4.0",
|
"@tauri-apps/plugin-fs": "2.4.0",
|
||||||
"@tauri-apps/plugin-shell": "2.3.0",
|
"@tauri-apps/plugin-shell": "2.3.0",
|
||||||
"@tauri-apps/plugin-sql": "2.3.0",
|
"@tauri-apps/plugin-sql": "2.3.0",
|
||||||
|
"animejs": "^4.0.2",
|
||||||
"ky": "1.8.1",
|
"ky": "1.8.1",
|
||||||
"lucide-svelte": "0.523.0",
|
"lucide-svelte": "0.523.0",
|
||||||
"osu-classes": "3.1.0",
|
"osu-classes": "3.1.0",
|
||||||
|
BIN
src/assets/cursor-additive.png
Normal file
BIN
src/assets/cursor-additive.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/cursor.png
Normal file
BIN
src/assets/cursor.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
35
src/lib/components/ui/checkbox/checkbox.svelte
Normal file
35
src/lib/components/ui/checkbox/checkbox.svelte
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Checkbox as CheckboxPrimitive, type WithoutChildrenOrChild } from "bits-ui";
|
||||||
|
import Check from "@lucide/svelte/icons/check";
|
||||||
|
import Minus from "@lucide/svelte/icons/minus";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
checked = $bindable(false),
|
||||||
|
indeterminate = $bindable(false),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
bind:ref
|
||||||
|
class={cn(
|
||||||
|
"border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer box-content size-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
bind:checked
|
||||||
|
bind:indeterminate
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{#snippet children({ checked, indeterminate })}
|
||||||
|
<div class="flex size-4 items-center justify-center text-current">
|
||||||
|
{#if indeterminate}
|
||||||
|
<Minus class="size-3.5" />
|
||||||
|
{:else}
|
||||||
|
<Check class={cn("size-3.5", !checked && "text-transparent")} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
</CheckboxPrimitive.Root>
|
6
src/lib/components/ui/checkbox/index.ts
Normal file
6
src/lib/components/ui/checkbox/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Root from "./checkbox.svelte";
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Checkbox,
|
||||||
|
};
|
7
src/lib/components/ui/label/index.ts
Normal file
7
src/lib/components/ui/label/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Root from "./label.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Label,
|
||||||
|
};
|
19
src/lib/components/ui/label/label.svelte
Normal file
19
src/lib/components/ui/label/label.svelte
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Label as LabelPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: LabelPrimitive.RootProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
bind:ref
|
||||||
|
class={cn(
|
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
176
src/lib/components/ui/osu-cursor/OsuCursor.svelte
Normal file
176
src/lib/components/ui/osu-cursor/OsuCursor.svelte
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import cursor_default from '$assets/cursor.png';
|
||||||
|
import cursor_additive from '$assets/cursor-additive.png';
|
||||||
|
import { animate } from 'animejs';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
let mouseX = $state(0);
|
||||||
|
let mouseY = $state(0);
|
||||||
|
let rotation = $state(0);
|
||||||
|
let isMouseDown = $state(false);
|
||||||
|
let isHoveringInteractive = $state(false);
|
||||||
|
|
||||||
|
let dragStartX = $state(0);
|
||||||
|
let dragStartY = $state(0);
|
||||||
|
let degrees = $state(0);
|
||||||
|
let applyRotation = $state(false);
|
||||||
|
|
||||||
|
function isInteractive(el: Element | null): boolean {
|
||||||
|
while (el) {
|
||||||
|
const tag = el.tagName.toLowerCase();
|
||||||
|
const role = el.getAttribute('role');
|
||||||
|
const computed = getComputedStyle(el);
|
||||||
|
if (
|
||||||
|
['button', 'a', 'input', 'select', 'textarea', 'label', 'option'].includes(tag) ||
|
||||||
|
(role && ['button', 'link', 'checkbox', 'combobox'].includes(role)) ||
|
||||||
|
computed.cursor === 'pointer'
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
el = el.parentElement;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
mouseX = e.clientX;
|
||||||
|
mouseY = e.clientY;
|
||||||
|
|
||||||
|
const deltaX = e.pageX - window.pageXOffset - dragStartX;
|
||||||
|
const deltaY = e.pageY - window.pageYOffset - dragStartY;
|
||||||
|
|
||||||
|
if (!applyRotation && isMouseDown && deltaX * deltaX + deltaY * deltaY > 30 * 100) {
|
||||||
|
applyRotation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newDegrees = (Math.atan2(-deltaX, deltaY) * 180) / Math.PI + 24.3;
|
||||||
|
|
||||||
|
let diff = (newDegrees - degrees) % 360;
|
||||||
|
if (diff < -180) diff += 360;
|
||||||
|
if (diff > 180) diff -= 360;
|
||||||
|
if (isMouseDown && applyRotation) {
|
||||||
|
degrees += diff;
|
||||||
|
} else {
|
||||||
|
degrees = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const el = document.elementFromPoint(mouseX, mouseY);
|
||||||
|
isHoveringInteractive = isInteractive(el);
|
||||||
|
|
||||||
|
if (!isMouseDown) {
|
||||||
|
if (isHoveringInteractive) {
|
||||||
|
animate(cursorAdditive, {
|
||||||
|
opacity: 1,
|
||||||
|
duration: 800,
|
||||||
|
ease: (t: number) => (t - 1) ** 5 + 1,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
animate(cursorAdditive, {
|
||||||
|
opacity: 0,
|
||||||
|
duration: 800,
|
||||||
|
ease: (t: number) => (t - 1) ** 5 + 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
animate(cursor, {
|
||||||
|
duration: 180,
|
||||||
|
translateX: mouseX,
|
||||||
|
translateY: mouseY - 50,
|
||||||
|
ease: (t: number) => (t - 1) ** 3 + 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
animate(cursor, {
|
||||||
|
duration: 1500,
|
||||||
|
rotate: degrees,
|
||||||
|
transformOrigin: '0px 0px 0',
|
||||||
|
ease: (t: number) => Math.pow(2, -10 * t) * Math.sin((t - 0.075) * 20.94) + 1 - 0.0005 * t,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseDown = (event: MouseEvent) => {
|
||||||
|
dragStartX = event.clientX;
|
||||||
|
dragStartY = event.clientY;
|
||||||
|
|
||||||
|
isMouseDown = true;
|
||||||
|
animate(cursorInner, {
|
||||||
|
scale: 0.9,
|
||||||
|
duration: 800,
|
||||||
|
ease: (t: number) => (t - 1) ** 3 + 1,
|
||||||
|
});
|
||||||
|
animate(cursorAdditive, {
|
||||||
|
opacity: 1,
|
||||||
|
scale: 0.9,
|
||||||
|
duration: 800,
|
||||||
|
ease: (t: number) => (t - 1) ** 5 + 1,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
rotation = 0;
|
||||||
|
isMouseDown = false;
|
||||||
|
applyRotation = false;
|
||||||
|
animate(cursorInner, {
|
||||||
|
scale: 1,
|
||||||
|
duration: 500,
|
||||||
|
ease: (t: number) => Math.pow(2, -10 * t) * Math.sin((t - 0.075) * 20.94) + 1 - 0.0005 * t,
|
||||||
|
});
|
||||||
|
animate(cursorAdditive, {
|
||||||
|
opacity: 0,
|
||||||
|
duration: 500,
|
||||||
|
scale: 1,
|
||||||
|
ease: (t: number) => (t - 1) ** 5 + 1,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let cursor: HTMLElement;
|
||||||
|
let cursorInner: HTMLDivElement;
|
||||||
|
let cursorAdditive: HTMLImageElement;
|
||||||
|
|
||||||
|
/* onMount(() => {
|
||||||
|
const processMouseMove = (e: MouseEvent) =>
|
||||||
|
handleMouseMove(e.clientX, e.clientY, e.pageX, e.pageY);
|
||||||
|
document.addEventListener('pointermove', processMouseMove);
|
||||||
|
document.addEventListener('pointerdown', handleMouseDown);
|
||||||
|
document.addEventListener('pointerup', handleMouseUp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('pointermove', processMouseMove);
|
||||||
|
document.removeEventListener('pointerdown', handleMouseDown);
|
||||||
|
document.removeEventListener('pointerup', handleMouseUp);
|
||||||
|
};
|
||||||
|
}); */
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window
|
||||||
|
onmousemove={handleMouseMove}
|
||||||
|
onmousedown={handleMouseDown}
|
||||||
|
onmouseup={handleMouseUp}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="h-7 w-7 fixed pointer-events-none z-[99999]" bind:this={cursor}>
|
||||||
|
<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 opacity-0"
|
||||||
|
src={cursor_additive}
|
||||||
|
bind:this={cursorAdditive}
|
||||||
|
alt="cursor"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(html),
|
||||||
|
:global(body),
|
||||||
|
:global(*),
|
||||||
|
:global(*:hover),
|
||||||
|
:global(button),
|
||||||
|
:global(a),
|
||||||
|
:global(input),
|
||||||
|
:global(select),
|
||||||
|
:global(textarea) {
|
||||||
|
cursor: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
@ -19,7 +19,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div data-tauri-drag-region class="titlebar z-[99999] border-b border-theme-800/90">
|
<div data-tauri-drag-region class="titlebar z-[60] border-b border-theme-800/90">
|
||||||
<div class="mr-auto ms-2 flex flex-row gap-2 items-center text-[1.05rem] font-semibold">
|
<div class="mr-auto ms-2 flex flex-row gap-2 items-center text-[1.05rem] font-semibold">
|
||||||
<img src={Logo} alt="EZPP Launcher Logo" class="h-11 w-11 inline-block" />
|
<img src={Logo} alt="EZPP Launcher Logo" class="h-11 w-11 inline-block" />
|
||||||
<span>EZPPLauncher</span>
|
<span>EZPPLauncher</span>
|
||||||
|
@ -3,14 +3,11 @@ import { ezppfarm } from './api/ezpp';
|
|||||||
|
|
||||||
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);
|
||||||
export let server_no_connection = false;
|
|
||||||
|
|
||||||
export const online_friends = writable<number | undefined>(undefined);
|
export const online_friends = writable<number | undefined>(undefined);
|
||||||
|
|
||||||
export const beatmap_sets = writable<number | undefined>(undefined);
|
export const beatmap_sets = writable<number | undefined>(undefined);
|
||||||
|
|
||||||
export const updateNoConnection = (noConnection: boolean) => (server_no_connection = noConnection);
|
|
||||||
|
|
||||||
export const setupValues = () => {
|
export const setupValues = () => {
|
||||||
updatePing();
|
updatePing();
|
||||||
updateFriends();
|
updateFriends();
|
||||||
@ -26,7 +23,7 @@ export const setupValues = () => {
|
|||||||
|
|
||||||
const updatePing = async () => {
|
const updatePing = async () => {
|
||||||
const serverPing = await ezppfarm.ping();
|
const serverPing = await ezppfarm.ping();
|
||||||
if (!serverPing || server_no_connection) {
|
if (!serverPing) {
|
||||||
server_connection_fails.update((num) => num + 1);
|
server_connection_fails.update((num) => num + 1);
|
||||||
} else {
|
} else {
|
||||||
server_connection_fails.set(0);
|
server_connection_fails.set(0);
|
||||||
@ -36,12 +33,8 @@ const updatePing = async () => {
|
|||||||
|
|
||||||
const updateFriends = async () => {
|
const updateFriends = async () => {
|
||||||
await new Promise((res) => setTimeout(res, Math.random() * 300));
|
await new Promise((res) => setTimeout(res, Math.random() * 300));
|
||||||
if (server_no_connection) {
|
const onlineFriends = Math.round(Math.random() * 10);
|
||||||
online_friends.set(undefined);
|
online_friends.set(onlineFriends);
|
||||||
} else {
|
|
||||||
const onlineFriends = Math.round(Math.random() * 10);
|
|
||||||
online_friends.set(onlineFriends);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateBeatmapSets = async () => {
|
const updateBeatmapSets = async () => {
|
||||||
|
3
src/lib/osuUtil.ts
Normal file
3
src/lib/osuUtil.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const clientNeedsUpdate = () => {
|
||||||
|
|
||||||
|
}
|
299
src/pages/Launch.svelte
Normal file
299
src/pages/Launch.svelte
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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 * as Select from '@/components/ui/select';
|
||||||
|
import { beatmap_sets, online_friends, server_connection_fails, server_ping } from '@/global';
|
||||||
|
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
|
import { LoaderCircle, Logs, Music2, Play, Users, Wifi, Gamepad2, WifiOff, Settings2 } from 'lucide-svelte';
|
||||||
|
import { Circle } from 'radix-icons-svelte';
|
||||||
|
import NumberFlow from '@number-flow/svelte';
|
||||||
|
import * as AlertDialog from '@/components/ui/alert-dialog';
|
||||||
|
import Progress from '@/components/ui/progress/progress.svelte';
|
||||||
|
import { numberHumanReadable } from '@/utils';
|
||||||
|
import { fade, fly, scale } from 'svelte/transition';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import Label from '@/components/ui/label/label.svelte';
|
||||||
|
|
||||||
|
let selectedTab = $state('settings');
|
||||||
|
let launching = $state(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AlertDialog.Root bind:open={launching}>
|
||||||
|
<AlertDialog.Content class="bg-theme-950 border-theme-800 p-0">
|
||||||
|
<div
|
||||||
|
class="flex flex-col items-center justify-center border-b border-theme-800 bg-black/40 rounded-t-lg p-3"
|
||||||
|
>
|
||||||
|
<img class="h-20 w-20" src={Logo} alt="logo" />
|
||||||
|
<span class="font-semibold text-xl">Launching...</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center justify-center gap-2 p-3 rounded-lg">
|
||||||
|
<Progress indeterminate />
|
||||||
|
<span class="text-muted-foreground">Downloading update files...</span>
|
||||||
|
</div>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog.Root>
|
||||||
|
|
||||||
|
<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 py-3">
|
||||||
|
<div class="flex flex-col items-center gap-2 border-b pb-6">
|
||||||
|
<Avatar.Root class="w-20 h-20 border-2 border-theme-800">
|
||||||
|
<Avatar.Image src="https://a.ez-pp.farm/1001" />
|
||||||
|
<Avatar.Fallback class="bg-theme-900">
|
||||||
|
<LoaderCircle class="animate-spin" size={32} />
|
||||||
|
</Avatar.Fallback>
|
||||||
|
</Avatar.Root>
|
||||||
|
<span class="font-semibold text-2xl text-theme-50">Quetzalcoatl</span>
|
||||||
|
<div class="flex flex-row gap-2">
|
||||||
|
<Badge variant="destructive">Owner</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-6 h-full px-3">
|
||||||
|
<Select.Root type="single">
|
||||||
|
<Select.Trigger
|
||||||
|
class="border-theme-800/90 bg-theme-900/90 !text-muted-foreground font-semibold"
|
||||||
|
>
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<Circle class="text-muted-foreground" />
|
||||||
|
osu!vn
|
||||||
|
</div>
|
||||||
|
</Select.Trigger>
|
||||||
|
<Select.Content class="bg-theme-950 border border-theme-900 rounded-lg">
|
||||||
|
<Select.Item value="light">osu!vn</Select.Item>
|
||||||
|
<Select.Item value="dark">osu!rx</Select.Item>
|
||||||
|
<Select.Item value="system">osu!ap</Select.Item>
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
<div class="bg-theme-900/90 border border-theme-800/90 rounded-lg p-2">
|
||||||
|
<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>
|
||||||
|
<span class="text-lg font-semibold text-theme-50">#727</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-0.5">
|
||||||
|
<span class="text-sm text-muted-foreground font-semibold">PP</span>
|
||||||
|
<span class="text-lg font-semibold text-theme-50">727</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-[1fr_auto] border-t border-theme-800 pt-2">
|
||||||
|
<span class="text-sm text-muted-foreground font-semibold">Accuracy</span>
|
||||||
|
<span class="text-sm font-semibold text-end text-theme-50">72.72%</span>
|
||||||
|
|
||||||
|
<span class="text-sm text-muted-foreground font-semibold">Play Count</span>
|
||||||
|
<span class="text-sm font-semibold text-end text-theme-50">727</span>
|
||||||
|
|
||||||
|
<span class="text-sm text-muted-foreground font-semibold">Play Time</span>
|
||||||
|
<span class="text-sm font-semibold text-end text-theme-50">727h</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="mt-auto bg-theme-900/90 border border-theme-800/90 rounded-lg p-2">
|
||||||
|
<div class="flex flex-row items-center justify-between">
|
||||||
|
<div class="flex flex-row items-center gap-2">
|
||||||
|
<Users class="text-muted-foreground" size="16" />
|
||||||
|
<span class="font-semibold text-muted-foreground text-sm"> Friends </span>
|
||||||
|
</div>
|
||||||
|
<Badge class="h-5 bg-green-500/20 hover:bg-green-500/20 text-green-500">3 online</Badge>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-6 w-full h-full bg-theme-900/40 p-6">
|
||||||
|
<div
|
||||||
|
class="flex flex-row flex-nowrap h-11 w-full bg-theme-800/50 border border-theme-800/90 rounded-lg p-[4px]"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="w-full flex justify-center items-center font-semibold text-sm rounded-lg {selectedTab ===
|
||||||
|
'home'
|
||||||
|
? 'bg-white/70 border border-white/60 text-theme-950'
|
||||||
|
: ''} transition-all"
|
||||||
|
onclick={() => (selectedTab = 'home')}
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="w-full flex justify-center items-center font-semibold text-sm rounded-lg {selectedTab ===
|
||||||
|
'settings'
|
||||||
|
? 'bg-white/70 border border-white/60 text-theme-950'
|
||||||
|
: ''} transition-all"
|
||||||
|
onclick={() => (selectedTab = 'settings')}
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{#if selectedTab === 'home'}
|
||||||
|
<div
|
||||||
|
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: 400, start: 0.98 }}
|
||||||
|
>
|
||||||
|
<div class="grid grid-cols-2 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 {!$beatmap_sets
|
||||||
|
? 'opacity-100'
|
||||||
|
: 'opacity-0'} transition-opacity duration-1000"
|
||||||
|
>
|
||||||
|
<LoaderCircle class="animate-spin" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="{!$beatmap_sets
|
||||||
|
? 'opacity-0'
|
||||||
|
: 'opacity-100'} transition-opacity duration-1000"
|
||||||
|
>
|
||||||
|
<NumberFlow value={$beatmap_sets ?? 0} trend={0} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted-foreground text-sm">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 {$server_connection_fails > 1
|
||||||
|
? 'bg-red-500/20'
|
||||||
|
: 'bg-yellow-500/20'}"
|
||||||
|
>
|
||||||
|
{#if $server_connection_fails > 1}
|
||||||
|
<Users class="text-red-500" size="26" />
|
||||||
|
{:else}
|
||||||
|
<Users class="text-yellow-500" size="26" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="relative font-bold text-xl {$server_connection_fails > 1
|
||||||
|
? 'text-red-400'
|
||||||
|
: 'text-yellow-400'}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute top-1 left-1/2 -translate-x-1/2 {!$online_friends ||
|
||||||
|
$server_connection_fails > 1
|
||||||
|
? 'opacity-100'
|
||||||
|
: 'opacity-0'} transition-opacity duration-1000"
|
||||||
|
>
|
||||||
|
<LoaderCircle class="animate-spin" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="{!$online_friends || $server_connection_fails > 1
|
||||||
|
? 'opacity-0'
|
||||||
|
: 'opacity-100'} transition-opacity duration-1000"
|
||||||
|
>
|
||||||
|
<NumberFlow value={$online_friends ?? 0} trend={0} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted-foreground text-sm">Friends online</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 {$server_connection_fails > 1
|
||||||
|
? 'bg-red-500/20'
|
||||||
|
: 'bg-green-500/20'}"
|
||||||
|
>
|
||||||
|
{#if $server_connection_fails > 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 {$server_connection_fails > 1
|
||||||
|
? 'text-red-400'
|
||||||
|
: 'text-green-400'}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute top-1 left-1/2 -translate-x-1/2 {!$server_ping ||
|
||||||
|
$server_connection_fails > 1
|
||||||
|
? 'opacity-100'
|
||||||
|
: 'opacity-0'} transition-opacity duration-1000"
|
||||||
|
>
|
||||||
|
<LoaderCircle class="animate-spin" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="{!$server_ping || $server_connection_fails > 1
|
||||||
|
? 'opacity-0'
|
||||||
|
: 'opacity-100'} transition-opacity duration-1000"
|
||||||
|
>
|
||||||
|
<NumberFlow value={$server_ping ?? 0} trend={0} suffix="ms" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted-foreground text-sm">Ping to Server</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
onclick={() => {
|
||||||
|
launching = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
launching = false;
|
||||||
|
}, 5000);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Play />
|
||||||
|
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: 400, 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-2">
|
||||||
|
<span class="text-sm text-muted-foreground font-semibold">osu! Version</span>
|
||||||
|
<span class="text-sm font-semibold text-end text-theme-50">
|
||||||
|
<Badge>20250626.1</Badge>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="text-sm text-muted-foreground font-semibold">Beatmap Sets</span>
|
||||||
|
<span class="text-sm font-semibold text-end text-theme-50"
|
||||||
|
>{numberHumanReadable($beatmap_sets ?? 0)}</span
|
||||||
|
>
|
||||||
|
|
||||||
|
<span class="text-sm text-muted-foreground font-semibold">Skins</span>
|
||||||
|
<span class="text-sm font-semibold text-end text-theme-50"
|
||||||
|
>{numberHumanReadable(727)}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else if selectedTab === 'settings'}
|
||||||
|
<div
|
||||||
|
class="bg-theme-900/90 flex flex-col justify-center gap-3 border border-theme-800/90 rounded-lg"
|
||||||
|
in:scale={{ duration: 400, 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" class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<Label class="text-sm" for="setting-custom-cursor">Cursor Smoothening</Label>
|
||||||
|
<div class="text-muted-foreground text-xs">
|
||||||
|
Makes the custom cursor movement smoother.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Checkbox id="setting-custom-cursor" class="flex items-center justify-center w-5 h-5"
|
||||||
|
></Checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
8
src/pages/Loading.svelte
Normal file
8
src/pages/Loading.svelte
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { LoaderCircle } from "lucide-svelte";
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<div class="flex flex-col items-center justify-center mt-[50px] h-[calc(100vh-50px)] w-full">
|
||||||
|
<LoaderCircle class="animate-spin" size={80} />
|
||||||
|
<span></span>
|
||||||
|
</div>
|
65
src/pages/SetupWizard.svelte
Normal file
65
src/pages/SetupWizard.svelte
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
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 * as Select from '@/components/ui/select';
|
||||||
|
import { beatmap_sets, online_friends, server_connection_fails, server_ping } from '@/global';
|
||||||
|
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
|
import { LoaderCircle, Logs, Music2, Play, Users, Wifi, Gamepad2, WifiOff } from 'lucide-svelte';
|
||||||
|
import { Circle } from 'radix-icons-svelte';
|
||||||
|
import NumberFlow from '@number-flow/svelte';
|
||||||
|
import * as AlertDialog from '@/components/ui/alert-dialog';
|
||||||
|
import Progress from '@/components/ui/progress/progress.svelte';
|
||||||
|
import { numberHumanReadable } from '@/utils';
|
||||||
|
|
||||||
|
let selectedStep = $state(1);
|
||||||
|
const steps = [
|
||||||
|
'Welcome',
|
||||||
|
'Login/Register',
|
||||||
|
'Locate your osu! Installation',
|
||||||
|
'Select your default mode',
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<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'
|
||||||
|
: '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] border-theme-600 rounded-full"
|
||||||
|
>
|
||||||
|
<span class="text-lg font-semibold text-theme-100">{i + 1}</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="{selectedStep === i + 1 ? 'text-white' : 'text-muted-foreground'} transition-all"
|
||||||
|
>{step}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<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}
|
||||||
|
<h1 class="text-3xl">Welcome to EZPPLauncher!</h1>
|
||||||
|
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<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={() => (selectedStep = Math.min(selectedStep + 1, steps.length))}
|
||||||
|
disabled={selectedStep >= steps.length}>Next</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -3,11 +3,14 @@
|
|||||||
import '../app.css';
|
import '../app.css';
|
||||||
import { setupValues } from '@/global';
|
import { setupValues } from '@/global';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import OsuCursor from '@/components/ui/osu-cursor/OsuCursor.svelte';
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|
||||||
onMount(setupValues);
|
onMount(setupValues);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<OsuCursor />
|
||||||
|
|
||||||
<Titlebar />
|
<Titlebar />
|
||||||
<main>
|
<main>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
@ -1,271 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Avatar from '@/components/ui/avatar';
|
import Launch from "../pages/Launch.svelte";
|
||||||
import Badge from '@/components/ui/badge/badge.svelte';
|
import Loading from "../pages/Loading.svelte";
|
||||||
import Button from '@/components/ui/button/button.svelte';
|
import SetupWizard from "../pages/SetupWizard.svelte";
|
||||||
import * as Select from '@/components/ui/select';
|
|
||||||
import {
|
|
||||||
beatmap_sets,
|
|
||||||
online_friends,
|
|
||||||
server_connection_fails,
|
|
||||||
server_no_connection,
|
|
||||||
server_ping,
|
|
||||||
updateNoConnection,
|
|
||||||
} from '@/global';
|
|
||||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
|
|
||||||
import { LoaderCircle, Logs, Music2, Play, Users, Wifi, Gamepad2, WifiOff } from 'lucide-svelte';
|
|
||||||
import { Circle } from 'radix-icons-svelte';
|
|
||||||
import NumberFlow from '@number-flow/svelte';
|
|
||||||
import * as AlertDialog from '@/components/ui/alert-dialog';
|
|
||||||
import Progress from '@/components/ui/progress/progress.svelte';
|
|
||||||
import { numberHumanReadable } from '@/utils';
|
|
||||||
|
|
||||||
let selectedTab = $state('home');
|
|
||||||
let launching = $state(false);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AlertDialog.Root bind:open={launching}>
|
<Launch />
|
||||||
<AlertDialog.Content class="bg-theme-950 border-theme-900">
|
|
||||||
<div class="flex flex-col items-center justify-center gap-2">
|
|
||||||
<Progress indeterminate />
|
|
||||||
<span>Downloading update files...</span>
|
|
||||||
</div>
|
|
||||||
</AlertDialog.Content>
|
|
||||||
</AlertDialog.Root>
|
|
||||||
|
|
||||||
<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 py-3">
|
|
||||||
<div class="flex flex-col items-center gap-2 border-b pb-6">
|
|
||||||
<Avatar.Root class="w-20 h-20 border-2 border-theme-800">
|
|
||||||
<Avatar.Image src="https://a.ez-pp.farm/1001" />
|
|
||||||
<Avatar.Fallback class="bg-theme-900">
|
|
||||||
<LoaderCircle class="animate-spin" size={32} />
|
|
||||||
</Avatar.Fallback>
|
|
||||||
</Avatar.Root>
|
|
||||||
<span class="font-semibold text-2xl text-theme-50">Quetzalcoatl</span>
|
|
||||||
<div class="flex flex-row gap-2">
|
|
||||||
<Badge variant="destructive">Owner</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-6 h-full px-3">
|
|
||||||
<Select.Root type="single">
|
|
||||||
<Select.Trigger
|
|
||||||
class="border-theme-800/90 bg-theme-900/90 !text-muted-foreground font-semibold"
|
|
||||||
>
|
|
||||||
<div class="flex flex-row items-center gap-2">
|
|
||||||
<Circle class="text-muted-foreground" />
|
|
||||||
osu!vn
|
|
||||||
</div>
|
|
||||||
</Select.Trigger>
|
|
||||||
<Select.Content class="bg-theme-950 border border-theme-900 rounded-lg">
|
|
||||||
<Select.Item value="light">osu!vn</Select.Item>
|
|
||||||
<Select.Item value="dark">osu!rx</Select.Item>
|
|
||||||
<Select.Item value="system">osu!ap</Select.Item>
|
|
||||||
</Select.Content>
|
|
||||||
</Select.Root>
|
|
||||||
<div class="bg-theme-900/90 border border-theme-800/90 rounded-lg p-2">
|
|
||||||
<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>
|
|
||||||
<span class="text-lg font-semibold text-theme-50">#727</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-0.5">
|
|
||||||
<span class="text-sm text-muted-foreground font-semibold">PP</span>
|
|
||||||
<span class="text-lg font-semibold text-theme-50">727</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-[1fr_auto] border-t border-theme-800 pt-2">
|
|
||||||
<span class="text-sm text-muted-foreground font-semibold">Accuracy</span>
|
|
||||||
<span class="text-sm font-semibold text-end text-theme-50">72.72%</span>
|
|
||||||
|
|
||||||
<span class="text-sm text-muted-foreground font-semibold">Play Count</span>
|
|
||||||
<span class="text-sm font-semibold text-end text-theme-50">727</span>
|
|
||||||
|
|
||||||
<span class="text-sm text-muted-foreground font-semibold">Play Time</span>
|
|
||||||
<span class="text-sm font-semibold text-end text-theme-50">727h</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-auto bg-theme-900/90 border border-theme-800/90 rounded-lg p-2">
|
|
||||||
<div class="flex flex-row items-center justify-between">
|
|
||||||
<div class="flex flex-row items-center gap-2">
|
|
||||||
<Users class="text-muted-foreground" size="16" />
|
|
||||||
<span class="font-semibold text-muted-foreground text-sm"> Friends </span>
|
|
||||||
</div>
|
|
||||||
<Badge class="h-5 bg-green-500/20 hover:bg-green-500/20 text-green-500">3 online</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-6 w-full h-full bg-theme-900/40 p-6">
|
|
||||||
<div
|
|
||||||
class="flex flex-row flex-nowrap h-11 w-full bg-theme-800/50 border border-theme-800/90 rounded-lg p-[4px]"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="w-full flex justify-center items-center font-semibold text-sm rounded-lg {selectedTab ===
|
|
||||||
'home'
|
|
||||||
? 'bg-white/70 border border-white/60 text-theme-950'
|
|
||||||
: ''} transition-all"
|
|
||||||
onclick={() => (selectedTab = 'home')}
|
|
||||||
>
|
|
||||||
Home
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="w-full flex justify-center items-center font-semibold text-sm rounded-lg {selectedTab ===
|
|
||||||
'settings'
|
|
||||||
? 'bg-white/70 border border-white/60 text-theme-950'
|
|
||||||
: ''} transition-all"
|
|
||||||
onclick={() => (selectedTab = 'settings')}
|
|
||||||
>
|
|
||||||
Settings
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
<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 {!$beatmap_sets
|
|
||||||
? 'opacity-100'
|
|
||||||
: 'opacity-0'} transition-opacity duration-1000"
|
|
||||||
>
|
|
||||||
<LoaderCircle class="animate-spin" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="{!$beatmap_sets
|
|
||||||
? 'opacity-0'
|
|
||||||
: 'opacity-100'} transition-opacity duration-1000"
|
|
||||||
>
|
|
||||||
<NumberFlow value={$beatmap_sets ?? 0} trend={0} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-muted-foreground text-sm">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 {$server_connection_fails > 1
|
|
||||||
? 'bg-red-500/20'
|
|
||||||
: 'bg-yellow-500/20'}"
|
|
||||||
>
|
|
||||||
{#if $server_connection_fails > 1}
|
|
||||||
<Users class="text-red-500" size="26" />
|
|
||||||
{:else}
|
|
||||||
<Users class="text-yellow-500" size="26" />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="relative font-bold text-xl {$server_connection_fails > 1
|
|
||||||
? 'text-red-400'
|
|
||||||
: 'text-yellow-400'}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="absolute top-1 left-1/2 -translate-x-1/2 {!$online_friends ||
|
|
||||||
$server_connection_fails > 1
|
|
||||||
? 'opacity-100'
|
|
||||||
: 'opacity-0'} transition-opacity duration-1000"
|
|
||||||
>
|
|
||||||
<LoaderCircle class="animate-spin" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="{!$online_friends || $server_connection_fails > 1
|
|
||||||
? 'opacity-0'
|
|
||||||
: 'opacity-100'} transition-opacity duration-1000"
|
|
||||||
>
|
|
||||||
<NumberFlow value={$online_friends ?? 0} trend={0} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-muted-foreground text-sm">Friends online</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 {$server_connection_fails > 1
|
|
||||||
? 'bg-red-500/20'
|
|
||||||
: 'bg-green-500/20'}"
|
|
||||||
>
|
|
||||||
{#if $server_connection_fails > 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 {$server_connection_fails > 1
|
|
||||||
? 'text-red-400'
|
|
||||||
: 'text-green-400'}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="absolute top-1 left-1/2 -translate-x-1/2 {!$server_ping ||
|
|
||||||
$server_connection_fails > 1
|
|
||||||
? 'opacity-100'
|
|
||||||
: 'opacity-0'} transition-opacity duration-1000"
|
|
||||||
>
|
|
||||||
<LoaderCircle class="animate-spin" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="{!$server_ping || $server_connection_fails > 1
|
|
||||||
? 'opacity-0'
|
|
||||||
: 'opacity-100'} transition-opacity duration-1000"
|
|
||||||
>
|
|
||||||
<NumberFlow value={$server_ping ?? 0} trend={0} suffix="ms" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-muted-foreground text-sm">Ping to Server</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
size="lg"
|
|
||||||
onclick={() => {
|
|
||||||
launching = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
launching = false;
|
|
||||||
}, 5000);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Play />
|
|
||||||
Launch
|
|
||||||
</Button>
|
|
||||||
{#key server_no_connection}
|
|
||||||
<Button
|
|
||||||
size="lg"
|
|
||||||
onclick={() => {
|
|
||||||
updateNoConnection(!server_no_connection);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Wifi />
|
|
||||||
Connection test
|
|
||||||
</Button>
|
|
||||||
{/key}
|
|
||||||
</div>
|
|
||||||
<div class="mt-auto bg-theme-900/90 border border-theme-800/90 rounded-lg px-6 py-3">
|
|
||||||
<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-2">
|
|
||||||
<span class="text-sm text-muted-foreground font-semibold">osu! Version</span>
|
|
||||||
<span class="text-sm font-semibold text-end text-theme-50">
|
|
||||||
<Badge>20250626.1</Badge>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="text-sm text-muted-foreground font-semibold">Beatmap Sets</span>
|
|
||||||
<span class="text-sm font-semibold text-end text-theme-50">{numberHumanReadable($beatmap_sets ?? 0)}</span>
|
|
||||||
|
|
||||||
<span class="text-sm text-muted-foreground font-semibold">Skins</span>
|
|
||||||
<span class="text-sm font-semibold text-end text-theme-50">{numberHumanReadable(727)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
Loading…
x
Reference in New Issue
Block a user