mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
feat(ui): shared card grid with max six columns and virtualization
Add cardGridLayout helpers, useCardGridMetrics, useRemeasureGridVirtualizer, and VirtualCardGrid (TanStack row virtualization, always remeasure on layout changes). Apply to Artists (grid + list unchanged policy), Albums, Composers grid, playlists, radio stations, offline library, and album-heavy browse/detail pages. Respect disableMainstageVirtualLists for a non-virtual grid with the same column rules. Includes vitest coverage for column cap.
This commit is contained in:
@@ -6,11 +6,14 @@ import { useOfflineStore } from '../store/offlineStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import CachedImage from '../components/CachedImage';
|
||||
import { usePerfProbeFlags } from '../utils/perf/perfFlags';
|
||||
import { VirtualCardGrid } from '../components/VirtualCardGrid';
|
||||
|
||||
type FilterType = 'all' | 'album' | 'playlist' | 'artist';
|
||||
|
||||
export default function OfflineLibrary() {
|
||||
const { t } = useTranslation();
|
||||
const perfFlags = usePerfProbeFlags();
|
||||
const serverId = useAuthStore(s => s.activeServerId ?? '');
|
||||
const offlineAlbums = useOfflineStore(s => s.albums);
|
||||
const offlineTracks = useOfflineStore(s => s.tracks);
|
||||
@@ -62,7 +65,7 @@ export default function OfflineLibrary() {
|
||||
const cacheKey = album.coverArt ? coverArtCacheKey(album.coverArt, 300) : '';
|
||||
const trackCount = album.trackIds.filter(tid => !!offlineTracks[`${serverId}:${tid}`]).length;
|
||||
return (
|
||||
<div key={`${album.serverId}:${album.id}`} className="album-card card offline-library-card">
|
||||
<div className="album-card card offline-library-card">
|
||||
<div className="album-card-cover">
|
||||
{coverUrl ? (
|
||||
<CachedImage src={coverUrl} cacheKey={cacheKey} alt={`${album.name} Cover`} loading="lazy" />
|
||||
@@ -124,9 +127,14 @@ export default function OfflineLibrary() {
|
||||
return sortedArtists.map(artistName => (
|
||||
<div key={artistName} className="offline-artist-group">
|
||||
<h2 className="offline-artist-group-heading">{artistName}</h2>
|
||||
<div className="album-grid-wrap">
|
||||
{groups[artistName].map(renderCard)}
|
||||
</div>
|
||||
<VirtualCardGrid
|
||||
items={groups[artistName]}
|
||||
itemKey={(a, _i) => `${a.serverId}:${a.id}`}
|
||||
rowVariant="album"
|
||||
disableVirtualization={perfFlags.disableMainstageVirtualLists}
|
||||
layoutSignal={groups[artistName].length}
|
||||
renderItem={renderCard}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
@@ -172,9 +180,14 @@ export default function OfflineLibrary() {
|
||||
) : filter === 'artist' ? (
|
||||
renderArtistGroups()
|
||||
) : (
|
||||
<div className="album-grid-wrap">
|
||||
{filtered.map(renderCard)}
|
||||
</div>
|
||||
<VirtualCardGrid
|
||||
items={filtered}
|
||||
itemKey={(a, _i) => `${a.serverId}:${a.id}`}
|
||||
rowVariant="album"
|
||||
disableVirtualization={perfFlags.disableMainstageVirtualLists}
|
||||
layoutSignal={filtered.length}
|
||||
renderItem={renderCard}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user