feat(player): scroll web queue to now-playing track on open (Android parity) — #1931
test-web / test (push) Failing after 38s
test-web / test (push) Failing after 38s
QueueList gains an `active` prop; when it flips true (drawer opens) or on mount
(now-playing panel) it centers the current row in view. Index/length are read
untracked so it positions once per open rather than following auto-advance,
matching the Android queue. QueueDrawer passes active={queueDrawerOpen} since
its aside is always mounted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,5 +35,9 @@
|
|||||||
transition-transform duration-200
|
transition-transform duration-200
|
||||||
{player.queueDrawerOpen ? 'translate-x-0' : 'translate-x-full'}"
|
{player.queueDrawerOpen ? 'translate-x-0' : 'translate-x-full'}"
|
||||||
>
|
>
|
||||||
<QueueList onClose={() => closeQueueDrawer()} bind:closeButtonRef={closeButton} />
|
<QueueList
|
||||||
|
onClose={() => closeQueueDrawer()}
|
||||||
|
active={player.queueDrawerOpen}
|
||||||
|
bind:closeButtonRef={closeButton}
|
||||||
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { untrack } from 'svelte';
|
||||||
import { X } from 'lucide-svelte';
|
import { X } from 'lucide-svelte';
|
||||||
import { player } from '$lib/player/store.svelte';
|
import { player } from '$lib/player/store.svelte';
|
||||||
import QueueTrackRow from './QueueTrackRow.svelte';
|
import QueueTrackRow from './QueueTrackRow.svelte';
|
||||||
@@ -8,12 +9,33 @@
|
|||||||
// now-playing route (visible at lg+ widths) omits it.
|
// now-playing route (visible at lg+ widths) omits it.
|
||||||
// closeButtonRef: bind:this hook so the drawer can focus the X for
|
// closeButtonRef: bind:this hook so the drawer can focus the X for
|
||||||
// keyboard users on open.
|
// keyboard users on open.
|
||||||
|
// active: true when the queue is on-screen (drawer open, or the always-
|
||||||
|
// visible now-playing panel). Flipping it true scrolls the now-playing
|
||||||
|
// row into view — parity with the Android queue, which opens positioned
|
||||||
|
// on the current track.
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
closeButtonRef?: HTMLButtonElement;
|
closeButtonRef?: HTMLButtonElement;
|
||||||
|
active?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
let { onClose, closeButtonRef = $bindable() }: Props = $props();
|
let { onClose, closeButtonRef = $bindable(), active = true }: Props = $props();
|
||||||
|
|
||||||
|
let scrollBody: HTMLElement | undefined = $state();
|
||||||
|
|
||||||
|
// When the queue becomes visible, center the now-playing row in view. The
|
||||||
|
// index/length are read untracked so this fires once per open (matching the
|
||||||
|
// Android queue's open-positioned behavior) rather than following the track
|
||||||
|
// as it auto-advances. Deferred a frame so the drawer's slide-in has settled.
|
||||||
|
$effect(() => {
|
||||||
|
if (!active || !scrollBody) return;
|
||||||
|
const body = scrollBody;
|
||||||
|
const index = untrack(() => player.index);
|
||||||
|
if (untrack(() => player.queue.length) === 0) return;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
(body.children[index] as HTMLElement | undefined)?.scrollIntoView({ block: 'center' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function totalDurationLabel(tracks: { duration_sec: number }[]): string {
|
function totalDurationLabel(tracks: { duration_sec: number }[]): string {
|
||||||
const totalSec = tracks.reduce((s, tr) => s + (tr.duration_sec ?? 0), 0);
|
const totalSec = tracks.reduce((s, tr) => s + (tr.duration_sec ?? 0), 0);
|
||||||
@@ -45,7 +67,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1 overflow-y-auto">
|
<div bind:this={scrollBody} class="flex-1 overflow-y-auto">
|
||||||
{#if player.queue.length === 0}
|
{#if player.queue.length === 0}
|
||||||
<p class="text-text-secondary text-center p-8">No tracks queued.</p>
|
<p class="text-text-secondary text-center p-8">No tracks queued.</p>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
Reference in New Issue
Block a user