playlists + player: every service's list through one door with its names matched for real, and shuffle that readies exactly what it promises

This commit is contained in:
KeelrLeek committed 2026-08-24 18:10:34 -05:00
1 parent 30eb6fe025
commit 50414dae5c
16 files changed
+4157 -154

No files matched your search

+50 -9
View File
@@ -7,6 +7,7 @@
getArt,
getCatalogArt,
jumpToQueueIndex,
peekNextIndex,
streamSong,
discovery,
loadDiscovery,
@@ -101,11 +102,6 @@
// nothing, then a partial list.
const albumMode = $derived(!!player.catalogRef.albumId);
// Artist name for the Save action: album items carry "artist — album".
const artistName = $derived.by(() => {
const sub = currentItem?.sub ?? "";
return sub.includes(" — ") ? sub.split(" — ")[0].trim() : sub;
});
// Artist / album split for the separate, clickable identity lines.
// Per field, like the player bar's: the item's own artist/album win when
// present (the continuation fill writes them beside a bare-artist `sub`),
@@ -121,6 +117,16 @@
currentItem?.album ?? split[1] ?? "",
];
});
// The artist everything else in this panel is ABOUT — discovery, the
// chip, "More from", the in-library count, the Save action.
//
// It used to split `sub` on its own, which is only "artist — album" for
// album playback. A playlist item's `sub` is the PLAYLIST's name, so
// playing out of one loaded the whole Artist and About panes for an
// artist called "Modern Electro Ghetto Tech Mix" while the identity
// lines two rows above correctly read Harka. The item's own `artist`
// field is the answer, and `metaParts` already applies that rule.
const artistName = $derived(metaParts[0]);
const LOSSLESS = new Set(["FLAC", "WAV", "ALAC", "AIFF", "APE"]);
@@ -170,7 +176,9 @@
// tier, drop the album that's playing now, cap at 6.
const typeRank = (t) => (t === "album" ? 0 : t === "ep" ? 1 : 2);
const moreAlbums = $derived.by(() => {
const here = norm(currentItem?.sub?.split(" — ")[1] ?? "");
// The record playing now, by the same per-field rule — `sub` is the
// playlist's name, not "artist — album", whenever one is playing.
const here = norm(metaParts[1]);
return [...discovery.albums]
.filter((a) => norm(a.title) !== here)
.sort(
@@ -906,7 +914,26 @@
}
}
const upNext = $derived(player.queue[player.queueIndex + 1] ?? null);
// What actually plays next — shuffle included: peekNextIndex reads the
// predetermined order, so this card, the preloader and the advance all
// name the same track.
const upNextIndex = $derived(peekNextIndex());
const upNext = $derived(upNextIndex >= 0 ? (player.queue[upNextIndex] ?? null) : null);
// The NEXT track's own artwork — the card wore the CURRENT track's cover
// for a while, which read as "The Medic" having the Jets to Brazil sleeve.
let upNextArt = $state(null);
$effect(() => {
const item = upNext;
upNextArt = null;
if (!item) return;
let active = true;
if (item.art) {
getArt(item.art, 96).then((u) => active && (upNextArt = u)).catch(() => {});
} else if (item.artUrl) {
getCatalogArt(playerArtSource(item)).then((u) => active && (upNextArt = u)).catch(() => {});
}
return () => (active = false);
});
const fmtListeners = (n) => (n == null ? null : n.toLocaleString());
@@ -1249,8 +1276,12 @@
<div class="pane" style="--dir: {paneDir}px">
{#if pane === "queue"}
{#if upNext}
<button class="upnext" onclick={() => jumpToQueueIndex(player.queueIndex + 1)}>
{#if artUrl}<img src={artUrl} alt="" draggable="false" />{/if}
<button class="upnext" onclick={() => jumpToQueueIndex(upNextIndex)}>
{#if upNextArt}
<img src={upNextArt} alt="" draggable="false" />
{:else}
<span class="un-fallback"><Icon name="music" size={15} /></span>
{/if}
<span class="un-body">
<span class="un-lab">Up next</span>
<span class="un-title" title={upNext.title}>{upNext.title}</span>
@@ -2242,6 +2273,16 @@
border-color: color-mix(in srgb, var(--accent-hover) 45%, var(--border));
}
.upnext img { width: 34px; height: 34px; border-radius: 4px; flex: none; object-fit: cover; }
.un-fallback {
width: 34px;
height: 34px;
flex: none;
display: grid;
place-items: center;
border-radius: 6px;
background: var(--bg-highlight);
color: var(--text-tertiary);
}
.un-body { min-width: 0; flex: 1; display: block; }
.un-lab {
display: block;