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:
@@ -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)',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user