mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
refactor(player): E.42 — migrate playerStore re-exports to direct imports (#607)
Migrates ~74 call sites away from the playerStore re-export shims that were kept during M0–E.41 to avoid touching 30+ imports per PR. Now that the bigger refactor work is done, each helper goes back to its real home: - `initAudioListeners`, `installQueueUndoHotkey`, `flushPlayQueuePosition` → from their own store modules - `getPlaybackProgressSnapshot`, `subscribePlaybackProgress`, `PlaybackProgressSnapshot` → from `playbackProgress` - `resolveReplayGainDb`, `shuffleArray`, `songToTrack` → from `utils/*` - `_resetQueueUndoStacksForTest`, `consumePendingQueueListScrollTop`, `registerQueueListScrollTopReader` → from `queueUndo` - `PlayerState`, `Track` types → from `playerStoreTypes` Drops the corresponding 13 re-export stubs from `playerStore.ts` and the now-unused imports. Also drops dead section banners + per-wrapper comments above one-line action delegates. Trims one stale "(separate PR)" note in `transportLightActions.ts` since that follow-up landed in E.39. `playerStore.ts`: 180 → 112 LOC (−68). Down from Phase E's starting 3732 LOC. `bootstrap.test.ts` mock target updated from `../store/playerStore` to `../store/queueUndoHotkey` to keep the spy reachable after the import change.
This commit is contained in:
committed by
GitHub
parent
e92535a5f5
commit
9fac6eb490
+1
-1
@@ -1,3 +1,4 @@
|
||||
import { initAudioListeners } from '../store/initAudioListeners';
|
||||
import { lazy, Suspense, useEffect, useState } from 'react';
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
@@ -10,7 +11,6 @@ import ZipDownloadOverlay from '../components/ZipDownloadOverlay';
|
||||
import FpsOverlay from '../components/FpsOverlay';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useGlobalShortcutsStore } from '../store/globalShortcutsStore';
|
||||
import { initAudioListeners } from '../store/playerStore';
|
||||
import { initHotCachePrefetch } from '../hotCachePrefetch';
|
||||
import { initMiniPlayerBridgeOnMain } from '../utils/miniPlayerBridge';
|
||||
import { IS_WINDOWS } from '../utils/platform';
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { flushPlayQueuePosition } from '../store/queueSync';
|
||||
import { shuffleArray } from '../utils/shuffleArray';
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import { getPlaybackProgressSnapshot } from '../store/playbackProgress';
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
@@ -14,13 +18,7 @@ import {
|
||||
} from '../api/subsonic';
|
||||
import i18n from '../i18n';
|
||||
import { switchActiveServer } from '../utils/switchActiveServer';
|
||||
import {
|
||||
usePlayerStore,
|
||||
getPlaybackProgressSnapshot,
|
||||
songToTrack,
|
||||
shuffleArray,
|
||||
flushPlayQueuePosition,
|
||||
} from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useKeybindingsStore, buildInAppBinding } from '../store/keybindingsStore';
|
||||
import { useGlobalShortcutsStore } from '../store/globalShortcutsStore';
|
||||
import { useZipDownloadStore } from '../store/zipDownloadStore';
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
* payload) and the failure-tolerant path (no throw when invoke or storage
|
||||
* misbehaves).
|
||||
*/
|
||||
import { installQueueUndoHotkey } from '../store/queueUndoHotkey';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('@tauri-apps/api/core', () => ({
|
||||
invoke: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../store/playerStore', () => ({
|
||||
vi.mock('../store/queueUndoHotkey', () => ({
|
||||
installQueueUndoHotkey: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -21,7 +22,6 @@ vi.mock('./windowKind', () => ({
|
||||
}));
|
||||
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { installQueueUndoHotkey } from '../store/playerStore';
|
||||
import { getWindowKind } from './windowKind';
|
||||
import {
|
||||
pushLoggingModeToBackend,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { installQueueUndoHotkey } from '../store/queueUndoHotkey';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { installQueueUndoHotkey } from '../store/playerStore';
|
||||
import { getWindowKind } from './windowKind';
|
||||
|
||||
/** Sync backend HTTP User-Agent from the main webview once at startup. */
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Play, ListPlus, HardDriveDownload, Check } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SubsonicAlbum, buildCoverArtUrl, coverArtCacheKey, getAlbum } from '../api/subsonic';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useOfflineStore } from '../store/offlineStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import CachedImage from './CachedImage';
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { Play, ChevronRight, Heart, ChevronDown, Check, RotateCcw, Square, AudioLines } from 'lucide-react';
|
||||
import { useTracklistColumns, type ColDef } from '../utils/useTracklistColumns';
|
||||
import { SubsonicSong } from '../api/subsonic';
|
||||
import { Track, usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useDragDrop } from '../contexts/DragDropContext';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -11,7 +12,7 @@ import {
|
||||
getArtistInfo,
|
||||
} from '../api/subsonic';
|
||||
import CachedImage, { useCachedUrl } from './CachedImage';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { playAlbum } from '../utils/playAlbum';
|
||||
import AlbumRow from './AlbumRow';
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* snapshots — tests survive a refactor that re-orders or re-styles the
|
||||
* markup as long as the menu items + their handlers stay observable.
|
||||
*/
|
||||
import type { Track } from '@/store/playerStoreTypes';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('@/api/subsonic', () => ({
|
||||
@@ -43,7 +44,7 @@ vi.mock('@/utils/orbitBulkGuard', () => ({
|
||||
|
||||
import ContextMenu from './ContextMenu';
|
||||
import { renderWithProviders } from '@/test/helpers/renderWithProviders';
|
||||
import { usePlayerStore, type Track } from '@/store/playerStore';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useAuthStore, type ServerProfile } from '@/store/authStore';
|
||||
import { resetAllStores } from '@/test/helpers/storeReset';
|
||||
import { makeTrack, makeServer } from '@/test/helpers/factories';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Play, ListPlus, Radio, Heart, Download, ChevronRight, ChevronsRight, User, Disc3, ListMusic, Plus, Info, Sparkles, Star, Trash2, HeartCrack, Share2, Orbit as OrbitIcon } from 'lucide-react';
|
||||
import { useOrbitStore } from '../store/orbitStore';
|
||||
@@ -10,7 +12,7 @@ import {
|
||||
import LastfmIcon from './LastfmIcon';
|
||||
import StarRating from './StarRating';
|
||||
import { lastfmLoveTrack, lastfmUnloveTrack } from '../api/lastfm';
|
||||
import { usePlayerStore, Track, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { SubsonicAlbum, SubsonicArtist, star, unstar, getSimilarSongs2, getSimilarSongs, getTopSongs, buildDownloadUrl, getAlbum, getArtist, getPlaylists, getPlaylist, updatePlaylist, SubsonicPlaylist, setRating } from '../api/subsonic';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress';
|
||||
import React, { useCallback, useEffect, useState, useRef, memo, useMemo } from 'react';
|
||||
import {
|
||||
Play, Pause, SkipBack, SkipForward,
|
||||
ChevronDown, Repeat, Repeat1, Square, Music, Heart, MicVocal,
|
||||
Moon, Sunrise,
|
||||
} from 'lucide-react';
|
||||
import { usePlayerStore, getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { buildCoverArtUrl, coverArtCacheKey, getArtistInfo, star, unstar } from '../api/subsonic';
|
||||
import { useCachedUrl } from './CachedImage';
|
||||
import { getCachedBlob } from '../utils/imageCache';
|
||||
@@ -13,7 +14,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useLyrics, type WordLyricsLine } from '../hooks/useLyrics';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import type { LrcLine } from '../api/lrclib';
|
||||
import type { Track } from '../store/playerStore';
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import { EaseScroller, targetForFraction } from '../utils/easeScroll';
|
||||
import { usePlaybackDelayPress } from '../hooks/usePlaybackDelayPress';
|
||||
import PlaybackDelayModal from './PlaybackDelayModal';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Play, ListPlus } from 'lucide-react';
|
||||
import { getRandomAlbums, SubsonicAlbum, buildCoverArtUrl, coverArtCacheKey, getAlbum } from '../api/subsonic';
|
||||
import CachedImage, { useCachedUrl } from './CachedImage';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { playAlbum } from '../utils/playAlbum';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Search, Disc3, Users, Music, TextSearch } from 'lucide-react';
|
||||
import { search, SearchResults, buildCoverArtUrl, coverArtCacheKey, type SubsonicArtist } from '../api/subsonic';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CachedImage, { FETCH_QUEUE_BIAS_SEARCH_ARTIST_OVER_ALBUM } from './CachedImage';
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress';
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { usePlayerStore, getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import type { LrcLine } from '../api/lrclib';
|
||||
import { useLyrics, type WordLyricsLine } from '../hooks/useLyrics';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { Track } from '../store/playerStore';
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import { EaseScroller, targetForFraction } from '../utils/easeScroll';
|
||||
import OverlayScrollArea from './OverlayScrollArea';
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress';
|
||||
import React, { useState, useCallback, useMemo, useRef, useEffect, useSyncExternalStore, CSSProperties } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -6,7 +8,7 @@ import {
|
||||
Shuffle, Repeat, Repeat1, Heart, Music, MicVocal, ListMusic, X,
|
||||
Moon, Sunrise,
|
||||
} from 'lucide-react';
|
||||
import { usePlayerStore, getPlaybackProgressSnapshot, subscribePlaybackProgress, Track } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { buildCoverArtUrl, coverArtCacheKey, star, unstar } from '../api/subsonic';
|
||||
import { useCachedUrl } from './CachedImage';
|
||||
import LyricsPane from './LyricsPane';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { X, Search, Disc3, Users, Music, Music2, Clock, ChevronRight } from 'lucide-react';
|
||||
import { search, SearchResults, buildCoverArtUrl, coverArtCacheKey, type SubsonicArtist } from '../api/subsonic';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CachedImage, { FETCH_QUEUE_BIAS_SEARCH_ARTIST_OVER_ALBUM } from './CachedImage';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { X, RefreshCw, Shuffle, Settings2, Share2, HelpCircle, Activity } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useOrbitStore } from '../store/orbitStore';
|
||||
import { useHelpModalStore } from '../store/helpModalStore';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { getSong } from '../api/subsonic';
|
||||
import {
|
||||
endOrbitSession,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress';
|
||||
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import {
|
||||
@@ -6,7 +7,7 @@ import {
|
||||
PictureInPicture2, ArrowLeftRight, Moon, Sunrise, Ellipsis,
|
||||
} from 'lucide-react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { usePlayerStore, getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useThemeStore } from '../store/themeStore';
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { registerQueueListScrollTopReader, consumePendingQueueListScrollTop } from '../store/queueUndo';
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import React, { useState, useRef, useMemo, useEffect, useLayoutEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import {
|
||||
Track,
|
||||
usePlayerStore,
|
||||
songToTrack,
|
||||
registerQueueListScrollTopReader,
|
||||
consumePendingQueueListScrollTop,
|
||||
} from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useOrbitStore } from '../store/orbitStore';
|
||||
import OrbitGuestQueue from './OrbitGuestQueue';
|
||||
import OrbitQueueHead from './OrbitQueueHead';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Play, ListPlus, Star } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SubsonicSong, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import CachedImage from './CachedImage';
|
||||
import { enqueueAndPlay } from '../utils/playSong';
|
||||
import { useDragDrop } from '../contexts/DragDropContext';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { memo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Play, ListPlus } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { SubsonicSong } from '../api/subsonic';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { enqueueAndPlay } from '../utils/playSong';
|
||||
import { useDragDrop } from '../contexts/DragDropContext';
|
||||
import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playbackProgress';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { usePlayerStore, getPlaybackProgressSnapshot, subscribePlaybackProgress } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { usePreviewStore } from '../store/previewStore';
|
||||
import { useAuthStore, type SeekbarStyle } from '../store/authStore';
|
||||
import { bumpPerfCounter } from '../utils/perfTelemetry';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
import i18n from '../i18n';
|
||||
import { getSong, setRating, star, unstar } from '../api/subsonic';
|
||||
import { songToTrack, usePlayerStore } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { usePreviewStore } from '../store/previewStore';
|
||||
import { useLyricsStore } from '../store/lyricsStore';
|
||||
import { showToast } from '../utils/toast';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
@@ -9,8 +10,6 @@ import { useAuthStore } from '../store/authStore';
|
||||
import { useOfflineStore } from '../store/offlineStore';
|
||||
import { useHotCacheStore } from '../store/hotCacheStore';
|
||||
import { getCachedLyrics, putCachedLyrics, lyricsCacheKey } from '../utils/lyricsPersistentCache';
|
||||
import type { Track } from '../store/playerStore';
|
||||
|
||||
export type LyricsSource = 'server' | 'lrclib' | 'netease' | 'embedded' | 'lyricsplus';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useOrbitStore } from '../store/orbitStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { getSong } from '../api/subsonic';
|
||||
import {
|
||||
readOrbitState,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useOrbitStore } from '../store/orbitStore';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { getSong } from '../api/subsonic';
|
||||
import {
|
||||
writeOrbitState,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { Track } from './store/playerStoreTypes';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { buildStreamUrl } from './api/subsonic';
|
||||
import { useAuthStore } from './store/authStore';
|
||||
import { HOT_CACHE_PROTECT_AFTER_CURRENT, useHotCacheStore, type HotCacheEntry } from './store/hotCacheStore';
|
||||
import { useOfflineStore } from './store/offlineStore';
|
||||
import { usePlayerStore, type Track } from './store/playerStore';
|
||||
import { usePlayerStore } from './store/playerStore';
|
||||
import {
|
||||
bumpHotCachePreviousTrackGrace,
|
||||
clearHotCachePreviousGrace,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Search, X, ListPlus } from 'lucide-react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { getAlbum, getArtist, getArtistInfo, setRating, buildCoverArtUrl, coverArtCacheKey, buildDownloadUrl, star, unstar, SubsonicSong, SubsonicAlbum } from '../api/subsonic';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior';
|
||||
import { useDownloadModalStore } from '../store/downloadModalStore';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { useState, useEffect, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
|
||||
import AlbumCard from '../components/AlbumCard';
|
||||
import GenreFilterBar from '../components/GenreFilterBar';
|
||||
@@ -5,7 +6,6 @@ import YearFilterButton from '../components/YearFilterButton';
|
||||
import StarFilterButton from '../components/StarFilterButton';
|
||||
import SortDropdown from '../components/SortDropdown';
|
||||
import { getAlbumList, getAlbumsByGenre, getAlbum, SubsonicAlbum, buildDownloadUrl } from '../api/subsonic';
|
||||
import { songToTrack } from '../store/playerStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useOfflineStore } from '../store/offlineStore';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import { useEffect, useState, useRef, Fragment, useMemo } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { getArtist, getArtistInfo, getTopSongs, getSimilarSongs2, getAlbum, search, setRating, SubsonicArtist, SubsonicAlbum, SubsonicSong, SubsonicArtistInfo, buildCoverArtUrl, coverArtCacheKey, star, unstar, uploadArtistImage } from '../api/subsonic';
|
||||
@@ -8,7 +9,7 @@ import { ArrowLeft, Users, ExternalLink, Heart, Play, Square, Shuffle, Radio, Ha
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import { useOrbitSongRowBehavior } from '../hooks/useOrbitSongRowBehavior';
|
||||
import { open } from '@tauri-apps/plugin-shell';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { usePreviewStore } from '../store/previewStore';
|
||||
import { useOfflineStore } from '../store/offlineStore';
|
||||
import { useOfflineJobStore } from '../store/offlineJobStore';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTracklistColumns, type ColDef } from '../utils/useTracklistColumns';
|
||||
import AlbumRow from '../components/AlbumRow';
|
||||
@@ -8,7 +9,7 @@ import {
|
||||
SubsonicAlbum, SubsonicArtist, SubsonicSong, InternetRadioStation,
|
||||
buildCoverArtUrl, coverArtCacheKey,
|
||||
} from '../api/subsonic';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { usePreviewStore } from '../store/previewStore';
|
||||
import StarRating from '../components/StarRating';
|
||||
import { Cast, ChevronDown, ChevronLeft, ChevronRight, Check, Heart, ListPlus, Play, Square, Star, Users, X, SlidersHorizontal, ArrowUp, ArrowDown, RotateCcw, AudioLines } from 'lucide-react';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
getMusicFolders,
|
||||
@@ -7,7 +8,7 @@ import {
|
||||
SubsonicArtist,
|
||||
SubsonicAlbum,
|
||||
} from '../api/subsonic';
|
||||
import { usePlayerStore, Track } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Folder, FolderOpen, Music, ChevronRight } from 'lucide-react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import AlbumCard from '../components/AlbumCard';
|
||||
import { ndListLosslessAlbumsPage } from '../api/navidromeBrowse';
|
||||
@@ -6,7 +7,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useOfflineStore } from '../store/offlineStore';
|
||||
import { useDownloadModalStore } from '../store/downloadModalStore';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useZipDownloadStore } from '../store/zipDownloadStore';
|
||||
import { useRangeSelection } from '../hooks/useRangeSelection';
|
||||
import { usePerfProbeFlags } from '../utils/perfFlags';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ArrowUpDown, ArrowDown, ArrowUp, TrendingUp, UsersRound, Play, ListPlus } from 'lucide-react';
|
||||
import { getAlbumList, getAlbum, SubsonicAlbum, buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import CachedImage from '../components/CachedImage';
|
||||
import { playAlbum } from '../utils/playAlbum';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
getAlbum, getArtist, getArtistInfo, getTopSongs,
|
||||
SubsonicSong, SubsonicArtistInfo, SubsonicAlbum,
|
||||
} from '../api/subsonic';
|
||||
import { songToTrack } from '../store/playerStore';
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import {
|
||||
lastfmIsConfigured,
|
||||
lastfmGetTrackInfo, lastfmGetArtistStats,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useParams, useNavigate, useLocation } from 'react-router-dom';
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
search, setRating, star, unstar,
|
||||
getRandomSongs, buildDownloadUrl, filterSongsToActiveLibrary, SubsonicPlaylist, SubsonicSong,
|
||||
} from '../api/subsonic';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { usePlaylistStore } from '../store/playlistStore';
|
||||
import { usePreviewStore } from '../store/previewStore';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ListMusic, Play, Plus, Trash2, CheckSquare2, Check, Clock3, Sparkles, Pencil } from 'lucide-react';
|
||||
import { deletePlaylist, SubsonicPlaylist, getPlaylist, buildCoverArtUrl, coverArtCacheKey, updatePlaylist, getGenres, SubsonicGenre, filterSongsToActiveLibrary } from '../api/subsonic';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { usePlaylistStore } from '../store/playlistStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import CachedImage from '../components/CachedImage';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { getGenres, SubsonicSong, SubsonicGenre, star, unstar } from '../api/subsonic';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { usePreviewStore } from '../store/previewStore';
|
||||
import { useAuthStore, RANDOM_MIX_SIZE_OPTIONS } from '../store/authStore';
|
||||
import { Play, RefreshCw, ChevronDown, ChevronRight, ChevronUp, Heart, Square, AudioLines } from 'lucide-react';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Play, ListPlus, RefreshCw, Sparkles } from 'lucide-react';
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
coverArtCacheKey,
|
||||
} from '../api/subsonic';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import CachedImage from '../components/CachedImage';
|
||||
import SongRail from '../components/SongRail';
|
||||
import VirtualSongList from '../components/VirtualSongList';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { lastfmGetTrackLoved, lastfmScrobble, lastfmUpdateNowPlaying } from '../api/lastfm';
|
||||
import { reportNowPlaying, scrobbleSong } from '../api/subsonic';
|
||||
@@ -44,7 +45,7 @@ import {
|
||||
import {
|
||||
playbackSourceHintForResolvedUrl,
|
||||
} from './playbackUrlRouting';
|
||||
import { usePlayerStore, type Track } from './playerStore';
|
||||
import { usePlayerStore } from './playerStore';
|
||||
import { promoteCompletedStreamToHotCache } from './promoteStreamCache';
|
||||
import {
|
||||
flushQueueSyncToServer,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { isHotCachePreviousTrackUnderGrace } from '../utils/hotCacheGate';
|
||||
import type { Track } from './playerStore';
|
||||
import { emitAnalysisStorageChanged } from './analysisSync';
|
||||
import { useAuthStore } from './authStore';
|
||||
|
||||
|
||||
@@ -12,23 +12,11 @@ type SetState = (
|
||||
type GetState = () => PlayerState;
|
||||
|
||||
/**
|
||||
* Four Last.fm love-related actions, factored out of the playerStore
|
||||
* `create()` body so the action set can be tested + reasoned about
|
||||
* separately:
|
||||
*
|
||||
* - `toggleLastfmLove` — flip the current track's love state on the
|
||||
* server, write through to the local cache map keyed by
|
||||
* `${title}::${artist}` so other queue rows showing the same song
|
||||
* update too.
|
||||
* - `setLastfmLoved` — force-set the boolean (used by the
|
||||
* `track:lastfm-loved` SSE-style event). Updates the cache when a
|
||||
* current track exists.
|
||||
* - `setLastfmLovedForSong` — write the cache for an arbitrary
|
||||
* title/artist pair (used by the QueuePanel love button on
|
||||
* not-yet-current tracks).
|
||||
* - `syncLastfmLovedTracks` — startup-time bulk fetch of the user's
|
||||
* loved-tracks list, merged into the local cache (local likes win
|
||||
* on conflict) plus a recompute of the current track's flag.
|
||||
* Four Last.fm love-related actions. The `lastfmLovedCache` is a map
|
||||
* keyed by `${title}::${artist}` (not by track id) so other queue rows
|
||||
* showing the same song update too when one is loved/unloved.
|
||||
* `syncLastfmLovedTracks` merges the server's loved list with local
|
||||
* cache — local likes win on conflict.
|
||||
*/
|
||||
export function createLastfmActions(set: SetState, get: GetState): Pick<
|
||||
PlayerState,
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* = current track + next `LOUDNESS_BACKFILL_WINDOW_AHEAD` entries, with
|
||||
* duplicates collapsed.
|
||||
*/
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { Track } from './playerStore';
|
||||
import {
|
||||
LOUDNESS_BACKFILL_WINDOW_AHEAD,
|
||||
collectLoudnessBackfillWindowTrackIds,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Track } from './playerStore';
|
||||
|
||||
import type { Track } from './playerStoreTypes';
|
||||
/**
|
||||
* After a bulk enqueue (queue replace, append-many, lucky-mix) the runtime
|
||||
* warms the loudness cache for the current track + the next N entries so
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
* guard, the window collection, and the no-sync-engine flag on each
|
||||
* refresh call.
|
||||
*/
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { Track } from './playerStore';
|
||||
|
||||
const hoisted = vi.hoisted(() => {
|
||||
const auth = { normalizationEngine: 'loudness' as 'off' | 'replaygain' | 'loudness' };
|
||||
const player = { currentTrack: null as Track | null };
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { useAuthStore } from './authStore';
|
||||
import { collectLoudnessBackfillWindowTrackIds } from './loudnessBackfillWindow';
|
||||
import { refreshLoudnessForTrack } from './loudnessRefresh';
|
||||
import { usePlayerStore, type Track } from './playerStore';
|
||||
|
||||
import { usePlayerStore } from './playerStore';
|
||||
/**
|
||||
* After a bulk enqueue (queue replace, append-many, lucky-mix) warm the
|
||||
* loudness cache for the current track + the next N entries so the
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
* album-mode resolution). useAuthStore is mocked through a hoisted state
|
||||
* object so each test can flip flags without rebuilding the store.
|
||||
*/
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { Track } from './playerStore';
|
||||
|
||||
const { authState } = vi.hoisted(() => ({
|
||||
authState: {
|
||||
normalizationEngine: 'off' as 'off' | 'replaygain' | 'loudness',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { PlayerState, Track } from './playerStoreTypes';
|
||||
import { useAuthStore } from './authStore';
|
||||
import { resolveReplayGainDb } from '../utils/resolveReplayGainDb';
|
||||
import type { PlayerState, Track } from './playerStore';
|
||||
|
||||
/**
|
||||
* Compute the normalization fields that should land in the next state commit
|
||||
* when the runtime switches tracks or rewrites the queue. Three branches:
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* regression test from §4.2 of the pre-refactor testing plan v2 — the
|
||||
* cleanup function returned by `initAudioListeners` must actually unsub.
|
||||
*/
|
||||
import { initAudioListeners } from './initAudioListeners';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('@/api/subsonic', async () => {
|
||||
@@ -40,7 +41,7 @@ vi.mock('@/utils/orbitBulkGuard', () => ({
|
||||
orbitBulkGuard: vi.fn(async () => true),
|
||||
}));
|
||||
|
||||
import { initAudioListeners, usePlayerStore } from './playerStore';
|
||||
import { usePlayerStore } from './playerStore';
|
||||
import {
|
||||
emitTauriEvent,
|
||||
onInvoke,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* Mocks `savePlayQueue` at the module boundary so we can assert the exact
|
||||
* args passed to the Subsonic API call.
|
||||
*/
|
||||
import { initAudioListeners } from './initAudioListeners';
|
||||
import { flushPlayQueuePosition } from './queueSync';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// Explicit (non-spread) mock map — the `...actual` spread pattern lets the
|
||||
@@ -40,11 +42,7 @@ vi.mock('@/api/lastfm', () => ({
|
||||
}));
|
||||
|
||||
import { savePlayQueue } from '@/api/subsonic';
|
||||
import {
|
||||
flushPlayQueuePosition,
|
||||
initAudioListeners,
|
||||
usePlayerStore,
|
||||
} from './playerStore';
|
||||
import { usePlayerStore } from './playerStore';
|
||||
import { emitTauriEvent, onInvoke } from '@/test/mocks/tauri';
|
||||
import { resetPlayerStore, resetAuthStore } from '@/test/helpers/storeReset';
|
||||
import { makeTrack, makeTracks } from '@/test/helpers/factories';
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
* Drive emits via the `audio:progress` Tauri event (the only public path
|
||||
* to `emitPlaybackProgress`).
|
||||
*/
|
||||
import { initAudioListeners } from './initAudioListeners';
|
||||
import { getPlaybackProgressSnapshot, subscribePlaybackProgress, type PlaybackProgressSnapshot } from './playbackProgress';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('@/api/subsonic', async () => {
|
||||
@@ -36,13 +38,7 @@ vi.mock('@/api/lastfm', () => ({
|
||||
lastfmGetAllLovedTracks: vi.fn(async () => []),
|
||||
}));
|
||||
|
||||
import {
|
||||
getPlaybackProgressSnapshot,
|
||||
initAudioListeners,
|
||||
subscribePlaybackProgress,
|
||||
usePlayerStore,
|
||||
type PlaybackProgressSnapshot,
|
||||
} from './playerStore';
|
||||
import { usePlayerStore } from './playerStore';
|
||||
import { emitTauriEvent, onInvoke } from '@/test/mocks/tauri';
|
||||
import { resetPlayerStore, resetAuthStore } from '@/test/helpers/storeReset';
|
||||
import { makeTrack } from '@/test/helpers/factories';
|
||||
|
||||
@@ -1,62 +1,8 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import { resolveReplayGainDb } from '../utils/resolveReplayGainDb';
|
||||
import { shuffleArray } from '../utils/shuffleArray';
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import {
|
||||
emitPlaybackProgress,
|
||||
getPlaybackProgressSnapshot,
|
||||
subscribePlaybackProgress,
|
||||
type PlaybackProgressSnapshot,
|
||||
} from './playbackProgress';
|
||||
import { flushPlayQueuePosition } from './queueSync';
|
||||
import { initAudioListeners } from './initAudioListeners';
|
||||
import { installQueueUndoHotkey } from './queueUndoHotkey';
|
||||
import { emitPlaybackProgress } from './playbackProgress';
|
||||
import type { PlayerState } from './playerStoreTypes';
|
||||
import { readInitialQueueVisibility } from './queueVisibilityStorage';
|
||||
|
||||
// Re-export so MainApp + the 3 playerStore characterization tests keep
|
||||
// their existing `from './playerStore'` imports.
|
||||
export { initAudioListeners };
|
||||
|
||||
// Re-export so bootstrap.ts + bootstrap.test keep their existing
|
||||
// `from './playerStore'` imports.
|
||||
export { installQueueUndoHotkey };
|
||||
|
||||
// Re-export so TauriEventBridge + persistence test keep their existing
|
||||
// `from './playerStore'` imports.
|
||||
export { flushPlayQueuePosition };
|
||||
|
||||
// Re-export the playback-progress public surface so existing call sites
|
||||
// (PlayerBar, FullscreenPlayer, WaveformSeek, LyricsPane, MobilePlayerView,
|
||||
// TauriEventBridge, plus the progress characterization test) keep their
|
||||
// `from './playerStore'` imports working.
|
||||
export {
|
||||
getPlaybackProgressSnapshot,
|
||||
subscribePlaybackProgress,
|
||||
type PlaybackProgressSnapshot,
|
||||
};
|
||||
import {
|
||||
_resetQueueUndoStacksForTest,
|
||||
consumePendingQueueListScrollTop,
|
||||
pushQueueUndoFromGetter,
|
||||
registerQueueListScrollTopReader,
|
||||
} from './queueUndo';
|
||||
|
||||
// Re-export for backward compatibility with the ~30 call sites that still
|
||||
// import these helpers from playerStore. Phase E (store splits) will migrate
|
||||
// the imports to '../utils/*' directly and drop these re-exports.
|
||||
export { resolveReplayGainDb, shuffleArray, songToTrack };
|
||||
|
||||
// Re-export the queue-undo public API so existing callers (QueuePanel,
|
||||
// test/helpers/storeReset) keep their `from './playerStore'` imports.
|
||||
export {
|
||||
_resetQueueUndoStacksForTest,
|
||||
consumePendingQueueListScrollTop,
|
||||
registerQueueListScrollTopReader,
|
||||
};
|
||||
|
||||
import type { PlayerState, Track } from './playerStoreTypes';
|
||||
export type { PlayerState, Track };
|
||||
import { createLastfmActions } from './lastfmActions';
|
||||
import { createMiscActions } from './miscActions';
|
||||
import { runNext } from './nextAction';
|
||||
@@ -70,12 +16,6 @@ import { createTransportLightActions } from './transportLightActions';
|
||||
import { createUiStateActions } from './uiStateActions';
|
||||
import { createUndoRedoActions } from './undoRedoActions';
|
||||
|
||||
|
||||
// ─── Module-level playback primitives ─────────────────────────────────────────
|
||||
|
||||
|
||||
// ─── Store ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const usePlayerStore = create<PlayerState>()(
|
||||
persist(
|
||||
(set, get) => {
|
||||
@@ -125,20 +65,11 @@ export const usePlayerStore = create<PlayerState>()(
|
||||
...createMiscActions(set, get),
|
||||
...createScheduleActions(set, get),
|
||||
|
||||
// ── playTrack ────────────────────────────────────────────────────────────
|
||||
playTrack: (track, queue, manual = true, _orbitConfirmed = false, targetQueueIndex) =>
|
||||
runPlayTrack(set, get, track, queue, manual, _orbitConfirmed, targetQueueIndex),
|
||||
|
||||
// ── resume ───────────────────────────────────────────────────────────────
|
||||
resume: () => runResume(set, get),
|
||||
|
||||
// ── next ────────────────────────────────────────────────────────────────
|
||||
next: (manual = true) => runNext(set, get, manual),
|
||||
|
||||
|
||||
// ── seek ─────────────────────────────────────────────────────────────────
|
||||
seek: (progress) => runSeek(set, get, progress),
|
||||
|
||||
updateReplayGainForCurrentTrack: () => runUpdateReplayGainForCurrentTrack(set, get),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
* suffix fallback, the null-result skip, and the swallow-on-error
|
||||
* contract.
|
||||
*/
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { Track } from './playerStore';
|
||||
|
||||
const { invokeMock, setEntryMock, buildStreamUrlMock } = vi.hoisted(() => ({
|
||||
invokeMock: vi.fn(async (_cmd: string, _args?: Record<string, unknown>) => null as { path: string; size: number } | null),
|
||||
setEntryMock: vi.fn(),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { buildStreamUrl } from '../api/subsonic';
|
||||
import { useHotCacheStore } from './hotCacheStore';
|
||||
import type { Track } from './playerStore';
|
||||
|
||||
/**
|
||||
* Promote a track whose stream cache is full to the on-disk hot cache.
|
||||
* Rust copies the cached bytes into the hot-cache directory and returns
|
||||
|
||||
@@ -24,12 +24,8 @@ type SetState = (
|
||||
type GetState = () => PlayerState;
|
||||
|
||||
/**
|
||||
* Queue-mutation actions factored out of the playerStore `create()` body.
|
||||
* All eleven members of the cluster: insertion (`enqueue`, `enqueueAt`,
|
||||
* `playNext`, `enqueueRadio`, `setRadioArtistId`), pruning (`clearQueue`,
|
||||
* `pruneUpcomingToCurrent`, `removeTrack`), and rearrangement
|
||||
* (`reorderQueue`, `shuffleQueue`, `shuffleUpcomingQueue`). All but
|
||||
* `setRadioArtistId` push a queue-undo snapshot and call
|
||||
* Eleven queue-mutation actions. Shared invariant: every action except
|
||||
* `setRadioArtistId` pushes a queue-undo snapshot and calls
|
||||
* `syncQueueToServer` so the Navidrome `savePlayQueue` stays in sync.
|
||||
*/
|
||||
export function createQueueMutationActions(set: SetState, get: GetState): Pick<
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
* in for `savePlayQueue`, the playerStore, and the playback-progress
|
||||
* snapshot.
|
||||
*/
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { Track } from './playerStore';
|
||||
|
||||
const { savePlayQueueMock, playerState, progressSnapshot } = vi.hoisted(() => ({
|
||||
savePlayQueueMock: vi.fn(async (_ids: string[], _currentId: string | undefined, _pos: number) => undefined),
|
||||
playerState: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { savePlayQueue } from '../api/subsonic';
|
||||
import { getPlaybackProgressSnapshot } from './playbackProgress';
|
||||
import { usePlayerStore, type Track } from './playerStore';
|
||||
|
||||
import { usePlayerStore } from './playerStore';
|
||||
/**
|
||||
* Server-side play-queue persistence. Subsonic's `savePlayQueue` accepts
|
||||
* the current queue, the active track id, and the position in ms — so the
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* push, and (c) the scroll-top reader / consumer pair that QueuePanel uses
|
||||
* to restore list scroll position after an undo/redo commit.
|
||||
*/
|
||||
import type { PlayerState, Track } from './playerStoreTypes';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import type { PlayerState, Track } from './playerStore';
|
||||
import {
|
||||
QUEUE_UNDO_MAX,
|
||||
_resetQueueUndoStacksForTest,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { PlayerState, Track } from './playerStore';
|
||||
|
||||
import type { PlayerState, Track } from './playerStoreTypes';
|
||||
/** Hard cap on undo/redo depth — keeps memory bounded for very long sessions. */
|
||||
export const QUEUE_UNDO_MAX = 32;
|
||||
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
* atSeconds > 0.05, the wantPlaying=false branch that issues audio_pause,
|
||||
* and the generation-mismatch bail-out.
|
||||
*/
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { Track } from './playerStore';
|
||||
|
||||
const hoisted = vi.hoisted(() => {
|
||||
const auth = {
|
||||
activeServerId: 'srv',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { setDeferHotCachePrefetch } from '../utils/hotCacheGate';
|
||||
import { resolvePlaybackUrl } from '../utils/resolvePlaybackUrl';
|
||||
@@ -7,8 +8,7 @@ import { getPlayGeneration, setIsAudioPaused } from './engineState';
|
||||
import { touchHotCacheOnPlayback } from './hotCacheTouch';
|
||||
import { isReplayGainActive, loudnessGainDbForEngineBind } from './loudnessGainCache';
|
||||
import { playbackSourceHintForResolvedUrl, recordEnginePlayUrl } from './playbackUrlRouting';
|
||||
import { usePlayerStore, type Track } from './playerStore';
|
||||
|
||||
import { usePlayerStore } from './playerStore';
|
||||
/**
|
||||
* Reload the Rust audio engine to match a queue-undo snapshot. Zustand
|
||||
* alone can rewrite the queue + currentTrack, but the engine is still
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
* the player-store state surface so the test can drive every input
|
||||
* independently.
|
||||
*/
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { Track } from './playerStore';
|
||||
|
||||
const { setRatingMock, recordSkipStarMock, playerSetStateMock, playerStateGet } = vi.hoisted(() => {
|
||||
const playerState = {
|
||||
queue: [] as Track[],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Track } from './playerStoreTypes';
|
||||
import { setRating } from '../api/subsonic';
|
||||
import { useAuthStore } from './authStore';
|
||||
import { usePlayerStore, type Track } from './playerStore';
|
||||
|
||||
import { usePlayerStore } from './playerStore';
|
||||
/**
|
||||
* Skip → 1★ behaviour: every user-initiated `next()` on an unrated track
|
||||
* counts in `authStore.skipStarManualSkipCountsByKey` (persisted). Once the
|
||||
|
||||
@@ -15,18 +15,12 @@ type SetState = (
|
||||
type GetState = () => PlayerState;
|
||||
|
||||
/**
|
||||
* Light transport actions factored out of the playerStore `create()`
|
||||
* body — everything except `resume` (~165 LOC, separate PR) and the
|
||||
* scheduled pause/resume timer setters.
|
||||
*
|
||||
* - `stop` — full reset: stops audio/radio, clears timers + seek + visual
|
||||
* state, blanks playback metadata.
|
||||
* - `pause` — pauses audio (or radio), flushes queue position so other
|
||||
* devices can pick up the resume point.
|
||||
* - `resetAudioPause` — flips the engine-paused flag without touching
|
||||
* the UI `isPlaying` state. Used by `audio:ended` paths.
|
||||
* - `togglePlay` — guarded toggle so a double media-key tap can't race
|
||||
* pause + resume into a stuck state.
|
||||
* Light transport actions — everything except `resume` (own module,
|
||||
* see `resumeAction.ts`) and scheduled timers (`scheduleActions.ts`).
|
||||
* `togglePlay` is guarded so a double media-key tap can't race
|
||||
* pause + resume into a stuck state. `resetAudioPause` flips the
|
||||
* engine-paused flag without touching the UI `isPlaying`, used by
|
||||
* `audio:ended` paths.
|
||||
*/
|
||||
export function createTransportLightActions(set: SetState, get: GetState): Pick<
|
||||
PlayerState,
|
||||
|
||||
@@ -8,13 +8,8 @@ type SetState = (
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Pure-UI state setters that mutate playerStore fields with no audio
|
||||
* engine / network side effects: starred + rating optimistic overrides,
|
||||
* context menu modal, song info modal, queue panel visibility (with
|
||||
* localStorage round-trip), fullscreen toggle, repeat mode cycle.
|
||||
*
|
||||
* Factored out of the playerStore `create()` body so the action set
|
||||
* stays a flat object literal instead of a giant inline block.
|
||||
* Pure-UI state setters: no audio engine / network side effects.
|
||||
* Add new actions here only if they fit that contract.
|
||||
*/
|
||||
export function createUiStateActions(set: SetState): Pick<
|
||||
PlayerState,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* the fields the test cares about. Keeps tests focused on behaviour rather
|
||||
* than on assembling boilerplate.
|
||||
*/
|
||||
import type { Track } from '@/store/playerStore';
|
||||
import type { Track } from '@/store/playerStoreTypes';
|
||||
import type { ServerProfile } from '@/store/authStore';
|
||||
import type { SubsonicSong } from '@/api/subsonic';
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
* // or for cross-store tests:
|
||||
* beforeEach(resetAllStores);
|
||||
*/
|
||||
import { usePlayerStore, _resetQueueUndoStacksForTest } from '@/store/playerStore';
|
||||
import { _resetQueueUndoStacksForTest } from '@/store/queueUndo';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { usePreviewStore } from '@/store/previewStore';
|
||||
import { useOrbitStore } from '@/store/orbitStore';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import type { NavigateFunction } from 'react-router-dom';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { getAlbum, getArtist, getSong, type SubsonicSong } from '../api/subsonic';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { songToTrack, usePlayerStore } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { findServerIdForShareUrl, type EntitySharePayloadV1 } from './shareLink';
|
||||
import { showToast } from './toast';
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* refactor (2026-05-12). This test pins the artist-first / random-fallback
|
||||
* order, the dedup contract against existingIds, and the autoAdded flag.
|
||||
*/
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('../api/subsonic', () => ({
|
||||
@@ -26,7 +27,6 @@ import {
|
||||
enrichSongsForMixRatingFilter,
|
||||
getMixMinRatingsConfigFromAuth,
|
||||
} from './mixRatingFilter';
|
||||
import type { Track } from '../store/playerStore';
|
||||
import { makeSubsonicSong } from '@/test/helpers/factories';
|
||||
|
||||
const seed = (overrides: Partial<Track> = {}): Track => ({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import { getRandomSongs, getSimilarSongs2, getTopSongs } from '../api/subsonic';
|
||||
import {
|
||||
enrichSongsForMixRatingFilter,
|
||||
@@ -6,8 +7,6 @@ import {
|
||||
} from './mixRatingFilter';
|
||||
import { shuffleArray } from './shuffleArray';
|
||||
import { songToTrack } from './songToTrack';
|
||||
import type { Track } from '../store/playerStore';
|
||||
|
||||
/**
|
||||
* Infinite queue source strategy (Instant Mix-like):
|
||||
* 1) Prefer artist-driven candidates (Top + Similar) around the current track.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import {
|
||||
filterSongsToActiveLibrary,
|
||||
getAlbum,
|
||||
@@ -11,7 +13,7 @@ import {
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import i18n from '../i18n';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { songToTrack, usePlayerStore, type Track } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useLuckyMixStore } from '../store/luckyMixStore';
|
||||
import { isLuckyMixAvailable } from '../hooks/useLuckyMixAvailable';
|
||||
import { showToast } from './toast';
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import {
|
||||
createPlaylist,
|
||||
updatePlaylist,
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
} from '../api/subsonic';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { useOrbitStore } from '../store/orbitStore';
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { encodeSharePayload, decodeOrbitSharePayloadFromText } from './shareLink';
|
||||
import {
|
||||
makeInitialOrbitState,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getAlbum } from '../api/subsonic';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { songToTrack } from '../store/playerStore';
|
||||
import { songToTrack } from './songToTrack';
|
||||
import { useOrbitStore } from '../store/orbitStore';
|
||||
|
||||
function fadeOut(setVolume: (v: number) => void, from: number, durationMs: number): Promise<void> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import { shuffleArray } from '../utils/shuffleArray';
|
||||
import { getAlbum, getArtist } from '../api/subsonic';
|
||||
import { shuffleArray, songToTrack, usePlayerStore } from '../store/playerStore';
|
||||
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
/**
|
||||
* All tracks from the artist’s albums, shuffled — same idea as Artist page “shuffle play”.
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import { getAlbum, getSong } from '../api/subsonic';
|
||||
import { playAlbum } from './playAlbum';
|
||||
import { playArtistShuffled } from './playArtistShuffled';
|
||||
import { songToTrack, usePlayerStore } from '../store/playerStore';
|
||||
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
/**
|
||||
* `getSong` → `getAlbum` → `getArtist`: one opaque Subsonic id may refer to a track,
|
||||
* album, or artist depending on the server.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { usePlayerStore, songToTrack } from '../store/playerStore';
|
||||
import { songToTrack } from '../utils/songToTrack';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import type { SubsonicSong } from '../api/subsonic';
|
||||
|
||||
function fadeOut(setVolume: (v: number) => void, from: number, durationMs: number): Promise<void> {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* routing through the HTTP source) and the no-op detection in
|
||||
* `queuesStructuralEqual` that prevents unnecessary store rewrites.
|
||||
*/
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { Track } from '../store/playerStore';
|
||||
import {
|
||||
normalizeAnalysisTrackId,
|
||||
queuesStructuralEqual,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Track } from '../store/playerStore';
|
||||
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
/**
|
||||
* Strip the `stream:` prefix that some Rust events attach to track ids when
|
||||
* they're routed through the HTTP source. Both forms identify the same track,
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
* Originally lived in `playerStore.ts`; extracted in M0 of the frontend
|
||||
* refactor (2026-05-12).
|
||||
*/
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { resolveReplayGainDb } from './resolveReplayGainDb';
|
||||
import type { Track } from '../store/playerStore';
|
||||
|
||||
describe('resolveReplayGainDb', () => {
|
||||
const t = (overrides: Partial<Track> = {}): Track => ({
|
||||
id: 'x',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Track } from '../store/playerStore';
|
||||
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
/**
|
||||
* Resolve the ReplayGain dB value for a track based on the configured mode.
|
||||
* In 'auto' mode, picks album-gain when an adjacent queue neighbour shares the
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* playback queue. Originally lived in `playerStore.ts`; extracted in M0 of
|
||||
* the frontend refactor (2026-05-12).
|
||||
*/
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { songToTrack } from './songToTrack';
|
||||
import type { Track } from '../store/playerStore';
|
||||
import { makeSubsonicSong } from '@/test/helpers/factories';
|
||||
|
||||
describe('songToTrack', () => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
import type { SubsonicSong } from '../api/subsonic';
|
||||
import type { Track } from '../store/playerStore';
|
||||
|
||||
export function songToTrack(song: SubsonicSong): Track {
|
||||
return {
|
||||
id: song.id,
|
||||
|
||||
Reference in New Issue
Block a user