mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
383bbbd75f
* refactor(mini-player): H6 — extract helpers + constants
Pure code-move: window-size constants, localStorage keys + read helpers,
toMini track shape, initialSnapshot, and fmt(seconds) → utils/miniPlayerHelpers.ts.
MiniPlayer.tsx: 820 → 745 LOC.
* refactor(mini-player): H6 — extract MiniTitlebar + MiniMeta + MiniControls
Three small visual subcomponents move into components/miniPlayer/.
MiniPlayer drops the now-unused Pin/PinOff/Maximize2/X/Play/Pause/
SkipBack/SkipForward lucide icons and the CachedImage import.
MiniPlayer.tsx: 745 → 665 LOC.
* refactor(mini-player): H6 — extract MiniToolbar + useMiniVolumePopover
The whole toolbar (volume button + portaled popover, shuffle, gapless/
crossfade/infinite, queue toggle) moves into MiniToolbar.tsx. The volume
popover open-state + ref/style positioning + outside-click/Escape close
move into the useMiniVolumePopover hook.
MiniPlayer.tsx: 665 → 492 LOC.
* refactor(mini-player): H6 — extract MiniQueue + useMiniQueueDrag
The OverlayScrollArea + queue.map block moves into MiniQueue.tsx. The
PsyDnD wiring (drop-inside emits mini:reorder, drop-outside emits
mini:remove, reorder math collapsing same-position + adjusting for shift)
moves into the useMiniQueueDrag hook.
MiniPlayer.tsx: 492 → 346 LOC.
* refactor(mini-player): H6 — extract useMiniSync + useMiniWindowSetup + useMiniKeyboardShortcuts
Three more hooks pull the remaining side-effect islands out of MiniPlayer:
- useMiniSync owns mini:ready emit on mount + focus, plus the
mini:sync / audio:progress / audio:ended listeners. The hidden-window
visibility ref that gates progress now lives inside the hook.
- useMiniWindowSetup bundles three small window-bound effects:
Linux WebKitGTK smooth-scroll, the cold-start expanded-size restore
when queueOpen=true, and always-on-top reapply on mount + focus.
- useMiniKeyboardShortcuts moves the keyboard-shortcut bridge wiring.
MiniPlayer.tsx: 346 → 218 LOC.
75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
|
|
/** Manages the open-state, refs, and fixed positioning of the portaled
|
|
* mini-player volume popover. The trigger sits inside a short window, so the
|
|
* popover flips above when there's not enough room below. Closes on outside
|
|
* click or Escape. */
|
|
export function useMiniVolumePopover() {
|
|
const [volumeOpen, setVolumeOpen] = useState(false);
|
|
const [volumePopStyle, setVolumePopStyle] = useState<React.CSSProperties>({});
|
|
const volumeBtnRef = useRef<HTMLButtonElement>(null);
|
|
const volumePopRef = useRef<HTMLDivElement>(null);
|
|
|
|
const updateVolumePopStyle = () => {
|
|
if (!volumeBtnRef.current) return;
|
|
const rect = volumeBtnRef.current.getBoundingClientRect();
|
|
const MARGIN = 6;
|
|
const POP_W = 40;
|
|
const POP_H = 150;
|
|
const spaceBelow = window.innerHeight - rect.bottom - MARGIN;
|
|
const spaceAbove = rect.top - MARGIN;
|
|
const useAbove = spaceBelow < POP_H && spaceAbove > spaceBelow;
|
|
const left = Math.min(
|
|
Math.max(rect.left + rect.width / 2 - POP_W / 2, 6),
|
|
window.innerWidth - POP_W - 6,
|
|
);
|
|
setVolumePopStyle({
|
|
position: 'fixed',
|
|
left,
|
|
width: POP_W,
|
|
...(useAbove
|
|
? { bottom: window.innerHeight - rect.top + MARGIN }
|
|
: { top: rect.bottom + MARGIN }),
|
|
zIndex: 99998,
|
|
});
|
|
};
|
|
|
|
useLayoutEffect(() => {
|
|
if (!volumeOpen) return;
|
|
updateVolumePopStyle();
|
|
}, [volumeOpen]);
|
|
|
|
useEffect(() => {
|
|
if (!volumeOpen) return;
|
|
const onReposition = () => updateVolumePopStyle();
|
|
window.addEventListener('resize', onReposition);
|
|
window.addEventListener('scroll', onReposition, true);
|
|
return () => {
|
|
window.removeEventListener('resize', onReposition);
|
|
window.removeEventListener('scroll', onReposition, true);
|
|
};
|
|
}, [volumeOpen]);
|
|
|
|
useEffect(() => {
|
|
if (!volumeOpen) return;
|
|
const onDown = (e: MouseEvent) => {
|
|
const target = e.target as Node;
|
|
if (
|
|
!volumeBtnRef.current?.contains(target) &&
|
|
!volumePopRef.current?.contains(target)
|
|
) setVolumeOpen(false);
|
|
};
|
|
const onKey = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') setVolumeOpen(false);
|
|
};
|
|
document.addEventListener('mousedown', onDown);
|
|
document.addEventListener('keydown', onKey);
|
|
return () => {
|
|
document.removeEventListener('mousedown', onDown);
|
|
document.removeEventListener('keydown', onKey);
|
|
};
|
|
}, [volumeOpen]);
|
|
|
|
return { volumeOpen, setVolumeOpen, volumePopStyle, volumeBtnRef, volumePopRef };
|
|
}
|