mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
feat(player): restore queue list scroll on queue undo/redo
Store the main queue panel viewport scrollTop in undo snapshots and reapply it after undo/redo so Ctrl+Z / Ctrl+Shift+Z restores scroll position alongside queue state.
This commit is contained in:
@@ -1,6 +1,12 @@
|
|||||||
import React, { useState, useRef, useMemo, useEffect, useLayoutEffect } from 'react';
|
import React, { useState, useRef, useMemo, useEffect, useLayoutEffect } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { Track, usePlayerStore, songToTrack } from '../store/playerStore';
|
import {
|
||||||
|
Track,
|
||||||
|
usePlayerStore,
|
||||||
|
songToTrack,
|
||||||
|
registerQueueListScrollTopReader,
|
||||||
|
consumePendingQueueListScrollTop,
|
||||||
|
} from '../store/playerStore';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '../store/orbitStore';
|
||||||
import OrbitGuestQueue from './OrbitGuestQueue';
|
import OrbitGuestQueue from './OrbitGuestQueue';
|
||||||
import OrbitQueueHead from './OrbitQueueHead';
|
import OrbitQueueHead from './OrbitQueueHead';
|
||||||
@@ -414,6 +420,21 @@ function QueuePanelHostOrSolo() {
|
|||||||
|
|
||||||
const queueListRef = useRef<HTMLDivElement>(null);
|
const queueListRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
registerQueueListScrollTopReader(() => queueListRef.current?.scrollTop);
|
||||||
|
return () => registerQueueListScrollTopReader(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const top = consumePendingQueueListScrollTop();
|
||||||
|
if (top === undefined) return;
|
||||||
|
const el = queueListRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
suppressNextAutoScrollRef.current = true;
|
||||||
|
el.scrollTop = top;
|
||||||
|
el.dispatchEvent(new Event('scroll', { bubbles: false }));
|
||||||
|
}, [queue, queueIndex, currentTrack?.id]);
|
||||||
|
|
||||||
const asideRef = useRef<HTMLElement>(null);
|
const asideRef = useRef<HTMLElement>(null);
|
||||||
|
|
||||||
const { isDragging: isPsyDragging, startDrag, payload: psyPayload } = useDragDrop();
|
const { isDragging: isPsyDragging, startDrag, payload: psyPayload } = useDragDrop();
|
||||||
|
|||||||
@@ -362,15 +362,38 @@ type QueueUndoSnapshot = {
|
|||||||
currentTime?: number;
|
currentTime?: number;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
isPlaying?: boolean;
|
isPlaying?: boolean;
|
||||||
|
/** Main queue panel list `scrollTop` when the snapshot was taken. */
|
||||||
|
queueListScrollTop?: number;
|
||||||
};
|
};
|
||||||
const queueUndoStack: QueueUndoSnapshot[] = [];
|
const queueUndoStack: QueueUndoSnapshot[] = [];
|
||||||
const queueRedoStack: QueueUndoSnapshot[] = [];
|
const queueRedoStack: QueueUndoSnapshot[] = [];
|
||||||
|
|
||||||
|
/** QueuePanel registers a reader so undo snapshots capture list scroll position. */
|
||||||
|
let queueListScrollTopReader: (() => number | undefined) | null = null;
|
||||||
|
|
||||||
|
export function registerQueueListScrollTopReader(reader: (() => number | undefined) | null): void {
|
||||||
|
queueListScrollTopReader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readQueueListScrollTopForUndo(): number | undefined {
|
||||||
|
return queueListScrollTopReader?.() ?? undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set in applyQueueHistorySnapshot; QueuePanel consumes in useLayoutEffect after commit. */
|
||||||
|
let pendingQueueListScrollTop: number | undefined;
|
||||||
|
|
||||||
|
export function consumePendingQueueListScrollTop(): number | undefined {
|
||||||
|
const v = pendingQueueListScrollTop;
|
||||||
|
pendingQueueListScrollTop = undefined;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
function shallowCloneQueueTracks(queue: Track[]): Track[] {
|
function shallowCloneQueueTracks(queue: Track[]): Track[] {
|
||||||
return queue.map(t => ({ ...t }));
|
return queue.map(t => ({ ...t }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function queueUndoSnapshotFromState(s: PlayerState): QueueUndoSnapshot {
|
function queueUndoSnapshotFromState(s: PlayerState): QueueUndoSnapshot {
|
||||||
|
const scrollTop = readQueueListScrollTopForUndo();
|
||||||
return {
|
return {
|
||||||
queue: shallowCloneQueueTracks(s.queue),
|
queue: shallowCloneQueueTracks(s.queue),
|
||||||
queueIndex: s.queueIndex,
|
queueIndex: s.queueIndex,
|
||||||
@@ -378,6 +401,7 @@ function queueUndoSnapshotFromState(s: PlayerState): QueueUndoSnapshot {
|
|||||||
currentTime: s.currentTime,
|
currentTime: s.currentTime,
|
||||||
progress: s.progress,
|
progress: s.progress,
|
||||||
isPlaying: s.isPlaying,
|
isPlaying: s.isPlaying,
|
||||||
|
...(scrollTop !== undefined ? { queueListScrollTop: scrollTop } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1926,6 +1950,9 @@ export const usePlayerStore = create<PlayerState>()(
|
|||||||
invoke('audio_stop').catch(console.error);
|
invoke('audio_stop').catch(console.error);
|
||||||
isAudioPaused = false;
|
isAudioPaused = false;
|
||||||
syncQueueToServer(nextQueue, null, 0);
|
syncQueueToServer(nextQueue, null, 0);
|
||||||
|
if (typeof snap.queueListScrollTop === 'number' && Number.isFinite(snap.queueListScrollTop)) {
|
||||||
|
pendingQueueListScrollTop = Math.max(0, snap.queueListScrollTop);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1946,6 +1973,9 @@ export const usePlayerStore = create<PlayerState>()(
|
|||||||
wantPlaying: playingRestore,
|
wantPlaying: playingRestore,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (typeof snap.queueListScrollTop === 'number' && Number.isFinite(snap.queueListScrollTop)) {
|
||||||
|
pendingQueueListScrollTop = Math.max(0, snap.queueListScrollTop);
|
||||||
|
}
|
||||||
syncQueueToServer(nextQueue, nextTrack, tRestore);
|
syncQueueToServer(nextQueue, nextTrack, tRestore);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user