From bc516488c85e78944b694e62a0e1b9a2632bc222 Mon Sep 17 00:00:00 2001 From: intergrav Date: Tue, 21 Jul 2026 02:57:44 -0400 Subject: [PATCH] refactor: various cleanups and improvements - improved aria labeling - rewrote the visualizer to represent audio freqs more accurately, also removed cutoff settings as a side effect - various simplifications and improvements to consistency across most files, along with some small fixes to the player (llm-assisted, i have reviewed thoroughly) --- src/App.svelte | 23 +-- src/app.css | 8 +- src/lib/Controls.svelte | 49 ++--- src/lib/Library.svelte | 57 +++--- src/lib/LibraryTreeItem.svelte | 84 +++++---- src/lib/NowPlaying.svelte | 94 ++++----- src/lib/Queue.svelte | 40 ++-- src/lib/Sidebar.svelte | 85 ++++++--- src/lib/Visualizer.svelte | 230 ++++++++++++++++------- src/lib/animations.ts | 14 +- src/lib/app.svelte.ts | 54 +++--- src/lib/artwork.ts | 7 +- src/lib/client.svelte.ts | 9 +- src/lib/dialogs/SettingsModal.svelte | 34 ---- src/lib/offline/indexedDbStore.svelte.ts | 8 +- src/lib/offline/offlineManager.svelte.ts | 12 +- src/lib/player.svelte.ts | 79 +++++--- src/lib/queue.svelte.ts | 5 +- src/lib/settings.svelte.ts | 10 +- 19 files changed, 538 insertions(+), 364 deletions(-) diff --git a/src/App.svelte b/src/App.svelte index 57bed0c..d27258c 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -17,16 +17,12 @@ import Sidebar from "./lib/Sidebar.svelte"; import { settings } from "./lib/settings.svelte.js"; import { ui } from "./lib/ui.svelte.js"; -type WindowControlsOverlay = EventTarget & { - visible: boolean; -}; - type WindowControlsOverlayNavigator = Navigator & { - windowControlsOverlay?: WindowControlsOverlay; + windowControlsOverlay?: EventTarget & { visible: boolean }; }; onMount(() => { - init(); + void init(); const overlay = (navigator as WindowControlsOverlayNavigator) .windowControlsOverlay; @@ -115,16 +111,11 @@ $effect(() => { ); const classList = document.documentElement.classList; - const classes = { - "dynamic-colors": settings.dynamicColors, - "rounded-corners": settings.roundedCorners, - "transparent-borders": settings.transparentOutlines, - miniplayer: ui.miniplayer, - "show-library-mobile": ui.showLibraryMobile, - }; - for (const [name, active] of Object.entries(classes)) { - classList.toggle(name, !!active); - } + classList.toggle("dynamic-colors", settings.dynamicColors); + classList.toggle("rounded-corners", settings.roundedCorners); + classList.toggle("transparent-borders", settings.transparentOutlines); + classList.toggle("miniplayer", ui.miniplayer); + classList.toggle("show-library-mobile", ui.showLibraryMobile); }); diff --git a/src/app.css b/src/app.css index 88c8e01..4d05327 100644 --- a/src/app.css +++ b/src/app.css @@ -146,10 +146,8 @@ body { color: var(--text); block-size: 100vh; inline-size: 100vw; - padding-top: env(safe-area-inset-top); - padding-right: env(safe-area-inset-right); - padding-left: env(safe-area-inset-left); - padding-bottom: env(safe-area-inset-bottom); + padding: env(safe-area-inset-top) env(safe-area-inset-right) + env(safe-area-inset-bottom) env(safe-area-inset-left); } @supports (block-size: 100dvh) { @@ -186,7 +184,7 @@ body { border-inline-end: var(--border); } -@media screen and (max-width: 32rem) { +@media (max-width: 32rem) { html:not(.miniplayer) body { background: var(--bg); } diff --git a/src/lib/Controls.svelte b/src/lib/Controls.svelte index 2150f1e..10b5f59 100644 --- a/src/lib/Controls.svelte +++ b/src/lib/Controls.svelte @@ -28,42 +28,45 @@ let menu = $state<{ } | null>(null); -
+
{#snippet favoriteButton(track: Song)} {/snippet} {#if settings.controlRating && player.track && !trackIsRadio} + {@const track = player.track}
{#if settings.controlFavorite} - {@render favoriteButton(player.track!)} + {@render favoriteButton(track)} {/if} -
+
{#each [1, 2, 3, 4, 5] as rating}
{/if} -
+
{#if menu} void; + toggle: () => void; }; type StatusRow = { @@ -94,7 +94,7 @@ let menu = $state<{ keyboard?: boolean; } | null>(null); let libraryElement = $state(); -let virtualizer: VirtualizerHandle | undefined = $state(); +let virtualizer = $state(); let headerHeight = $state(0); const treeEntryAnimations = new WeakMap(); @@ -135,7 +135,7 @@ function parseAlbumsPrefix( const [command, options] = raw.split("?", 2); const match = command.match(/^albums:([^:]+)(?::(.+))?$/i); if (!match) return null; - const typePart = match[1]; + const typePart = match[1].toLowerCase(); const arg = match[2]?.trim(); const searchParams = new URLSearchParams(options); const numberParam = (name: "size" | "offset", min: number, max: number) => { @@ -149,10 +149,10 @@ function parseAlbumsPrefix( offset: numberParam("offset", 0, Number.MAX_SAFE_INTEGER), }; const normalizedType = simpleAlbumTypes.find( - (t) => t.toLowerCase() === typePart.toLowerCase(), + (type) => type.toLowerCase() === typePart, ); if (normalizedType) return { type: normalizedType, ...params }; - if (typePart.toLowerCase() === "byyear" && arg) { + if (typePart === "byyear" && arg) { const yearMatch = arg.match(/^(\d{4})(?:-(\d{4}))?$/); if (yearMatch) { const from = parseInt(yearMatch[1], 10); @@ -160,7 +160,7 @@ function parseAlbumsPrefix( return { type: "byYear", fromYear: from, toYear: to, ...params }; } } - if (typePart.toLowerCase() === "bygenre" && arg) { + if (typePart === "bygenre" && arg) { return { type: "byGenre", genre: arg, ...params }; } return null; @@ -379,8 +379,7 @@ async function addNode(node: ItemRow, asNext: boolean) { function appendItem(rows: LibraryRow[], node: ItemRow) { rows.push(node); const state = nodeStates[node.key]; - if (!state?.expanded) return; - if (state.loading || !state.items) return; + if (!state?.expanded || state.loading || !state.items) return; const childType: TreeType = node.type === "artist" ? "album" : "song"; const occurrences = new Map(); @@ -406,21 +405,19 @@ function appendSection( key: string, label: string, items: ItemRow[], - section?: SectionName, + section: SectionName, ) { - const isExpanded = section ? expanded[section] : true; + const isExpanded = expanded[section]; rows.push({ kind: "section", key, label, expanded: isExpanded, - toggle: section - ? () => { - expanded[section] = !expanded[section]; - if (expanded[section]) animateTreeEntries([key]); - else if (lib.focusedId?.startsWith(`${key}/`)) lib.focusedId = key; - } - : undefined, + toggle: () => { + expanded[section] = !expanded[section]; + if (expanded[section]) animateTreeEntries([key]); + else if (lib.focusedId?.startsWith(`${key}/`)) lib.focusedId = key; + }, }); if (isExpanded) for (const item of items) appendItem(rows, item); } @@ -690,7 +687,11 @@ function showKeyboardMenu(row: ItemRow) { } function onKey(event: KeyboardEvent) { - if ((event.target as HTMLElement).matches("input, button, select, textarea")) + if ( + (event.target as HTMLElement).closest( + "input, button, select, textarea, [contenteditable]", + ) + ) return; const focusedIndex = navigableRows.findIndex( (row) => row.key === lib.focusedId, @@ -728,7 +729,7 @@ function onKey(event: KeyboardEvent) { : "nearest", ); } - } else if (code === "Enter") { + } else if (key === "Enter") { event.preventDefault(); const row = navigableRows[focusedIndex]; if (!row || (row.kind === "item" && isNodeBusy(row))) return; @@ -740,9 +741,9 @@ function onKey(event: KeyboardEvent) { animateActivation(button); animateButtonRejection(button); } else animateActivation(button); - if (row.kind === "section") row.toggle?.(); + if (row.kind === "section") row.toggle(); else void activateNode(row); - } else if (code === "KeyA") { + } else if (code === "KeyA" && !event.ctrlKey && !event.metaKey) { event.preventDefault(); const row = navigableRows[focusedIndex]; if (row?.kind === "item" && !isNodeBusy(row)) @@ -764,6 +765,7 @@ function onKey(event: KeyboardEvent) {