From 80a840ed2abfa6522a5d0e3f9da3f2d0471317ba Mon Sep 17 00:00:00 2001 From: Harshith Mohan Date: Wed, 29 May 2024 13:50:42 +0530 Subject: [PATCH] Add option to use fanart as thumbnail fallback (#924) --- .../Collection/DisplaySettingsModal.tsx | 13 ++++++++++--- .../Collection/Episode/EpisodeSummary.tsx | 5 ++++- src/components/Collection/constants.ts | 9 +++++++++ src/core/patches.ts | 5 +++++ src/core/react-query/settings/helpers.ts | 1 + src/core/types/api/settings.ts | 1 + src/hooks/useEpisodeThumbnail.ts | 14 ++++++++++++-- src/pages/collection/Series.tsx | 19 ++++++++++--------- .../collection/series/SeriesEpisodes.tsx | 4 +++- 9 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/components/Collection/DisplaySettingsModal.tsx b/src/components/Collection/DisplaySettingsModal.tsx index 8fb113a4f..0a78be226 100644 --- a/src/components/Collection/DisplaySettingsModal.tsx +++ b/src/components/Collection/DisplaySettingsModal.tsx @@ -57,7 +57,7 @@ const DisplaySettingsModal = ({ onClose, show }: Props) => { noGap >
-
+
Poster View Options
{
-
+
List View Options
{
-
+
Image Options
{ isChecked={imageSettings.showRandomFanart} onChange={handleSettingChange} /> +
diff --git a/src/components/Collection/Episode/EpisodeSummary.tsx b/src/components/Collection/Episode/EpisodeSummary.tsx index 98c3a9603..897da75e3 100644 --- a/src/components/Collection/Episode/EpisodeSummary.tsx +++ b/src/components/Collection/Episode/EpisodeSummary.tsx @@ -1,5 +1,6 @@ import React from 'react'; import AnimateHeight from 'react-animate-height'; +import { useOutletContext } from 'react-router-dom'; import { mdiCheckboxBlankCircleOutline, mdiCheckboxMarkedCircleOutline, @@ -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 = { @@ -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(); + const thumbnail = useEpisodeThumbnail(episode, fanart); const [open, toggleOpen] = useToggle(false); const episodeId = get(episode, 'IDs.ID', 0); diff --git a/src/components/Collection/constants.ts b/src/components/Collection/constants.ts index 12a626c7a..dc67e9a81 100644 --- a/src/components/Collection/constants.ts +++ b/src/components/Collection/constants.ts @@ -1,3 +1,7 @@ +import type React from 'react'; + +import type { ImageType } from '@/core/types/api/common'; + export const posterItemSize = { width: 209, height: 363, @@ -10,3 +14,8 @@ export const listItemSize = { widthAlt: 907, gap: 32, }; + +export type SeriesContextType = { + fanart?: ImageType; + scrollRef: React.RefObject; +}; diff --git a/src/core/patches.ts b/src/core/patches.ts index 4ee4f03c0..219565f7c 100644 --- a/src/core/patches.ts +++ b/src/core/patches.ts @@ -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 WebUISettingsType>; diff --git a/src/core/react-query/settings/helpers.ts b/src/core/react-query/settings/helpers.ts index 30e5183ce..adf614454 100644 --- a/src/core/react-query/settings/helpers.ts +++ b/src/core/react-query/settings/helpers.ts @@ -280,6 +280,7 @@ export const initialSettings: SettingsType = { image: { showRandomFanart: false, showRandomPoster: false, + useThumbnailFallback: false, }, }, dashboard: { diff --git a/src/core/types/api/settings.ts b/src/core/types/api/settings.ts index 18bd043b0..5d880de94 100644 --- a/src/core/types/api/settings.ts +++ b/src/core/types/api/settings.ts @@ -202,6 +202,7 @@ export type WebUISettingsType = { image: { showRandomPoster: boolean; showRandomFanart: boolean; + useThumbnailFallback: boolean; }; }; dashboard: { diff --git a/src/hooks/useEpisodeThumbnail.ts b/src/hooks/useEpisodeThumbnail.ts index f632888a0..45357260b 100644 --- a/src/hooks/useEpisodeThumbnail.ts +++ b/src/hooks/useEpisodeThumbnail.ts @@ -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; diff --git a/src/pages/collection/Series.tsx b/src/pages/collection/Series.tsx index 9cfee1c8e..273db992c 100644 --- a/src/pages/collection/Series.tsx +++ b/src/pages/collection/Series.tsx @@ -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'; @@ -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 }) => ( ( ); +const getImagePath = ({ ID, Source, Type }: ImageType) => `/api/v3/Image/${Source}/${Type}/${ID}`; + const Series = () => { const navigate = useNavigate(); const { seriesId } = useParams(); @@ -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(); const [showEditSeriesModal, setShowEditSeriesModal] = useState(false); const { scrollRef } = useOutletContext<{ scrollRef: React.RefObject }>(); @@ -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) { @@ -143,10 +143,11 @@ const Series = () => { seriesId={series.IDs.ID} /> - + +
); diff --git a/src/pages/collection/series/SeriesEpisodes.tsx b/src/pages/collection/series/SeriesEpisodes.tsx index 8c69d5f68..95b8b1d1e 100644 --- a/src/pages/collection/series/SeriesEpisodes.tsx +++ b/src/pages/collection/series/SeriesEpisodes.tsx @@ -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 = () => { @@ -110,7 +112,7 @@ const SeriesEpisodes = () => { [startDate, endDate], ); - const { scrollRef } = useOutletContext<{ scrollRef: React.RefObject }>(); + const { scrollRef } = useOutletContext(); const rowVirtualizer = useVirtualizer({ count: episodeCount,