feat(trending): add filter options (#2137)
Signed-off-by: Florian Hoech <code@florians-web.de>
This commit is contained in:
@@ -5964,6 +5964,23 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
example: en
|
||||
- in: query
|
||||
name: mediaType
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- all
|
||||
- movie
|
||||
- tv
|
||||
default: all
|
||||
- in: query
|
||||
name: timeWindow
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- day
|
||||
- week
|
||||
default: day
|
||||
responses:
|
||||
'200':
|
||||
description: Results
|
||||
|
||||
@@ -715,9 +715,11 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
|
||||
public getMovieTrending = async ({
|
||||
page = 1,
|
||||
timeWindow = 'day',
|
||||
language = this.locale,
|
||||
}: {
|
||||
page?: number;
|
||||
timeWindow?: 'day' | 'week';
|
||||
language?: string;
|
||||
} = {}): Promise<TmdbSearchMovieResponse> => {
|
||||
try {
|
||||
const data = await this.get<TmdbSearchMovieResponse>(
|
||||
@@ -725,6 +727,7 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
|
||||
{
|
||||
params: {
|
||||
page,
|
||||
language,
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -738,9 +741,11 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
|
||||
public getTvTrending = async ({
|
||||
page = 1,
|
||||
timeWindow = 'day',
|
||||
language = this.locale,
|
||||
}: {
|
||||
page?: number;
|
||||
timeWindow?: 'day' | 'week';
|
||||
language?: string;
|
||||
} = {}): Promise<TmdbSearchTvResponse> => {
|
||||
try {
|
||||
const data = await this.get<TmdbSearchTvResponse>(
|
||||
@@ -748,6 +753,7 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
|
||||
{
|
||||
params: {
|
||||
page,
|
||||
language,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -673,10 +673,41 @@ discoverRoutes.get('/trending', async (req, res, next) => {
|
||||
const tmdb = createTmdbWithRegionLanguage(req.user);
|
||||
|
||||
try {
|
||||
const data = await tmdb.getAllTrending({
|
||||
page: Number(req.query.page),
|
||||
language: (req.query.language as string) ?? req.locale,
|
||||
});
|
||||
const mediaType = (req.query.mediaType as 'all' | 'movie' | 'tv') ?? 'all';
|
||||
const timeWindow =
|
||||
(req.query.timeWindow as 'day' | 'week') === 'week' ? 'week' : 'day';
|
||||
const language = (req.query.language as string) ?? req.locale;
|
||||
const page = Number(req.query.page);
|
||||
|
||||
const trendingFetchers = {
|
||||
movie: async () => ({
|
||||
data: await tmdb.getMovieTrending({ page, language, timeWindow }),
|
||||
mapper: mapMovieResult,
|
||||
type: MediaType.MOVIE,
|
||||
}),
|
||||
tv: async () => ({
|
||||
data: await tmdb.getTvTrending({ page, language, timeWindow }),
|
||||
mapper: mapTvResult,
|
||||
type: MediaType.TV,
|
||||
}),
|
||||
all: async () => ({
|
||||
data: await tmdb.getAllTrending({ page, language, timeWindow }),
|
||||
mapper: (result: any, media?: Media) => {
|
||||
if (isMovie(result)) {
|
||||
return mapMovieResult(result, media);
|
||||
} else if (isPerson(result)) {
|
||||
return mapPersonResult(result);
|
||||
} else if (isCollection(result)) {
|
||||
return mapCollectionResult(result);
|
||||
} else {
|
||||
return mapTvResult(result, media);
|
||||
}
|
||||
},
|
||||
type: null,
|
||||
}),
|
||||
} as const;
|
||||
|
||||
const { data, mapper, type } = await trendingFetchers[mediaType]();
|
||||
|
||||
const media = await Media.getRelatedMedia(
|
||||
req.user,
|
||||
@@ -687,27 +718,16 @@ discoverRoutes.get('/trending', async (req, res, next) => {
|
||||
page: data.page,
|
||||
totalPages: data.total_pages,
|
||||
totalResults: data.total_results,
|
||||
results: data.results.map((result) =>
|
||||
isMovie(result)
|
||||
? mapMovieResult(
|
||||
result,
|
||||
media.find(
|
||||
(med) =>
|
||||
med.tmdbId === result.id && med.mediaType === MediaType.MOVIE
|
||||
)
|
||||
)
|
||||
: isPerson(result)
|
||||
? mapPersonResult(result)
|
||||
: isCollection(result)
|
||||
? mapCollectionResult(result)
|
||||
: mapTvResult(
|
||||
result,
|
||||
media.find(
|
||||
(med) =>
|
||||
med.tmdbId === result.id && med.mediaType === MediaType.TV
|
||||
)
|
||||
)
|
||||
),
|
||||
results: data.results.map((result) => {
|
||||
// - If "type" is set (case: "movie" or "tv"), the mediaType must also match.
|
||||
// - If "type" is not set (case: "all"), only filter by tmdbId.
|
||||
const selectedMedia = media.find(
|
||||
(med) =>
|
||||
med.tmdbId === result.id && (type ? med.mediaType === type : true)
|
||||
);
|
||||
|
||||
return mapper(result, selectedMedia);
|
||||
}),
|
||||
});
|
||||
} catch (e) {
|
||||
logger.debug('Something went wrong retrieving trending items', {
|
||||
|
||||
@@ -2,21 +2,32 @@ import Header from '@app/components/Common/Header';
|
||||
import ListView from '@app/components/Common/ListView';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
import useDiscover from '@app/hooks/useDiscover';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import Error from '@app/pages/_error';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { CircleStackIcon, FunnelIcon } from '@heroicons/react/24/solid';
|
||||
import type {
|
||||
MovieResult,
|
||||
PersonResult,
|
||||
TvResult,
|
||||
} from '@server/models/Search';
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages('components.Discover', {
|
||||
trending: 'Trending',
|
||||
timeWindowDay: 'Daily',
|
||||
timeWindowWeek: 'Weekly',
|
||||
});
|
||||
|
||||
type MediaType = 'all' | 'movie' | 'tv';
|
||||
|
||||
type TimeWindow = 'day' | 'week';
|
||||
|
||||
const Trending = () => {
|
||||
const intl = useIntl();
|
||||
const [currentMediaType, setCurrentMediaType] = useState<MediaType>('all');
|
||||
const [currentTimeWindow, setCurrentTimeWindow] = useState<TimeWindow>('day');
|
||||
const {
|
||||
isLoadingInitialData,
|
||||
isEmpty,
|
||||
@@ -26,7 +37,8 @@ const Trending = () => {
|
||||
fetchMore,
|
||||
error,
|
||||
} = useDiscover<MovieResult | TvResult | PersonResult>(
|
||||
'/api/v1/discover/trending'
|
||||
'/api/v1/discover/trending',
|
||||
{ mediaType: currentMediaType, timeWindow: currentTimeWindow }
|
||||
);
|
||||
|
||||
if (error) {
|
||||
@@ -36,8 +48,53 @@ const Trending = () => {
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.trending)} />
|
||||
<div className="mb-5 mt-1">
|
||||
<div className="mb-5 mt-1 flex flex-col justify-between lg:flex-row lg:items-end">
|
||||
<Header>{intl.formatMessage(messages.trending)}</Header>
|
||||
<div className="mt-2 flex flex-grow flex-col sm:flex-row lg:flex-grow-0">
|
||||
<div className="mb-2 flex flex-grow sm:mb-0 sm:mr-2 lg:flex-grow-0">
|
||||
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-sm text-gray-100">
|
||||
<CircleStackIcon className="h-6 w-6" />
|
||||
</span>
|
||||
<select
|
||||
id="mediaType"
|
||||
name="mediaType"
|
||||
onChange={(e) => setCurrentMediaType(e.target.value as MediaType)}
|
||||
value={currentMediaType}
|
||||
className="rounded-r-only"
|
||||
>
|
||||
<option value="all">
|
||||
{intl.formatMessage(globalMessages.all)}
|
||||
</option>
|
||||
<option value="movie">
|
||||
{intl.formatMessage(globalMessages.movies)}
|
||||
</option>
|
||||
<option value="tv">
|
||||
{intl.formatMessage(globalMessages.tvshows)}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="mb-2 flex flex-grow sm:mb-0 sm:mr-2 lg:flex-grow-0">
|
||||
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-sm text-gray-100">
|
||||
<FunnelIcon className="h-6 w-6" />
|
||||
</span>
|
||||
<select
|
||||
id="timeWindow"
|
||||
name="timeWindow"
|
||||
onChange={(e) =>
|
||||
setCurrentTimeWindow(e.target.value as TimeWindow)
|
||||
}
|
||||
value={currentTimeWindow}
|
||||
className="rounded-r-only"
|
||||
>
|
||||
<option value="day">
|
||||
{intl.formatMessage(messages.timeWindowDay)}
|
||||
</option>
|
||||
<option value="week">
|
||||
{intl.formatMessage(messages.timeWindowWeek)}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ListView
|
||||
items={titles}
|
||||
|
||||
@@ -124,6 +124,8 @@
|
||||
"components.Discover.resetwarning": "Reset all sliders to default. This will also delete any custom sliders!",
|
||||
"components.Discover.stopediting": "Stop Editing",
|
||||
"components.Discover.studios": "Studios",
|
||||
"components.Discover.timeWindowDay": "Daily",
|
||||
"components.Discover.timeWindowWeek": "Weekly",
|
||||
"components.Discover.tmdbmoviegenre": "TMDB Movie Genre",
|
||||
"components.Discover.tmdbmoviekeyword": "TMDB Movie Keyword",
|
||||
"components.Discover.tmdbmoviestreamingservices": "TMDB Movie Streaming Services",
|
||||
|
||||
Reference in New Issue
Block a user