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/.
|
* 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 { Box, SxProps, Theme } from '@mui/material';
|
||||||
import { IReaderSettings } from '@/typings';
|
import { IReaderSettings } from '@/typings';
|
||||||
import { SpinnerImage } from '@/components/util/SpinnerImage';
|
import { SpinnerImage } from '@/components/util/SpinnerImage';
|
||||||
@@ -23,7 +23,6 @@ interface IProps {
|
|||||||
export const DoublePage = forwardRef((props: IProps, ref: any) => {
|
export const DoublePage = forwardRef((props: IProps, ref: any) => {
|
||||||
const { image1src, image2src, index, onImageLoad, settings } = props;
|
const { image1src, image2src, index, onImageLoad, settings } = props;
|
||||||
|
|
||||||
const imgRef = useRef<HTMLImageElement>(null);
|
|
||||||
const baseImgStyle = imageStyle(settings);
|
const baseImgStyle = imageStyle(settings);
|
||||||
const imgStyle = {
|
const imgStyle = {
|
||||||
...baseImgStyle,
|
...baseImgStyle,
|
||||||
@@ -56,7 +55,6 @@ export const DoublePage = forwardRef((props: IProps, ref: any) => {
|
|||||||
src={image1src}
|
src={image1src}
|
||||||
onImageLoad={onImageLoad}
|
onImageLoad={onImageLoad}
|
||||||
alt={`Page #${index}`}
|
alt={`Page #${index}`}
|
||||||
imgRef={imgRef}
|
|
||||||
spinnerStyle={spinnerStyle}
|
spinnerStyle={spinnerStyle}
|
||||||
imgStyle={{ ...imgStyle, objectPosition: settings.readerType === 'DoubleLTR' ? 'right' : 'left' }}
|
imgStyle={{ ...imgStyle, objectPosition: settings.readerType === 'DoubleLTR' ? 'right' : 'left' }}
|
||||||
/>
|
/>
|
||||||
@@ -64,7 +62,6 @@ export const DoublePage = forwardRef((props: IProps, ref: any) => {
|
|||||||
src={image2src}
|
src={image2src}
|
||||||
onImageLoad={onImageLoad}
|
onImageLoad={onImageLoad}
|
||||||
alt={`Page #${index + 1}`}
|
alt={`Page #${index + 1}`}
|
||||||
imgRef={imgRef}
|
|
||||||
spinnerStyle={{
|
spinnerStyle={{
|
||||||
...spinnerStyle,
|
...spinnerStyle,
|
||||||
width: 'calc(50% - 5px)',
|
width: 'calc(50% - 5px)',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* 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 Box from '@mui/material/Box';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import { useMediaQuery } from '@mui/material';
|
import { useMediaQuery } from '@mui/material';
|
||||||
@@ -67,8 +67,6 @@ export const Page = forwardRef((props: IProps, ref: any) => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobileWidth = useMediaQuery(theme.breakpoints.down('md'));
|
const isMobileWidth = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
|
||||||
const imgRef = useRef<HTMLImageElement>(null);
|
|
||||||
|
|
||||||
const imgStyle = imageStyle(settings);
|
const imgStyle = imageStyle(settings);
|
||||||
const isDoublePageReader = ['DoubleRTL', 'DoubleLTR'].includes(settings.readerType);
|
const isDoublePageReader = ['DoubleRTL', 'DoubleLTR'].includes(settings.readerType);
|
||||||
|
|
||||||
@@ -87,7 +85,6 @@ export const Page = forwardRef((props: IProps, ref: any) => {
|
|||||||
src={src}
|
src={src}
|
||||||
onImageLoad={onImageLoad}
|
onImageLoad={onImageLoad}
|
||||||
alt={`Page #${index}`}
|
alt={`Page #${index}`}
|
||||||
imgRef={imgRef}
|
|
||||||
spinnerStyle={{
|
spinnerStyle={{
|
||||||
...imgStyle,
|
...imgStyle,
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { Box } from '@mui/material';
|
|||||||
import { IReaderProps } from '@/typings';
|
import { IReaderProps } from '@/typings';
|
||||||
import { Page } from '@/components/reader/Page';
|
import { Page } from '@/components/reader/Page';
|
||||||
import { DoublePage } from '@/components/reader/DoublePage';
|
import { DoublePage } from '@/components/reader/DoublePage';
|
||||||
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
|
|
||||||
const isSpreadPage = (image: HTMLImageElement): boolean => {
|
const isSpreadPage = (image: HTMLImageElement): boolean => {
|
||||||
const aspectRatio = image.height / image.width;
|
const aspectRatio = image.height / image.width;
|
||||||
@@ -29,23 +30,22 @@ const isSinglePage = (index: number, spreadPages: boolean[], offsetFirstPage: bo
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function DoublePagedPager(props: IReaderProps) {
|
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 selfRef = useRef<HTMLDivElement>(null);
|
||||||
const pagesRef = useRef<HTMLImageElement[]>([]);
|
|
||||||
|
|
||||||
const [pagesToSpreadState, setPagesToSpreadState] = useState(Array(pages.length).fill(false));
|
const [pagesToSpreadState, setPagesToSpreadState] = useState(Array(pages.length).fill(false));
|
||||||
const [pagesLoadState, setPagesLoadState] = useState<boolean[]>(Array(pages.length).fill(false));
|
const [pagesLoadState, setPagesLoadState] = useState<boolean[]>(Array(pages.length).fill(false));
|
||||||
|
|
||||||
function getPagesToDisplay(): number {
|
function getPagesToDisplay(): number {
|
||||||
let pagesToDisplay = 1; // has to be at least one so skipping forward while pages are still loading is possible
|
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]) {
|
if (pagesLoadState[curPage]) {
|
||||||
pagesToDisplay = 1;
|
pagesToDisplay = 1;
|
||||||
if (pagesToSpreadState[curPage]) return pagesToDisplay;
|
if (pagesToSpreadState[curPage]) return pagesToDisplay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (curPage + 1 < pages.length && pagesRef.current[curPage + 1]) {
|
if (curPage + 1 < pages.length) {
|
||||||
if (pagesLoadState[curPage + 1]) {
|
if (pagesLoadState[curPage + 1]) {
|
||||||
if (isSinglePage(curPage, pagesToSpreadState, settings.offsetFirstPage)) return pagesToDisplay;
|
if (isSinglePage(curPage, pagesToSpreadState, settings.offsetFirstPage)) return pagesToDisplay;
|
||||||
pagesToDisplay = 2;
|
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(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('keydown', keyboardControl);
|
document.addEventListener('keydown', keyboardControl);
|
||||||
|
|
||||||
@@ -154,21 +146,37 @@ export function DoublePagedPager(props: IReaderProps) {
|
|||||||
}
|
}
|
||||||
}, [settings.offsetFirstPage]);
|
}, [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 (
|
return (
|
||||||
<Box ref={selfRef} onClick={clickControl}>
|
<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
|
<Box
|
||||||
id="display"
|
id="display"
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* 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 CircularProgress from '@mui/material/CircularProgress';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { Theme, SxProps, Stack, Button } from '@mui/material';
|
import { Theme, SxProps, Stack, Button } from '@mui/material';
|
||||||
@@ -22,16 +22,14 @@ interface IProps {
|
|||||||
src: string;
|
src: string;
|
||||||
alt: string;
|
alt: string;
|
||||||
|
|
||||||
imgRef?: React.RefObject<HTMLImageElement>;
|
|
||||||
|
|
||||||
spinnerStyle?: SxProps<Theme>;
|
spinnerStyle?: SxProps<Theme>;
|
||||||
imgStyle?: CSSProperties;
|
imgStyle?: CSSProperties;
|
||||||
|
|
||||||
onImageLoad?: () => void;
|
onImageLoad?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SpinnerImage(props: IProps) {
|
export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef<HTMLImageElement | null>) => {
|
||||||
const { src, alt, onImageLoad, imgRef, spinnerStyle, imgStyle } = props;
|
const { src, alt, onImageLoad, spinnerStyle, imgStyle } = props;
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -118,16 +116,10 @@ export function SpinnerImage(props: IProps) {
|
|||||||
}}
|
}}
|
||||||
ref={imgRef}
|
ref={imgRef}
|
||||||
src={imageSourceUrl}
|
src={imageSourceUrl}
|
||||||
|
onLoad={() => URL.revokeObjectURL(imageSourceUrl)}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
SpinnerImage.defaultProps = {
|
|
||||||
spinnerStyle: {},
|
|
||||||
imgStyle: {},
|
|
||||||
onImageLoad: () => {},
|
|
||||||
imgRef: undefined,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -746,6 +746,19 @@ export class RequestManager {
|
|||||||
return `${this.getValidUrlFor(imageUrl, apiVersion)}`;
|
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 {
|
public requestImage(url: string): { response: Promise<string> } & AbortableRequest {
|
||||||
const { abortRequest, signal } = this.createAbortController();
|
const { abortRequest, signal } = this.createAbortController();
|
||||||
const response = this.restClient
|
const response = this.restClient
|
||||||
|
|||||||
Reference in New Issue
Block a user