Skip to content

Commit

Permalink
feat(series): change navigaton
Browse files Browse the repository at this point in the history
- use `s` route for series and episode information
  • Loading branch information
AntonLantukh committed May 19, 2023
1 parent 5982c9a commit 2d1667a
Show file tree
Hide file tree
Showing 23 changed files with 469 additions and 408 deletions.
2 changes: 1 addition & 1 deletion src/components/Filter/Filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const Filter: FC<Props> = ({ name, value, defaultLabel, options, setValue, value
{options.map((option) => (
<Button label={`${valuePrefix}${option}`} onClick={() => setValue(option)} key={option} active={value === option} role="option" />
))}
<Button label={defaultLabel} onClick={() => setValue('all')} active={value === 'all'} key={defaultLabel} role="option" />
<Button label={defaultLabel} onClick={() => setValue('')} active={value === ''} key={defaultLabel} role="option" />
</div>
) : (
<div className={styles.filterDropDown}>
Expand Down
3 changes: 2 additions & 1 deletion src/components/VideoLayout/VideoLayout.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
'player related'
'details related';
grid-template-columns: 1fr 360px;
grid-template-rows: auto 1fr;
grid-template-rows: auto 0.3fr;

@include responsive.mobile-and-tablet() {
grid-template-areas:
Expand Down Expand Up @@ -71,6 +71,7 @@

@include responsive.desktop-only() {
padding-left: 24px;
overflow-y: scroll;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/components/VideoLayout/VideoLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ const VideoLayout: React.FC<Props> = ({
accessModel={accessModel}
isLoggedIn={isLoggedIn}
hasSubscription={hasSubscription}
hasLoadMore={hasLoadMore}
loadMore={loadMore}
/>
</div>
);
Expand Down
29 changes: 24 additions & 5 deletions src/components/VideoList/VideoList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import classNames from 'classnames';
import InfiniteScroll from 'react-infinite-scroller';

import styles from './VideoList.module.scss';

Expand All @@ -8,6 +9,7 @@ import { isLocked } from '#src/utils/entitlements';
import { testId } from '#src/utils/common';
import type { AccessModel } from '#types/Config';
import type { Playlist, PlaylistItem } from '#types/playlist';
import InfiniteScrollLoader from '#components/InfiniteScrollLoader/InfiniteScrollLoader';

type Props = {
playlist?: Playlist;
Expand All @@ -22,6 +24,8 @@ type Props = {
accessModel: AccessModel;
isLoggedIn: boolean;
hasSubscription: boolean;
hasLoadMore?: boolean;
loadMore?: () => void;
};

function VideoList({
Expand All @@ -37,12 +41,13 @@ function VideoList({
accessModel,
isLoggedIn,
hasSubscription,
hasLoadMore,
loadMore,
}: Props) {
return (
<div className={classNames(styles.container, !!className && className)} data-testid={testId('video-list')}>
{!!header && header}
{playlist &&
playlist.playlist.map((playlistItem: PlaylistItem) => {
const List = () => {
return (
<>
{playlist?.playlist?.map((playlistItem: PlaylistItem) => {
const { mediaid } = playlistItem;

return (
Expand All @@ -59,6 +64,20 @@ function VideoList({
/>
);
})}
</>
);
};

return (
<div className={classNames(styles.container, !!className && className)} data-testid={testId('video-list')}>
{!!header && header}
{loadMore ? (
<InfiniteScroll pageStart={0} loadMore={loadMore} hasMore={hasLoadMore} loader={<InfiniteScrollLoader key="loader" />}>
<List />
</InfiniteScroll>
) : (
<List />
)}
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/containers/AppRoutes/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function AppRoutes() {
<Route index element={<Home />} />
<Route path="/p/:id" element={<PlaylistScreenRouter />} />
<Route path="/m/:id/*" element={<MediaScreenRouter />} />
<Route path="/s/:id/:slug" element={<Series />} />
<Route path="/s/:id/*" element={<Series />} />
<Route path="/q/*" element={<Search />} />
<Route path="/u/*" element={<User />} />
<Route path="/o/about" element={<About />} />
Expand Down
77 changes: 0 additions & 77 deletions src/containers/SeriesRedirect/SeriesRedirect.tsx

This file was deleted.

10 changes: 5 additions & 5 deletions src/hooks/series/useEpisodeMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@ import type { PlaylistItem } from '#types/playlist';

// Get `episodeNumber` and `seasonNumber` for the new series flow
export const useEpisodeMetadata = (
episode: PlaylistItem,
episode: PlaylistItem | undefined,
series: Series | undefined,
options: { enabled: boolean },
): { isLoading: boolean; data: EpisodeMetadata | undefined } => {
const oldFlowMetadata = { episodeNumber: episode?.episodeNumber || '0', seasonNumber: episode?.seasonNumber || '0' };

const { isLoading, data }: UseQueryResult<EpisodeMetadata | undefined, ApiError | null> = useQuery(
['episodeId', episode.mediaid],
['episodeId', series?.series_id, episode?.mediaid],
async () => {
if (!episode.mediaid) {
if (!episode) {
throw Error('No episode id provided');
}

const seriesDictionary = await getSeriesByMediaIds([episode.mediaid]);
// Get an item details of the associated series (we need its episode and season)
const { season_number, episode_number } = (seriesDictionary?.[episode.mediaid] || []).find((el) => el.series_id === series?.series_id) || {};
// Add seriesId to work with watch history
return { episodeNumber: String(episode_number || 0), seasonNumber: String(season_number || 0), seriesId: series?.series_id };

return { episodeNumber: String(episode_number || 0), seasonNumber: String(season_number || 0) };
},
{
// Only enable this query when having new series flow
Expand Down
21 changes: 10 additions & 11 deletions src/hooks/series/useSeriesData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,17 @@ import { useQuery, UseQueryResult } from 'react-query';

import usePlaylist from '#src/hooks/usePlaylist';
import type { Playlist, PlaylistItem } from '#types/playlist';
import type { Series } from '#types/series';
import type { Series, SeriesData } from '#types/series';
import { getMediaById, getSeries } from '#src/services/api.service';
import type { ApiError } from '#src/utils/api';

const DEFAULT_DATA = { title: '', playlist: [] };

type Data = {
playlist: Playlist;
series: Series | undefined;
media: PlaylistItem | undefined;
};

export const useSeriesData = (
legacySeriesPlaylistId: string | undefined,
seriesId: string | undefined,
): {
data: Data;
data: SeriesData;
isPlaylistError: boolean;
isLoading: boolean;
} => {
Expand Down Expand Up @@ -52,13 +46,18 @@ export const useSeriesData = (
const usePlaylistFallback = isSeriesError && seriesError?.code === 404;

// We enable it only after new series api unsuccessful load (404 error showing that such the series with the following id doesn't exist)
const { data: playlistData, isLoading: isPlaylistLoading, error: playlistError } = usePlaylist(legacySeriesPlaylistId, {}, usePlaylistFallback, false);
const { data: playlistData, isLoading: isPlaylistLoading, isError: isPlaylistError } = usePlaylist(legacySeriesPlaylistId, {}, usePlaylistFallback, false);

const seriesPlaylist = seriesData?.playlist || playlistData || DEFAULT_DATA;

return {
data: { playlist: seriesPlaylist, series: seriesData?.series, media: seriesData?.media },
isPlaylistError: Boolean(seriesError && playlistError),
data: {
playlist: seriesPlaylist,
series: seriesData?.series,
media: seriesData?.media,
contentType: seriesData?.media?.contentType || playlistData?.contentType,
},
isPlaylistError,
isLoading: isSeriesLoading || isPlaylistLoading,
};
};
5 changes: 3 additions & 2 deletions src/hooks/usePlaylist.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useQuery } from 'react-query';

import { generatePlaylistPlaceholder } from '#src/utils/collection';
import type { GetPlaylistParams } from '#types/playlist';
import type { GetPlaylistParams, Playlist } from '#types/playlist';
import { getPlaylistById } from '#src/services/api.service';
import { queryClient } from '#src/containers/QueryProvider/QueryProvider';
import type { ApiError } from '#src/utils/api';

const placeholderData = generatePlaylistPlaceholder(30);

Expand All @@ -22,7 +23,7 @@ export default function usePlaylist(playlistId?: string, params: GetPlaylistPara
const queryKey = ['playlist', playlistId, params];
const isEnabled = !!playlistId && enabled;

return useQuery(queryKey, () => callback(playlistId, params), {
return useQuery<Playlist | undefined, ApiError>(queryKey, () => callback(playlistId, params), {
enabled: isEnabled,
placeholderData: usePlaceholderData && isEnabled ? placeholderData : undefined,
refetchInterval: (data, _) => (data?.refetch ? 1000 * 30 : false),
Expand Down
46 changes: 35 additions & 11 deletions src/pages/ScreenRouting/mediaScreens/MediaSeries/MediaSeries.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,50 @@
import type { ScreenComponent } from '#types/screens';
import { getSeriesPlaylistIdFromCustomParams } from '#src/utils/media';
import SeriesRedirect from '#src/containers/SeriesRedirect/SeriesRedirect';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Navigate } from 'react-router';

import { seriesURL } from '#src/utils/formatting';
import { useSeriesData } from '#src/hooks/series/useSeriesData';
import ErrorPage from '#components/ErrorPage/ErrorPage';
import { useWatchHistoryStore } from '#src/stores/WatchHistoryStore';
import type { PlaylistItem } from '#types/playlist';
import type { ScreenComponent } from '#types/screens';
import useQueryParam from '#src/hooks/useQueryParam';
import Loading from '#src/pages/Loading/Loading';
import { useWatchHistoryStore } from '#src/stores/WatchHistoryStore';
import { getSeriesPlaylistIdFromCustomParams } from '#src/utils/media';

/**
* This media screen is used to redirect a series linking media item to an episode page.
* This media series screen is used to redirect a series linking media item to the series page.
*/
const MediaSeries: ScreenComponent<PlaylistItem> = ({ data: media, isLoading }) => {
const seriesId = getSeriesPlaylistIdFromCustomParams(media) || '';
const MediaSeries: ScreenComponent<PlaylistItem> = ({ data: media, isLoading: isMediaLoading }) => {
const { t } = useTranslation('video');
const play = useQueryParam('play') === '1';
const feedId = useQueryParam('r');

const mediaId = media.mediaid;

const legacySeriesPlaylistId = getSeriesPlaylistIdFromCustomParams(media) || '';
const { isLoading: isSeriesLoading, isPlaylistError, data } = useSeriesData(legacySeriesPlaylistId, mediaId);
const { series, playlist: seriesPlaylist } = data || {};

// Retrieve watch history for new flow and find an episode of the selected series (if present)
const watchHistoryDictionary = useWatchHistoryStore((state) => state.watchHistory);
const episodeInProgress = watchHistoryDictionary.find((episode) => episode?.seriesId === media.mediaid);
const episodeInProgress = watchHistoryDictionary.find((episode) => episode?.seriesId === mediaId);

// Prevent rendering the SeriesRedirect multiple times when we are loading data
if (isLoading) {
if (isSeriesLoading || isMediaLoading) {
return <Loading />;
}

return <SeriesRedirect seriesId={seriesId} mediaId={media.mediaid} episodeId={episodeInProgress?.mediaid} />;
if (isPlaylistError || !seriesPlaylist) {
return <ErrorPage title={t('series_error')} />;
}

// According to the new approach we use mediaId as a seriesId
return (
<Navigate
to={seriesURL({ episodeId: episodeInProgress?.mediaid, seriesId: series ? mediaId : legacySeriesPlaylistId, play, playlistId: feedId })}
replace
/>
);
};

export default MediaSeries;
Loading

0 comments on commit 2d1667a

Please sign in to comment.