Skip to content

Commit

Permalink
Add option to use fanart as thumbnail fallback (#924)
Browse files Browse the repository at this point in the history
  • Loading branch information
harshithmohan authored May 29, 2024
1 parent e516e79 commit 80a840e
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 16 deletions.
13 changes: 10 additions & 3 deletions src/components/Collection/DisplaySettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const DisplaySettingsModal = ({ onClose, show }: Props) => {
noGap
>
<div className="flex flex-col gap-y-6 p-6">
<div className="flex flex-col gap-y-6">
<div className="flex flex-col gap-y-4">
<div className="font-semibold">Poster View Options</div>
<div className="flex flex-col gap-y-1">
<Checkbox
Expand Down Expand Up @@ -86,7 +86,7 @@ const DisplaySettingsModal = ({ onClose, show }: Props) => {

<div className="border-b border-panel-border" />

<div className="flex flex-col gap-y-6">
<div className="flex flex-col gap-y-4">
<div className="font-semibold">List View Options</div>
<div className="flex flex-col gap-1">
<Checkbox
Expand Down Expand Up @@ -122,7 +122,7 @@ const DisplaySettingsModal = ({ onClose, show }: Props) => {

<div className="border-b border-panel-border" />

<div className="flex flex-col gap-y-6">
<div className="flex flex-col gap-y-4">
<div className="font-semibold">Image Options</div>
<div className="flex flex-col gap-1">
<Checkbox
Expand All @@ -139,6 +139,13 @@ const DisplaySettingsModal = ({ onClose, show }: Props) => {
isChecked={imageSettings.showRandomFanart}
onChange={handleSettingChange}
/>
<Checkbox
justify
label="Show fanart if thumbnail is missing"
id="image-useThumbnailFallback"
isChecked={imageSettings.useThumbnailFallback}
onChange={handleSettingChange}
/>
</div>
</div>
</div>
Expand Down
5 changes: 4 additions & 1 deletion src/components/Collection/Episode/EpisodeSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import AnimateHeight from 'react-animate-height';
import { useOutletContext } from 'react-router-dom';
import {
mdiCheckboxBlankCircleOutline,
mdiCheckboxMarkedCircleOutline,
Expand All @@ -23,6 +24,7 @@ import useEventCallback from '@/hooks/useEventCallback';
import EpisodeDetails from './EpisodeDetails';
import EpisodeFiles from './EpisodeFiles';

import type { SeriesContextType } from '@/components/Collection/constants';
import type { EpisodeType } from '@/core/types/api/episode';

type Props = {
Expand Down Expand Up @@ -80,7 +82,8 @@ const SelectedStateButton = React.memo((

const EpisodeSummary = React.memo(
({ anidbSeriesId, episode, nextUp, onSelectionChange, page, selected, seriesId }: Props) => {
const thumbnail = useEpisodeThumbnail(episode);
const { fanart } = useOutletContext<SeriesContextType>();
const thumbnail = useEpisodeThumbnail(episode, fanart);
const [open, toggleOpen] = useToggle(false);
const episodeId = get(episode, 'IDs.ID', 0);

Expand Down
9 changes: 9 additions & 0 deletions src/components/Collection/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import type React from 'react';

import type { ImageType } from '@/core/types/api/common';

export const posterItemSize = {
width: 209,
height: 363,
Expand All @@ -10,3 +14,8 @@ export const listItemSize = {
widthAlt: 907,
gap: 32,
};

export type SeriesContextType = {
fanart?: ImageType;
scrollRef: React.RefObject<HTMLDivElement>;
};
5 changes: 5 additions & 0 deletions src/core/patches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,10 @@ export const webuiSettingsPatches = {
};
return { ...webuiSettings, settingsRevision: 6 };
},
7: (oldWebuiSettings) => {
const webuiSettings = oldWebuiSettings;
webuiSettings.collection.image.useThumbnailFallback = false;
return { ...webuiSettings, settingsRevision: 7 };
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as Record<number, (oldWebuiSettings: any) => WebUISettingsType>;
1 change: 1 addition & 0 deletions src/core/react-query/settings/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ export const initialSettings: SettingsType = {
image: {
showRandomFanart: false,
showRandomPoster: false,
useThumbnailFallback: false,
},
},
dashboard: {
Expand Down
1 change: 1 addition & 0 deletions src/core/types/api/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ export type WebUISettingsType = {
image: {
showRandomPoster: boolean;
showRandomFanart: boolean;
useThumbnailFallback: boolean;
};
};
dashboard: {
Expand Down
14 changes: 12 additions & 2 deletions src/hooks/useEpisodeThumbnail.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { useMemo } from 'react';

import { useSettingsQuery } from '@/core/react-query/settings/queries';

import type { ImageType } from '@/core/types/api/common';
import type { EpisodeType } from '@/core/types/api/episode';

function useEpisodeThumbnail(episode: EpisodeType): ImageType | null {
return useMemo(() => episode.TvDB?.[0]?.Thumbnail ?? null, [episode]);
function useEpisodeThumbnail(
episode: EpisodeType,
fanart: ImageType | undefined,
): ImageType | null {
const { useThumbnailFallback } = useSettingsQuery().data.WebUI_Settings.collection.image;
return useMemo(() => {
if (episode.TvDB?.[0]?.Thumbnail) return episode.TvDB[0].Thumbnail;
if (useThumbnailFallback && fanart) return fanart;
return null;
}, [episode.TvDB, fanart, useThumbnailFallback]);
}

export default useEpisodeThumbnail;
19 changes: 10 additions & 9 deletions src/pages/collection/Series.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { ReactNode } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { Outlet, useParams } from 'react-router';
import { Link, NavLink, useNavigate, useOutletContext } from 'react-router-dom';
Expand All @@ -25,10 +24,11 @@ import { useSeriesImagesQuery, useSeriesQuery } from '@/core/react-query/series/
import { useSettingsQuery } from '@/core/react-query/settings/queries';
import useEventCallback from '@/hooks/useEventCallback';

import type { SeriesContextType } from '@/components/Collection/constants';
import type { ImageType } from '@/core/types/api/common';
import type { SeriesType } from '@/core/types/api/series';

type SeriesTabProps = (props: { icon: string, text: string, to: string }) => ReactNode;
type SeriesTabProps = (props: { icon: string, text: string, to: string }) => React.ReactNode;
const SeriesTab: SeriesTabProps = ({ icon, text, to }) => (
<NavLink
to={to}
Expand All @@ -43,6 +43,8 @@ const SeriesTab: SeriesTabProps = ({ icon, text, to }) => (
</NavLink>
);

const getImagePath = ({ ID, Source, Type }: ImageType) => `/api/v3/Image/${Source}/${Type}/${ID}`;

const Series = () => {
const navigate = useNavigate();
const { seriesId } = useParams();
Expand All @@ -53,7 +55,7 @@ const Series = () => {
const imagesQuery = useSeriesImagesQuery(toNumber(seriesId!), !!seriesId);
const groupQuery = useGroupQuery(series?.IDs?.ParentGroup ?? 0, !!series?.IDs?.ParentGroup);

const [fanartUri, setFanartUri] = useState('');
const [fanart, setFanart] = useState<ImageType>();
const [showEditSeriesModal, setShowEditSeriesModal] = useState(false);
const { scrollRef } = useOutletContext<{ scrollRef: React.RefObject<HTMLDivElement> }>();

Expand All @@ -62,17 +64,15 @@ const Series = () => {
useEffect(() => {
if (!imagesQuery.isSuccess) return;

const getImagePath = ({ ID, Source, Type }: ImageType) => `/api/v3/Image/${Source}/${Type}/${ID}`;

const allFanarts: ImageType[] = get(imagesQuery.data, 'Fanarts', []);
if (!Array.isArray(allFanarts) || allFanarts.length === 0) return;

if (showRandomFanart) {
setFanartUri(getImagePath(allFanarts[Math.floor(Math.random() * allFanarts.length)]));
setFanart(allFanarts[Math.floor(Math.random() * allFanarts.length)]);
return;
}

setFanartUri(getImagePath(allFanarts.find(fanart => fanart.Preferred) ?? allFanarts[0]));
setFanart(allFanarts.find(image => image.Preferred) ?? allFanarts[0]);
}, [imagesQuery.data, imagesQuery.isSuccess, series, showRandomFanart]);

if (seriesQuery.isError) {
Expand Down Expand Up @@ -143,10 +143,11 @@ const Series = () => {
seriesId={series.IDs.ID}
/>

<Outlet context={{ scrollRef }} />
<Outlet context={{ fanart, scrollRef } satisfies SeriesContextType} />

<div
className="fixed left-0 top-0 -z-10 size-full opacity-5"
style={{ background: fanartUri !== '' ? `center / cover no-repeat url('${fanartUri}')` : undefined }}
style={{ background: fanart ? `center / cover no-repeat url('${getImagePath(fanart)}')` : undefined }}
/>
</div>
);
Expand Down
4 changes: 3 additions & 1 deletion src/pages/collection/series/SeriesEpisodes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { dayjs } from '@/core/util';
import useEventCallback from '@/hooks/useEventCallback';
import useFlattenListResult from '@/hooks/useFlattenListResult';

import type { SeriesContextType } from '@/components/Collection/constants';

const pageSize = 26;

const SeriesEpisodes = () => {
Expand Down Expand Up @@ -110,7 +112,7 @@ const SeriesEpisodes = () => {
[startDate, endDate],
);

const { scrollRef } = useOutletContext<{ scrollRef: React.RefObject<HTMLDivElement> }>();
const { scrollRef } = useOutletContext<SeriesContextType>();

const rowVirtualizer = useVirtualizer({
count: episodeCount,
Expand Down

0 comments on commit 80a840e

Please sign in to comment.