diff --git a/src/modules/core/components/SpinnerImage.tsx b/src/modules/core/components/SpinnerImage.tsx index df7453c5..a43e6c6a 100644 --- a/src/modules/core/components/SpinnerImage.tsx +++ b/src/modules/core/components/SpinnerImage.tsx @@ -30,11 +30,14 @@ interface IProps { imgStyle?: SxProps; onLoad?: () => void; + onError?: () => void; shouldDecode?: boolean; useFetchApi?: boolean; priority?: Priority; + + retryKeyPrefix?: string; } export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef) => { @@ -45,9 +48,11 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef @@ -134,7 +143,7 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef - + ) : ( diff --git a/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopActions.tsx b/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopActions.tsx index 62d895a9..6eeee1ca 100644 --- a/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopActions.tsx +++ b/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopActions.tsx @@ -15,13 +15,14 @@ import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import DownloadIcon from '@mui/icons-material/Download'; import ReplayIcon from '@mui/icons-material/Replay'; -import { useMemo } from 'react'; +import { useMemo, useRef } from 'react'; import DeleteIcon from '@mui/icons-material/Delete'; import { actionToTranslationKey, ChapterAction, Chapters } from '@/modules/chapter/services/Chapters.ts'; import { ReaderStateChapters } from '@/modules/reader/types/Reader.types.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { DownloadStateIndicator } from '@/modules/core/components/DownloadStateIndicator.tsx'; import { DownloadStatusFieldsFragment } from '@/lib/graphql/generated/graphql.ts'; +import { ReaderStatePages } from '@/modules/reader/types/ReaderProgressBar.types.ts'; const DownloadButton = ({ currentChapter, @@ -60,11 +61,17 @@ const DownloadButton = ({ export const ReaderNavBarDesktopActions = ({ currentChapter, -}: Required>) => { + pageLoadStates, + setPageLoadStates, + setRetryFailedPagesKeyPrefix, +}: Required> & + Pick) => { const { id, isBookmarked, realUrl } = currentChapter ?? { id: -1, isBookmarked: false, realUrl: '' }; const { t } = useTranslation(); + const pageRetryKeyPrefix = useRef(0); + const { data: downloaderData } = requestManager.useGetDownloadStatus(); const queue = downloaderData?.downloadStatus.queue ?? []; @@ -73,6 +80,11 @@ export const ReaderNavBarDesktopActions = ({ [queue, id], ); + const haveSomePagesFailedToLoad = useMemo( + () => pageLoadStates.some((pageLoadState) => pageLoadState.error), + [pageLoadStates], + ); + const bookmarkAction: Extract = isBookmarked ? 'unbookmark' : 'bookmark'; return ( @@ -83,7 +95,17 @@ export const ReaderNavBarDesktopActions = ({ - + { + setPageLoadStates((statePageLoadStates) => + statePageLoadStates.map((pageLoadState) => ({ loaded: pageLoadState.loaded })), + ); + setRetryFailedPagesKeyPrefix(`${pageRetryKeyPrefix.current}`); + pageRetryKeyPrefix.current = (pageRetryKeyPrefix.current + 1) % 1000; + }} + disabled={!haveSomePagesFailedToLoad} + color="inherit" + > diff --git a/src/modules/reader/components/overlay/progress-bar/variants/StandardReaderProgressBar.tsx b/src/modules/reader/components/overlay/progress-bar/variants/StandardReaderProgressBar.tsx index 1d23b352..972860ca 100644 --- a/src/modules/reader/components/overlay/progress-bar/variants/StandardReaderProgressBar.tsx +++ b/src/modules/reader/components/overlay/progress-bar/variants/StandardReaderProgressBar.tsx @@ -68,8 +68,8 @@ export const StandardReaderProgressBar = () => { backgroundColor: lighten(theme.palette.background.paper, 0.1), }), ...applyStyles( - pageLoadStates[primary.index] && - (!secondary || pageLoadStates[secondary.index]), + pageLoadStates[primary.index]?.loaded && + (!secondary || pageLoadStates[secondary.index]?.loaded), { backgroundColor: darken(theme.palette.background.paper, 0.35), ...theme.applyStyles('dark', { diff --git a/src/modules/reader/components/viewer/ReaderPage.tsx b/src/modules/reader/components/viewer/ReaderPage.tsx index 28d3db14..7ea139a5 100644 --- a/src/modules/reader/components/viewer/ReaderPage.tsx +++ b/src/modules/reader/components/viewer/ReaderPage.tsx @@ -51,12 +51,12 @@ export const ReaderPage = forwardRef( doublePage = false, position, shouldLoad, + retryKeyPrefix, ...props }: Omit, 'ref' | 'spinnerStyle' | 'imgStyle'> & { display: boolean; doublePage?: boolean; position?: 'left' | 'right'; - onLoad?: () => void; }, ref: ForwardedRef, ) => { @@ -71,6 +71,7 @@ export const ReaderPage = forwardRef( return ( { const page = actualPages[pagesIndex]; @@ -240,7 +244,14 @@ export const ReaderViewer = forwardRef((_, ref: ForwardedRef pageLoadStates.toSpliced(index, 1, true)); + setPageLoadStates((statePageLoadStates) => + statePageLoadStates.toSpliced(index, 1, { loaded: true }), + ); + }} + onError={(pageIndex) => { + setPageLoadStates((statePageLoadStates) => + statePageLoadStates.toSpliced(pageIndex, 1, { loaded: false, error: true }), + ); }} /> diff --git a/src/modules/reader/components/viewer/pager/BasePager.tsx b/src/modules/reader/components/viewer/pager/BasePager.tsx index e2db473d..52a0bbbb 100644 --- a/src/modules/reader/components/viewer/pager/BasePager.tsx +++ b/src/modules/reader/components/viewer/pager/BasePager.tsx @@ -23,7 +23,7 @@ export const BasePager = ({ imageRefs, createPage, slots, -}: ReaderPagerProps & { +}: Omit & { createPage: ( page: ReaderStatePages['pages'][number], pagesIndex: number, diff --git a/src/modules/reader/components/viewer/pager/ReaderDoublePagedPager.tsx b/src/modules/reader/components/viewer/pager/ReaderDoublePagedPager.tsx index 3f65baf0..d21c7868 100644 --- a/src/modules/reader/components/viewer/pager/ReaderDoublePagedPager.tsx +++ b/src/modules/reader/components/viewer/pager/ReaderDoublePagedPager.tsx @@ -37,7 +37,13 @@ const getPagePosition = ( return isLtrReadingDirection ? 'right' : 'left'; }; -export const ReaderDoublePagedPager = ({ onLoad, ...props }: ReaderPagerProps) => { +export const ReaderDoublePagedPager = ({ + onLoad, + onError, + pageLoadStates, + retryFailedPagesKeyPrefix, + ...props +}: ReaderPagerProps) => { const { currentPageIndex, pages, totalPages } = props; const { readingDirection } = ReaderService.useSettings(); @@ -63,10 +69,12 @@ export const ReaderDoublePagedPager = ({ onLoad, ...props }: ReaderPagerProps) = {createReaderPage( page, () => onLoad?.(pagesIndex), + () => onError?.(primary.index), shouldLoad, shouldDisplay && isPrimaryPage, currentPage.primary.index, totalPages, + pageLoadStates[primary.index].error ? retryFailedPagesKeyPrefix : undefined, hasSecondaryPage ? getPagePosition('first', themeDirection, readingDirection.value) : undefined, @@ -76,10 +84,12 @@ export const ReaderDoublePagedPager = ({ onLoad, ...props }: ReaderPagerProps) = createReaderPage( { ...page, primary: { ...page.secondary! } }, () => onLoad?.(pagesIndex, false), + () => onError?.(secondary.index), shouldLoad, shouldDisplay && isSecondaryPage, currentSecondaryPageIndex, totalPages, + pageLoadStates[secondary.index].error ? retryFailedPagesKeyPrefix : undefined, getPagePosition('second', themeDirection, readingDirection.value), true, )} diff --git a/src/modules/reader/components/viewer/pager/ReaderHorizontalPager.tsx b/src/modules/reader/components/viewer/pager/ReaderHorizontalPager.tsx index 91ede33d..9df2371b 100644 --- a/src/modules/reader/components/viewer/pager/ReaderHorizontalPager.tsx +++ b/src/modules/reader/components/viewer/pager/ReaderHorizontalPager.tsx @@ -13,7 +13,13 @@ import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; import { ReaderPagerProps, ReadingDirection } from '@/modules/reader/types/Reader.types.ts'; import { createReaderPage } from '@/modules/reader/utils/ReaderPager.utils.tsx'; -export const ReaderHorizontalPager = ({ onLoad, ...props }: ReaderPagerProps) => { +export const ReaderHorizontalPager = ({ + onLoad, + onError, + pageLoadStates, + retryFailedPagesKeyPrefix, + ...props +}: ReaderPagerProps) => { const { currentPageIndex, totalPages } = props; const { pageGap, readingDirection } = ReaderService.useSettings(); @@ -28,10 +34,12 @@ export const ReaderHorizontalPager = ({ onLoad, ...props }: ReaderPagerProps) => createReaderPage( page, () => onLoad?.(pagesIndex), + () => onError?.(page.primary.index), shouldLoad, true, currentPageIndex, totalPages, + pageLoadStates[page.primary.index].error ? retryFailedPagesKeyPrefix : undefined, undefined, undefined, setRef, diff --git a/src/modules/reader/components/viewer/pager/ReaderPagedPager.tsx b/src/modules/reader/components/viewer/pager/ReaderPagedPager.tsx index da730ba1..d57e4981 100644 --- a/src/modules/reader/components/viewer/pager/ReaderPagedPager.tsx +++ b/src/modules/reader/components/viewer/pager/ReaderPagedPager.tsx @@ -10,7 +10,13 @@ import { BasePager } from '@/modules/reader/components/viewer/pager/BasePager.ts import { ReaderPagerProps } from '@/modules/reader/types/Reader.types.ts'; import { createReaderPage } from '@/modules/reader/utils/ReaderPager.utils.tsx'; -export const ReaderPagedPager = ({ onLoad, ...props }: ReaderPagerProps) => { +export const ReaderPagedPager = ({ + onLoad, + onError, + pageLoadStates, + retryFailedPagesKeyPrefix, + ...props +}: ReaderPagerProps) => { const { currentPageIndex, totalPages } = props; return ( @@ -20,10 +26,12 @@ export const ReaderPagedPager = ({ onLoad, ...props }: ReaderPagerProps) => { createReaderPage( page, () => onLoad?.(pagesIndex), + () => onError?.(page.primary.index), shouldLoad, shouldDisplay && currentPageIndex === page.primary.index, currentPageIndex, totalPages, + pageLoadStates[page.primary.index].error ? retryFailedPagesKeyPrefix : undefined, ) } slots={{ diff --git a/src/modules/reader/components/viewer/pager/ReaderVerticalPager.tsx b/src/modules/reader/components/viewer/pager/ReaderVerticalPager.tsx index 60c0a85c..5545a182 100644 --- a/src/modules/reader/components/viewer/pager/ReaderVerticalPager.tsx +++ b/src/modules/reader/components/viewer/pager/ReaderVerticalPager.tsx @@ -11,7 +11,13 @@ import { BasePager } from '@/modules/reader/components/viewer/pager/BasePager.ts import { ReaderPagerProps } from '@/modules/reader/types/Reader.types.ts'; import { createReaderPage } from '@/modules/reader/utils/ReaderPager.utils.tsx'; -export const ReaderVerticalPager = ({ onLoad, ...props }: ReaderPagerProps) => { +export const ReaderVerticalPager = ({ + onLoad, + onError, + pageLoadStates, + retryFailedPagesKeyPrefix, + ...props +}: ReaderPagerProps) => { const { currentPageIndex, totalPages } = props; const { pageGap } = ReaderService.useSettings(); @@ -23,10 +29,12 @@ export const ReaderVerticalPager = ({ onLoad, ...props }: ReaderPagerProps) => { createReaderPage( page, () => onLoad?.(pagesIndex), + () => onError?.(page.primary.index), shouldLoad, true, currentPageIndex, totalPages, + pageLoadStates[page.primary.index].error ? retryFailedPagesKeyPrefix : undefined, undefined, undefined, setRef, diff --git a/src/modules/reader/contexts/state/ReaderStatePagesContext.tsx b/src/modules/reader/contexts/state/ReaderStatePagesContext.tsx index f609337a..127864f9 100644 --- a/src/modules/reader/contexts/state/ReaderStatePagesContext.tsx +++ b/src/modules/reader/contexts/state/ReaderStatePagesContext.tsx @@ -25,6 +25,8 @@ export const ReaderStatePagesContext = createContext({ setPages: () => undefined, transitionPageMode: ReaderTransitionPageMode.NONE, setTransitionPageMode: () => undefined, + retryFailedPagesKeyPrefix: '', + setRetryFailedPagesKeyPrefix: () => undefined, }); export const userReaderStatePagesContext = () => useContext(ReaderStatePagesContext); diff --git a/src/modules/reader/contexts/state/ReaderStatePagesContextProvider.tsx b/src/modules/reader/contexts/state/ReaderStatePagesContextProvider.tsx index 5cc1f4ad..80cbd2ed 100644 --- a/src/modules/reader/contexts/state/ReaderStatePagesContextProvider.tsx +++ b/src/modules/reader/contexts/state/ReaderStatePagesContextProvider.tsx @@ -23,6 +23,8 @@ export const ReaderStatePagesContextProvider = ({ children }: { children: ReactN const [transitionPageMode, setTransitionPageMode] = useState( ReaderTransitionPageMode.NONE, ); + const [retryFailedPagesKeyPrefix, setRetryFailedPagesKeyPrefix] = + useState(''); const value = useMemo( () => ({ @@ -40,8 +42,19 @@ export const ReaderStatePagesContextProvider = ({ children }: { children: ReactN setPages, transitionPageMode, setTransitionPageMode, + retryFailedPagesKeyPrefix, + setRetryFailedPagesKeyPrefix, }), - [totalPages, pages, currentPageIndex, pageToScrollToIndex, pageUrls, pageLoadStates, transitionPageMode], + [ + totalPages, + pages, + currentPageIndex, + pageToScrollToIndex, + pageUrls, + pageLoadStates, + transitionPageMode, + retryFailedPagesKeyPrefix, + ], ); return {children}; diff --git a/src/modules/reader/screens/Reader.tsx b/src/modules/reader/screens/Reader.tsx index 30d2847b..1187a85e 100644 --- a/src/modules/reader/screens/Reader.tsx +++ b/src/modules/reader/screens/Reader.tsx @@ -147,7 +147,7 @@ export const Reader = () => { setTotalPages(pagesPayload.chapter.pageCount); setPages(createPagesData(pages)); setPageUrls(pages); - setPageLoadStates(pages.map(() => false)); + setPageLoadStates(pages.map(() => ({ loaded: false }))); setCurrentPageIndex(initialReaderPageIndex); setPageToScrollToIndex(initialReaderPageIndex); } else { @@ -156,7 +156,7 @@ export const Reader = () => { setTotalPages(0); setPages([createPageData('', 0)]); setPageUrls([]); - setPageLoadStates([]); + setPageLoadStates([{ loaded: false }]); } setTransitionPageMode(ReaderTransitionPageMode.NONE); diff --git a/src/modules/reader/types/Reader.types.ts b/src/modules/reader/types/Reader.types.ts index 69791e23..038efc9a 100644 --- a/src/modules/reader/types/Reader.types.ts +++ b/src/modules/reader/types/Reader.types.ts @@ -209,9 +209,18 @@ export enum ReaderHotkey { } export interface ReaderPagerProps - extends Pick { + extends Pick< + ReaderStatePages, + | 'currentPageIndex' + | 'pages' + | 'totalPages' + | 'transitionPageMode' + | 'pageLoadStates' + | 'retryFailedPagesKeyPrefix' + > { imageRefs: MutableRefObject<(HTMLElement | null)[]>; onLoad?: (pagesIndex: number, isPrimary?: boolean) => void; + onError?: (pageIndex: number) => void; } export enum PageInViewportType { diff --git a/src/modules/reader/types/ReaderProgressBar.types.ts b/src/modules/reader/types/ReaderProgressBar.types.ts index 5b4b516a..09ada296 100644 --- a/src/modules/reader/types/ReaderProgressBar.types.ts +++ b/src/modules/reader/types/ReaderProgressBar.types.ts @@ -31,12 +31,14 @@ export interface ReaderStatePages { setPageToScrollToIndex: React.Dispatch>; pageUrls: string[]; setPageUrls: React.Dispatch>; - pageLoadStates: boolean[]; - setPageLoadStates: React.Dispatch>; + pageLoadStates: { loaded: boolean; error?: boolean }[]; + setPageLoadStates: React.Dispatch>; pages: PageData[]; setPages: React.Dispatch>; transitionPageMode: ReaderTransitionPageMode; setTransitionPageMode: React.Dispatch>; + retryFailedPagesKeyPrefix: string; + setRetryFailedPagesKeyPrefix: React.Dispatch>; } export interface ReaderProgressBarProps diff --git a/src/modules/reader/utils/ReaderPager.utils.tsx b/src/modules/reader/utils/ReaderPager.utils.tsx index 2b0defc3..0ffd2d03 100644 --- a/src/modules/reader/utils/ReaderPager.utils.tsx +++ b/src/modules/reader/utils/ReaderPager.utils.tsx @@ -202,10 +202,12 @@ const getPageDownloadPriority = (currentPageIndex: number, pageIndex: number, to export const createReaderPage = ( { primary: { index, alt, url } }: ReaderStatePages['pages'][number], onLoad: () => void, + onError: (() => void) | undefined, shouldLoad: boolean, display: boolean, currentPageIndex: number, totalPages: number, + retryKeyPrefix?: string, position?: 'left' | 'right', isDoublePage?: boolean, setRef?: (ref: HTMLElement | null) => void, @@ -219,8 +221,10 @@ export const createReaderPage = ( priority={getPageDownloadPriority(currentPageIndex, index, totalPages)} position={position} onLoad={onLoad} + onError={onError} doublePage={isDoublePage} shouldLoad={shouldLoad} + retryKeyPrefix={retryKeyPrefix} /> );