import React, {Fragment, ReactNode, useState} from 'react'; import {useSeasonEpisodes} from '@app/titles/requests/use-season-episodes'; import {SiteSectionHeading} from '@app/titles/site-section-heading'; import {Trans} from '@common/i18n/trans'; import {ContentGridLayout} from '@app/channels/content-grid/content-grid-layout'; import {EpisodePoster} from '@app/episodes/episode-poster/episode-poster'; import {Link, useParams} from 'react-router-dom'; import {getWatchLink} from '@app/videos/watch-page/get-watch-link'; import {Episode} from '@app/titles/models/episode'; import {Title} from '@app/titles/models/title'; import {CompactSeasonEpisode} from '@app/episodes/compact-season-episode'; import { Menu, MenuItem, MenuTrigger, } from '@common/ui/navigation/menu/menu-trigger'; import {Button} from '@common/ui/buttons/button'; import {message} from '@common/i18n/message'; import {SortIcon} from '@common/icons/material/Sort'; import {ExpandMoreIcon} from '@common/icons/material/ExpandMore'; import {FormattedDate} from '@common/i18n/formatted-date'; import {Skeleton} from '@common/ui/skeleton/skeleton'; import {AnimatePresence, m} from 'framer-motion'; import {opacityAnimation} from '@common/ui/animation/opacity-animation'; import {UseInfiniteDataResult} from '@common/ui/infinite-scroll/use-infinite-data'; import {InfiniteScrollSentinel} from '@common/ui/infinite-scroll/infinite-scroll-sentinel'; import {FormattedDuration} from '@common/i18n/formatted-duration'; import {GetTitleResponse} from '@app/titles/requests/use-title'; interface Props { data: GetTitleResponse; label?: ReactNode; showSeasonSelector?: boolean; } export function TitlePageEpisodeGrid({data, label, showSeasonSelector}: Props) { const {season} = useParams(); const [selectedSeason, setSelectedSeason] = useState( season ? parseInt(season) : 1, ); const query = useSeasonEpisodes( data.episodes, { perPage: 21, excludeDescription: 'true', }, { season: selectedSeason, willSortOrFilter: true, defaultOrderBy: 'episode_number', defaultOrderDir: 'asc', titleId: data.title.id, }, ); const {isInitialLoading, items, sortDescriptor, setSortDescriptor} = query; return (
{showSeasonSelector && ( )} { const [orderBy, orderDir] = value.split(':'); setSortDescriptor({ orderBy, orderDir: orderDir as 'asc' | 'desc', }); }} /> } > {label || } {isInitialLoading ? ( ) : ( )}
); } interface GridItemProps { episode: Episode; title: Title; } function GridItem({episode, title}: GridItemProps) { const runtime = episode.runtime || title.runtime; const name = ( -{' '} {episode.name} ); return (
) : null } />
{episode.release_date && (
)}
{episode.primary_video ? ( {name} ) : ( name )}
); } interface EpisodeGridProps { episodes: Episode[]; title: Title; query: UseInfiniteDataResult; } function EpisodeGrid({title, episodes, query}: EpisodeGridProps) { return ( {episodes.map(episode => ( ))} ); } function SkeletonGrid() { return ( {[...new Array(6).keys()].map(number => (
))}
); } interface SeasonSelectorProps { selectedSeason: number; onSeasonChange: (newSeason: number) => void; seasonCount: number; } function SeasonSelector({ selectedSeason, onSeasonChange, seasonCount, }: SeasonSelectorProps) { return ( onSeasonChange(newValue as number)} selectionMode="single" > {[...new Array(seasonCount).keys()].map(number => { const seasonNumber = number + 1; return ( ); })} ); } const SortOptions = [ { value: 'episode_number:desc', label: message('Newest'), }, { value: 'episode_number:asc', label: message('Oldest'), }, ]; interface SortButtonProps { value: string; onValueChange: (newValue: string) => void; } function SortButton({value, onValueChange}: SortButtonProps) { let selectedOption = SortOptions.find(option => option.value === value); if (!selectedOption) { selectedOption = SortOptions[0]; } return ( onValueChange(newValue as string)} selectionMode="single" > {SortOptions.map(option => ( ))} ); }