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,