playlists + lyrics: setlists with pages of their own, and the words timed to the voice

This commit is contained in:
KeelrLeek committed 2026-08-17 18:05:31 -05:00
1 parent c4ff6269a3
commit df3d15553a
49 files changed
+14419 -599

No files matched your search

+40
View File
@@ -13,6 +13,7 @@
goToView,
openRecentAlbum,
getArt,
playlistCoverFor,
} from "../lib/state.svelte.js";
import * as api from "../lib/api.js";
import Icon from "./Icon.svelte";
@@ -145,6 +146,15 @@
$effect(() => {
if (!recentsStore.loaded) refreshRecents();
});
// Pinned playlists ride between the nav and the history — quick access
// to the lists the user chose to keep close, with their covers.
const pinnedPlaylists = $derived(
playlistStore.playlists.filter((p) => p.pinned).slice(0, 6),
);
$effect(() => {
for (const pl of pinnedPlaylists) void playlistCoverFor(pl);
});
</script>
<nav class="sidebar" class:collapsed={ui.sidebarCollapsed}>
@@ -226,6 +236,28 @@
</button>
{/each}
{#each pinnedPlaylists as pl (pl.id)}
<button
class="rec"
class:on={ui.view === "playlist" && ui.playlistId === pl.id}
title="{pl.name} — pinned playlist"
onclick={() => goToView("playlist", { playlistId: pl.id, label: pl.name })}
>
{#if playlistStore.covers[pl.id]}
<img src={playlistStore.covers[pl.id]} alt="" draggable="false" />
{:else}
<span class="art-placeholder"><Icon name="playlist" size={15} /></span>
{/if}
<span class="tx">
<span class="n">
<span class="pin-mini" aria-hidden="true"><Icon name="pin" size={10} /></span>
{pl.name}
</span>
<span class="s">{pl.track_count} {pl.track_count === 1 ? "track" : "tracks"}</span>
</span>
</button>
{/each}
<div class="rule"></div>
{#each recentRows as row (row.key)}
@@ -698,6 +730,14 @@
opacity: 0;
}
/* Pinned playlist rows share the recents' whole construction; the only
addition is the tiny pin riding the name, which fades with the text. */
.pin-mini {
display: inline-flex;
color: var(--text-muted);
flex: none;
}
.eq {
display: inline-flex;
align-items: flex-end;