Improve memoization of page rendering
This commit is contained in:
@@ -6,10 +6,15 @@
|
|||||||
* 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 { ComponentProps, ForwardedRef, forwardRef, memo } from 'react';
|
import { ComponentProps, memo, useCallback } from 'react';
|
||||||
import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
|
import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
|
||||||
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
||||||
import { IReaderSettings, ReaderCustomFilter, TReaderScrollbarContext } from '@/modules/reader/types/Reader.types.ts';
|
import {
|
||||||
|
IReaderSettings,
|
||||||
|
ReaderCustomFilter,
|
||||||
|
ReaderPagerProps,
|
||||||
|
TReaderScrollbarContext,
|
||||||
|
} from '@/modules/reader/types/Reader.types.ts';
|
||||||
import {
|
import {
|
||||||
getImageMarginStyling,
|
getImageMarginStyling,
|
||||||
getImagePlaceholderStyling,
|
getImagePlaceholderStyling,
|
||||||
@@ -49,97 +54,100 @@ const getCustomFilterString = (customFilter: ReaderCustomFilter): string =>
|
|||||||
})
|
})
|
||||||
.join(' ');
|
.join(' ');
|
||||||
|
|
||||||
const BaseReaderPage = forwardRef(
|
const BaseReaderPage = ({
|
||||||
(
|
pageIndex,
|
||||||
{
|
pagesIndex,
|
||||||
display,
|
isPrimaryPage,
|
||||||
doublePage = false,
|
display,
|
||||||
position,
|
doublePage = false,
|
||||||
marginTop,
|
position,
|
||||||
shouldLoad,
|
marginTop,
|
||||||
readingMode,
|
shouldLoad,
|
||||||
customFilter,
|
readingMode,
|
||||||
pageScaleMode,
|
customFilter,
|
||||||
shouldStretchPage,
|
pageScaleMode,
|
||||||
readerWidth,
|
shouldStretchPage,
|
||||||
scrollbarXSize,
|
readerWidth,
|
||||||
scrollbarYSize,
|
scrollbarXSize,
|
||||||
...props
|
scrollbarYSize,
|
||||||
}: Omit<ComponentProps<typeof SpinnerImage>, 'ref' | 'spinnerStyle' | 'imgStyle'> &
|
onLoad,
|
||||||
Pick<
|
onError,
|
||||||
IReaderSettings,
|
setRef,
|
||||||
'readingMode' | 'customFilter' | 'pageScaleMode' | 'shouldStretchPage' | 'readerWidth'
|
...props
|
||||||
> &
|
}: Omit<ComponentProps<typeof SpinnerImage>, 'ref' | 'spinnerStyle' | 'imgStyle' | 'onLoad' | 'onError'> &
|
||||||
Pick<TReaderScrollbarContext, 'scrollbarXSize' | 'scrollbarYSize'> & {
|
Pick<IReaderSettings, 'readingMode' | 'customFilter' | 'pageScaleMode' | 'shouldStretchPage' | 'readerWidth'> &
|
||||||
display: boolean;
|
Pick<TReaderScrollbarContext, 'scrollbarXSize' | 'scrollbarYSize'> & {
|
||||||
doublePage?: boolean;
|
pageIndex: number;
|
||||||
position?: 'left' | 'right';
|
pagesIndex: number;
|
||||||
marginTop?: number;
|
isPrimaryPage: boolean;
|
||||||
},
|
display: boolean;
|
||||||
ref: ForwardedRef<HTMLImageElement | null>,
|
doublePage?: boolean;
|
||||||
) => {
|
position?: 'left' | 'right';
|
||||||
const isTabletWidth = MediaQuery.useIsTabletWidth();
|
marginTop?: number;
|
||||||
|
onLoad: ReaderPagerProps['onLoad'];
|
||||||
|
onError: ReaderPagerProps['onError'];
|
||||||
|
setRef?: (pagesIndex: number, ref: HTMLElement | null) => void;
|
||||||
|
}) => {
|
||||||
|
const isTabletWidth = MediaQuery.useIsTabletWidth();
|
||||||
|
|
||||||
if (!display && !shouldLoad) {
|
const handleLoad = useCallback(() => onLoad?.(pagesIndex, isPrimaryPage), [onLoad, pagesIndex, isPrimaryPage]);
|
||||||
return null;
|
const handleError = useCallback(() => onError?.(pageIndex), [onError, pageIndex]);
|
||||||
}
|
const updateRef = useCallback((element: HTMLElement | null) => setRef?.(pagesIndex, element), [pagesIndex, setRef]);
|
||||||
|
|
||||||
return (
|
if (!display && !shouldLoad) {
|
||||||
<SpinnerImage
|
return null;
|
||||||
{...props}
|
}
|
||||||
shouldLoad={shouldLoad}
|
|
||||||
shouldDecode
|
return (
|
||||||
ref={ref}
|
<SpinnerImage
|
||||||
spinnerStyle={{
|
{...props}
|
||||||
backgroundColor: 'background.paper',
|
onLoad={handleLoad}
|
||||||
...getImagePlaceholderStyling(
|
onError={handleError}
|
||||||
readingMode,
|
shouldLoad={shouldLoad}
|
||||||
shouldStretchPage,
|
shouldDecode
|
||||||
pageScaleMode,
|
ref={updateRef}
|
||||||
readerWidth,
|
spinnerStyle={{
|
||||||
scrollbarXSize,
|
backgroundColor: 'background.paper',
|
||||||
scrollbarYSize,
|
...getImagePlaceholderStyling(
|
||||||
doublePage,
|
readingMode,
|
||||||
isTabletWidth,
|
shouldStretchPage,
|
||||||
),
|
pageScaleMode,
|
||||||
...applyStyles(!display, {
|
readerWidth,
|
||||||
display: 'none',
|
scrollbarXSize,
|
||||||
}),
|
scrollbarYSize,
|
||||||
...getImageMarginStyling(doublePage, position),
|
doublePage,
|
||||||
}}
|
isTabletWidth,
|
||||||
imgStyle={{
|
),
|
||||||
...getImageWidthStyling(
|
...applyStyles(!display, {
|
||||||
readingMode,
|
display: 'none',
|
||||||
shouldStretchPage,
|
}),
|
||||||
pageScaleMode,
|
...getImageMarginStyling(doublePage, position),
|
||||||
doublePage,
|
}}
|
||||||
readerWidth,
|
imgStyle={{
|
||||||
true,
|
...getImageWidthStyling(readingMode, shouldStretchPage, pageScaleMode, doublePage, readerWidth, true),
|
||||||
),
|
display: 'block',
|
||||||
display: 'block',
|
...applyStyles(!display, {
|
||||||
...applyStyles(!display, {
|
display: 'none',
|
||||||
display: 'none',
|
}),
|
||||||
}),
|
filter: getCustomFilterString(customFilter),
|
||||||
filter: getCustomFilterString(customFilter),
|
objectFit: 'contain',
|
||||||
objectFit: 'contain',
|
objectPosition: position,
|
||||||
objectPosition: position,
|
userSelect: 'none',
|
||||||
userSelect: 'none',
|
...getImageMarginStyling(doublePage, position),
|
||||||
...getImageMarginStyling(doublePage, position),
|
...applyStyles(marginTop !== undefined, {
|
||||||
...applyStyles(marginTop !== undefined, {
|
mt: `${marginTop}px`,
|
||||||
mt: `${marginTop}px`,
|
}),
|
||||||
}),
|
}}
|
||||||
}}
|
hideImgStyle={{
|
||||||
hideImgStyle={{
|
visibility: 'hidden',
|
||||||
visibility: 'hidden',
|
minWidth: 0,
|
||||||
minWidth: 0,
|
minHeight: 0,
|
||||||
minHeight: 0,
|
width: 0,
|
||||||
width: 0,
|
height: 0,
|
||||||
height: 0,
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
};
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export const ReaderPage = withPropsFrom(
|
export const ReaderPage = withPropsFrom(
|
||||||
memo(BaseReaderPage),
|
memo(BaseReaderPage),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
ForwardedRef,
|
ForwardedRef,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
memo,
|
memo,
|
||||||
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
@@ -162,6 +163,30 @@ const BaseReaderViewer = forwardRef(
|
|||||||
|
|
||||||
const inViewportType = READING_MODE_TO_IN_VIEWPORT_TYPE[readingMode];
|
const inViewportType = READING_MODE_TO_IN_VIEWPORT_TYPE[readingMode];
|
||||||
|
|
||||||
|
const onLoad = useCallback(
|
||||||
|
(pagesIndex: number, isPrimary: boolean = true) => {
|
||||||
|
const page = actualPages[pagesIndex];
|
||||||
|
const { index, url } = isPrimary ? page.primary : page.secondary!;
|
||||||
|
|
||||||
|
if (readingMode === ReadingMode.DOUBLE_PAGE) {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
setPagesToSpreadState((prevState) => prevState.toSpliced(index, 1, isSpreadPage(img)));
|
||||||
|
};
|
||||||
|
img.src = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPageLoadStates((statePageLoadStates) => statePageLoadStates.toSpliced(index, 1, { loaded: true }));
|
||||||
|
},
|
||||||
|
[actualPages, readingMode],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onError = useCallback((pageIndex: number) => {
|
||||||
|
setPageLoadStates((statePageLoadStates) =>
|
||||||
|
statePageLoadStates.toSpliced(pageIndex, 1, { loaded: false, error: true }),
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// reset spread state
|
// reset spread state
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
setPagesToSpreadState(Array(totalPages).fill(false));
|
setPagesToSpreadState(Array(totalPages).fill(false));
|
||||||
@@ -310,27 +335,8 @@ const BaseReaderViewer = forwardRef(
|
|||||||
pageLoadStates={pageLoadStates}
|
pageLoadStates={pageLoadStates}
|
||||||
retryFailedPagesKeyPrefix={retryFailedPagesKeyPrefix}
|
retryFailedPagesKeyPrefix={retryFailedPagesKeyPrefix}
|
||||||
imageRefs={imageRefs}
|
imageRefs={imageRefs}
|
||||||
onLoad={(pagesIndex, isPrimary = true) => {
|
onLoad={onLoad}
|
||||||
const page = actualPages[pagesIndex];
|
onError={onError}
|
||||||
const { index, url } = isPrimary ? page.primary : page.secondary!;
|
|
||||||
|
|
||||||
if (readingMode === ReadingMode.DOUBLE_PAGE) {
|
|
||||||
const img = new Image();
|
|
||||||
img.onload = () => {
|
|
||||||
setPagesToSpreadState((prevState) => prevState.toSpliced(index, 1, isSpreadPage(img)));
|
|
||||||
};
|
|
||||||
img.src = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
setPageLoadStates((statePageLoadStates) =>
|
|
||||||
statePageLoadStates.toSpliced(index, 1, { loaded: true }),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
onError={(pageIndex) => {
|
|
||||||
setPageLoadStates((statePageLoadStates) =>
|
|
||||||
statePageLoadStates.toSpliced(pageIndex, 1, { loaded: false, error: true }),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 { memo, ReactNode, useEffect, useMemo, useRef } from 'react';
|
import { memo, ReactNode, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
import Box, { BoxProps } from '@mui/material/Box';
|
import Box, { BoxProps } from '@mui/material/Box';
|
||||||
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
||||||
import { getImageWidthStyling, getPageIndexesToLoad } from '@/modules/reader/utils/ReaderPager.utils.tsx';
|
import { getImageWidthStyling, getPageIndexesToLoad } from '@/modules/reader/utils/ReaderPager.utils.tsx';
|
||||||
@@ -37,7 +37,7 @@ const BaseBasePager = ({
|
|||||||
pagesIndex: number,
|
pagesIndex: number,
|
||||||
shouldLoad: boolean,
|
shouldLoad: boolean,
|
||||||
shouldDisplay: boolean,
|
shouldDisplay: boolean,
|
||||||
setRef: (element: HTMLElement | null) => void,
|
setRef: (pagesIndex: number, element: HTMLElement | null) => void,
|
||||||
) => ReactNode;
|
) => ReactNode;
|
||||||
slots?: { boxProps?: BoxProps };
|
slots?: { boxProps?: BoxProps };
|
||||||
}) => {
|
}) => {
|
||||||
@@ -50,6 +50,14 @@ const BaseBasePager = ({
|
|||||||
previousCurrentPageIndex.current = currentPageIndex;
|
previousCurrentPageIndex.current = currentPageIndex;
|
||||||
}, [pagesIndexesToRender]);
|
}, [pagesIndexesToRender]);
|
||||||
|
|
||||||
|
const setRef = useCallback(
|
||||||
|
(pagesIndex: number, element: HTMLElement | null) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
imageRefs.current[pagesIndex] = element;
|
||||||
|
},
|
||||||
|
[imageRefs],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
{...slots?.boxProps}
|
{...slots?.boxProps}
|
||||||
@@ -70,10 +78,7 @@ const BaseBasePager = ({
|
|||||||
pagesIndex,
|
pagesIndex,
|
||||||
pagesIndexesToRender.includes(pagesIndex),
|
pagesIndexesToRender.includes(pagesIndex),
|
||||||
[ReaderTransitionPageMode.NONE, ReaderTransitionPageMode.BOTH].includes(transitionPageMode),
|
[ReaderTransitionPageMode.NONE, ReaderTransitionPageMode.BOTH].includes(transitionPageMode),
|
||||||
(element) => {
|
setRef,
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
imageRefs.current[pagesIndex] = element;
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
<ReaderTransitionPage
|
<ReaderTransitionPage
|
||||||
|
|||||||
@@ -69,8 +69,10 @@ const BaseReaderDoublePagedPager = ({
|
|||||||
<Fragment key={`${primary.url}_${secondary?.url}`}>
|
<Fragment key={`${primary.url}_${secondary?.url}`}>
|
||||||
{createReaderPage(
|
{createReaderPage(
|
||||||
page,
|
page,
|
||||||
() => onLoad?.(pagesIndex),
|
pagesIndex,
|
||||||
() => onError?.(primary.index),
|
true,
|
||||||
|
onLoad,
|
||||||
|
onError,
|
||||||
shouldLoad,
|
shouldLoad,
|
||||||
shouldDisplay && isPrimaryPage,
|
shouldDisplay && isPrimaryPage,
|
||||||
currentPage.primary.index,
|
currentPage.primary.index,
|
||||||
@@ -82,8 +84,10 @@ const BaseReaderDoublePagedPager = ({
|
|||||||
{hasSecondaryPage &&
|
{hasSecondaryPage &&
|
||||||
createReaderPage(
|
createReaderPage(
|
||||||
{ ...page, primary: { ...page.secondary! } },
|
{ ...page, primary: { ...page.secondary! } },
|
||||||
() => onLoad?.(pagesIndex, false),
|
pagesIndex,
|
||||||
() => onError?.(secondary.index),
|
false,
|
||||||
|
onLoad,
|
||||||
|
onError,
|
||||||
shouldLoad,
|
shouldLoad,
|
||||||
shouldDisplay && isSecondaryPage,
|
shouldDisplay && isSecondaryPage,
|
||||||
currentSecondaryPageIndex,
|
currentSecondaryPageIndex,
|
||||||
|
|||||||
@@ -36,8 +36,10 @@ const BaseReaderHorizontalPager = ({
|
|||||||
createPage={(page, pagesIndex, shouldLoad, _, setRef) =>
|
createPage={(page, pagesIndex, shouldLoad, _, setRef) =>
|
||||||
createReaderPage(
|
createReaderPage(
|
||||||
page,
|
page,
|
||||||
() => onLoad?.(pagesIndex),
|
pagesIndex,
|
||||||
() => onError?.(page.primary.index),
|
true,
|
||||||
|
onLoad,
|
||||||
|
onError,
|
||||||
shouldLoad,
|
shouldLoad,
|
||||||
true,
|
true,
|
||||||
currentPageIndex,
|
currentPageIndex,
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ const BaseReaderPagedPager = ({
|
|||||||
createPage={(page, pagesIndex, shouldLoad, shouldDisplay) =>
|
createPage={(page, pagesIndex, shouldLoad, shouldDisplay) =>
|
||||||
createReaderPage(
|
createReaderPage(
|
||||||
page,
|
page,
|
||||||
() => onLoad?.(pagesIndex),
|
pagesIndex,
|
||||||
() => onError?.(page.primary.index),
|
true,
|
||||||
|
onLoad,
|
||||||
|
onError,
|
||||||
shouldLoad,
|
shouldLoad,
|
||||||
shouldDisplay && currentPageIndex === page.primary.index,
|
shouldDisplay && currentPageIndex === page.primary.index,
|
||||||
currentPageIndex,
|
currentPageIndex,
|
||||||
|
|||||||
@@ -33,8 +33,10 @@ const BaseReaderVerticalPager = ({
|
|||||||
createPage={(page, pagesIndex, shouldLoad, _, setRef) =>
|
createPage={(page, pagesIndex, shouldLoad, _, setRef) =>
|
||||||
createReaderPage(
|
createReaderPage(
|
||||||
page,
|
page,
|
||||||
() => onLoad?.(pagesIndex),
|
pagesIndex,
|
||||||
() => onError?.(page.primary.index),
|
true,
|
||||||
|
onLoad,
|
||||||
|
onError,
|
||||||
shouldLoad,
|
shouldLoad,
|
||||||
true,
|
true,
|
||||||
currentPageIndex,
|
currentPageIndex,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Direction, Theme } from '@mui/material/styles';
|
import { Direction, Theme } from '@mui/material/styles';
|
||||||
import { ReactNode } from 'react';
|
import { ComponentProps, ReactNode } from 'react';
|
||||||
import {
|
import {
|
||||||
IReaderSettings,
|
IReaderSettings,
|
||||||
PageInViewportType,
|
PageInViewportType,
|
||||||
@@ -227,7 +227,16 @@ export const createPageData = (url: string, index: number): ReaderStatePages['pa
|
|||||||
|
|
||||||
export const createPagesData = (pageUrls: string[]): ReaderStatePages['pages'] => pageUrls.map(createPageData);
|
export const createPagesData = (pageUrls: string[]): ReaderStatePages['pages'] => pageUrls.map(createPageData);
|
||||||
|
|
||||||
const getPageDownloadPriority = (currentPageIndex: number, pageIndex: number, totalPages: number): number => {
|
const getPageDownloadPriority = (
|
||||||
|
currentPageIndex: number,
|
||||||
|
pageIndex: number,
|
||||||
|
totalPages: number,
|
||||||
|
shouldLoad: boolean,
|
||||||
|
): number => {
|
||||||
|
if (!shouldLoad) {
|
||||||
|
return Number.MAX_SAFE_INTEGER;
|
||||||
|
}
|
||||||
|
|
||||||
const distanceToCurrentPage = Math.abs(pageIndex - currentPageIndex);
|
const distanceToCurrentPage = Math.abs(pageIndex - currentPageIndex);
|
||||||
const priorityBasedOnDistance = totalPages - distanceToCurrentPage;
|
const priorityBasedOnDistance = totalPages - distanceToCurrentPage;
|
||||||
|
|
||||||
@@ -241,8 +250,10 @@ const getPageDownloadPriority = (currentPageIndex: number, pageIndex: number, to
|
|||||||
|
|
||||||
export const createReaderPage = (
|
export const createReaderPage = (
|
||||||
{ primary: { index, alt, url } }: ReaderStatePages['pages'][number],
|
{ primary: { index, alt, url } }: ReaderStatePages['pages'][number],
|
||||||
onLoad: () => void,
|
pagesIndex: number,
|
||||||
onError: (() => void) | undefined,
|
isPrimaryPage: boolean,
|
||||||
|
onLoad: ComponentProps<typeof ReaderPage>['onLoad'],
|
||||||
|
onError: ComponentProps<typeof ReaderPage>['onError'],
|
||||||
shouldLoad: boolean,
|
shouldLoad: boolean,
|
||||||
display: boolean,
|
display: boolean,
|
||||||
currentPageIndex: number,
|
currentPageIndex: number,
|
||||||
@@ -251,15 +262,18 @@ export const createReaderPage = (
|
|||||||
position?: 'left' | 'right',
|
position?: 'left' | 'right',
|
||||||
isDoublePage?: boolean,
|
isDoublePage?: boolean,
|
||||||
marginTop?: number,
|
marginTop?: number,
|
||||||
setRef?: (ref: HTMLElement | null) => void,
|
setRef?: (pagesIndex: number, ref: HTMLElement | null) => void,
|
||||||
): ReactNode => (
|
): ReactNode => (
|
||||||
<ReaderPage
|
<ReaderPage
|
||||||
ref={setRef}
|
setRef={setRef}
|
||||||
|
pageIndex={index}
|
||||||
|
pagesIndex={pagesIndex}
|
||||||
|
isPrimaryPage={isPrimaryPage}
|
||||||
key={url}
|
key={url}
|
||||||
src={url}
|
src={url}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
display={display}
|
display={display}
|
||||||
priority={getPageDownloadPriority(currentPageIndex, index, totalPages)}
|
priority={getPageDownloadPriority(currentPageIndex, index, totalPages, shouldLoad)}
|
||||||
position={position}
|
position={position}
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
|
|||||||
Reference in New Issue
Block a user