global player pt. 10 fix displaying shuffled queue

This commit is contained in:
2026-02-06 02:17:38 -08:00
parent 28aed104f6
commit c5c402a6b4

View File

@@ -2,6 +2,12 @@
import type { Track } from "$lib/player/types";
export type GlobalPlayerNowPlaying = Track | null;
export type QueueDisplayItem = {
track: Track;
queueIndex: number;
isCurrent: boolean;
};
</script>
<script lang="ts">
@@ -233,6 +239,70 @@
return `${title}${artist}`;
}
function computeQueueDisplay(): QueueDisplayItem[] {
const q = snap.queue;
if (q.length === 0) return [];
const current = snap.currentIndex;
// Non-shuffle: display stable queue order.
if (!snap.shuffleEnabled) {
return q.map((track, queueIndex) => ({
track,
queueIndex,
isCurrent: current === queueIndex,
}));
}
// Shuffle: display play order (history up to cursor + future order).
// This prevents the UI's "current position" from appearing to jump around.
//
// Invariants we try to preserve:
// - Past (history[0..cursor]) stays in the same order.
// - Current item is history[cursor] when available, otherwise currentIndex.
// - Future comes from `order` (already excludes visited indices).
const out: QueueDisplayItem[] = [];
const seen = new Set<number>();
const pushIndex = (queueIndex: number) => {
if (!Number.isInteger(queueIndex)) return;
if (queueIndex < 0 || queueIndex >= q.length) return;
if (seen.has(queueIndex)) return;
seen.add(queueIndex);
out.push({
track: q[queueIndex]!,
queueIndex,
isCurrent: current === queueIndex,
});
};
// Past + current
const hist = snap.history;
const cursor = snap.cursor;
for (let i = 0; i < hist.length; i += 1) {
// Only show "past" up through current cursor; the rest would be "future via history"
// and is confusing to duplicate (future is represented by `order`).
if (i > cursor) break;
pushIndex(hist[i]!);
}
// If history is empty or cursor doesn't match currentIndex, ensure currentIndex is shown.
if (current != null) pushIndex(current);
// Future
for (const i of snap.order) pushIndex(i);
// Any remaining items (not yet visited nor scheduled) go at the end (rare but possible after edits).
for (let i = 0; i < q.length; i += 1) pushIndex(i);
return out;
}
const queueDisplay = $derived(computeQueueDisplay());
const canPrev = $derived(
snap.queue.length > 0 &&
snap.currentIndex != null &&
@@ -417,7 +487,7 @@
<p class="text-sm text-muted-foreground">Queue is empty.</p>
{:else}
<ul class="max-h-64 overflow-auto rounded border">
{#each snap.queue as t, i (t.id)}
{#each queueDisplay as item (item.track.id)}
<li
class="flex items-center gap-2 border-b px-2 py-2 last:border-b-0"
>
@@ -425,20 +495,20 @@
class="min-w-0 flex-1 truncate text-left text-sm hover:underline"
type="button"
onclick={() => {
jumpToTrack(t.id);
jumpToTrack(item.track.id);
void syncAndAutoplay();
}}
>
{#if snap.currentIndex === i}
{#if item.isCurrent}
<span class="text-muted-foreground"></span>
{/if}
{titleForTrack(t)}
{titleForTrack(item.track)}
</button>
<button
class="rounded border px-2 py-1 text-xs"
type="button"
onclick={() => removeTrack(t.id)}
onclick={() => removeTrack(item.track.id)}
>
Remove
</button>
@@ -562,7 +632,7 @@
<p class="text-sm text-muted-foreground">Queue is empty.</p>
{:else}
<ul class="rounded border">
{#each snap.queue as t, i (t.id)}
{#each queueDisplay as item (item.track.id)}
<li
class="flex items-center gap-2 border-b px-2 py-2 last:border-b-0"
>
@@ -570,20 +640,20 @@
class="min-w-0 flex-1 truncate text-left text-sm hover:underline"
type="button"
onclick={() => {
jumpToTrack(t.id);
jumpToTrack(item.track.id);
void syncAndAutoplay();
}}
>
{#if snap.currentIndex === i}
{#if item.isCurrent}
<span class="text-muted-foreground"></span>
{/if}
{titleForTrack(t)}
{titleForTrack(item.track)}
</button>
<button
class="rounded border px-2 py-1 text-xs"
type="button"
onclick={() => removeTrack(t.id)}
onclick={() => removeTrack(item.track.id)}
>
Remove
</button>