mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
2380543d59
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.
67 lines
2.5 KiB
TypeScript
67 lines
2.5 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { filterSongsToActiveLibrary } from '../api/subsonicLibrary';
|
|
import { getPlaylist } from '../api/subsonicPlaylists';
|
|
import type { SubsonicPlaylist } from '../api/subsonicTypes';
|
|
|
|
export interface PlaylistsLibraryScopeCountsResult {
|
|
filteredSongCountByPlaylist: Record<string, number>;
|
|
filteredDurationByPlaylist: Record<string, number>;
|
|
}
|
|
|
|
/**
|
|
* Recompute song count + total duration for each playlist under the current
|
|
* library scope. Chunked into batches of 4 parallel fetches to avoid hammering
|
|
* Navidrome on large playlists. Re-runs when the playlist list changes or
|
|
* when the active library filter version bumps.
|
|
*/
|
|
export function usePlaylistsLibraryScopeCounts(
|
|
playlists: SubsonicPlaylist[],
|
|
musicLibraryFilterVersion: number,
|
|
): PlaylistsLibraryScopeCountsResult {
|
|
const [filteredSongCountByPlaylist, setFilteredSongCountByPlaylist] = useState<Record<string, number>>({});
|
|
const [filteredDurationByPlaylist, setFilteredDurationByPlaylist] = useState<Record<string, number>>({});
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
const run = async () => {
|
|
if (playlists.length === 0) {
|
|
if (!cancelled) {
|
|
setFilteredSongCountByPlaylist({});
|
|
setFilteredDurationByPlaylist({});
|
|
}
|
|
return;
|
|
}
|
|
const ids = playlists.map((pl) => pl.id);
|
|
const next: Record<string, number> = {};
|
|
const nextDuration: Record<string, number> = {};
|
|
for (let i = 0; i < ids.length; i += 4) {
|
|
const chunk = ids.slice(i, i + 4);
|
|
const rows = await Promise.all(
|
|
chunk.map(async (id) => {
|
|
try {
|
|
const { songs } = await getPlaylist(id);
|
|
const filtered = await filterSongsToActiveLibrary(songs);
|
|
const duration = filtered.reduce((acc, s) => acc + (s.duration ?? 0), 0);
|
|
return [id, filtered.length, duration] as const;
|
|
} catch {
|
|
return [id, -1, -1] as const;
|
|
}
|
|
}),
|
|
);
|
|
for (const [id, count, duration] of rows) {
|
|
if (count >= 0) next[id] = count;
|
|
if (duration >= 0) nextDuration[id] = duration;
|
|
}
|
|
}
|
|
if (!cancelled) {
|
|
setFilteredSongCountByPlaylist(next);
|
|
setFilteredDurationByPlaylist(nextDuration);
|
|
}
|
|
};
|
|
run();
|
|
return () => { cancelled = true; };
|
|
}, [playlists, musicLibraryFilterVersion]);
|
|
|
|
return { filteredSongCountByPlaylist, filteredDurationByPlaylist };
|
|
}
|