diff --git a/src/components/reader/DoublePage.tsx b/src/components/reader/DoublePage.tsx index da2c5189db..5ae2b71242 100644 --- a/src/components/reader/DoublePage.tsx +++ b/src/components/reader/DoublePage.tsx @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { forwardRef, useRef } from 'react'; +import { forwardRef } from 'react'; import { Box, SxProps, Theme } from '@mui/material'; import { IReaderSettings } from '@/typings'; import { SpinnerImage } from '@/components/util/SpinnerImage'; @@ -23,7 +23,6 @@ interface IProps { export const DoublePage = forwardRef((props: IProps, ref: any) => { const { image1src, image2src, index, onImageLoad, settings } = props; - const imgRef = useRef(null); const baseImgStyle = imageStyle(settings); const imgStyle = { ...baseImgStyle, @@ -56,7 +55,6 @@ export const DoublePage = forwardRef((props: IProps, ref: any) => { src={image1src} onImageLoad={onImageLoad} alt={`Page #${index}`} - imgRef={imgRef} spinnerStyle={spinnerStyle} imgStyle={{ ...imgStyle, objectPosition: settings.readerType === 'DoubleLTR' ? 'right' : 'left' }} /> @@ -64,7 +62,6 @@ export const DoublePage = forwardRef((props: IProps, ref: any) => { src={image2src} onImageLoad={onImageLoad} alt={`Page #${index + 1}`} - imgRef={imgRef} spinnerStyle={{ ...spinnerStyle, width: 'calc(50% - 5px)', diff --git a/src/components/reader/Page.tsx b/src/components/reader/Page.tsx index 74f1e7537e..508d7f34a4 100644 --- a/src/components/reader/Page.tsx +++ b/src/components/reader/Page.tsx @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { forwardRef, useRef } from 'react'; +import { forwardRef } from 'react'; import Box from '@mui/material/Box'; import { useTheme } from '@mui/material/styles'; import { useMediaQuery } from '@mui/material'; @@ -67,8 +67,6 @@ export const Page = forwardRef((props: IProps, ref: any) => { const theme = useTheme(); const isMobileWidth = useMediaQuery(theme.breakpoints.down('md')); - const imgRef = useRef(null); - const imgStyle = imageStyle(settings); const isDoublePageReader = ['DoubleRTL', 'DoubleLTR'].includes(settings.readerType); @@ -87,7 +85,6 @@ export const Page = forwardRef((props: IProps, ref: any) => { src={src} onImageLoad={onImageLoad} alt={`Page #${index}`} - imgRef={imgRef} spinnerStyle={{ ...imgStyle, height: '100vh', diff --git a/src/components/reader/pager/DoublePagedPager.tsx b/src/components/reader/pager/DoublePagedPager.tsx index eff27f8741..2c50e6409f 100644 --- a/src/components/reader/pager/DoublePagedPager.tsx +++ b/src/components/reader/pager/DoublePagedPager.tsx @@ -11,6 +11,7 @@ import { Box } from '@mui/material'; import { IReaderProps } from '@/typings'; import { Page } from '@/components/reader/Page'; import { DoublePage } from '@/components/reader/DoublePage'; +import { requestManager } from '@/lib/requests/RequestManager.ts'; const isSpreadPage = (image: HTMLImageElement): boolean => { const aspectRatio = image.height / image.width; @@ -29,23 +30,22 @@ const isSinglePage = (index: number, spreadPages: boolean[], offsetFirstPage: bo }; export function DoublePagedPager(props: IReaderProps) { - const { pages, settings, setCurPage, initialPage, curPage, nextChapter, prevChapter } = props; + const { pages, settings, setCurPage, initialPage, curPage, chapter, nextChapter, prevChapter } = props; const selfRef = useRef(null); - const pagesRef = useRef([]); const [pagesToSpreadState, setPagesToSpreadState] = useState(Array(pages.length).fill(false)); const [pagesLoadState, setPagesLoadState] = useState(Array(pages.length).fill(false)); function getPagesToDisplay(): number { let pagesToDisplay = 1; // has to be at least one so skipping forward while pages are still loading is possible - if (curPage < pages.length && pagesRef.current[curPage]) { + if (curPage < pages.length) { if (pagesLoadState[curPage]) { pagesToDisplay = 1; if (pagesToSpreadState[curPage]) return pagesToDisplay; } } - if (curPage + 1 < pages.length && pagesRef.current[curPage + 1]) { + if (curPage + 1 < pages.length) { if (pagesLoadState[curPage + 1]) { if (isSinglePage(curPage, pagesToSpreadState, settings.offsetFirstPage)) return pagesToDisplay; pagesToDisplay = 2; @@ -124,14 +124,6 @@ export function DoublePagedPager(props: IReaderProps) { } } - function handleImageLoad(index: number) { - return () => { - setPagesLoadState((prevState) => prevState.toSpliced(index, 1, true)); - const image = pagesRef.current[index]; - setPagesToSpreadState((prevState) => prevState.toSpliced(index, 1, isSpreadPage(image))); - }; - } - useEffect(() => { document.addEventListener('keydown', keyboardControl); @@ -154,21 +146,37 @@ export function DoublePagedPager(props: IReaderProps) { } }, [settings.offsetFirstPage]); + useEffect(() => { + const imageRequests: [number, ReturnType<(typeof requestManager)['requestImage']>][] = pages.map((page) => [ + page.index, + requestManager.requestImage(page.src), + ]); + + imageRequests.forEach(async ([index, imageRequest]) => { + try { + const imageUrl = await imageRequest.response; + const img = new Image(); + img.onload = () => { + URL.revokeObjectURL(imageUrl); + + setPagesLoadState((prevState) => prevState.toSpliced(index, 1, true)); + setPagesToSpreadState((prevState) => prevState.toSpliced(index, 1, isSpreadPage(img))); + }; + img.src = imageUrl; + } catch (e) { + // ignore + } + }); + + return () => { + imageRequests.forEach(([index, imageRequest]) => + imageRequest.abortRequest(new Error(`DoublePagedPager::preload(${index}): chapter changed`)), + ); + }; + }, [chapter.id]); + return ( - - {pages.map((page) => ( - { - pagesRef.current[page.index] = e; - }} - key={`${page.index}`} - src={page.src} - onLoad={handleImageLoad(page.index)} - alt={`${page.index}`} - /> - ))} - ; - spinnerStyle?: SxProps; imgStyle?: CSSProperties; onImageLoad?: () => void; } -export function SpinnerImage(props: IProps) { - const { src, alt, onImageLoad, imgRef, spinnerStyle, imgStyle } = props; +export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef) => { + const { src, alt, onImageLoad, spinnerStyle, imgStyle } = props; const { t } = useTranslation(); @@ -118,16 +116,10 @@ export function SpinnerImage(props: IProps) { }} ref={imgRef} src={imageSourceUrl} + onLoad={() => URL.revokeObjectURL(imageSourceUrl)} alt={alt} draggable={false} /> ); -} - -SpinnerImage.defaultProps = { - spinnerStyle: {}, - imgStyle: {}, - onImageLoad: () => {}, - imgRef: undefined, -}; +}); diff --git a/src/lib/requests/RequestManager.ts b/src/lib/requests/RequestManager.ts index 6d2d83aaf5..f852e81a92 100644 --- a/src/lib/requests/RequestManager.ts +++ b/src/lib/requests/RequestManager.ts @@ -746,6 +746,19 @@ export class RequestManager { return `${this.getValidUrlFor(imageUrl, apiVersion)}`; } + /** + * After the image has been handled, {@see URL#revokeObjectURL} has to be called. + * + * @example + * + * const imageRequest = requestManager.requestImage("someUrl"); + * const imageUrl = await imageRequest.response + * + * const img = new Image(); + * img.onLoad = () => URL.revokeObjectURL(imageUrl); + * img.src = imageUrl; + * + */ public requestImage(url: string): { response: Promise } & AbortableRequest { const { abortRequest, signal } = this.createAbortController(); const response = this.restClient