refactor(miniPlayer): co-locate mini player feature into features/miniPlayer

This commit is contained in:
Psychotoxical
2026-06-29 23:22:00 +02:00
parent 6dd0d4a130
commit e6269c7b85
21 changed files with 59 additions and 51 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ import { initPinnedOfflineSync } from '../utils/offline/pinnedOfflineSync';
import { initResumeIncompleteOfflinePins, scheduleResumeIncompleteOfflinePins } from '../utils/offline/resumeIncompleteOfflinePins';
import { runLegacyOfflineFileMigration } from '../utils/migrations/legacyOfflineFileMigration';
import { reconcileLibraryTierForServer } from '../utils/offline/libraryTierReconcile';
import { initMiniPlayerBridgeOnMain } from '../utils/miniPlayerBridge';
import { initMiniPlayerBridgeOnMain } from '@/features/miniPlayer';
import { runAdvancedModeMigration } from '../utils/migrations/advancedModeMigration';
import { bootstrapAllIndexedServers } from '../utils/library/librarySession';
import { hydrateQueueFromIndex } from '../utils/library/queueRestore';
+1 -1
View File
@@ -28,7 +28,7 @@ vi.mock('../utils/perf/perfFlags', () => ({
vi.mock('../i18n', () => ({
default: { changeLanguage: vi.fn() },
}));
vi.mock('../components/MiniPlayer', () => ({ default: () => <div data-testid="mini-player" /> }));
vi.mock('@/features/miniPlayer', () => ({ default: () => <div data-testid="mini-player" /> }));
vi.mock('../components/GlobalConfirmModal', () => ({ default: () => <div data-testid="confirm-modal" /> }));
vi.mock('@/ui/TooltipPortal', () => ({ default: () => <div data-testid="tooltip-portal" /> }));
vi.mock('../components/FpsOverlay', () => ({ default: () => <div data-testid="fps-overlay" /> }));
+1 -1
View File
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { DragDropProvider } from '../contexts/DragDropContext';
import MiniPlayer from '../components/MiniPlayer';
import MiniPlayer from '@/features/miniPlayer';
import GlobalConfirmModal from '../components/GlobalConfirmModal';
import TooltipPortal from '@/ui/TooltipPortal';
import FpsOverlay from '../components/FpsOverlay';
@@ -1,10 +1,10 @@
import { star, unstar } from '../api/subsonicStarRating';
import { star, unstar } from '@/api/subsonicStarRating';
import { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { emit } from '@tauri-apps/api/event';
import { useTranslation } from 'react-i18next';
import { Play, Trash2, Disc3, User, Heart, Info } from 'lucide-react';
import type { MiniTrackInfo } from '../utils/miniPlayerBridge';
import type { MiniTrackInfo } from '@/features/miniPlayer/utils/miniPlayerBridge';
interface Props {
x: number;
@@ -1,6 +1,6 @@
import { Pause, Play, SkipBack, SkipForward } from 'lucide-react';
import { formatTrackTime } from '../../utils/format/formatDuration';
import type { MiniControlAction } from '../../utils/miniPlayerBridge';
import { formatTrackTime } from '@/utils/format/formatDuration';
import type { MiniControlAction } from '@/features/miniPlayer/utils/miniPlayerBridge';
interface Props {
isPlaying: boolean;
@@ -1,7 +1,7 @@
import { emit } from '@tauri-apps/api/event';
import CachedImage from '@/ui/CachedImage';
import { OpenArtistRefInline } from '../OpenArtistRefInline';
import type { MiniTrackInfo } from '../../utils/miniPlayerBridge';
import { OpenArtistRefInline } from '@/components/OpenArtistRefInline';
import type { MiniTrackInfo } from '@/features/miniPlayer/utils/miniPlayerBridge';
interface Props {
track: MiniTrackInfo | null;
@@ -1,29 +1,29 @@
import { usePlaybackCoverArt } from '../hooks/usePlaybackCoverArt';
import { usePlaybackTrackCoverRef } from '../cover/useLibraryCoverRef';
import { usePlaybackCoverArt } from '@/hooks/usePlaybackCoverArt';
import { usePlaybackTrackCoverRef } from '@/cover/useLibraryCoverRef';
import { useEffect, useRef, useState } from 'react';
import { emit } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api/core';
import { useTranslation } from 'react-i18next';
import { usePlayerStore } from '../store/playerStore';
import { registerQueueDragHitTest } from '../contexts/DragDropContext';
import MiniContextMenu from './MiniContextMenu';
import type { MiniSyncPayload, MiniControlAction, MiniTrackInfo } from '../utils/miniPlayerBridge';
import { usePlayerStore } from '@/store/playerStore';
import { registerQueueDragHitTest } from '@/contexts/DragDropContext';
import MiniContextMenu from '@/features/miniPlayer/components/MiniContextMenu';
import type { MiniSyncPayload, MiniControlAction, MiniTrackInfo } from '@/features/miniPlayer/utils/miniPlayerBridge';
import {
COLLAPSED_SIZE, EXPANDED_SIZE, COLLAPSED_MIN, EXPANDED_MIN,
EXPANDED_H_KEY, QUEUE_OPEN_KEY,
readStoredExpandedHeight, readQueueOpen, initialSnapshot,
} from '../utils/componentHelpers/miniPlayerHelpers';
import { MiniTitlebar } from './miniPlayer/MiniTitlebar';
import { MiniMeta } from './miniPlayer/MiniMeta';
import { MiniControls } from './miniPlayer/MiniControls';
import { MiniToolbar } from './miniPlayer/MiniToolbar';
import { MiniQueue } from './miniPlayer/MiniQueue';
import { useMiniVolumePopover } from '../hooks/useMiniVolumePopover';
import { useMiniCrossfadePopover } from '../hooks/useMiniCrossfadePopover';
import { useMiniQueueDrag } from '../hooks/useMiniQueueDrag';
import { useMiniSync } from '../hooks/useMiniSync';
import { useMiniWindowSetup } from '../hooks/useMiniWindowSetup';
import { useMiniKeyboardShortcuts } from '../hooks/useMiniKeyboardShortcuts';
} from '@/features/miniPlayer/utils/miniPlayerHelpers';
import { MiniTitlebar } from '@/features/miniPlayer/components/MiniTitlebar';
import { MiniMeta } from '@/features/miniPlayer/components/MiniMeta';
import { MiniControls } from '@/features/miniPlayer/components/MiniControls';
import { MiniToolbar } from '@/features/miniPlayer/components/MiniToolbar';
import { MiniQueue } from '@/features/miniPlayer/components/MiniQueue';
import { useMiniVolumePopover } from '@/features/miniPlayer/hooks/useMiniVolumePopover';
import { useMiniCrossfadePopover } from '@/features/miniPlayer/hooks/useMiniCrossfadePopover';
import { useMiniQueueDrag } from '@/features/miniPlayer/hooks/useMiniQueueDrag';
import { useMiniSync } from '@/features/miniPlayer/hooks/useMiniSync';
import { useMiniWindowSetup } from '@/features/miniPlayer/hooks/useMiniWindowSetup';
import { useMiniKeyboardShortcuts } from '@/features/miniPlayer/hooks/useMiniKeyboardShortcuts';
export default function MiniPlayer() {
const { t } = useTranslation();
@@ -2,7 +2,7 @@ import React from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';
import type { TFunction } from 'i18next';
import OverlayScrollArea from '@/ui/OverlayScrollArea';
import type { MiniSyncPayload, MiniTrackInfo } from '../../utils/miniPlayerBridge';
import type { MiniSyncPayload, MiniTrackInfo } from '@/features/miniPlayer/utils/miniPlayerBridge';
// Stable initial rect so the virtualizer never re-initializes on re-render (an
// inline literal would be a new ref each render → render loop). Replaced by the
@@ -1,6 +1,6 @@
import { Maximize2, Pin, PinOff, X } from 'lucide-react';
import type { TFunction } from 'i18next';
import { IS_LINUX } from '../../utils/platform';
import { IS_LINUX } from '@/utils/platform';
interface Props {
trackTitle: string | undefined;
@@ -3,8 +3,8 @@ import { createPortal } from 'react-dom';
import { emit } from '@tauri-apps/api/event';
import { Blend, Infinity as InfinityIcon, ListMusic, MoveRight, Shuffle, Volume2, VolumeX, Waves } from 'lucide-react';
import type { TFunction } from 'i18next';
import type { MiniSyncPayload } from '../../utils/miniPlayerBridge';
import { getTransitionMode } from '../../utils/playback/playbackTransition';
import type { MiniSyncPayload } from '@/features/miniPlayer/utils/miniPlayerBridge';
import { getTransitionMode } from '@/utils/playback/playbackTransition';
interface Props {
state: MiniSyncPayload;
@@ -1,4 +1,4 @@
import { useMiniAnchoredPopover } from './useMiniAnchoredPopover';
import { useMiniAnchoredPopover } from '@/features/miniPlayer/hooks/useMiniAnchoredPopover';
/** Open-state, refs, and fixed positioning of the portaled mini-player
* crossfade settings popover (seconds slider + trim-silence toggle). Opened by
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { emit } from '@tauri-apps/api/event';
import { useKeybindingsStore, matchInAppBinding } from '../store/keybindingsStore';
import { useKeybindingsStore, matchInAppBinding } from '@/store/keybindingsStore';
/** Mini-window keyboard shortcuts. Space/Arrow{Left,Right} run the standard
* play-pause/next/prev shortcut actions (source: 'mini-window' so the bridge
@@ -1,7 +1,7 @@
import React, { useEffect, useRef, useState } from 'react';
import { emit } from '@tauri-apps/api/event';
import { useDragDrop } from '../contexts/DragDropContext';
import { usePlayerStore } from '../store/playerStore';
import { useDragDrop } from '@/contexts/DragDropContext';
import { usePlayerStore } from '@/store/playerStore';
interface Args {
queueOpen: boolean;
@@ -1,7 +1,7 @@
import { useEffect, useRef } from 'react';
import { emit, listen } from '@tauri-apps/api/event';
import { useWindowVisibility } from './useWindowVisibility';
import type { MiniSyncPayload } from '../utils/miniPlayerBridge';
import { useWindowVisibility } from '@/hooks/useWindowVisibility';
import type { MiniSyncPayload } from '@/features/miniPlayer/utils/miniPlayerBridge';
interface ProgressPayload {
current_time: number;
@@ -1,4 +1,4 @@
import { useMiniAnchoredPopover } from './useMiniAnchoredPopover';
import { useMiniAnchoredPopover } from '@/features/miniPlayer/hooks/useMiniAnchoredPopover';
/** Open-state, refs, and fixed positioning of the portaled mini-player volume
* popover. Thin wrapper over {@link useMiniAnchoredPopover} that preserves the
@@ -1,10 +1,10 @@
import { useEffect } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { useAuthStore } from '../store/authStore';
import { IS_LINUX } from '../utils/platform';
import { useAuthStore } from '@/store/authStore';
import { IS_LINUX } from '@/utils/platform';
import {
EXPANDED_SIZE, EXPANDED_MIN, readStoredExpandedHeight,
} from '../utils/componentHelpers/miniPlayerHelpers';
} from '@/features/miniPlayer/utils/miniPlayerHelpers';
/** Three window-bound setup effects bundled together:
* - Linux WebKitGTK smooth-scroll per-window (re-applies after auth hydrates
+8
View File
@@ -0,0 +1,8 @@
/**
* Mini player feature — the compact always-on-top mini-player window UI
* (controls, meta, queue, titlebar, toolbar, context menu) plus the main↔mini
* IPC bridge. The webview entry shell (`app/MiniPlayerApp`) stays in `app/` and
* renders this feature's default export.
*/
export { default } from './components/MiniPlayer';
export { initMiniPlayerBridgeOnMain } from './utils/miniPlayerBridge';
@@ -1,11 +1,11 @@
import { getCurrentWindow } from '@tauri-apps/api/window';
import { listen, emitTo } from '@tauri-apps/api/event';
import { usePlayerStore } from '../store/playerStore';
import { useAuthStore } from '../store/authStore';
import { setTransitionMode, type TransitionMode } from './playback/playbackTransition';
import { resolveQueueTrack } from './library/queueTrackView';
import type { SubsonicOpenArtistRef } from '../api/subsonicTypes';
import type { Track } from '../store/playerStoreTypes';
import { usePlayerStore } from '@/store/playerStore';
import { useAuthStore } from '@/store/authStore';
import { setTransitionMode, type TransitionMode } from '@/utils/playback/playbackTransition';
import { resolveQueueTrack } from '@/utils/library/queueTrackView';
import type { SubsonicOpenArtistRef } from '@/api/subsonicTypes';
import type { Track } from '@/store/playerStoreTypes';
export const MINI_WINDOW_LABEL = 'mini';
@@ -1,7 +1,7 @@
import { usePlayerStore } from '../../store/playerStore';
import { resolveQueueTrack } from '../library/queueTrackView';
import type { MiniSyncPayload, MiniTrackInfo } from '../miniPlayerBridge';
import type { Track } from '../../store/playerStoreTypes';
import { usePlayerStore } from '@/store/playerStore';
import { resolveQueueTrack } from '@/utils/library/queueTrackView';
import type { MiniSyncPayload, MiniTrackInfo } from '@/features/miniPlayer/utils/miniPlayerBridge';
import type { Track } from '@/store/playerStoreTypes';
/** Half-width of the mini initial-snapshot queue window (matches the bridge). */
const MINI_SNAPSHOT_HALF = 100;