mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
8ff630cb5c
* refactor(queue-panel): H4 — extract helpers + Save/LoadPlaylistModal Pure code-move: formatTime, formatQueueReplayGainParts, renderStars and the DurationMode type → utils/queuePanelHelpers.tsx; the two playlist modals → own files under components/queuePanel/. QueuePanel.tsx: 1256 → 1104 LOC. * refactor(queue-panel): H4 — extract QueueHeader Pure code-move: the title/count/duration/collapse-button header → its own component file. No prop or behaviour changes. QueuePanel.tsx: 1104 → 1004 LOC. * refactor(queue-panel): H4 — extract QueueCurrentTrack + QueueLufsTargetMenu The currently-playing track block (cover, info, replay-gain / LUFS badge with its target-listbox portal) moves into two own files. Pure code-move via prop plumbing. setLoudnessTargetLufs is typed as LoudnessLufsPreset throughout the new components. QueuePanel.tsx: 1004 → 812 LOC. * refactor(queue-panel): H4 — extract useQueuePanelDrag hook Moves the psy-drag wiring (hit-test registration, drop-inside dispatch for song/songs/album/queue_reorder payloads, drop-outside removal) into its own hook. Drops the dead isRadioDrag variable since the parsedData.type === 'radio' guard inside onPsyDrop already handles that case. QueuePanel.tsx: 812 → 715 LOC. * refactor(queue-panel): H4 — extract useQueueLufsTgtPopover hook Pulls the LUFS-target popover open-state, button/menu refs, fixed-position recompute on open/resize/scroll, and auto-close-when-RG-collapses out of QueuePanel. QueuePanel.tsx: 715 → 662 LOC. * refactor(queue-panel): H4 — extract QueueToolbar The toolbar-button switch (shuffle/save/load/share/clear/gapless/crossfade/ infinite) and the crossfade popover (with its close-on-outside-click effect) move into one component. crossfadeBtnRef / crossfadePopoverRef and showCrossfadePopover state are now component-local — QueuePanel no longer sees them. QueuePanel.tsx: 662 → 541 LOC. * refactor(queue-panel): H4 — extract QueueList The OverlayScrollArea + queue.map block (with track rows, lucky-mix dice overlay, and radio/auto-added section dividers) moves into its own component. PlayerState['contextMenu'] + PlayerState['playTrack'] are re-used for prop typing, the local StartDrag alias matches the DragDropContext signature. QueuePanel.tsx: 541 → 434 LOC. * refactor(queue-panel): H4 — extract QueueTabBar + useQueueAutoScroll, final cleanup QueueTabBar is the bottom queue/lyrics/info tab switcher. useQueueAutoScroll groups the three list-scroll effects (publish scrollTop reader, restore pending snapshot, scroll next track into view on advance). Drops the dead toggleQueue and replayGainMode selectors plus the now-unused Play, Radio, MicVocal, ListMusic, Info imports and OverlayScrollArea. QueuePanel.tsx: 434 → 383 LOC. Every new file under 400.
79 lines
2.5 KiB
TypeScript
79 lines
2.5 KiB
TypeScript
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
|
|
/** Manages the open/closed state, button + menu refs, and positioning of the
|
|
* LUFS-target listbox popover inside the queue panel's current-track header.
|
|
* The popover collapses automatically when the parent replay-gain expansion
|
|
* is toggled off. */
|
|
export function useQueueLufsTgtPopover(expandReplayGain: boolean) {
|
|
const [lufsTgtOpen, setLufsTgtOpen] = useState(false);
|
|
const [lufsTgtPopStyle, setLufsTgtPopStyle] = useState<React.CSSProperties>({});
|
|
const lufsTgtBtnRef = useRef<HTMLButtonElement>(null);
|
|
const lufsTgtMenuRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (!lufsTgtOpen) return;
|
|
const handle = (e: MouseEvent) => {
|
|
if (
|
|
lufsTgtBtnRef.current?.contains(e.target as Node) ||
|
|
lufsTgtMenuRef.current?.contains(e.target as Node)
|
|
) return;
|
|
setLufsTgtOpen(false);
|
|
};
|
|
document.addEventListener('mousedown', handle);
|
|
return () => document.removeEventListener('mousedown', handle);
|
|
}, [lufsTgtOpen]);
|
|
|
|
const updateLufsTgtPopStyle = () => {
|
|
if (!lufsTgtBtnRef.current) return;
|
|
const rect = lufsTgtBtnRef.current.getBoundingClientRect();
|
|
const MARGIN = 6;
|
|
const WIDTH = 160;
|
|
const MAX_H = 220;
|
|
const spaceBelow = window.innerHeight - rect.bottom - MARGIN;
|
|
const spaceAbove = rect.top - MARGIN;
|
|
const useAbove = spaceBelow < 120 && spaceAbove > spaceBelow;
|
|
const left = Math.min(
|
|
Math.max(rect.right - WIDTH, 8),
|
|
window.innerWidth - WIDTH - 8,
|
|
);
|
|
setLufsTgtPopStyle({
|
|
position: 'fixed',
|
|
left,
|
|
width: WIDTH,
|
|
...(useAbove
|
|
? { bottom: window.innerHeight - rect.top + MARGIN }
|
|
: { top: rect.bottom + MARGIN }),
|
|
maxHeight: Math.min(MAX_H, useAbove ? spaceAbove : spaceBelow),
|
|
zIndex: 99998,
|
|
});
|
|
};
|
|
|
|
useLayoutEffect(() => {
|
|
if (!lufsTgtOpen) return;
|
|
updateLufsTgtPopStyle();
|
|
}, [lufsTgtOpen]);
|
|
|
|
useEffect(() => {
|
|
if (!lufsTgtOpen) return;
|
|
const onResize = () => updateLufsTgtPopStyle();
|
|
window.addEventListener('resize', onResize);
|
|
window.addEventListener('scroll', onResize, true);
|
|
return () => {
|
|
window.removeEventListener('resize', onResize);
|
|
window.removeEventListener('scroll', onResize, true);
|
|
};
|
|
}, [lufsTgtOpen]);
|
|
|
|
useEffect(() => {
|
|
if (!expandReplayGain) setLufsTgtOpen(false);
|
|
}, [expandReplayGain]);
|
|
|
|
return {
|
|
lufsTgtOpen,
|
|
setLufsTgtOpen,
|
|
lufsTgtPopStyle,
|
|
lufsTgtBtnRef,
|
|
lufsTgtMenuRef,
|
|
};
|
|
}
|