feat(mini-player): persistent geometry, queue DnD + context menu, overlay scrollbar, live theme sync

This iteration fills in the pieces that make the mini player usable as a
daily standalone window:

- Window position persists to <app_config_dir>/mini_player_pos.json on
  every WindowEvent::Moved (throttled 250 ms); first launch lands in the
  bottom-right of the main window's monitor.
- Queue keeps a 260 px floor when expanded (~2 visible rows). The
  user-resized expanded height is restored on next toggle via
  localStorage so reopening doesn't snap back to the default 440 px.
- set_mini_player_always_on_top(true) now forces a false-then-true cycle
  so the WM re-evaluates the layer after a hide/show; the frontend also
  re-asserts the pin state on mount and on window focus, so the user no
  longer has to click the pin button twice for it to stick.
- Native scrollbar in the queue is hidden in favour of a JS-driven
  overlay thumb so items use the full width of the queue area.
- PsyDnD reorder works inside the mini queue: drag emits 'mini:reorder',
  the bridge calls reorderQueue on the source-of-truth store in main.
- Localized queue-item context menu (MiniContextMenu) with Play now /
  Remove from queue / Open album / Go to artist / Favorite / Song info.
  Cross-window actions forward to main via new bridge events
  (mini:reorder, mini:remove, mini:navigate, mini:song-info); a
  psy:navigate CustomEvent picked up by AppShell handles routing.
- Theme, font and language changes in main propagate live to the mini
  via a 'storage' event listener that re-hydrates the persisted Zustand
  stores and calls i18n.changeLanguage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-19 12:08:05 +02:00
parent 42ad24cce1
commit 2871db9a96
6 changed files with 608 additions and 26 deletions
+41 -3
View File
@@ -9190,17 +9190,47 @@ html.no-compositing .fsr-lyric-line.fsrl-active .fsr-lyric-word.active {
justify-content: flex-end;
}
.mini-queue {
.mini-queue-wrap {
grid-column: 1 / -1;
grid-row: 3;
overflow-y: auto;
overscroll-behavior: contain;
position: relative;
background: var(--bg-card);
border-radius: 8px;
overflow: hidden;
min-height: 0;
}
.mini-queue {
height: 100%;
overflow-y: auto;
overscroll-behavior: contain;
padding: 4px;
display: flex;
flex-direction: column;
gap: 1px;
box-sizing: border-box;
/* Hide native scrollbar — we render an overlay thumb beside it. */
scrollbar-width: none;
}
.mini-queue::-webkit-scrollbar {
display: none;
}
.mini-queue__thumb {
position: absolute;
top: 4px;
right: 3px;
width: 4px;
border-radius: 2px;
background: var(--text-muted);
opacity: 0;
transition: opacity 0.18s ease;
pointer-events: none;
will-change: transform;
}
.mini-queue-wrap:hover .mini-queue__thumb,
.mini-queue.is-scrolling ~ .mini-queue__thumb {
opacity: 0.55;
}
.mini-queue__empty {
@@ -9228,6 +9258,14 @@ html.no-compositing .fsr-lyric-line.fsrl-active .fsr-lyric-word.active {
background: var(--bg-hover);
}
.mini-queue__item--ctx {
background: var(--bg-hover);
}
.mini-queue-wrap--drop-active .mini-queue {
scroll-behavior: auto;
}
.mini-queue__item--current {
background: var(--accent-dim);
color: var(--accent);