3 Commits

6 changed files with 95 additions and 25 deletions

View File

@@ -39,6 +39,7 @@
"tslib": "2.8.1", "tslib": "2.8.1",
"typescript": "5.8.3", "typescript": "5.8.3",
"vite": "7.0.0", "vite": "7.0.0",
"vite-plugin-devtools-json": "^0.2.0",
}, },
}, },
}, },
@@ -575,10 +576,14 @@
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
"varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="], "varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="],
"vite": ["vite@7.0.0", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g=="], "vite": ["vite@7.0.0", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g=="],
"vite-plugin-devtools-json": ["vite-plugin-devtools-json@0.2.0", "", { "dependencies": { "uuid": "^11.1.0" }, "peerDependencies": { "vite": "^2.7.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "sha512-K7PoaWOEJECZ1n3VbhJXsUAX2PsO0xY7KFMM/Leh7tUev0M5zi+lz+vnVVdCK17IOK9Jp9rdzHXc08cnQirGbg=="],
"vitefu": ["vitefu@1.0.7", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-eRWXLBbJjW3X5z5P5IHcSm2yYbYRPb2kQuc+oqsbAl99WB5kVsPbiiox+cymo8twTzifA6itvhr2CmjnaZZp0Q=="], "vitefu": ["vitefu@1.0.7", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-eRWXLBbJjW3X5z5P5IHcSm2yYbYRPb2kQuc+oqsbAl99WB5kVsPbiiox+cymo8twTzifA6itvhr2CmjnaZZp0Q=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],

View File

@@ -50,6 +50,7 @@
"tailwindcss-animate": "1.0.7", "tailwindcss-animate": "1.0.7",
"tslib": "2.8.1", "tslib": "2.8.1",
"typescript": "5.8.3", "typescript": "5.8.3",
"vite": "7.0.0" "vite": "7.0.0",
"vite-plugin-devtools-json": "^0.2.0"
} }
} }

View File

@@ -2,11 +2,14 @@ import { writable } from 'svelte/store';
import { ezppfarm } from './api/ezpp'; import { ezppfarm } from './api/ezpp';
export const server_ping = writable<number | undefined>(undefined); export const server_ping = writable<number | undefined>(undefined);
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();
@@ -23,7 +26,7 @@ export const setupValues = () => {
const updatePing = async () => { const updatePing = async () => {
const serverPing = await ezppfarm.ping(); const serverPing = await ezppfarm.ping();
if (!serverPing) { if (!serverPing || server_no_connection) {
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);
@@ -32,9 +35,13 @@ const updatePing = async () => {
}; };
const updateFriends = async () => { const updateFriends = async () => {
await new Promise((res) => setTimeout(res, Math.random() * 1000)); await new Promise((res) => setTimeout(res, Math.random() * 300));
const onlineFriends = Math.round(Math.random() * 10); if (server_no_connection) {
online_friends.set(onlineFriends); online_friends.set(undefined);
} else {
const onlineFriends = Math.round(Math.random() * 10);
online_friends.set(onlineFriends);
}
}; };
const updateBeatmapSets = async () => { const updateBeatmapSets = async () => {

View File

@@ -14,6 +14,10 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }
export const numberHumanReadable = (number: number) => {
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.');
};
export const playAudio = (path: string, volume: number) => { export const playAudio = (path: string, volume: number) => {
const audio = new Audio(path); const audio = new Audio(path);
audio.volume = volume; audio.volume = volume;

View File

@@ -3,13 +3,21 @@
import Badge from '@/components/ui/badge/badge.svelte'; 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 * as Select from '@/components/ui/select';
import { beatmap_sets, online_friends, server_ping } from '@/global'; import {
beatmap_sets,
online_friends,
server_connection_fails,
server_no_connection,
server_ping,
updateNoConnection,
} from '@/global';
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'; import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
import { LoaderCircle, Logs, Music2, Play, Users, Wifi, Gamepad2 } from 'lucide-svelte'; import { LoaderCircle, Logs, Music2, Play, Users, Wifi, Gamepad2, WifiOff } from 'lucide-svelte';
import { Circle } from 'radix-icons-svelte'; import { Circle } from 'radix-icons-svelte';
import NumberFlow from '@number-flow/svelte'; import NumberFlow from '@number-flow/svelte';
import * as AlertDialog from '@/components/ui/alert-dialog'; import * as AlertDialog from '@/components/ui/alert-dialog';
import Progress from '@/components/ui/progress/progress.svelte'; import Progress from '@/components/ui/progress/progress.svelte';
import { numberHumanReadable } from '@/utils';
let selectedTab = $state('home'); let selectedTab = $state('home');
let launching = $state(false); let launching = $state(false);
@@ -28,12 +36,15 @@
<div class="w-full h-full border-r border-theme-800/90 flex flex-col gap-6 py-3"> <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"> <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.Root class="w-20 h-20 border-2 border-theme-800">
<Avatar.Image src="https://a.ez-pp.farm/0" /> <Avatar.Image src="https://a.ez-pp.farm/1001" />
<Avatar.Fallback class="bg-theme-900"> <Avatar.Fallback class="bg-theme-900">
<LoaderCircle class="animate-spin" size={32} /> <LoaderCircle class="animate-spin" size={32} />
</Avatar.Fallback> </Avatar.Fallback>
</Avatar.Root> </Avatar.Root>
<span class="font-semibold text-2xl text-theme-50">User</span> <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>
<div class="flex flex-col gap-6 h-full px-3"> <div class="flex flex-col gap-6 h-full px-3">
<Select.Root type="single"> <Select.Root type="single">
@@ -142,19 +153,32 @@
<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" class="bg-theme-800/90 border border-theme-700/90 rounded-lg px-2 py-4 w-full flex flex-col gap-1 items-center justify-center"
> >
<div class="flex items-center justify-center p-2 rounded-lg bg-yellow-500/20"> <div
<Users class="text-yellow-500" size="26" /> 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>
<div class="relative font-bold text-xl text-yellow-400"> <div
class="relative font-bold text-xl {$server_connection_fails > 1
? 'text-red-400'
: 'text-yellow-400'}"
>
<div <div
class="absolute top-1 left-1/2 -translate-x-1/2 {!$online_friends class="absolute top-1 left-1/2 -translate-x-1/2 {!$online_friends ||
$server_connection_fails > 1
? 'opacity-100' ? 'opacity-100'
: 'opacity-0'} transition-opacity duration-1000" : 'opacity-0'} transition-opacity duration-1000"
> >
<LoaderCircle class="animate-spin" /> <LoaderCircle class="animate-spin" />
</div> </div>
<div <div
class="{!$online_friends class="{!$online_friends || $server_connection_fails > 1
? 'opacity-0' ? 'opacity-0'
: 'opacity-100'} transition-opacity duration-1000" : 'opacity-100'} transition-opacity duration-1000"
> >
@@ -166,19 +190,34 @@
<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" 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-green-500/20"> <div
<Wifi class="text-green-500" size="26" /> 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>
<div class="relative font-bold text-xl text-green-400"> <div
class="relative font-bold text-xl {$server_connection_fails > 1
? 'text-red-400'
: 'text-green-400'}"
>
<div <div
class="absolute top-1 left-1/2 -translate-x-1/2 {!$server_ping class="absolute top-1 left-1/2 -translate-x-1/2 {!$server_ping ||
$server_connection_fails > 1
? 'opacity-100' ? 'opacity-100'
: 'opacity-0'} transition-opacity duration-1000" : 'opacity-0'} transition-opacity duration-1000"
> >
<LoaderCircle class="animate-spin" /> <LoaderCircle class="animate-spin" />
</div> </div>
<div <div
class="{!$server_ping ? 'opacity-0' : 'opacity-100'} transition-opacity duration-1000" class="{!$server_ping || $server_connection_fails > 1
? 'opacity-0'
: 'opacity-100'} transition-opacity duration-1000"
> >
<NumberFlow value={$server_ping ?? 0} trend={0} suffix="ms" /> <NumberFlow value={$server_ping ?? 0} trend={0} suffix="ms" />
</div> </div>
@@ -198,6 +237,17 @@
<Play /> <Play />
Launch Launch
</Button> </Button>
{#key server_no_connection}
<Button
size="lg"
onclick={() => {
updateNoConnection(!server_no_connection);
}}
>
<Wifi />
Connection test
</Button>
{/key}
</div> </div>
<div class="mt-auto bg-theme-900/90 border border-theme-800/90 rounded-lg px-6 py-3"> <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"> <div class="flex flex-row items-center gap-2">
@@ -206,13 +256,15 @@
</div> </div>
<div class="grid grid-cols-[1fr_auto] gap-1 mt-2 border-t border-theme-800 pt-2 px-2 pb-2"> <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 text-muted-foreground font-semibold">osu! Version</span>
<span class="text-sm font-semibold text-end text-theme-50">20250626.1</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 text-muted-foreground font-semibold">Beatmap Sets</span>
<span class="text-sm font-semibold text-end text-theme-50">{$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 text-muted-foreground font-semibold">Skins</span>
<span class="text-sm font-semibold text-end text-theme-50">727</span> <span class="text-sm font-semibold text-end text-theme-50">{numberHumanReadable(727)}</span>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,12 +1,13 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import { sveltekit } from "@sveltejs/kit/vite"; import { sveltekit } from "@sveltejs/kit/vite";
import devtoolsJson from 'vite-plugin-devtools-json';
// @ts-expect-error process is a nodejs global // @ts-expect-error process is a nodejs global
const host = process.env.TAURI_DEV_HOST; const host = process.env.TAURI_DEV_HOST;
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(async () => ({ export default defineConfig(async () => ({
plugins: [sveltekit()], plugins: [sveltekit(), devtoolsJson()],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// //