Fix/double page mode not aborting preload image requests (#628)

* Use request manager to preload images in DoublePagedPager

Was forgotten to update along with f852ce70e7

The requests for the preload also never got aborted

* Revoke object urls after load
This commit is contained in:
schroda
2024-03-01 18:27:46 +01:00
committed by GitHub
parent d242552751
commit 79d35404d3
5 changed files with 53 additions and 46 deletions

View File

@@ -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<HTMLImageElement>(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)',

View File

@@ -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<HTMLImageElement>(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',

View File

@@ -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<HTMLDivElement>(null);
const pagesRef = useRef<HTMLImageElement[]>([]);
const [pagesToSpreadState, setPagesToSpreadState] = useState(Array(pages.length).fill(false));
const [pagesLoadState, setPagesLoadState] = useState<boolean[]>(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 (
<Box ref={selfRef} onClick={clickControl}>
<Box id="preload" sx={{ display: 'none' }}>
{pages.map((page) => (
<img
ref={(e: HTMLImageElement) => {
pagesRef.current[page.index] = e;
}}
key={`${page.index}`}
src={page.src}
onLoad={handleImageLoad(page.index)}
alt={`${page.index}`}
/>
))}
</Box>
<Box
id="display"
sx={{

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import React, { useState, CSSProperties, useEffect } from 'react';
import { useState, CSSProperties, useEffect, forwardRef, ForwardedRef } from 'react';
import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box';
import { Theme, SxProps, Stack, Button } from '@mui/material';
@@ -22,16 +22,14 @@ interface IProps {
src: string;
alt: string;
imgRef?: React.RefObject<HTMLImageElement>;
spinnerStyle?: SxProps<Theme>;
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<HTMLImageElement | null>) => {
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,
};
});

View File

@@ -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<string> } & AbortableRequest {
const { abortRequest, signal } = this.createAbortController();
const response = this.restClient