Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 2 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions src/components/reader/DoublePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down Expand Up @@ -56,15 +55,13 @@ 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' }}
/>
<SpinnerImage
src={image2src}
onImageLoad={onImageLoad}
alt={`Page #${index + 1}`}
imgRef={imgRef}
spinnerStyle={{
...spinnerStyle,
width: 'calc(50% - 5px)',
Expand Down
5 changes: 1 addition & 4 deletions src/components/reader/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);

Expand All @@ -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',
Expand Down
58 changes: 33 additions & 25 deletions src/components/reader/pager/DoublePagedPager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);

Expand All @@ -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={{
Expand Down
18 changes: 5 additions & 13 deletions src/components/util/SpinnerImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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();

Expand Down Expand Up @@ -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,
};
});
13 changes: 13 additions & 0 deletions src/lib/requests/RequestManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading