feat: implement skin management

This commit is contained in:
HorizonCode committed 2026-02-22 17:05:52 +01:00
1 parent 96680ff8b5
commit 2e0ef81bcb
8 files changed
+607 -258

No files matched your search

@@ -0,0 +1,143 @@
<script lang="ts">
// Props
let { children, class: className = '', topOffset = 0 } = $props();
let wrapper: HTMLDivElement;
let container: HTMLDivElement;
let thumb: HTMLDivElement;
// Thumb state
let thumbHeight = $state(20);
let thumbTop = $state(0);
// Drag state
let dragging = false;
let startY = 0;
let startScroll = 0;
/** Update thumb height & position */
function updateScrollbar() {
if (!container) return;
const { scrollTop, scrollHeight, clientHeight } = container;
const scrollable = scrollHeight - clientHeight;
if (scrollable <= 0) {
thumbHeight = 0;
thumbTop = 0;
return;
}
thumbHeight = Math.max((clientHeight / scrollHeight) * clientHeight, 24);
const trackHeight = clientHeight - thumbHeight - topOffset;
thumbTop = (scrollTop / scrollable) * trackHeight;
}
/** Start dragging the thumb */
function startDrag(e: PointerEvent) {
dragging = true;
startY = e.clientY;
startScroll = container.scrollTop;
thumb.setPointerCapture(e.pointerId);
}
/** Drag the thumb */
function drag(e: PointerEvent) {
if (!dragging) return;
const delta = e.clientY - startY;
const { scrollHeight, clientHeight } = container;
const scrollable = scrollHeight - clientHeight;
const trackHeight = clientHeight - thumbHeight - topOffset;
container.scrollTop = startScroll + (delta / trackHeight) * scrollable;
}
/** End dragging */
function endDrag(e: PointerEvent) {
dragging = false;
thumb.releasePointerCapture(e.pointerId);
}
// Reactive effect to update scrollbar
$effect(() => updateScrollbar());
// ResizeObserver for dynamic content
$effect(() => {
if (!container) return;
const observer = new ResizeObserver(updateScrollbar);
observer.observe(container);
return () => observer.disconnect();
});
</script>
<div class="wrapper" bind:this={wrapper}>
<!-- Scrollable content -->
<div class={`content ${className}`} bind:this={container} onscroll={updateScrollbar}>
{@render children?.()}
</div>
<!-- Custom scrollbar -->
<div class="scrollbar" style="top: {topOffset}px;">
<div
class="thumb"
bind:this={thumb}
onpointerdown={startDrag}
onpointermove={drag}
onpointerup={endDrag}
onpointercancel={endDrag}
style="height:{thumbHeight}px; transform:translateY({thumbTop}px);"
></div>
</div>
</div>
<style>
.wrapper {
position: relative;
height: 100%;
width: 100%;
}
/* Scrollable content */
.content {
height: 100%;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: none; /* Firefox */
}
.content::-webkit-scrollbar {
display: none; /* Chrome, Safari, Edge */
}
/* Scrollbar track */
.scrollbar {
position: absolute;
right: 2px;
bottom: 0;
width: 12px;
background: var(--color-theme-950);
border-radius: 999px;
}
/* Thumb */
.thumb {
position: absolute;
width: 100%;
background: var(--color-theme-800);
border-radius: 999px;
cursor: grab;
transition: background 0.15s;
}
.thumb:active {
cursor: grabbing;
background: var(--color-theme-600);
}
.thumb:hover {
background: var(--color-theme-700);
}
</style>
+2 -1
View File
@@ -32,7 +32,8 @@ export const serverConnectionFails = writable(0);
export const onlineFriends = writable<number | undefined>(undefined);
export const beatmapSets = writable<number | undefined>(undefined);
export const skins = writable<number | undefined>(undefined);
export const skinsCount = writable<number | undefined>(undefined);
export const skins = writable<{ name: string; author: string | undefined; modified: number }[]>([]);
export const osuStream = writable<string | undefined>(undefined);
export const osuBuild = writable<string | undefined>(undefined);
+17 -7
View File
@@ -5,6 +5,18 @@ import { betterFetch } from '@better-fetch/fetch';
const updateUrl = 'https://ez-pp.farm/api/ezpplauncher';
export function normalizeOsuName(name: string): string {
return (
name
.trim()
// Remove leading symbols/spaces
.replace(/^[^a-zA-Z0-9]+/, '')
// Normalize spacing
.replace(/\s+/g, ' ')
.toLowerCase()
);
}
export const getHWID = async () => {
const hwid = await invoke('get_hwid');
return typeof hwid === 'string' ? hwid : undefined;
@@ -55,13 +67,6 @@ export const getBeatmapSetsCount = async (folder: string) => {
return typeof result === 'number' ? result : 0;
};
export const getSkinsCount = async (folder: string) => {
const result = await invoke('get_skins_count', {
folder,
});
return typeof result === 'number' ? result : 0;
};
export const getSkin = async (folder: string) => {
const result = await invoke('get_osu_skin', {
folder,
@@ -151,3 +156,8 @@ export const downloadUpdate = async (
}
};
export const installUpdate = async () => await invoke('install_ezpp_launcher_update');
export const getSkins = async (folder: string) =>
await invoke<{ name: string; author: string | undefined; modified: number }[] | undefined>(
'get_skins',
{ folder }
);