refactor(mini-player): H6 — split MiniPlayer.tsx 820 → 218 LOC across 11 files (#669)

* 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.
This commit is contained in:
Frank Stellmacher
2026-05-13 23:07:45 +02:00
committed by GitHub
parent 463d3e0c5b
commit 383bbbd75f
12 changed files with 926 additions and 689 deletions
+82
View File
@@ -0,0 +1,82 @@
import { usePlayerStore } from '../store/playerStore';
import type { MiniSyncPayload, MiniTrackInfo } from './miniPlayerBridge';
export const COLLAPSED_SIZE = { w: 340, h: 260 };
export const EXPANDED_SIZE = { w: 340, h: 500 };
// Minimum window dimensions per state. When the queue is open the floor must
// keep at least two queue rows visible; a stricter min would let the user
// collapse the queue area to nothing while it's still toggled on.
export const COLLAPSED_MIN = { w: 320, h: 240 };
export const EXPANDED_MIN = { w: 320, h: 340 };
// Persist the expanded-window height so reopening the queue restores the
// user's preferred size instead of snapping back to EXPANDED_SIZE.h.
export const EXPANDED_H_KEY = 'psysonic_mini_expanded_h';
export function readStoredExpandedHeight(): number {
try {
const raw = localStorage.getItem(EXPANDED_H_KEY);
if (raw) {
const n = parseInt(raw, 10);
if (Number.isFinite(n) && n >= EXPANDED_MIN.h) return n;
}
} catch {}
return EXPANDED_SIZE.h;
}
// Persist whether the queue panel was open so the next launch restores
// the same state. Same scope as the height: localStorage of the mini
// webview (shared across mini sessions, separate from the main store).
export const QUEUE_OPEN_KEY = 'psysonic_mini_queue_open';
export function readQueueOpen(): boolean {
try { return localStorage.getItem(QUEUE_OPEN_KEY) === '1'; } catch { return false; }
}
export function toMini(t: any): MiniTrackInfo {
return {
id: t.id,
title: t.title,
artist: t.artist,
album: t.album,
albumId: t.albumId,
artistId: t.artistId,
coverArt: t.coverArt,
duration: t.duration,
starred: !!t.starred,
year: t.year,
};
}
/**
* Hydrate from the persisted playerStore so initial paint shows real content
* instead of "—" while we wait for the mini:sync event from the main window.
* The persisted state covers the cold-start window (webview boot + bundle).
*/
export function initialSnapshot(): MiniSyncPayload {
try {
const s = usePlayerStore.getState();
return {
track: s.currentTrack ? toMini(s.currentTrack) : null,
queue: (s.queue ?? []).map(toMini),
queueIndex: s.queueIndex ?? 0,
isPlaying: s.isPlaying,
volume: s.volume ?? 1,
gaplessEnabled: false,
crossfadeEnabled: false,
infiniteQueueEnabled: false,
isMobile: false,
};
} catch {
return {
track: null, queue: [], queueIndex: 0, isPlaying: false,
volume: 1, gaplessEnabled: false, crossfadeEnabled: false,
infiniteQueueEnabled: false, isMobile: false,
};
}
}
export function fmt(seconds: number): string {
if (!isFinite(seconds) || seconds < 0) seconds = 0;
const m = Math.floor(seconds / 60);
const s = Math.floor(seconds % 60);
return `${m}:${s.toString().padStart(2, '0')}`;
}