global player pt. 10 fix displaying shuffled queue
This commit is contained in:
@@ -2,6 +2,12 @@
|
|||||||
import type { Track } from "$lib/player/types";
|
import type { Track } from "$lib/player/types";
|
||||||
|
|
||||||
export type GlobalPlayerNowPlaying = Track | null;
|
export type GlobalPlayerNowPlaying = Track | null;
|
||||||
|
|
||||||
|
export type QueueDisplayItem = {
|
||||||
|
track: Track;
|
||||||
|
queueIndex: number;
|
||||||
|
isCurrent: boolean;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -233,6 +239,70 @@
|
|||||||
return `${title} — ${artist}`;
|
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(
|
const canPrev = $derived(
|
||||||
snap.queue.length > 0 &&
|
snap.queue.length > 0 &&
|
||||||
snap.currentIndex != null &&
|
snap.currentIndex != null &&
|
||||||
@@ -417,7 +487,7 @@
|
|||||||
<p class="text-sm text-muted-foreground">Queue is empty.</p>
|
<p class="text-sm text-muted-foreground">Queue is empty.</p>
|
||||||
{:else}
|
{:else}
|
||||||
<ul class="max-h-64 overflow-auto rounded border">
|
<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
|
<li
|
||||||
class="flex items-center gap-2 border-b px-2 py-2 last:border-b-0"
|
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"
|
class="min-w-0 flex-1 truncate text-left text-sm hover:underline"
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
jumpToTrack(t.id);
|
jumpToTrack(item.track.id);
|
||||||
void syncAndAutoplay();
|
void syncAndAutoplay();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#if snap.currentIndex === i}
|
{#if item.isCurrent}
|
||||||
<span class="text-muted-foreground">▶ </span>
|
<span class="text-muted-foreground">▶ </span>
|
||||||
{/if}
|
{/if}
|
||||||
{titleForTrack(t)}
|
{titleForTrack(item.track)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="rounded border px-2 py-1 text-xs"
|
class="rounded border px-2 py-1 text-xs"
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => removeTrack(t.id)}
|
onclick={() => removeTrack(item.track.id)}
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
@@ -562,7 +632,7 @@
|
|||||||
<p class="text-sm text-muted-foreground">Queue is empty.</p>
|
<p class="text-sm text-muted-foreground">Queue is empty.</p>
|
||||||
{:else}
|
{:else}
|
||||||
<ul class="rounded border">
|
<ul class="rounded border">
|
||||||
{#each snap.queue as t, i (t.id)}
|
{#each queueDisplay as item (item.track.id)}
|
||||||
<li
|
<li
|
||||||
class="flex items-center gap-2 border-b px-2 py-2 last:border-b-0"
|
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"
|
class="min-w-0 flex-1 truncate text-left text-sm hover:underline"
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
jumpToTrack(t.id);
|
jumpToTrack(item.track.id);
|
||||||
void syncAndAutoplay();
|
void syncAndAutoplay();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#if snap.currentIndex === i}
|
{#if item.isCurrent}
|
||||||
<span class="text-muted-foreground">▶ </span>
|
<span class="text-muted-foreground">▶ </span>
|
||||||
{/if}
|
{/if}
|
||||||
{titleForTrack(t)}
|
{titleForTrack(item.track)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="rounded border px-2 py-1 text-xs"
|
class="rounded border px-2 py-1 text-xs"
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => removeTrack(t.id)}
|
onclick={() => removeTrack(item.track.id)}
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user