mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
ff99b10faa
* refactor(album-detail): extract sanitizeFilename + useAlbumDetailData Move sanitizeFilename into utils/albumDetailHelpers.ts. Pull the album + related-albums fetch and the starred state seeds (isStarred, starredSongs) into hooks/useAlbumDetailData.ts. AlbumDetail.tsx: 511 → 483 LOC. * refactor(album-detail): extract useAlbumOfflineState hook Move the four primitive-selector offline status reads (cache map + job-status filters + progress totals) into hooks/useAlbumOfflineState.ts. Keeps the re-render minimisation comment with the code that needs it. AlbumDetail.tsx: 483 → 461 LOC. * refactor(album-detail): extract useAlbumDetailSort hook Pull sortKey/Dir/clickCount state, the 3-click natural-reset cycle in handleSort, and the displayedSongs memo (filter + sort comparator) into hooks/useAlbumDetailSort.ts. Rating comparator keeps the same priority chain as the row renderer. AlbumDetail.tsx: 461 → 414 LOC. * refactor(album-detail): extract AlbumDetailToolbar subcomponent Pull the search input + bulk-action cluster (selection count, add-to- playlist popover, clear-selection button) into components/albumDetail/AlbumDetailToolbar.tsx. Parent retains showPlPicker to coordinate the popover close with selection clears. AlbumDetail.tsx: 414 → 369 LOC.
52 lines
2.3 KiB
TypeScript
52 lines
2.3 KiB
TypeScript
import { useOfflineStore } from '../store/offlineStore';
|
|
import { useOfflineJobStore } from '../store/offlineJobStore';
|
|
|
|
interface UseAlbumOfflineStateResult {
|
|
resolvedOfflineStatus: 'none' | 'downloading' | 'cached';
|
|
offlineProgress: { done: number; total: number } | null;
|
|
}
|
|
|
|
/**
|
|
* Combined offline-cache status for an album. Splits the read across
|
|
* three primitive Zustand selectors so the page only re-renders when
|
|
* one of the resolved scalars (status / done count / total count)
|
|
* actually flips — not on every `jobs` array mutation during batch
|
|
* downloads (each track flip would otherwise trigger a full page render).
|
|
*
|
|
* Resolution rules:
|
|
* - If there's any queued / downloading job for this album, status is
|
|
* `downloading` and we expose a `{ done, total }` progress tuple.
|
|
* - Else we look at the persisted cache map: a fully-cached album is
|
|
* one where every trackId in the album-meta has a matching track entry.
|
|
* - Else `none`.
|
|
*
|
|
* `albumId` is allowed to be empty (e.g. while the page is still
|
|
* fetching) — in that case every selector short-circuits to a benign
|
|
* default.
|
|
*/
|
|
export function useAlbumOfflineState(albumId: string, serverId: string): UseAlbumOfflineStateResult {
|
|
const offlineStatus = useOfflineStore((s): 'none' | 'downloading' | 'cached' => {
|
|
if (!albumId) return 'none';
|
|
const meta = s.albums[`${serverId}:${albumId}`];
|
|
const isDownloaded = meta && meta.trackIds.length > 0 && meta.trackIds.every(tid => !!s.tracks[`${serverId}:${tid}`]);
|
|
return isDownloaded ? 'cached' : 'none';
|
|
});
|
|
const isOfflineDownloading = useOfflineJobStore(s =>
|
|
!!albumId && s.jobs.some(j => j.albumId === albumId && (j.status === 'queued' || j.status === 'downloading')),
|
|
);
|
|
const offlineProgressDone = useOfflineJobStore(s => {
|
|
if (!albumId) return 0;
|
|
return s.jobs.filter(j => j.albumId === albumId && (j.status === 'done' || j.status === 'error')).length;
|
|
});
|
|
const offlineProgressTotal = useOfflineJobStore(s => {
|
|
if (!albumId) return 0;
|
|
return s.jobs.filter(j => j.albumId === albumId).length;
|
|
});
|
|
const resolvedOfflineStatus = isOfflineDownloading ? 'downloading' : offlineStatus;
|
|
const offlineProgress = offlineProgressTotal > 0
|
|
? { done: offlineProgressDone, total: offlineProgressTotal }
|
|
: null;
|
|
|
|
return { resolvedOfflineStatus, offlineProgress };
|
|
}
|