import { useMemo } from 'react' import ContentRow from '../ui/ContentRow' import { useTmdbDiscoverMovies } from '../../hooks/use-tmdb' import { useLibraryByTmdbId } from '../../hooks/use-jellyfin' import { usePreferencesStore } from '../../stores/preferences-store' import { mapTmdbToJf } from '../../lib/tmdb-mapping' import { filterToMissing } from '../../pages/discover/helpers' import type { TmdbMovie } from '../../api/tmdb' interface CanonicalList { id: string title: string subtitle: string params: Record /** Optional client-side filter applied on top of TMDB results. */ extra?: (m: TmdbMovie) => boolean } /** * Hand-curated approximations of the canonical "you should have seen this" * lists - AFI / Sight & Sound / Oscar winners - built from TMDB discover * parameters rather than the volatile user-created /list endpoint. * * Each entry is one ContentRow. The rows self-hide via the existing * filterToMissing pipeline when the user already owns everything in them. */ const LISTS: CanonicalList[] = [ { id: 'best-picture-winners', title: 'Best Picture winners', subtitle: 'Academy Award winners across the decades', params: { // TMDB keyword 210024 = "academy award - best picture winner" with_keywords: '210024', sort_by: 'vote_average.desc', 'vote_count.gte': '500', }, }, { id: 'top-250', title: 'The canonical 250', subtitle: 'Films that have settled into the canon - massive vote count, top scores', params: { 'vote_count.gte': '10000', 'vote_average.gte': '8', sort_by: 'vote_average.desc', }, }, { id: 'highest-grossing', title: 'Highest grossing of all time', subtitle: 'The films that made everyone show up', params: { sort_by: 'revenue.desc', 'vote_count.gte': '500', }, }, { id: 'modern-classics', title: 'Modern classics', subtitle: 'Post-2000 films that already feel essential', params: { 'primary_release_date.gte': '2000-01-01', 'vote_count.gte': '3000', 'vote_average.gte': '8', sort_by: 'vote_average.desc', }, }, { id: 'foreign-canon', title: 'Foreign cinema canon', subtitle: 'Non-English films with critical pedigree', params: { 'vote_count.gte': '1500', 'vote_average.gte': '7.8', sort_by: 'vote_average.desc', }, extra: m => !!m.original_language && m.original_language !== 'en', }, { id: 'animation-canon', title: 'Animation canon', subtitle: 'Highest-rated animated features across studios', params: { with_genres: '16', 'vote_count.gte': '2000', 'vote_average.gte': '7.5', sort_by: 'vote_average.desc', }, }, ] /** * Section wrapper for the canonical-lists block. Only renders on the * movies tab because the TMDB keywords + revenue sorts only make sense * for films - TV equivalents would need different queries. */ export default function CanonicalLists() { return (
Canonical lists

The books and ballots

Films that show up on every "best of" list - filtered to ones you don't own yet.

{LISTS.map(list => ( ))}
) } function CanonicalRow({ list }: { list: CanonicalList }) { const movies = useTmdbDiscoverMovies(list.params) const lib = useLibraryByTmdbId() const hideAdult = usePreferencesStore(s => s.hideAdult) const items = useMemo(() => { let raw = (movies.data?.results || []).map(m => ({ ...m, media_type: 'movie' as const })) if (list.extra) raw = raw.filter(list.extra) return mapTmdbToJf(filterToMissing(raw, lib.data, hideAdult, m => !!m.adult), lib.data) }, [movies.data, lib.data, hideAdult, list]) if (items.length === 0) return null return ( ) }