refactor(playlists): G.79 — extract smart helpers + cover images + 2 lazy-fetch hooks (cluster) (#646)

Four-cut cluster opening the Playlists refactor. 1039 → 763 LOC
(−276).

playlistsSmart — full smart-playlist module: SMART_PREFIX /
LIMIT_MAX / YEAR_MIN / YEAR_MAX constants, GenreMode / YearMode /
SmartFilters / PendingSmartPlaylist / NdSmartRuleNode types,
defaultSmartFilters seed, clampYear / isSmartPlaylistName /
displayPlaylistName / asRecord helpers, parseSmartRulesToFilters
(the Navidrome JSON rule walker), and buildSmartRulesPayload (the
reverse — page-state → Navidrome JSON). The payload builder
becomes parameterized on the filters object so it lives outside
the component.

PlaylistCoverImages — two tiny CachedImage wrappers
(PlaylistSmartCoverCell for the 200 px collage cells,
PlaylistCardMainCover for the 256 px main card cover).

useSmartCoverCollage — replaces the inline useEffect that builds
the 2×2 cover collage for each smart playlist (pulls playlist
tracks, filters to active library scope, collects up to four
unique cover-art ids). Returns the per-playlist id map and
re-fetches on playlist list change or library filter version bump.

usePlaylistsLibraryScopeCounts — replaces the inline useEffect
that recomputes per-playlist song count + total duration under
the current library scope. Chunked into batches of four parallel
fetches.

Playlists drops the inline definitions; pure code move otherwise.
This commit is contained in:
Frank Stellmacher
2026-05-13 15:52:09 +02:00
committed by GitHub
parent 84c682aeb8
commit 2380543d59
5 changed files with 345 additions and 290 deletions
@@ -0,0 +1,29 @@
import React, { useMemo } from 'react';
import { buildCoverArtUrl, coverArtCacheKey } from '../../api/subsonicStreamUrl';
import CachedImage from '../CachedImage';
export function PlaylistSmartCoverCell({ coverId }: { coverId: string }) {
const src = useMemo(() => buildCoverArtUrl(coverId, 200), [coverId]);
const cacheKey = useMemo(() => coverArtCacheKey(coverId, 200), [coverId]);
return (
<CachedImage
className="playlist-cover-cell"
src={src}
cacheKey={cacheKey}
alt=""
/>
);
}
export function PlaylistCardMainCover({ coverArt, alt }: { coverArt: string; alt: string }) {
const src = useMemo(() => buildCoverArtUrl(coverArt, 256), [coverArt]);
const cacheKey = useMemo(() => coverArtCacheKey(coverArt, 256), [coverArt]);
return (
<CachedImage
src={src}
cacheKey={cacheKey}
alt={alt}
className="album-card-cover-img"
/>
);
}