From b46603a4df25e3534f304cdd96f3a2d718e29691 Mon Sep 17 00:00:00 2001 From: Anton Lantukh Date: Fri, 26 May 2023 14:14:01 +0200 Subject: [PATCH] feat(series): review fixes --- src/components/VideoList/VideoList.tsx | 55 +++---- src/containers/AppRoutes/AppRoutes.tsx | 4 +- src/hooks/series/useNextEpisode.ts | 18 +-- src/hooks/series/useSeriesId.ts | 4 +- .../LegacySeries.tsx} | 7 +- .../utils.ts | 45 ++++++ src/pages/ScreenRouting/MediaScreenRouter.tsx | 4 +- .../mediaScreens/MediaSeries/MediaSeries.tsx | 9 +- .../mediaScreens/MediaSeries/utils.ts | 144 ------------------ src/utils/media.ts | 12 +- src/utils/series.ts | 87 +---------- src/utils/structuredData.ts | 46 +----- 12 files changed, 93 insertions(+), 342 deletions(-) rename src/pages/{DeprecatedSeries/DeprecatedSeries.tsx => LegacySeries/LegacySeries.tsx} (98%) rename src/pages/{DeprecatedSeries => LegacySeries}/utils.ts (52%) delete mode 100644 src/pages/ScreenRouting/mediaScreens/MediaSeries/utils.ts diff --git a/src/components/VideoList/VideoList.tsx b/src/components/VideoList/VideoList.tsx index c7b00ef6f..9929f451a 100644 --- a/src/components/VideoList/VideoList.tsx +++ b/src/components/VideoList/VideoList.tsx @@ -28,6 +28,9 @@ type Props = { loadMore?: () => void; }; +// eslint-disable-next-line @typescript-eslint/no-empty-function +const defaultLoadMore = () => {}; + function VideoList({ playlist, header, @@ -42,46 +45,26 @@ function VideoList({ isLoggedIn, hasSubscription, hasLoadMore, - loadMore, + loadMore = defaultLoadMore, }: Props) { return (
{!!header && header} - {loadMore ? ( - }> - <> - {playlist?.playlist?.map((playlistItem: PlaylistItem) => ( - onListItemClick && onListItemClick(playlistItem, playlistItem.feedid)} - onHover={() => onListItemHover && onListItemHover(playlistItem)} - loading={isLoading} - isActive={activeMediaId === playlistItem.mediaid} - activeLabel={activeLabel} - isLocked={isLocked(accessModel, isLoggedIn, hasSubscription, playlistItem)} - item={playlistItem} - /> - ))} - - - ) : ( - <> - {playlist?.playlist?.map((playlistItem: PlaylistItem) => ( - onListItemClick && onListItemClick(playlistItem, playlistItem.feedid)} - onHover={() => onListItemHover && onListItemHover(playlistItem)} - loading={isLoading} - isActive={activeMediaId === playlistItem.mediaid} - activeLabel={activeLabel} - isLocked={isLocked(accessModel, isLoggedIn, hasSubscription, playlistItem)} - item={playlistItem} - /> - ))} - - )} + }> + {playlist?.playlist?.map((playlistItem: PlaylistItem) => ( + onListItemClick && onListItemClick(playlistItem, playlistItem.feedid)} + onHover={() => onListItemHover && onListItemHover(playlistItem)} + loading={isLoading} + isActive={activeMediaId === playlistItem.mediaid} + activeLabel={activeLabel} + isLocked={isLocked(accessModel, isLoggedIn, hasSubscription, playlistItem)} + item={playlistItem} + /> + ))} +
); } diff --git a/src/containers/AppRoutes/AppRoutes.tsx b/src/containers/AppRoutes/AppRoutes.tsx index d0ef76fcb..abb580b1b 100644 --- a/src/containers/AppRoutes/AppRoutes.tsx +++ b/src/containers/AppRoutes/AppRoutes.tsx @@ -8,7 +8,7 @@ import About from '#src/pages/About/About'; import Home from '#src/pages/Home/Home'; import Search from '#src/pages/Search/Search'; import User from '#src/pages/User/User'; -import DeprecatedSeries from '#src/pages/DeprecatedSeries/DeprecatedSeries'; +import LegacySeries from '#src/pages/LegacySeries/LegacySeries'; import MediaScreenRouter from '#src/pages/ScreenRouting/MediaScreenRouter'; import PlaylistScreenRouter from '#src/pages/ScreenRouting/PlaylistScreenRouter'; import Layout from '#src/containers/Layout/Layout'; @@ -22,7 +22,7 @@ export default function AppRoutes() { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/hooks/series/useNextEpisode.ts b/src/hooks/series/useNextEpisode.ts index f7468a2ae..1e58c310b 100644 --- a/src/hooks/series/useNextEpisode.ts +++ b/src/hooks/series/useNextEpisode.ts @@ -1,28 +1,18 @@ import { useEffect, useState } from 'react'; -import type { Playlist, PlaylistItem } from '#types/playlist'; +import type { PlaylistItem } from '#types/playlist'; import type { EpisodeMetadata, Series } from '#types/series'; import { getNextItem } from '#src/utils/series'; -export const useNextEpisode = ({ - episode, - seriesPlaylist, - series, - episodeMetadata, -}: { - episode: PlaylistItem | undefined; - seriesPlaylist: Playlist; - series: Series | undefined; - episodeMetadata: EpisodeMetadata | undefined; -}) => { +export const useNextEpisode = ({ series, episodeMetadata }: { series: Series | undefined; episodeMetadata: EpisodeMetadata | undefined }) => { const [nextItem, setNextItem] = useState(undefined); useEffect(() => { async function fetchData() { - const item = await getNextItem(episode, seriesPlaylist, series, episodeMetadata); + const item = await getNextItem(series, episodeMetadata); setNextItem(item); } fetchData(); - }, [episode, seriesPlaylist, series, episodeMetadata]); + }, [series, episodeMetadata]); return nextItem; }; diff --git a/src/hooks/series/useSeriesId.ts b/src/hooks/series/useSeriesId.ts index 382d21a84..3fd079711 100644 --- a/src/hooks/series/useSeriesId.ts +++ b/src/hooks/series/useSeriesId.ts @@ -2,10 +2,10 @@ import { useQuery } from 'react-query'; import type { PlaylistItem } from '#types/playlist'; import { getSeriesByMediaIds } from '#src/services/api.service'; -import { getSeriesIdFromEpisode } from '#src/utils/media'; +import { getLegacySeriesPlaylistIdFromEpisodeTags } from '#src/utils/media'; const useGetSeriesId = (item: PlaylistItem) => { - const staticSeriesId = getSeriesIdFromEpisode(item); + const staticSeriesId = getLegacySeriesPlaylistIdFromEpisodeTags(item); const { isLoading, data } = useQuery(['seriesId', item.mediaid], async () => { // get all series for the given media id diff --git a/src/pages/DeprecatedSeries/DeprecatedSeries.tsx b/src/pages/LegacySeries/LegacySeries.tsx similarity index 98% rename from src/pages/DeprecatedSeries/DeprecatedSeries.tsx rename to src/pages/LegacySeries/LegacySeries.tsx index 28077723d..9b244bfcc 100644 --- a/src/pages/DeprecatedSeries/DeprecatedSeries.tsx +++ b/src/pages/LegacySeries/LegacySeries.tsx @@ -4,9 +4,8 @@ import { Helmet } from 'react-helmet'; import { useTranslation } from 'react-i18next'; import shallow from 'zustand/shallow'; -import { filterSeries, getEpisodesInSeason, getFiltersFromSeries, getNextItem } from './utils'; +import { filterSeries, getEpisodesInSeason, getFiltersFromSeries, getNextItem, generateLegacyEpisodeJSONLD } from './utils'; -import { generateLegacyEpisodeJSONLD } from '#src/utils/structuredData'; import VideoLayout from '#components/VideoLayout/VideoLayout'; import InlinePlayer from '#src/containers/InlinePlayer/InlinePlayer'; import { isLocked } from '#src/utils/entitlements'; @@ -30,7 +29,7 @@ import useQueryParam from '#src/hooks/useQueryParam'; import Loading from '#src/pages/Loading/Loading'; import usePlaylist from '#src/hooks/usePlaylist'; -const DeprecatedSeries = () => { +const LegacySeries = () => { const breakpoint = useBreakpoint(); const { t } = useTranslation('video'); const [playTrailer, setPlayTrailer] = useState(false); @@ -229,4 +228,4 @@ const DeprecatedSeries = () => { ); }; -export default DeprecatedSeries; +export default LegacySeries; diff --git a/src/pages/DeprecatedSeries/utils.ts b/src/pages/LegacySeries/utils.ts similarity index 52% rename from src/pages/DeprecatedSeries/utils.ts rename to src/pages/LegacySeries/utils.ts index d0d106889..6d10c029a 100644 --- a/src/pages/DeprecatedSeries/utils.ts +++ b/src/pages/LegacySeries/utils.ts @@ -1,4 +1,7 @@ +import { deprecatedSeriesURL } from '#src/utils/formatting'; +import { secondsToISO8601 } from '#src/utils/datetime'; import type { Playlist, PlaylistItem } from '#types/playlist'; +import type { EpisodeMetadata } from '#types/series'; /** * Get an array of options for a season filter @@ -42,3 +45,45 @@ export const getEpisodesInSeason = (episode: PlaylistItem | undefined, seriesPla return seriesPlaylist.playlist.filter((i) => i.seasonNumber === episode?.seasonNumber)?.length; }; + +export const generateLegacySeriesMetadata = (seriesPlaylist: Playlist, seriesId: string | undefined) => { + // Use playlist for old flow and media id for a new flow + const seriesCanonical = `${window.location.origin}/s/${seriesId}`; + + return { + '@type': 'TVSeries', + '@id': seriesCanonical, + name: seriesPlaylist.title, + numberOfEpisodes: String(seriesPlaylist.playlist.length), + numberOfSeasons: String( + seriesPlaylist.playlist.reduce(function (list, playlistItem) { + return !playlistItem.seasonNumber || list.includes(playlistItem.seasonNumber) ? list : list.concat(playlistItem.seasonNumber); + }, [] as string[]).length, + ), + }; +}; + +export const generateLegacyEpisodeJSONLD = ( + seriesPlaylist: Playlist, + episode: PlaylistItem | undefined, + episodeMetadata: EpisodeMetadata | undefined, + seriesId: string, +) => { + const episodeCanonical = `${window.location.origin}${deprecatedSeriesURL({ episodeId: episode?.mediaid, seriesId })}`; + const seriesMetadata = generateLegacySeriesMetadata(seriesPlaylist, seriesId); + + if (!episode) { + return JSON.stringify(seriesMetadata); + } + + return JSON.stringify({ + '@context': 'http://schema.org/', + '@type': 'TVEpisode', + '@id': episodeCanonical, + episodeNumber: episodeMetadata?.episodeNumber, + seasonNumber: episodeMetadata?.seasonNumber, + name: episode.title, + uploadDate: secondsToISO8601(episode.pubdate), + partOfSeries: seriesMetadata, + }); +}; diff --git a/src/pages/ScreenRouting/MediaScreenRouter.tsx b/src/pages/ScreenRouting/MediaScreenRouter.tsx index 4a664f8d6..cc8c6c4a3 100644 --- a/src/pages/ScreenRouting/MediaScreenRouter.tsx +++ b/src/pages/ScreenRouting/MediaScreenRouter.tsx @@ -8,7 +8,7 @@ import useMedia from '#src/hooks/useMedia'; import Loading from '#src/pages/Loading/Loading'; import ErrorPage from '#components/ErrorPage/ErrorPage'; import type { PlaylistItem } from '#types/playlist'; -import { isEpisode, isDeprecatedSeriesFlow } from '#src/utils/media'; +import { isEpisode, isLegacySeriesFlow } from '#src/utils/media'; import MediaMovie from '#src/pages/ScreenRouting/mediaScreens/MediaMovie/MediaMovie'; import MediaSeries from '#src/pages/ScreenRouting/mediaScreens/MediaSeries/MediaSeries'; import MediaLiveChannel from '#src/pages/ScreenRouting/mediaScreens/MediaLiveChannel/MediaLiveChannel'; @@ -27,7 +27,7 @@ mediaScreenMap.registerDefault(MediaMovie); // Register legacy series and episode screens when `contentType` is missing mediaScreenMap.register(MediaEpisode, (item) => !!item && isEpisode(item)); -mediaScreenMap.register(MediaSeries, (item) => !!item && isDeprecatedSeriesFlow(item)); +mediaScreenMap.register(MediaSeries, (item) => !!item && isLegacySeriesFlow(item)); const MediaScreenRouter = () => { const params = useParams(); diff --git a/src/pages/ScreenRouting/mediaScreens/MediaSeries/MediaSeries.tsx b/src/pages/ScreenRouting/mediaScreens/MediaSeries/MediaSeries.tsx index b76bb0ebd..ffaf2c145 100644 --- a/src/pages/ScreenRouting/mediaScreens/MediaSeries/MediaSeries.tsx +++ b/src/pages/ScreenRouting/mediaScreens/MediaSeries/MediaSeries.tsx @@ -5,13 +5,12 @@ import { useTranslation } from 'react-i18next'; import shallow from 'zustand/shallow'; import { useSearchParams } from 'react-router-dom'; -import { filterSeries, getEpisodesInSeason, getFiltersFromSeries } from './utils'; - import { generateEpisodeJSONLD } from '#src/utils/structuredData'; import VideoLayout from '#components/VideoLayout/VideoLayout'; import InlinePlayer from '#src/containers/InlinePlayer/InlinePlayer'; import { isLocked } from '#src/utils/entitlements'; import useEntitlement from '#src/hooks/useEntitlement'; +import { getEpisodesInSeason, getFiltersFromSeries } from '#src/utils/series'; import { deprecatedSeriesURL, formatSeriesMetaString, formatVideoMetaString, mediaURL } from '#src/utils/formatting'; import useMedia from '#src/hooks/useMedia'; import { useSeriesData } from '#src/hooks/series/useSeriesData'; @@ -77,9 +76,9 @@ const MediaSeriesContent: ScreenComponent = ({ data: seriesMedia } } = useEpisodes(seriesId, seasonFilter, { enabled: seasonFilter !== undefined && !!series }); const firstEpisode = useMemo(() => episodes?.[0]?.episodes?.[0], [episodes]); - const filteredPlaylist = useMemo(() => filterSeries(seriesPlaylist, episodes), [seriesPlaylist, episodes]); + const playlist = useMemo(() => ({ ...seriesPlaylist, playlist: episodes?.flatMap((e) => e.episodes) || [] }), [seriesPlaylist, episodes]); const episodesInSeason = getEpisodesInSeason(episodeMetadata, series); - const nextItem = useNextEpisode({ episode, seriesPlaylist, series, episodeMetadata }); + const nextItem = useNextEpisode({ series, episodeMetadata }); // Watch history const watchHistoryArray = useWatchHistoryStore((state) => state.watchHistory); @@ -226,7 +225,7 @@ const MediaSeriesContent: ScreenComponent = ({ data: seriesMedia } accessModel={accessModel} isLoggedIn={isLoggedIn} hasSubscription={hasSubscription} - playlist={filteredPlaylist} + playlist={playlist} relatedTitle={inlineLayout ? seriesPlaylist.title : t('episodes')} onItemClick={onCardClick} setFilter={setSeasonFilter} diff --git a/src/pages/ScreenRouting/mediaScreens/MediaSeries/utils.ts b/src/pages/ScreenRouting/mediaScreens/MediaSeries/utils.ts deleted file mode 100644 index 2d6cd439d..000000000 --- a/src/pages/ScreenRouting/mediaScreens/MediaSeries/utils.ts +++ /dev/null @@ -1,144 +0,0 @@ -import type { Playlist, PlaylistItem } from '#types/playlist'; -import type { EpisodeMetadata, EpisodesWithPagination, Series } from '#types/series'; -import { getEpisodes, getSeasonWithEpisodes } from '#src/services/api.service'; -import { mediaURL } from '#src/utils/formatting'; -import { secondsToISO8601 } from '#src/utils/datetime'; - -/** - * Get an array of options for a season filter - */ -export const getFiltersFromSeries = (series: Series | undefined): string[] => { - return series && 'seasons' in series ? series.seasons.map((season) => String(season.season_number)) : []; -}; - -/** - * Get a playlist with episodes based on the selected filter - */ -export const filterSeries = (playlist: Playlist, episodes: EpisodesWithPagination[] | undefined): Playlist => { - // Get a flattened list of episodes and return it as part of the playlist - return { ...playlist, playlist: episodes?.flatMap((e) => e.episodes) || [] }; -}; - -/** - * Retrieve a first episode of the next season - */ -const getNextSeasonEpisode = async (seasonNumber: number, series: Series | undefined) => { - const seasons = series?.seasons; - - // The seasons is the last one in case there is only one season available or it is the same as the last season in the seasons array - const isLastSeason = seasons?.length === 1 || seasons?.[seasons?.length - 1]?.season_number === seasonNumber; - - if (isLastSeason) { - return; - } else { - // Need to know the order of seasons to choose the next season number - const hasAscendingOrder = (Number(seasons?.[1]?.season_number) || 0) > (Number(seasons?.[0]?.season_number) || 0); - const nextSeasonNumber = hasAscendingOrder ? seasonNumber + 1 : seasonNumber - 1; - - return (await getSeasonWithEpisodes(series?.series_id, nextSeasonNumber, 0, 1))?.episodes?.[0]; - } -}; - -/** - * Get a next episode of the selected season / episodes list - */ -const getNextEpisode = async (seasonNumber: number, seriesId: string, pageWithEpisode: number) => { - if (seasonNumber) { - return (await getSeasonWithEpisodes(seriesId, seasonNumber, pageWithEpisode, 1))?.episodes?.[0]; - } else { - return (await getEpisodes(seriesId, pageWithEpisode, 1))?.episodes?.[0]; - } -}; - -const getNewFlowNextEpisode = async (series: Series | undefined, episodeMetadata: EpisodeMetadata | undefined) => { - if (!episodeMetadata || !series) { - return; - } - - const seasonNumber = Number(episodeMetadata.seasonNumber); - const episodeNumber = Number(episodeMetadata.episodeNumber); - - // Get initial data to collect information about total number of elements - const { episodes, pagination } = - seasonNumber !== 0 ? await getSeasonWithEpisodes(series?.series_id, seasonNumber, 0) : await getEpisodes(series?.series_id, 0); - - if (episodes.length === 1 && seasonNumber) { - return getNextSeasonEpisode(seasonNumber, series); - } - - // Both episodes and seasons can be sorted by asc / desc - const hasAscendingOrder = (Number(episodes[1].episodeNumber) || 0) > (Number(episodes[0].episodeNumber) || 0); - const nextEpisodeNumber = hasAscendingOrder ? episodeNumber + 1 : episodeNumber - 1; - // First page has 0 number, that is why we use Math.floor here und subtract 1 - const pageWithEpisode = Math.floor(hasAscendingOrder ? nextEpisodeNumber - 1 : pagination.total - nextEpisodeNumber - 1); - // Consider the case when we have a next episode in the retrieved list - const nextElementIndex = episodes.findIndex((el) => Number(el.episodeNumber) === nextEpisodeNumber); - - if (nextElementIndex !== -1) { - return episodes[nextElementIndex]; - } - - // Fetch the next episodes of the season when there is more to fetch - if (pageWithEpisode < pagination.total) { - return getNextEpisode(seasonNumber, series?.series_id, pageWithEpisode); - // Switch selected season in case the current one has nor more episodes inside - } else if (seasonNumber) { - return getNextSeasonEpisode(seasonNumber, series); - } -}; - -export const getNextItem = async ( - episode: PlaylistItem | undefined, - series: Series | undefined, - episodeMetadata: EpisodeMetadata | undefined, -): Promise => { - if (!episode) return; - - const nextEpisode = await getNewFlowNextEpisode(series, episodeMetadata); - - return nextEpisode; -}; - -/** Get a total amount of episodes in a season */ -export const getEpisodesInSeason = (episodeMetadata: EpisodeMetadata | undefined, series: Series | undefined) => { - return (series?.seasons || []).find((el) => el.season_number === Number(episodeMetadata?.seasonNumber))?.episode_count; -}; - -export const generateNewSeriesMetadata = (series: Series, media: PlaylistItem, seriesId: string | undefined) => { - // Use playlist for old flow and media id for a new flow - const seriesCanonical = `${window.location.origin}/m/${seriesId}`; - - return { - '@type': 'TVSeries', - '@id': seriesCanonical, - name: media.title, - numberOfEpisodes: String(series?.episode_count), - numberOfSeasons: String(series?.season_count || 0), - }; -}; - -export const generateNewEpisodeJSONLD = ( - series: Series, - media: PlaylistItem, - episode: PlaylistItem | undefined, - episodeMetadata: EpisodeMetadata | undefined, - seriesId: string, -) => { - const episodeCanonical = `${window.location.origin}${mediaURL({ media, episodeId: episode?.mediaid })}`; - const seriesMetadata = generateNewSeriesMetadata(series, media, seriesId); - - if (!episode) { - return JSON.stringify(seriesMetadata); - } - - return JSON.stringify({ - '@context': 'http://schema.org/', - '@type': 'TVEpisode', - '@id': episodeCanonical, - episodeNumber: episodeMetadata?.episodeNumber, - seasonNumber: episodeMetadata?.seasonNumber, - name: episode.title, - uploadDate: secondsToISO8601(episode.pubdate), - partOfSeries: seriesMetadata, - }); -}; diff --git a/src/utils/media.ts b/src/utils/media.ts index be9627bdb..a4943dd90 100644 --- a/src/utils/media.ts +++ b/src/utils/media.ts @@ -16,21 +16,21 @@ export const getSeriesPlaylistIdFromCustomParams = (item: (PlaylistItem & Deprec item ? item.seriesPlayListId || item.seriesPlaylistId || item.seriesId : undefined; // For the deprecated flow we store seriesId in the media custom params -export const isDeprecatedSeriesFlow = (item: PlaylistItem) => { +export const isLegacySeriesFlow = (item: PlaylistItem) => { return typeof getSeriesPlaylistIdFromCustomParams(item) !== 'undefined'; }; // For the new series flow we use contentType custom param to define media item to be series // In this case media item and series item have the same id -export const isNewSeriesFlow = (item: PlaylistItem) => item.contentType === CONTENT_TYPE.series; +export const isSeriesContentType = (item: PlaylistItem) => item.contentType === CONTENT_TYPE.series; -export const isSeries = (item: PlaylistItem) => isDeprecatedSeriesFlow(item) || isNewSeriesFlow(item); +export const isSeries = (item: PlaylistItem) => isLegacySeriesFlow(item) || isSeriesContentType(item); export const isEpisode = (item: PlaylistItem) => { return typeof item?.episodeNumber !== 'undefined' || item?.contentType === CONTENT_TYPE.episode; }; -export const getSeriesIdFromEpisode = (item: PlaylistItem | undefined) => { +export const getLegacySeriesPlaylistIdFromEpisodeTags = (item: PlaylistItem | undefined) => { if (!item || !isEpisode(item)) { return; } @@ -44,10 +44,6 @@ export const getSeriesIdFromEpisode = (item: PlaylistItem | undefined) => { return seriesIdTag.split('_')[1]; } - if (item.seriesId) { - return item.seriesId; - } - return; }; diff --git a/src/utils/series.ts b/src/utils/series.ts index 13c44c4de..c917c122b 100644 --- a/src/utils/series.ts +++ b/src/utils/series.ts @@ -1,44 +1,11 @@ -import type { Playlist, PlaylistItem } from '#types/playlist'; -import type { EpisodeMetadata, EpisodesWithPagination, Series } from '#types/series'; +import type { EpisodeMetadata, Series } from '#types/series'; import { getEpisodes, getSeasonWithEpisodes } from '#src/services/api.service'; /** * Get an array of options for a season filter */ -export const getFiltersFromSeries = (playlist: Playlist, series: Series | undefined): string[] => { - const isNewFlow = !!series; - - // For the new series flow we already have episodes sorted correctly on the back-end side - if (isNewFlow) { - return 'seasons' in series ? series.seasons.map((season) => String(season.season_number)) : []; - } - - // Old series doesn't have sorting supported and just aggregates all episodes in one playlist - // So we need to sort the playlist manually based on the selected filter (season). - return playlist.playlist - .reduce((filters: string[], item) => (item.seasonNumber && filters.includes(item.seasonNumber) ? filters : filters.concat(item.seasonNumber || '')), []) - .slice() - .sort(); -}; - -/** - * Get a playlist with episodes based on the selected filter - */ -export const filterSeries = (playlist: Playlist, episodes: EpisodesWithPagination[] | undefined, filter: string | undefined): Playlist => { - const isNewFlow = !!episodes?.length; - - if (isNewFlow) { - // Get a flattened list of episodes and return it as part of the playlist - return { ...playlist, playlist: episodes.flatMap((e) => e.episodes) }; - } - - if (filter === '') return playlist; - - return { - ...playlist, - // Filter episodes manually for the old flow where our playlists includes all episodes with all seasons - playlist: playlist.playlist.filter(({ seasonNumber }) => seasonNumber === filter), - }; +export const getFiltersFromSeries = (series: Series | undefined): string[] => { + return series && 'seasons' in series ? series.seasons.map((season) => String(season.season_number)) : []; }; /** @@ -72,7 +39,7 @@ const getNextEpisode = async (seasonNumber: number, seriesId: string, pageWithEp } }; -const getNewFlowNextEpisode = async (series: Series | undefined, episodeMetadata: EpisodeMetadata | undefined) => { +export const getNextItem = async (series: Series | undefined, episodeMetadata: EpisodeMetadata | undefined) => { if (!episodeMetadata || !series) { return; } @@ -109,49 +76,7 @@ const getNewFlowNextEpisode = async (series: Series | undefined, episodeMetadata } }; -export const getNextItem = async ( - episode: PlaylistItem | undefined, - seriesPlaylist: Playlist, - series: Series | undefined, - episodeMetadata: EpisodeMetadata | undefined, -): Promise => { - const isNewFlow = !!series; - - if (!episode || !seriesPlaylist) return; - - // Using new flow when episodes are available - if (isNewFlow) { - const nextEpisode = await getNewFlowNextEpisode(series, episodeMetadata); - - return nextEpisode; - } - - // For the old flow we already have all the episodes and seasons so we just need to find them in the array - const index = seriesPlaylist?.playlist?.findIndex(({ mediaid }) => mediaid === episode.mediaid); - - return seriesPlaylist?.playlist?.[index + 1]; -}; - /** Get a total amount of episodes in a season */ -export const getEpisodesInSeason = ( - episode: PlaylistItem | undefined, - episodeMetadata: EpisodeMetadata | undefined, - seriesPlaylist: Playlist, - series: Series | undefined, -) => { - const isNewFlow = !!series; - - if (isNewFlow) { - return (series?.seasons || []).find((el) => el.season_number === Number(episodeMetadata?.seasonNumber))?.episode_count; - } - - return seriesPlaylist.playlist.filter((i) => i.seasonNumber === episode?.seasonNumber)?.length; -}; - -export const getFirstEpisode = (seriesPlaylist: Playlist, episodesData: EpisodesWithPagination[] | undefined) => { - if (episodesData) { - return episodesData?.[0]?.episodes?.[0]; - } - - return seriesPlaylist?.playlist?.[0]; +export const getEpisodesInSeason = (episodeMetadata: EpisodeMetadata | undefined, series: Series | undefined) => { + return (series?.seasons || []).find((el) => el.season_number === Number(episodeMetadata?.seasonNumber))?.episode_count; }; diff --git a/src/utils/structuredData.ts b/src/utils/structuredData.ts index 68f7c9c53..08340adce 100644 --- a/src/utils/structuredData.ts +++ b/src/utils/structuredData.ts @@ -1,7 +1,7 @@ -import { deprecatedSeriesURL, mediaURL } from './formatting'; +import { mediaURL } from './formatting'; import { secondsToISO8601 } from './datetime'; -import type { Playlist, PlaylistItem } from '#types/playlist'; +import type { PlaylistItem } from '#types/playlist'; import type { EpisodeMetadata, Series } from '#types/series'; export const generateMovieJSONLD = (item: PlaylistItem) => { @@ -19,48 +19,6 @@ export const generateMovieJSONLD = (item: PlaylistItem) => { }); }; -export const generateLegacySeriesMetadata = (seriesPlaylist: Playlist, seriesId: string | undefined) => { - // Use playlist for old flow and media id for a new flow - const seriesCanonical = `${window.location.origin}/s/${seriesId}`; - - return { - '@type': 'TVSeries', - '@id': seriesCanonical, - name: seriesPlaylist.title, - numberOfEpisodes: String(seriesPlaylist.playlist.length), - numberOfSeasons: String( - seriesPlaylist.playlist.reduce(function (list, playlistItem) { - return !playlistItem.seasonNumber || list.includes(playlistItem.seasonNumber) ? list : list.concat(playlistItem.seasonNumber); - }, [] as string[]).length, - ), - }; -}; - -export const generateLegacyEpisodeJSONLD = ( - seriesPlaylist: Playlist, - episode: PlaylistItem | undefined, - episodeMetadata: EpisodeMetadata | undefined, - seriesId: string, -) => { - const episodeCanonical = `${window.location.origin}${deprecatedSeriesURL({ episodeId: episode?.mediaid, seriesId })}`; - const seriesMetadata = generateLegacySeriesMetadata(seriesPlaylist, seriesId); - - if (!episode) { - return JSON.stringify(seriesMetadata); - } - - return JSON.stringify({ - '@context': 'http://schema.org/', - '@type': 'TVEpisode', - '@id': episodeCanonical, - episodeNumber: episodeMetadata?.episodeNumber, - seasonNumber: episodeMetadata?.seasonNumber, - name: episode.title, - uploadDate: secondsToISO8601(episode.pubdate), - partOfSeries: seriesMetadata, - }); -}; - export const generateSeriesMetadata = (series: Series, media: PlaylistItem, seriesId: string | undefined) => { // Use playlist for old flow and media id for a new flow const seriesCanonical = `${window.location.origin}/m/${seriesId}`;