mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25: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.
108 lines
4.2 KiB
TypeScript
108 lines
4.2 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react';
|
|
import { emit } from '@tauri-apps/api/event';
|
|
import { useDragDrop } from '../contexts/DragDropContext';
|
|
import { usePlayerStore } from '../store/playerStore';
|
|
|
|
interface Args {
|
|
queueOpen: boolean;
|
|
miniQueueWrapRef: React.RefObject<HTMLDivElement | null>;
|
|
queueScrollRef: React.RefObject<HTMLDivElement | null>;
|
|
fallbackQueueLen: number;
|
|
}
|
|
|
|
/** Mini-player queue drag/drop wiring. Mirrors QueuePanel's pattern but with
|
|
* no external sources — psy-drop on the scroll viewport emits mini:reorder,
|
|
* psy-drop outside the wrap emits mini:remove. The reorder math collapses
|
|
* same-position drops and adjusts for index-shift after removing the source. */
|
|
export function useMiniQueueDrag({
|
|
queueOpen, miniQueueWrapRef, queueScrollRef, fallbackQueueLen,
|
|
}: Args) {
|
|
const { isDragging: isPsyDragging, startDrag, payload: psyPayload } = useDragDrop();
|
|
const psyDragFromIdxRef = useRef<number | null>(null);
|
|
const [dropTarget, setDropTarget] = useState<{ idx: number; before: boolean } | null>(null);
|
|
const dropTargetRef = useRef<{ idx: number; before: boolean } | null>(null);
|
|
|
|
const isReorderDrag = isPsyDragging && !!psyPayload && (() => {
|
|
try { return JSON.parse(psyPayload.data).type === 'queue_reorder'; } catch { return false; }
|
|
})();
|
|
|
|
useEffect(() => {
|
|
if (!isPsyDragging) {
|
|
dropTargetRef.current = null;
|
|
setDropTarget(null);
|
|
}
|
|
}, [isPsyDragging]);
|
|
|
|
// psy-drop inside the queue strip → mini:reorder.
|
|
// queueOpen must be in deps because the wrap (and thus queueScrollRef.current)
|
|
// only mounts when the queue is expanded — without it the ref is null on
|
|
// first run and the listener never attaches.
|
|
useEffect(() => {
|
|
if (!queueOpen) return;
|
|
const el = queueScrollRef.current;
|
|
if (!el) return;
|
|
const onPsyDrop = (e: Event) => {
|
|
const detail = (e as CustomEvent).detail;
|
|
if (!detail?.data) return;
|
|
let parsed: any = null;
|
|
try { parsed = JSON.parse(detail.data); } catch { return; }
|
|
const tgt = dropTargetRef.current;
|
|
dropTargetRef.current = null;
|
|
setDropTarget(null);
|
|
if (parsed.type !== 'queue_reorder') return;
|
|
const fromIdx = parsed.index as number;
|
|
psyDragFromIdxRef.current = null;
|
|
const queueLen = usePlayerStore.getState().queue.length || fallbackQueueLen;
|
|
const insertIdx = tgt
|
|
? (tgt.before ? tgt.idx : tgt.idx + 1)
|
|
: queueLen;
|
|
if (fromIdx === insertIdx || fromIdx === insertIdx - 1) return;
|
|
const adjusted = fromIdx < insertIdx ? insertIdx - 1 : insertIdx;
|
|
if (fromIdx === adjusted) return;
|
|
emit('mini:reorder', { from: fromIdx, to: adjusted }).catch(() => {});
|
|
};
|
|
el.addEventListener('psy-drop', onPsyDrop);
|
|
return () => el.removeEventListener('psy-drop', onPsyDrop);
|
|
}, [queueOpen, fallbackQueueLen, queueScrollRef]);
|
|
|
|
// Drop outside the mini queue strip → mini:remove (same UX as main QueuePanel).
|
|
useEffect(() => {
|
|
if (!queueOpen) return;
|
|
const onDocPsyDrop = (e: Event) => {
|
|
const d = (e as CustomEvent<{ data?: string; clientX?: number; clientY?: number }>).detail;
|
|
if (!d?.data) return;
|
|
const cx = d.clientX;
|
|
const cy = d.clientY;
|
|
if (typeof cx !== 'number' || typeof cy !== 'number') return;
|
|
let parsed: { type?: string; index?: number } | null = null;
|
|
try {
|
|
parsed = JSON.parse(d.data);
|
|
} catch {
|
|
return;
|
|
}
|
|
if (parsed?.type !== 'queue_reorder' || typeof parsed.index !== 'number') return;
|
|
const wrap = miniQueueWrapRef.current;
|
|
if (!wrap) return;
|
|
const r = wrap.getBoundingClientRect();
|
|
const inside =
|
|
cx >= r.left && cx <= r.right && cy >= r.top && cy <= r.bottom;
|
|
if (inside) return;
|
|
psyDragFromIdxRef.current = null;
|
|
dropTargetRef.current = null;
|
|
setDropTarget(null);
|
|
emit('mini:remove', { index: parsed.index }).catch(() => {});
|
|
};
|
|
document.addEventListener('psy-drop', onDocPsyDrop);
|
|
return () => document.removeEventListener('psy-drop', onDocPsyDrop);
|
|
}, [queueOpen, miniQueueWrapRef]);
|
|
|
|
return {
|
|
isReorderDrag,
|
|
psyDragFromIdxRef,
|
|
dropTarget,
|
|
setDropTarget,
|
|
dropTargetRef,
|
|
startDrag,
|
|
};
|
|
}
|