import { useSearchParams } from 'react-router'; import { Loader2 } from 'lucide-react'; import { MediaGrid } from './MediaCard'; import { VIEW_PARAM } from './shared'; import { useJellyfinItems, useJellyfinViews } from './useJellyfinData'; // One library, as a grid. // // Which library is `?view=`; the sort is `?sort=` and `?order=`. All three are in the URL rather than in // component state so a particular view of a particular library is a link you can keep — and so the nav can // tell which library is open without either panel talking to the other. const SORTS = [ { id: 'SortName', label: 'Name' }, { id: 'DateCreated', label: 'Recently added' }, { id: 'PremiereDate', label: 'Release date' }, { id: 'CommunityRating', label: 'Rating' }, { id: 'Random', label: 'Random' }, ] as const; const SORT_PARAM = 'sort'; const ORDER_PARAM = 'order'; // A show is a Series, not its episodes: a tvshows library browsed recursively for Episode would be thousands // of tiles of the same six posters. BoxSets are collapsed for the same reason. const TYPES_FOR_COLLECTION: Record = { movies: 'Movie', tvshows: 'Series', homevideos: 'Video,Photo', musicvideos: 'MusicVideo', boxsets: 'BoxSet', playlists: 'Playlist', }; const CHIP = 'rounded-md px-2 py-1 text-[11px] transition-colors'; export const LibrarySection = () => { const [params, setParams] = useSearchParams(); const viewId = params.get(VIEW_PARAM); const sortBy = params.get(SORT_PARAM) ?? 'SortName'; const sortOrder = params.get(ORDER_PARAM) === 'Descending' ? 'Descending' : 'Ascending'; const { data: viewData } = useJellyfinViews(); const view = viewData?.views.find((v) => v.Id === viewId) ?? null; const { data, isLoading } = useJellyfinItems( { parentId: viewId ?? undefined, includeItemTypes: TYPES_FOR_COLLECTION[view?.CollectionType ?? ''] ?? undefined, sortBy, sortOrder, limit: 500, collapseBoxSetItems: false, }, !!viewId, ); if (!viewId) { return (
Pick a library on the left.
); } const items = data?.Items ?? []; const wide = view?.CollectionType === 'homevideos'; const setSort = (id: string) => { const next = new URLSearchParams(params); // Clicking the sort you are already on flips the direction, which is the behaviour every table has and // saves a second control that would only ever be used with this one. if (id === sortBy) next.set(ORDER_PARAM, sortOrder === 'Ascending' ? 'Descending' : 'Ascending'); else next.delete(ORDER_PARAM); next.set(SORT_PARAM, id); setParams(next, { replace: true }); }; return (

{view?.Name ?? 'Library'}

{data?.TotalRecordCount != null ? `${data.TotalRecordCount.toLocaleString()} items` : ''}
{SORTS.map((sort) => ( ))}
{isLoading ? (
) : items.length ? ( ) : (

This library is empty.

)}
); };