fix: restore audio refactor + features lost in #419 squash-merge (#429)

The squash-merge of PR #419 was performed against an outdated PR base
that predated several main-side refactors and features. The resulting
squash inadvertently re-introduced files that had already been removed
(`src-tauri/src/audio.rs` monolith, `app-icon.png`) and reverted main's
content for ~20 files (`src-tauri/src/lib.rs` decompose, `src/App.tsx`
animation-pause, `src/components/AlbumRow.tsx` headerExtra, etc).

This commit:

* Restores all collateral-damage files to their pre-#419 main state
  (5662dafe), including the audio module split, lib decompose,
  Cargo.toml `windows` dep, animation-pause-on-blur logic, and
  `reducedAnimations` toggle plumbing in WaveformSeek/Settings.
* Keeps the genuine #419 work intact: tri-state duration toggle,
  position counter, persistent Now Playing collapse, animated EQ
  indicator (in QueuePanel.tsx, 8 locales, authStore, components.css).
* Merges authStore.ts so both `reducedAnimations` (from #426) and
  `queueNowPlayingCollapsed` (from #419) coexist.
* Adds the `.eq-bars.paused` CSS rule manually since components.css
  needed a 5662dafe base + the single #419 addition.
* Fixes a latent type error in OverlayScrollArea.tsx (`useRef<T>(null)`
  is `RefObject<T>` / read-only under current `@types/react`; widened to
  `useRef<T | null>(null)` so the existing `wrapRef.current = el`
  assignment compiles).

Verified locally with `cargo check` and `npm run build` — both green.
This commit is contained in:
Frank Stellmacher
2026-05-02 22:01:51 +02:00
committed by GitHub
parent 18b4a982ef
commit e44e6dcdf4
23 changed files with 1286 additions and 9912 deletions
+10 -5
View File
@@ -165,8 +165,6 @@ interface AuthState {
*/
lyricsStaticOnly: boolean;
showFullscreenLyrics: boolean;
/** Persisted UI toggle: is the Now Playing section in queue panel collapsed */
queueNowPlayingCollapsed: boolean;
/** 'rail' = classic 5-line sliding rail; 'apple' = full-screen scrolling list */
fsLyricsStyle: 'rail' | 'apple';
/** Sidebar lyrics scroll style: 'classic' = scrollIntoView center; 'apple' = scroll to 35% */
@@ -178,6 +176,10 @@ interface AuthState {
lastSeenChangelogVersion: string;
seekbarStyle: SeekbarStyle;
/** Cap animated seekbar styles to 30 fps (and similar GPU-friendly tweaks) for low-end hardware. */
reducedAnimations: boolean;
/** Persisted UI toggle: is the Now Playing section in queue panel collapsed */
queueNowPlayingCollapsed: boolean;
/** Alpha: native hi-res sample rate output (disabled = safe 44.1 kHz mode) */
enableHiRes: boolean;
@@ -318,7 +320,6 @@ interface AuthState {
setLyricsMode: (v: 'standard' | 'lyricsplus') => void;
setLyricsStaticOnly: (v: boolean) => void;
setShowFullscreenLyrics: (v: boolean) => void;
setQueueNowPlayingCollapsed: (v: boolean) => void;
setFsLyricsStyle: (v: 'rail' | 'apple') => void;
setSidebarLyricsStyle: (v: 'classic' | 'apple') => void;
setShowFsArtistPortrait: (v: boolean) => void;
@@ -326,6 +327,8 @@ interface AuthState {
setShowChangelogOnUpdate: (v: boolean) => void;
setLastSeenChangelogVersion: (v: string) => void;
setSeekbarStyle: (v: SeekbarStyle) => void;
setReducedAnimations: (v: boolean) => void;
setQueueNowPlayingCollapsed: (v: boolean) => void;
setEnableHiRes: (v: boolean) => void;
setAudioOutputDevice: (v: string | null) => void;
setHotCacheEnabled: (v: boolean) => void;
@@ -438,7 +441,6 @@ export const useAuthStore = create<AuthState>()(
lyricsMode: 'standard',
lyricsStaticOnly: false,
showFullscreenLyrics: true,
queueNowPlayingCollapsed: false,
fsLyricsStyle: 'rail',
sidebarLyricsStyle: 'classic',
showFsArtistPortrait: true,
@@ -446,6 +448,8 @@ export const useAuthStore = create<AuthState>()(
showChangelogOnUpdate: true,
lastSeenChangelogVersion: '',
seekbarStyle: 'truewave',
reducedAnimations: false,
queueNowPlayingCollapsed: false,
enableHiRes: false,
audioOutputDevice: null,
hotCacheEnabled: false,
@@ -599,7 +603,6 @@ export const useAuthStore = create<AuthState>()(
setLyricsMode: (v) => set({ lyricsMode: v }),
setLyricsStaticOnly: (v) => set({ lyricsStaticOnly: v }),
setShowFullscreenLyrics: (v: boolean) => set({ showFullscreenLyrics: v }),
setQueueNowPlayingCollapsed: (v: boolean) => set({ queueNowPlayingCollapsed: v }),
setFsLyricsStyle: (v) => set({ fsLyricsStyle: v }),
setSidebarLyricsStyle: (v) => set({ sidebarLyricsStyle: v }),
setShowFsArtistPortrait: (v: boolean) => set({ showFsArtistPortrait: v }),
@@ -608,6 +611,8 @@ export const useAuthStore = create<AuthState>()(
setLastSeenChangelogVersion: (v) => set({ lastSeenChangelogVersion: v }),
setSeekbarStyle: (v) => set({ seekbarStyle: v }),
setReducedAnimations: (v) => set({ reducedAnimations: v }),
setQueueNowPlayingCollapsed: (v: boolean) => set({ queueNowPlayingCollapsed: v }),
setEnableHiRes: (v) => set({ enableHiRes: v }),
setAudioOutputDevice: (v) => set({ audioOutputDevice: v }),
setHotCacheEnabled: (v) => set({ hotCacheEnabled: v }),
+20 -5
View File
@@ -21,6 +21,15 @@ interface PreviewState {
elapsed: number;
/** Total preview window in seconds (echoes the duration_sec arg). */
duration: number;
/**
* True only after the engine has emitted `audio:preview-start` for the
* current `previewingId` — i.e. audio is actually playing. Drives the
* progress-ring animation so the ring doesn't run ahead of the speaker
* during the engine's download/decode/seek warmup. Reset to false on every
* `startPreview` call so a switch from track A to track B doesn't carry
* over A's animation state.
*/
audioStarted: boolean;
startPreview: (song: { id: string; title: string; artist: string; coverArt?: string; duration?: number }, location: TrackPreviewLocation) => Promise<void>;
stopPreview: () => Promise<void>;
@@ -40,6 +49,7 @@ export const usePreviewStore = create<PreviewState>((set, get) => ({
previewingTrack: null,
elapsed: 0,
duration: 30,
audioStarted: false,
startPreview: async (song, location) => {
const auth = useAuthStore.getState();
@@ -77,6 +87,7 @@ export const usePreviewStore = create<PreviewState>((set, get) => ({
previewingTrack: { id: song.id, title: song.title, artist: song.artist, coverArt: song.coverArt },
elapsed: 0,
duration: previewDuration,
audioStarted: false,
});
try {
@@ -90,7 +101,7 @@ export const usePreviewStore = create<PreviewState>((set, get) => ({
} catch (e) {
// Roll back optimistic state on failure.
if (get().previewingId === song.id) {
set({ previewingId: null, previewingTrack: null, elapsed: 0 });
set({ previewingId: null, previewingTrack: null, elapsed: 0, audioStarted: false });
}
throw e;
}
@@ -102,15 +113,19 @@ export const usePreviewStore = create<PreviewState>((set, get) => ({
await invoke('audio_preview_stop');
} catch {
/* engine will emit preview-end anyway; clear locally as fallback */
set({ previewingId: null, previewingTrack: null, elapsed: 0 });
set({ previewingId: null, previewingTrack: null, elapsed: 0, audioStarted: false });
}
},
_onStart: (id) => {
if (get().previewingId !== id) {
const current = get().previewingId;
if (current !== id) {
// Engine fired start for an id we didn't track locally — keep id but
// leave previewingTrack as-is (the caller's startPreview() set it).
set({ previewingId: id, elapsed: 0 });
set({ previewingId: id, elapsed: 0, audioStarted: true });
} else {
// Audio is now actually playing — unblock the progress-ring animation.
set({ audioStarted: true });
}
},
@@ -121,6 +136,6 @@ export const usePreviewStore = create<PreviewState>((set, get) => ({
_onEnd: (id) => {
if (get().previewingId !== id) return;
set({ previewingId: null, previewingTrack: null, elapsed: 0 });
set({ previewingId: null, previewingTrack: null, elapsed: 0, audioStarted: false });
},
}));