Skip to content

Commit

Permalink
feat: add option to cache images locally (#1213)
Browse files Browse the repository at this point in the history
  • Loading branch information
sct committed Mar 18, 2021
1 parent dfd4ff9 commit 0ca3d43
Show file tree
Hide file tree
Showing 26 changed files with 293 additions and 108 deletions.
3 changes: 3 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ module.exports = {
env: {
commitTag: process.env.COMMIT_TAG || 'local',
},
images: {
domains: ['image.tmdb.org'],
},
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
Expand Down
1 change: 1 addition & 0 deletions server/interfaces/api/settingsInterfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface PublicSettingsResponse {
region: string;
originalLanguage: string;
partialRequestsEnabled: boolean;
cacheImages: boolean;
}

export interface CacheItem {
Expand Down
4 changes: 4 additions & 0 deletions server/lib/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export interface MainSettings {
applicationTitle: string;
applicationUrl: string;
csrfProtection: boolean;
cacheImages: boolean;
defaultPermissions: number;
hideAvailable: boolean;
localLogin: boolean;
Expand All @@ -88,6 +89,7 @@ interface FullPublicSettings extends PublicSettings {
region: string;
originalLanguage: string;
partialRequestsEnabled: boolean;
cacheImages: boolean;
}

export interface NotificationAgentConfig {
Expand Down Expand Up @@ -195,6 +197,7 @@ class Settings {
applicationTitle: 'Overseerr',
applicationUrl: '',
csrfProtection: false,
cacheImages: false,
defaultPermissions: Permission.REQUEST,
hideAvailable: false,
localLogin: true,
Expand Down Expand Up @@ -349,6 +352,7 @@ class Settings {
region: this.data.main.region,
originalLanguage: this.data.main.originalLanguage,
partialRequestsEnabled: this.data.main.partialRequestsEnabled,
cacheImages: this.data.main.cacheImages,
};
}

Expand Down
39 changes: 33 additions & 6 deletions src/components/CollectionDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useUser, Permission } from '../../hooks/useUser';
import useSettings from '../../hooks/useSettings';
import Link from 'next/link';
import { uniq } from 'lodash';
import CachedImage from '../Common/CachedImage';

const messages = defineMessages({
overviewunavailable: 'Overview unavailable.',
Expand Down Expand Up @@ -203,9 +204,26 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
className="media-page"
style={{
height: 493,
backgroundImage: `linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%), url(//image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath})`,
}}
>
{data.backdropPath && (
<div className="media-page-bg-image">
<CachedImage
alt=""
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`}
layout="fill"
objectFit="cover"
priority
/>
<div
className="absolute inset-0"
style={{
backgroundImage:
'linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%)',
}}
/>
</div>
)}
<PageTitle title={data.name} />
<Transition
enter="opacity-0 transition duration-300"
Expand Down Expand Up @@ -268,11 +286,20 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
</Modal>
</Transition>
<div className="media-header">
<img
src={`//image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`}
alt=""
className="media-poster"
/>
<div className="media-poster">
<CachedImage
src={
data.posterPath
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`
: '/images/overseerr_poster_not_found.png'
}
alt=""
layout="responsive"
width={600}
height={900}
priority
/>
</div>
<div className="media-title">
<div className="media-status">
<StatusBadge
Expand Down
18 changes: 18 additions & 0 deletions src/components/Common/CachedImage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Image, { ImageProps } from 'next/image';
import React from 'react';
import useSettings from '../../../hooks/useSettings';

/**
* The CachedImage component should be used wherever
* we want to offer the option to locally cache images.
*
* It uses the `next/image` Image component but overrides
* the `unoptimized` prop based on the application setting `cacheImages`.
**/
const CachedImage: React.FC<ImageProps> = (props) => {
const { currentSettings } = useSettings();

return <Image unoptimized={!currentSettings.cacheImages} {...props} />;
};

export default CachedImage;
47 changes: 23 additions & 24 deletions src/components/Common/ImageFader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import React, {
HTMLAttributes,
ForwardRefRenderFunction,
} from 'react';
import Image from 'next/image';
import CachedImage from '../CachedImage';

interface ImageFaderProps extends HTMLAttributes<HTMLDivElement> {
backgroundImages: string[];
rotationSpeed?: number;
isDarker?: boolean;
useImage?: boolean;
forceOptimize?: boolean;
}

const DEFAULT_ROTATION_SPEED = 6000;
Expand All @@ -20,7 +20,7 @@ const ImageFader: ForwardRefRenderFunction<HTMLDivElement, ImageFaderProps> = (
backgroundImages,
rotationSpeed = DEFAULT_ROTATION_SPEED,
isDarker,
useImage,
forceOptimize,
...props
},
ref
Expand All @@ -46,6 +46,14 @@ const ImageFader: ForwardRefRenderFunction<HTMLDivElement, ImageFaderProps> = (
'linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%)';
}

let overrides = {};

if (forceOptimize) {
overrides = {
unoptimized: false,
};
}

return (
<div ref={ref}>
{backgroundImages.map((imageUrl, i) => (
Expand All @@ -54,29 +62,20 @@ const ImageFader: ForwardRefRenderFunction<HTMLDivElement, ImageFaderProps> = (
className={`absolute inset-0 bg-cover bg-center transition-opacity duration-300 ease-in ${
i === activeIndex ? 'opacity-100' : 'opacity-0'
}`}
style={{
backgroundImage: !useImage
? `${gradient}, url(${imageUrl})`
: undefined,
}}
{...props}
>
{useImage && (
<>
<Image
className="absolute inset-0 w-full h-full"
alt=""
src={imageUrl}
layout="fill"
objectFit="cover"
quality={100}
/>
<div
className="absolute inset-0"
style={{ backgroundImage: gradient }}
/>
</>
)}
<CachedImage
className="absolute inset-0 w-full h-full"
alt=""
src={imageUrl}
layout="fill"
objectFit="cover"
{...overrides}
/>
<div
className="absolute inset-0"
style={{ backgroundImage: gradient }}
/>
</div>
))}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Discover/MovieGenreList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const MovieGenreList: React.FC = () => {
<li key={`genre-${genre.id}-${index}`}>
<GenreCard
name={genre.name}
image={`https://www.themoviedb.org/t/p/w1280_filter(duotone,${
image={`https://image.tmdb.org/t/p/w1280_filter(duotone,${
genreColorMap[genre.id] ?? genreColorMap[0]
})${genre.backdrops[4]}`}
url={`/discover/movies/genre/${genre.id}`}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Discover/MovieGenreSlider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const MovieGenreSlider: React.FC = () => {
<GenreCard
key={`genre-${genre.id}-${index}`}
name={genre.name}
image={`https://www.themoviedb.org/t/p/w1280_filter(duotone,${
image={`https://image.tmdb.org/t/p/w1280_filter(duotone,${
genreColorMap[genre.id] ?? genreColorMap[0]
})${genre.backdrops[4]}`}
url={`/discover/movies/genre/${genre.id}`}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Discover/TvGenreList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const TvGenreList: React.FC = () => {
<li key={`genre-${genre.id}-${index}`}>
<GenreCard
name={genre.name}
image={`https://www.themoviedb.org/t/p/w1280_filter(duotone,${
image={`https://image.tmdb.org/t/p/w1280_filter(duotone,${
genreColorMap[genre.id] ?? genreColorMap[0]
})${genre.backdrops[4]}`}
url={`/discover/tv/genre/${genre.id}`}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Discover/TvGenreSlider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const TvGenreSlider: React.FC = () => {
<GenreCard
key={`genre-tv-${genre.id}-${index}`}
name={genre.name}
image={`https://www.themoviedb.org/t/p/w1280_filter(duotone,${
image={`https://image.tmdb.org/t/p/w1280_filter(duotone,${
genreColorMap[genre.id] ?? genreColorMap[0]
})${genre.backdrops[4]}`}
url={`/discover/tv/genre/${genre.id}`}
Expand Down
5 changes: 2 additions & 3 deletions src/components/GenreCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Link from 'next/link';
import React, { useState } from 'react';
import { withProperties } from '../../utils/typeHelpers';
import CachedImage from '../Common/CachedImage';

interface GenreCardProps {
name: string;
Expand All @@ -27,9 +28,6 @@ const GenreCard: React.FC<GenreCardProps> = ({
? 'bg-gray-700 scale-105 ring-gray-500 bg-opacity-100'
: 'bg-gray-800 scale-100 ring-gray-700 bg-opacity-80'
} rounded-xl bg-cover bg-center overflow-hidden`}
style={{
backgroundImage: `url("${image}")`,
}}
onMouseEnter={() => {
setHovered(true);
}}
Expand All @@ -42,6 +40,7 @@ const GenreCard: React.FC<GenreCardProps> = ({
role="link"
tabIndex={0}
>
<CachedImage src={image} alt="" layout="fill" objectFit="cover" />
<div
className={`absolute z-10 inset-0 w-full h-full transition duration-300 bg-gray-800 ${
isHovered ? 'bg-opacity-10' : 'bg-opacity-30'
Expand Down
2 changes: 1 addition & 1 deletion src/components/Login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const Login: React.FC = () => {
<div className="relative flex flex-col min-h-screen bg-gray-900 py-14">
<PageTitle title={intl.formatMessage(messages.signin)} />
<ImageFader
useImage
forceOptimize
backgroundImages={[
'/images/rotate1.jpg',
'/images/rotate2.jpg',
Expand Down
67 changes: 50 additions & 17 deletions src/components/MovieDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import DownloadBlock from '../DownloadBlock';
import PageTitle from '../Common/PageTitle';
import useSettings from '../../hooks/useSettings';
import PlayButton, { PlayButtonLink } from '../Common/PlayButton';
import CachedImage from '../Common/CachedImage';

const messages = defineMessages({
releasedate: 'Release Date',
Expand Down Expand Up @@ -203,9 +204,26 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
className="media-page"
style={{
height: 493,
backgroundImage: `linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%), url(//image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath})`,
}}
>
{data.backdropPath && (
<div className="media-page-bg-image">
<CachedImage
alt=""
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`}
layout="fill"
objectFit="cover"
priority
/>
<div
className="absolute inset-0"
style={{
backgroundImage:
'linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%)',
}}
/>
</div>
)}
<PageTitle title={data.title} />
<SlideOver
show={showManager}
Expand Down Expand Up @@ -380,15 +398,20 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
)}
</SlideOver>
<div className="media-header">
<img
src={
data.posterPath
? `//image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`
: '/images/overseerr_poster_not_found.png'
}
alt=""
className="media-poster"
/>
<div className="media-poster">
<CachedImage
src={
data.posterPath
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`
: '/images/overseerr_poster_not_found.png'
}
alt=""
layout="responsive"
width={600}
height={900}
priority
/>
</div>
<div className="media-title">
<div className="media-status">
<StatusBadge
Expand Down Expand Up @@ -519,13 +542,23 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
<div className="mb-6">
<Link href={`/collection/${data.collection.id}`}>
<a>
<div
className="relative z-0 transition duration-300 scale-100 bg-gray-800 bg-center bg-cover rounded-lg shadow-md cursor-pointer transform-gpu group hover:scale-105"
style={{
backgroundImage: `linear-gradient(180deg, rgba(31, 41, 55, 0.47) 0%, rgba(31, 41, 55, 0.80) 100%), url(//image.tmdb.org/t/p/w1440_and_h320_multi_faces/${data.collection.backdropPath})`,
}}
>
<div className="flex items-center justify-between p-4 text-gray-200 transition duration-300 h-14 group-hover:text-white">
<div className="relative z-0 overflow-hidden transition duration-300 scale-100 bg-gray-800 bg-center bg-cover rounded-lg shadow-md cursor-pointer transform-gpu group hover:scale-105 ring-1 ring-gray-700 hover:ring-gray-500">
<div className="absolute inset-0 z-0">
<CachedImage
src={`https://image.tmdb.org/t/p/w1440_and_h320_multi_faces/${data.collection.backdropPath}`}
alt=""
layout="fill"
objectFit="cover"
/>
<div
className="absolute inset-0"
style={{
backgroundImage:
'linear-gradient(180deg, rgba(31, 41, 55, 0.47) 0%, rgba(31, 41, 55, 0.80) 100%)',
}}
/>
</div>
<div className="relative z-10 flex items-center justify-between p-4 text-gray-200 transition duration-300 h-14 group-hover:text-white">
<div>{data.collection.name}</div>
<Button buttonSize="sm">
{intl.formatMessage(messages.view)}
Expand Down

0 comments on commit 0ca3d43

Please sign in to comment.