From 2521a1e7aa0a4c036fa4350666c83f0a0bdce5a1 Mon Sep 17 00:00:00 2001 From: leejayhsu Date: Tue, 16 Jul 2024 18:12:24 -0700 Subject: [PATCH] feat(discover): hide available media filter --- overseerr-api.yml | 10 ++++++++ server/routes/discover.ts | 25 ++++++++++++++++--- .../Discover/FilterSlideover/index.tsx | 16 ++++++++++++ src/components/Discover/constants.ts | 5 ++++ 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/overseerr-api.yml b/overseerr-api.yml index c4c1e97b74..567cd37e57 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -4244,6 +4244,11 @@ paths: schema: type: string example: 8|9 + - in: query + name: hideAvailable + schema: + type: boolean + example: true responses: '200': description: Results @@ -4533,6 +4538,11 @@ paths: schema: type: string example: 8|9 + - in: query + name: hideAvailable + schema: + type: boolean + example: true responses: '200': description: Results diff --git a/server/routes/discover.ts b/server/routes/discover.ts index b35306446f..10b0f84f25 100644 --- a/server/routes/discover.ts +++ b/server/routes/discover.ts @@ -2,7 +2,7 @@ import PlexTvAPI from '@server/api/plextv'; import type { SortOptions } from '@server/api/themoviedb'; import TheMovieDb from '@server/api/themoviedb'; import type { TmdbKeyword } from '@server/api/themoviedb/interfaces'; -import { MediaType } from '@server/constants/media'; +import { MediaStatus, MediaType } from '@server/constants/media'; import { getRepository } from '@server/datasource'; import Media from '@server/entity/Media'; import { User } from '@server/entity/User'; @@ -70,6 +70,7 @@ const QueryFilterOptions = z.object({ network: z.coerce.string().optional(), watchProviders: z.coerce.string().optional(), watchRegion: z.coerce.string().optional(), + hideAvailable: z.coerce.boolean().optional(), }); export type FilterOptions = z.infer; @@ -108,6 +109,15 @@ discoverRoutes.get('/movies', async (req, res, next) => { data.results.map((result) => result.id) ); + let filteredResults = data.results; + + if (query.hideAvailable) { + filteredResults = filteredResults.filter((result) => { + const mediaItem = media.find((req) => req.tmdbId === result.id); + return !(mediaItem?.status === MediaStatus.AVAILABLE); + }); + } + let keywordData: TmdbKeyword[] = []; if (keywords) { const splitKeywords = keywords.split(','); @@ -124,7 +134,7 @@ discoverRoutes.get('/movies', async (req, res, next) => { totalPages: data.total_pages, totalResults: data.total_results, keywords: keywordData, - results: data.results.map((result) => + results: filteredResults.map((result) => mapMovieResult( result, media.find( @@ -385,6 +395,15 @@ discoverRoutes.get('/tv', async (req, res, next) => { data.results.map((result) => result.id) ); + let filteredResults = data.results; + + if (query.hideAvailable) { + filteredResults = filteredResults.filter((result) => { + const mediaItem = media.find((req) => req.tmdbId === result.id); + return !(mediaItem?.status === MediaStatus.AVAILABLE); + }); + } + let keywordData: TmdbKeyword[] = []; if (keywords) { const splitKeywords = keywords.split(','); @@ -401,7 +420,7 @@ discoverRoutes.get('/tv', async (req, res, next) => { totalPages: data.total_pages, totalResults: data.total_results, keywords: keywordData, - results: data.results.map((result) => + results: filteredResults.map((result) => mapTvResult( result, media.find( diff --git a/src/components/Discover/FilterSlideover/index.tsx b/src/components/Discover/FilterSlideover/index.tsx index 83d5a2e49a..1c8cd4255f 100644 --- a/src/components/Discover/FilterSlideover/index.tsx +++ b/src/components/Discover/FilterSlideover/index.tsx @@ -1,5 +1,6 @@ import Button from '@app/components/Common/Button'; import MultiRangeSlider from '@app/components/Common/MultiRangeSlider'; +import SlideCheckbox from '@app/components/Common/SlideCheckbox'; import SlideOver from '@app/components/Common/SlideOver'; import type { FilterOptions } from '@app/components/Discover/constants'; import { countActiveFilters } from '@app/components/Discover/constants'; @@ -39,6 +40,7 @@ const messages = defineMessages({ runtime: 'Runtime', streamingservices: 'Streaming Services', voteCount: 'Number of votes between {minValue} and {maxValue}', + hideAvailable: 'Hide available titles', }); type FilterSlideoverProps = { @@ -287,6 +289,20 @@ const FilterSlideover = ({ })} /> +
+ + {intl.formatMessage(messages.hideAvailable)} + + { + updateQueryParams( + 'hideAvailable', + currentFilters.hideAvailable === 'true' ? undefined : 'true' + ); + }} + /> +
{intl.formatMessage(messages.streamingservices)} diff --git a/src/components/Discover/constants.ts b/src/components/Discover/constants.ts index 0571f1fc70..08955c6956 100644 --- a/src/components/Discover/constants.ts +++ b/src/components/Discover/constants.ts @@ -108,6 +108,7 @@ export const QueryFilterOptions = z.object({ voteCountGte: z.string().optional(), watchRegion: z.string().optional(), watchProviders: z.string().optional(), + hideAvailable: z.string().optional(), }); export type FilterOptions = z.infer; @@ -187,6 +188,10 @@ export const prepareFilterValues = ( filterValues.watchRegion = values.watchRegion; } + if (values.hideAvailable) { + filterValues.hideAvailable = values.hideAvailable; + } + return filterValues; };