Skip to content

Commit

Permalink
feat: genre sliders (experiment) (#1182)
Browse files Browse the repository at this point in the history
  • Loading branch information
sct committed Mar 15, 2021
1 parent 8dc69bd commit 1c4515a
Show file tree
Hide file tree
Showing 10 changed files with 387 additions and 1 deletion.
64 changes: 64 additions & 0 deletions overseerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3780,6 +3780,70 @@ paths:
type: array
items:
$ref: '#/components/schemas/MovieResult'
/discover/genreslider/movie:
get:
summary: Get genre slider data for movies
description: Returns a list of genres with backdrops attached
tags:
- search
parameters:
- in: query
name: language
schema:
type: string
example: en
responses:
'200':
description: Genre slider data returned
content:
application/json:
schema:
type: array
items:
type: object
properties:
id:
type: number
example: 1
backdrops:
type: array
items:
type: string
name:
type: string
example: Genre Name
/discover/genreslider/tv:
get:
summary: Get genre slider data for TV series
description: Returns a list of genres with backdrops attached
tags:
- search
parameters:
- in: query
name: language
schema:
type: string
example: en
responses:
'200':
description: Genre slider data returned
content:
application/json:
schema:
type: array
items:
type: object
properties:
id:
type: number
example: 1
backdrops:
type: array
items:
type: string
name:
type: string
example: Genre Name
/request:
get:
summary: Get all requests
Expand Down
5 changes: 5 additions & 0 deletions server/interfaces/api/discoverInterfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface GenreSliderItem {
id: number;
name: string;
backdrops: string[];
}
85 changes: 85 additions & 0 deletions server/routes/discover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { getSettings } from '../lib/settings';
import { User } from '../entity/User';
import { mapProductionCompany } from '../models/Movie';
import { mapNetwork } from '../models/Tv';
import logger from '../logger';
import { sortBy } from 'lodash';
import { GenreSliderItem } from '../interfaces/api/discoverInterfaces';

const createTmdbWithRegionLanaguage = (user?: User): TheMovieDb => {
const settings = getSettings();
Expand Down Expand Up @@ -482,4 +485,86 @@ discoverRoutes.get<{ keywordId: string }>(
}
);

discoverRoutes.get<{ language: string }, GenreSliderItem[]>(
'/genreslider/movie',
async (req, res, next) => {
const tmdb = new TheMovieDb();

try {
const mappedGenres: GenreSliderItem[] = [];

const genres = await tmdb.getMovieGenres({
language: req.query.language as string,
});

await Promise.all(
genres.map(async (genre) => {
const genreData = await tmdb.getDiscoverMovies({ genre: genre.id });

mappedGenres.push({
id: genre.id,
name: genre.name,
backdrops: genreData.results
.filter((title) => !!title.backdrop_path)
.map((title) => title.backdrop_path) as string[],
});
})
);

const sortedData = sortBy(mappedGenres, 'name');

return res.status(200).json(sortedData);
} catch (e) {
logger.error('Something went wrong retrieving the movie genre slider', {
errorMessage: e.message,
});
return next({
status: 500,
message: 'Unable to retrieve movie genre slider.',
});
}
}
);

discoverRoutes.get<{ language: string }, GenreSliderItem[]>(
'/genreslider/tv',
async (req, res, next) => {
const tmdb = new TheMovieDb();

try {
const mappedGenres: GenreSliderItem[] = [];

const genres = await tmdb.getTvGenres({
language: req.query.language as string,
});

await Promise.all(
genres.map(async (genre) => {
const genreData = await tmdb.getDiscoverTv({ genre: genre.id });

mappedGenres.push({
id: genre.id,
name: genre.name,
backdrops: genreData.results
.filter((title) => !!title.backdrop_path)
.map((title) => title.backdrop_path) as string[],
});
})
);

const sortedData = sortBy(mappedGenres, 'name');

return res.status(200).json(sortedData);
} catch (e) {
logger.error('Something went wrong retrieving the tv genre slider', {
errorMessage: e.message,
});
return next({
status: 500,
message: 'Unable to retrieve tv genre slider.',
});
}
}
);

export default discoverRoutes;
2 changes: 1 addition & 1 deletion src/components/CompanyCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const CompanyCard: React.FC<CompanyCardProps> = ({ image, url, name }) => {
return (
<Link href={url}>
<a
className={`relative flex items-center justify-center h-32 w-64 sm:h-36 sm:w-72 p-8 shadow transition ease-in-out duration-150 cursor-pointer transform-gpu ring-1 ${
className={`relative flex items-center justify-center h-32 w-64 sm:h-36 sm:w-72 p-8 shadow transition ease-in-out duration-300 cursor-pointer transform-gpu ring-1 ${
isHovered
? 'bg-gray-700 scale-105 ring-gray-500'
: 'bg-gray-800 scale-100 ring-gray-700'
Expand Down
53 changes: 53 additions & 0 deletions src/components/Discover/MovieGenreSlider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useContext } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
import GenreCard from '../../GenreCard';
import Slider from '../../Slider';
import { GenreSliderItem } from '../../../../server/interfaces/api/discoverInterfaces';
import { LanguageContext } from '../../../context/LanguageContext';
import { genreColorMap } from '../constants';

const messages = defineMessages({
moviegenres: 'Movie Genres',
});

const MovieGenreSlider: React.FC = () => {
const { locale } = useContext(LanguageContext);
const intl = useIntl();
const { data, error } = useSWR<GenreSliderItem[]>(
`/api/v1/discover/genreslider/movie?language=${locale}`,
{
refreshInterval: 0,
revalidateOnFocus: false,
}
);

return (
<>
<div className="slider-header">
<div className="slider-title">
<span>{intl.formatMessage(messages.moviegenres)}</span>
</div>
</div>
<Slider
sliderKey="movie-genres"
isLoading={!data && !error}
isEmpty={false}
items={(data ?? []).map((genre, index) => (
<GenreCard
key={`genre-${genre.id}-${index}`}
name={genre.name}
image={`https://www.themoviedb.org/t/p/w1280_filter(duotone,${
genreColorMap[genre.id] ?? genreColorMap[0]
})${genre.backdrops[4]}`}
url={`/discover/movies/genre/${genre.id}`}
/>
))}
placeholder={<GenreCard.Placeholder />}
emptyMessage=""
/>
</>
);
};

export default React.memo(MovieGenreSlider);
53 changes: 53 additions & 0 deletions src/components/Discover/TvGenreSlider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useContext } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
import GenreCard from '../../GenreCard';
import Slider from '../../Slider';
import { GenreSliderItem } from '../../../../server/interfaces/api/discoverInterfaces';
import { LanguageContext } from '../../../context/LanguageContext';
import { genreColorMap } from '../constants';

const messages = defineMessages({
tvgenres: 'Series Genres',
});

const TvGenreSlider: React.FC = () => {
const { locale } = useContext(LanguageContext);
const intl = useIntl();
const { data, error } = useSWR<GenreSliderItem[]>(
`/api/v1/discover/genreslider/tv?language=${locale}`,
{
refreshInterval: 0,
revalidateOnFocus: false,
}
);

return (
<>
<div className="slider-header">
<div className="slider-title">
<span>{intl.formatMessage(messages.tvgenres)}</span>
</div>
</div>
<Slider
sliderKey="tv-genres"
isLoading={!data && !error}
isEmpty={false}
items={(data ?? []).map((genre, index) => (
<GenreCard
key={`genre-tv-${genre.id}-${index}`}
name={genre.name}
image={`https://www.themoviedb.org/t/p/w1280_filter(duotone,${
genreColorMap[genre.id] ?? genreColorMap[0]
})${genre.backdrops[4]}`}
url={`/discover/tv/genre/${genre.id}`}
/>
))}
placeholder={<GenreCard.Placeholder />}
emptyMessage=""
/>
</>
);
};

export default React.memo(TvGenreSlider);
62 changes: 62 additions & 0 deletions src/components/Discover/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
type AvailableColors =
| 'black'
| 'red'
| 'darkred'
| 'blue'
| 'lightblue'
| 'darkblue'
| 'orange'
| 'darkorange'
| 'green'
| 'lightgreen'
| 'purple'
| 'darkpurple'
| 'yellow'
| 'pink';

export const colorTones: Record<AvailableColors, [string, string]> = {
red: ['991B1B', 'FCA5A5'],
darkred: ['1F2937', 'F87171'],
blue: ['032541', '01b4e4'],
lightblue: ['1F2937', '60A5FA'],
darkblue: ['1F2937', '2864d2'],
orange: ['92400E', 'FCD34D'],
lightgreen: ['065F46', '6EE7B7'],
green: ['087d29', '21cb51'],
purple: ['5B21B6', 'C4B5FD'],
yellow: ['777e0d', 'e4ed55'],
darkorange: ['552c01', 'd47c1d'],
black: ['1F2937', 'D1D5DB'],
pink: ['9D174D', 'F9A8D4'],
darkpurple: ['480c8b', 'a96bef'],
};

export const genreColorMap: Record<number, [string, string]> = {
0: colorTones.black,
28: colorTones.red,
12: colorTones.blue,
16: colorTones.orange,
35: colorTones.lightgreen,
80: colorTones.darkblue,
99: colorTones.green,
18: colorTones.purple,
10751: colorTones.yellow,
14: colorTones.darkorange,
36: colorTones.green,
27: colorTones.black,
10402: colorTones.blue,
9648: colorTones.purple,
10749: colorTones.pink,
878: colorTones.lightblue,
10770: colorTones.red,
53: colorTones.darkpurple,
10752: colorTones.darkred,
37: colorTones.orange,
10759: colorTones.blue, // Action & Adventure
10762: colorTones.blue, // Kids
10764: colorTones.red, // Reality
10765: colorTones.lightblue, // Sci-Fi & Fantasy
10766: colorTones.darkpurple, // Soap
10767: colorTones.lightgreen, // Talk
10768: colorTones.darkred, // War & Politics
};
4 changes: 4 additions & 0 deletions src/components/Discover/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import MediaSlider from '../MediaSlider';
import PageTitle from '../Common/PageTitle';
import StudioSlider from './StudioSlider';
import NetworkSlider from './NetworkSlider';
import MovieGenreSlider from './MovieGenreSlider';
import TvGenreSlider from './TvGenreSlider';

const messages = defineMessages({
discover: 'Discover',
Expand Down Expand Up @@ -104,6 +106,7 @@ const Discover: React.FC = () => {
url="/api/v1/discover/movies"
linkUrl="/discover/movies"
/>
<MovieGenreSlider />
<MediaSlider
sliderKey="upcoming"
title={intl.formatMessage(messages.upcoming)}
Expand All @@ -117,6 +120,7 @@ const Discover: React.FC = () => {
url="/api/v1/discover/tv"
linkUrl="/discover/tv"
/>
<TvGenreSlider />
<MediaSlider
sliderKey="upcoming-tv"
title={intl.formatMessage(messages.upcomingtv)}
Expand Down

0 comments on commit 1c4515a

Please sign in to comment.