mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
perf(queue): virtualize the queue list (#857)
* perf(queue): virtualize the queue list Render the QueuePanel queue through @tanstack/react-virtual so the DOM stays O(visible rows) instead of O(queue length) — a 10k+ Artist Radio queue no longer creates tens of thousands of DOM nodes. Row logic (reorder DnD, radio/ auto dividers, lucky-mix loader, context menu) is unchanged, just wrapped in the virtual rows. Auto-scroll to the next track moves out of useQueueAutoScroll (which relied on every row being in the DOM) to a virtualizer scroll plus an exact scrollIntoView on the real target row. Phase 0 of the queue thin-state plan; no store/persist changes. * docs(changelog): virtualized queue list (#857)
This commit is contained in:
committed by
GitHub
parent
10c3d9a3ce
commit
06213cb5d8
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useLayoutEffect } from 'react';
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
import { registerQueueListScrollTopReader, consumePendingQueueListScrollTop } from '../store/queueUndo';
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
|
||||
@@ -6,16 +6,16 @@ interface Args {
|
||||
queue: Track[];
|
||||
queueIndex: number;
|
||||
currentTrack: Track | null;
|
||||
activeTab: string;
|
||||
queueListRef: React.RefObject<HTMLDivElement | null>;
|
||||
suppressNextAutoScrollRef: React.MutableRefObject<boolean>;
|
||||
}
|
||||
|
||||
/** Queue auto-scroll: keeps the next track in view as playback progresses,
|
||||
* publishes the list's scrollTop to the undo store, and restores any pending
|
||||
* scrollTop snapshot (set when an undo restores a prior queue state). */
|
||||
/** Queue scroll-position bridge for undo: publishes the list's scrollTop to the
|
||||
* undo store and restores any pending scrollTop snapshot (set when an undo
|
||||
* restores a prior queue state). The "scroll the next track into view" path
|
||||
* lives in `QueueList` now that the list is virtualized (scrollToIndex). */
|
||||
export function useQueueAutoScroll({
|
||||
queue, queueIndex, currentTrack, activeTab, queueListRef, suppressNextAutoScrollRef,
|
||||
queue, queueIndex, currentTrack, queueListRef, suppressNextAutoScrollRef,
|
||||
}: Args) {
|
||||
useLayoutEffect(() => {
|
||||
registerQueueListScrollTopReader(() => queueListRef.current?.scrollTop);
|
||||
@@ -31,21 +31,4 @@ export function useQueueAutoScroll({
|
||||
el.scrollTop = top;
|
||||
el.dispatchEvent(new Event('scroll', { bubbles: false }));
|
||||
}, [queue, queueIndex, currentTrack?.id, queueListRef, suppressNextAutoScrollRef]);
|
||||
|
||||
useEffect(function queueAutoScroll() {
|
||||
if (suppressNextAutoScrollRef.current) {
|
||||
suppressNextAutoScrollRef.current = false;
|
||||
return;
|
||||
}
|
||||
if (!queueListRef.current || queueIndex < 0) return;
|
||||
if (activeTab !== 'queue') return;
|
||||
const songs = queueListRef.current!.querySelectorAll<HTMLElement>('[data-queue-idx]');
|
||||
const nextSong = songs[queueIndex + 1];
|
||||
if (!nextSong) return;
|
||||
nextSong.scrollIntoView({ block: 'start', behavior: 'instant' });
|
||||
requestAnimationFrame(() => {
|
||||
queueListRef.current?.dispatchEvent(new Event('scroll', { bubbles: false }));
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentTrack, activeTab]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user