global player pt. 10 fix displaying shuffled queue
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user