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,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,
|
||||
|
||||
Reference in New Issue
Block a user