diff --git a/src/modules/reader/components/viewer/ReaderChapterViewer.tsx b/src/modules/reader/components/viewer/ReaderChapterViewer.tsx index 346cb59e..826bcbd9 100644 --- a/src/modules/reader/components/viewer/ReaderChapterViewer.tsx +++ b/src/modules/reader/components/viewer/ReaderChapterViewer.tsx @@ -149,6 +149,7 @@ const BaseReaderChapterViewer = ({ const ref = useRef(null); const isCurrentChapterRef = useRef(isCurrentChapter); const imageRefs = useRef<(HTMLElement | null)[]>(pages.map(() => null)); + const pagerRef = useRef(null); const actualPages = useMemo(() => { const arePagesLoaded = !!totalPages; @@ -404,14 +405,14 @@ const BaseReaderChapterViewer = ({ isCurrentChapter={isCurrentChapter} isPreviousChapterVisible={isPreviousChapterVisible} isNextChapterVisible={isNextChapterVisible} - firstImage={imageRefs.current[0]} - lastImage={imageRefs.current[imageRefs.current.length - 1]} + imageWrapper={pagerRef.current} /> {((!isContinuousReadingModeActive && isCurrentChapter) || (isContinuousReadingModeActive && (isInitialChapter || isLeadingChapter))) && ( )} ; }) => { useReaderInfiniteScrollUpdateChapter( @@ -47,7 +45,7 @@ const BaseReaderInfiniteScrollUpdateChapter = ({ readingMode, readingDirection, openChapter, - firstImage, + imageWrapper, ); useReaderInfiniteScrollUpdateChapter( 'last', @@ -58,7 +56,7 @@ const BaseReaderInfiniteScrollUpdateChapter = ({ readingMode, readingDirection, openChapter, - lastImage, + imageWrapper, ); return null; diff --git a/src/modules/reader/components/viewer/pager/BasePager.tsx b/src/modules/reader/components/viewer/pager/BasePager.tsx index c91e7aa6..1b7c0e22 100644 --- a/src/modules/reader/components/viewer/pager/BasePager.tsx +++ b/src/modules/reader/components/viewer/pager/BasePager.tsx @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { memo, ReactNode, useCallback, useEffect, useMemo, useRef } from 'react'; +import { forwardRef, memo, ReactNode, useCallback, useEffect, useMemo, useRef } from 'react'; import Box, { BoxProps } from '@mui/material/Box'; import { getPageIndexesToLoad, isATransitionPageVisible } from '@/modules/reader/utils/ReaderPager.utils.tsx'; import { ReaderStatePages } from '@/modules/reader/types/ReaderProgressBar.types.ts'; @@ -14,112 +14,130 @@ import { IReaderSettings, ReaderPagerProps, ReaderTransitionPageMode } from '@/m import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx'; -const BaseBasePager = ({ - currentPageIndex, - pages, - transitionPageMode, - imageRefs, - createPage, - slots, - readingMode, - imagePreLoadAmount, - isCurrentChapter, - isPreviousChapter, - isNextChapter, - customFilter, - pageScaleMode, - shouldStretchPage, - readerWidth, - scrollbarXSize, - scrollbarYSize, - readerNavBarWidth, -}: Omit & - Pick & { - createPage: ( - page: ReaderStatePages['pages'][number], - pagesIndex: number, - shouldLoad: boolean, - shouldDisplay: boolean, - setRef: (pagesIndex: number, element: HTMLElement | null) => void, - readingMode: ReaderPagerProps['readingMode'], - customFilter: ReaderPagerProps['customFilter'], - pageScaleMode: ReaderPagerProps['pageScaleMode'], - shouldStretchPage: ReaderPagerProps['shouldStretchPage'], - readerWidth: ReaderPagerProps['readerWidth'], - scrollbarXSize: ReaderPagerProps['scrollbarXSize'], - scrollbarYSize: ReaderPagerProps['scrollbarYSize'], - readerNavBarWidth: ReaderPagerProps['readerNavBarWidth'], - ) => ReactNode; - slots?: { boxProps?: BoxProps }; - }) => { - const previousCurrentPageIndex = useRef(-1); - const pagesIndexesToRender = useMemo( - () => - getPageIndexesToLoad( +const BaseBasePager = forwardRef< + HTMLDivElement, + Omit & + Pick & { + createPage: ( + page: ReaderStatePages['pages'][number], + pagesIndex: number, + shouldLoad: boolean, + shouldDisplay: boolean, + setRef: (pagesIndex: number, element: HTMLElement | null) => void, + readingMode: ReaderPagerProps['readingMode'], + customFilter: ReaderPagerProps['customFilter'], + pageScaleMode: ReaderPagerProps['pageScaleMode'], + shouldStretchPage: ReaderPagerProps['shouldStretchPage'], + readerWidth: ReaderPagerProps['readerWidth'], + scrollbarXSize: ReaderPagerProps['scrollbarXSize'], + scrollbarYSize: ReaderPagerProps['scrollbarYSize'], + readerNavBarWidth: ReaderPagerProps['readerNavBarWidth'], + ) => ReactNode; + slots?: { boxProps?: BoxProps }; + } +>( + ( + { + currentPageIndex, + pages, + transitionPageMode, + imageRefs, + createPage, + slots, + readingMode, + imagePreLoadAmount, + isCurrentChapter, + isPreviousChapter, + isNextChapter, + customFilter, + pageScaleMode, + shouldStretchPage, + readerWidth, + scrollbarXSize, + scrollbarYSize, + readerNavBarWidth, + }, + ref, + ) => { + const previousCurrentPageIndex = useRef(-1); + const pagesIndexesToRender = useMemo( + () => + getPageIndexesToLoad( + currentPageIndex, + pages, + previousCurrentPageIndex.current, + imagePreLoadAmount, + readingMode, + isCurrentChapter, + isPreviousChapter, + isNextChapter, + ), + [ currentPageIndex, pages, - previousCurrentPageIndex.current, imagePreLoadAmount, readingMode, isCurrentChapter, isPreviousChapter, isNextChapter, - ), - [currentPageIndex, pages, imagePreLoadAmount, readingMode, isCurrentChapter, isPreviousChapter, isNextChapter], - ); - useEffect(() => { - previousCurrentPageIndex.current = currentPageIndex; - }, [pagesIndexesToRender]); + ], + ); + useEffect(() => { + previousCurrentPageIndex.current = currentPageIndex; + }, [pagesIndexesToRender]); - const setRef = useCallback( - (pagesIndex: number, element: HTMLElement | null) => { - // eslint-disable-next-line no-param-reassign - imageRefs.current[pagesIndex] = element; - }, - [imageRefs], - ); + const setRef = useCallback( + (pagesIndex: number, element: HTMLElement | null) => { + // eslint-disable-next-line no-param-reassign + imageRefs.current[pagesIndex] = element; + }, + [imageRefs], + ); - return ( - - {pages.map((page, pagesIndex) => - createPage( - page, - pagesIndex, - pagesIndexesToRender.includes(pagesIndex), - [ReaderTransitionPageMode.NONE, ReaderTransitionPageMode.BOTH].includes(transitionPageMode), - setRef, - readingMode, - customFilter, - pageScaleMode, - shouldStretchPage, - readerWidth, - scrollbarXSize, - scrollbarYSize, - readerNavBarWidth, - ), - )} - - ); -}; + ...(Array.isArray(slots?.boxProps?.sx) ? (slots?.boxProps?.sx ?? []) : [slots?.boxProps?.sx]), + // hide pager, without actually unmounting it to prevent re-renders, while a chapter transition page is taking up the full screen + applyStyles( + !isContinuousReadingMode(readingMode) && + isATransitionPageVisible(transitionPageMode, readingMode), + { + visibility: 'hidden', + width: 0, + height: 0, + m: 0, + p: 0, + }, + ), + ]} + > + {pages.map((page, pagesIndex) => + createPage( + page, + pagesIndex, + pagesIndexesToRender.includes(pagesIndex), + [ReaderTransitionPageMode.NONE, ReaderTransitionPageMode.BOTH].includes(transitionPageMode), + setRef, + readingMode, + customFilter, + pageScaleMode, + shouldStretchPage, + readerWidth, + scrollbarXSize, + scrollbarYSize, + readerNavBarWidth, + ), + )} + + ); + }, +); export const BasePager = memo(BaseBasePager); diff --git a/src/modules/reader/components/viewer/pager/ReaderDoublePagedPager.tsx b/src/modules/reader/components/viewer/pager/ReaderDoublePagedPager.tsx index 4da27be3..6d79c3b7 100644 --- a/src/modules/reader/components/viewer/pager/ReaderDoublePagedPager.tsx +++ b/src/modules/reader/components/viewer/pager/ReaderDoublePagedPager.tsx @@ -7,7 +7,7 @@ */ import { Direction, useTheme } from '@mui/material/styles'; -import { Fragment, memo, useMemo } from 'react'; +import { forwardRef, Fragment, memo, useMemo } from 'react'; import { BasePager } from '@/modules/reader/components/viewer/pager/BasePager.tsx'; import { IReaderSettings, @@ -41,13 +41,10 @@ const getPagePosition = ( return isLtrReadingDirection ? 'right' : 'left'; }; -const BaseReaderDoublePagedPager = ({ - onLoad, - onError, - pageLoadStates, - retryFailedPagesKeyPrefix, - ...props -}: ReaderPagerProps & Pick) => { +const BaseReaderDoublePagedPager = forwardRef< + HTMLDivElement, + ReaderPagerProps & Pick +>(({ onLoad, onError, pageLoadStates, retryFailedPagesKeyPrefix, ...props }, ref) => { const { currentPageIndex, pages, totalPages, readingDirection, pageScaleMode } = props; const { direction: themeDirection } = useTheme(); @@ -57,6 +54,7 @@ const BaseReaderDoublePagedPager = ({ return ( { const { primary, secondary } = page; @@ -129,6 +127,6 @@ const BaseReaderDoublePagedPager = ({ }} /> ); -}; +}); export const ReaderDoublePagedPager = memo(BaseReaderDoublePagedPager); diff --git a/src/modules/reader/components/viewer/pager/ReaderHorizontalPager.tsx b/src/modules/reader/components/viewer/pager/ReaderHorizontalPager.tsx index 232b7e54..0e32b7e5 100644 --- a/src/modules/reader/components/viewer/pager/ReaderHorizontalPager.tsx +++ b/src/modules/reader/components/viewer/pager/ReaderHorizontalPager.tsx @@ -7,19 +7,16 @@ */ import { useTheme } from '@mui/material/styles'; -import { memo } from 'react'; +import { forwardRef, memo } from 'react'; import { BasePager } from '@/modules/reader/components/viewer/pager/BasePager.tsx'; import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; import { IReaderSettings, ReaderPagerProps, ReadingDirection } from '@/modules/reader/types/Reader.types.ts'; import { createReaderPage } from '@/modules/reader/utils/ReaderPager.utils.tsx'; -const BaseReaderHorizontalPager = ({ - onLoad, - onError, - pageLoadStates, - retryFailedPagesKeyPrefix, - ...props -}: ReaderPagerProps & Pick) => { +const BaseReaderHorizontalPager = forwardRef< + HTMLDivElement, + ReaderPagerProps & Pick +>(({ onLoad, onError, pageLoadStates, retryFailedPagesKeyPrefix, ...props }, ref) => { const { currentPageIndex, totalPages, pageGap, readingDirection } = props; const { direction: themeDirection } = useTheme(); @@ -28,6 +25,7 @@ const BaseReaderHorizontalPager = ({ return ( createReaderPage( @@ -70,6 +68,6 @@ const BaseReaderHorizontalPager = ({ }} /> ); -}; +}); export const ReaderHorizontalPager = memo(BaseReaderHorizontalPager); diff --git a/src/modules/reader/components/viewer/pager/ReaderPagedPager.tsx b/src/modules/reader/components/viewer/pager/ReaderPagedPager.tsx index 537cfae7..23ab4194 100644 --- a/src/modules/reader/components/viewer/pager/ReaderPagedPager.tsx +++ b/src/modules/reader/components/viewer/pager/ReaderPagedPager.tsx @@ -6,48 +6,45 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { memo } from 'react'; +import { forwardRef, memo } from 'react'; import { BasePager } from '@/modules/reader/components/viewer/pager/BasePager.tsx'; import { ReaderPagerProps } from '@/modules/reader/types/Reader.types.ts'; import { createReaderPage } from '@/modules/reader/utils/ReaderPager.utils.tsx'; -const BaseReaderPagedPager = ({ - onLoad, - onError, - pageLoadStates, - retryFailedPagesKeyPrefix, - ...props -}: ReaderPagerProps) => { - const { currentPageIndex, totalPages } = props; +const BaseReaderPagedPager = forwardRef( + ({ onLoad, onError, pageLoadStates, retryFailedPagesKeyPrefix, ...props }, ref) => { + const { currentPageIndex, totalPages } = props; - return ( - - createReaderPage( - page, - pagesIndex, - true, - pageLoadStates[page.primary.index].loaded, - onLoad, - onError, - shouldLoad, - shouldDisplay && shouldLoad && currentPageIndex === page.primary.index, - currentPageIndex, - totalPages, - ...baseProps, - pageLoadStates[page.primary.index].error ? retryFailedPagesKeyPrefix : undefined, - ) - } - slots={{ - boxProps: { - sx: { - margin: 'auto', + return ( + + createReaderPage( + page, + pagesIndex, + true, + pageLoadStates[page.primary.index].loaded, + onLoad, + onError, + shouldLoad, + shouldDisplay && shouldLoad && currentPageIndex === page.primary.index, + currentPageIndex, + totalPages, + ...baseProps, + pageLoadStates[page.primary.index].error ? retryFailedPagesKeyPrefix : undefined, + ) + } + slots={{ + boxProps: { + sx: { + margin: 'auto', + }, }, - }, - }} - /> - ); -}; + }} + /> + ); + }, +); export const ReaderPagedPager = memo(BaseReaderPagedPager); diff --git a/src/modules/reader/components/viewer/pager/ReaderVerticalPager.tsx b/src/modules/reader/components/viewer/pager/ReaderVerticalPager.tsx index beca7ea6..31c292d5 100644 --- a/src/modules/reader/components/viewer/pager/ReaderVerticalPager.tsx +++ b/src/modules/reader/components/viewer/pager/ReaderVerticalPager.tsx @@ -6,49 +6,46 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { memo } from 'react'; +import { forwardRef, memo } from 'react'; import { BasePager } from '@/modules/reader/components/viewer/pager/BasePager.tsx'; import { ReaderPagerProps, ReadingMode } from '@/modules/reader/types/Reader.types.ts'; import { createReaderPage } from '@/modules/reader/utils/ReaderPager.utils.tsx'; -const BaseReaderVerticalPager = ({ - onLoad, - onError, - pageLoadStates, - retryFailedPagesKeyPrefix, - ...props -}: ReaderPagerProps) => { - const { currentPageIndex, totalPages, readingMode, pageGap } = props; +const BaseReaderVerticalPager = forwardRef( + ({ onLoad, onError, pageLoadStates, retryFailedPagesKeyPrefix, ...props }, ref) => { + const { currentPageIndex, totalPages, readingMode, pageGap } = props; - const isWebtoonMode = readingMode === ReadingMode.WEBTOON; - const actualPageGap = isWebtoonMode ? 0 : pageGap; + const isWebtoonMode = readingMode === ReadingMode.WEBTOON; + const actualPageGap = isWebtoonMode ? 0 : pageGap; - return ( - - createReaderPage( - page, - pagesIndex, - true, - pageLoadStates[page.primary.index].loaded, - onLoad, - onError, - shouldLoad, - true, - currentPageIndex, - totalPages, - ...baseProps, - pageLoadStates[page.primary.index].error ? retryFailedPagesKeyPrefix : undefined, - undefined, - undefined, - page.primary.index !== 0 ? actualPageGap : 0, - setRef, - ) - } - slots={{ boxProps: { sx: { margin: 'auto' } } }} - /> - ); -}; + return ( + + createReaderPage( + page, + pagesIndex, + true, + pageLoadStates[page.primary.index].loaded, + onLoad, + onError, + shouldLoad, + true, + currentPageIndex, + totalPages, + ...baseProps, + pageLoadStates[page.primary.index].error ? retryFailedPagesKeyPrefix : undefined, + undefined, + undefined, + page.primary.index !== 0 ? actualPageGap : 0, + setRef, + ) + } + slots={{ boxProps: { sx: { margin: 'auto' } } }} + /> + ); + }, +); export const ReaderVerticalPager = memo(BaseReaderVerticalPager); diff --git a/src/modules/reader/hooks/useReaderInfiniteScrollUpdateChapter.ts b/src/modules/reader/hooks/useReaderInfiniteScrollUpdateChapter.ts index 9ce6cc23..7c79ee56 100644 --- a/src/modules/reader/hooks/useReaderInfiniteScrollUpdateChapter.ts +++ b/src/modules/reader/hooks/useReaderInfiniteScrollUpdateChapter.ts @@ -195,7 +195,6 @@ export const useReaderInfiniteScrollUpdateChapter = ( // gets immediately observed once on initial render let isInitialObserve = true; - let wasNextChapterOpened = false; const intersectionObserver = new IntersectionObserver( (entries) => { if (isInitialObserve) { @@ -203,10 +202,6 @@ export const useReaderInfiniteScrollUpdateChapter = ( return; } - if (wasNextChapterOpened) { - return; - } - const entry = entries[entries.length - 1]; const elementIntersectionInfo = getElementIntersectionInfo( @@ -233,16 +228,17 @@ export const useReaderInfiniteScrollUpdateChapter = ( const openChapterToOpen = initialOpenPreviousChapter || openNextChapter; if (openChapterToOpen) { openChapter(chapterToOpenId, !isChapterToOpenVisible, false); - wasNextChapterOpened = true; return; } if (openPreviousChapter) { openChapter(chapterId, false, false); - wasNextChapterOpened = true; } }, - { threshold: [OPEN_CHAPTER_INTERSECTION_RATIO], rootMargin: '-10px 0px 0px 0px' }, + { + threshold: [OPEN_CHAPTER_INTERSECTION_RATIO], + rootMargin: pageType === 'first' ? '0px 0px -10px 0px' : '-10px 0px 0px 0px', + }, ); intersectionObserver.observe(image); diff --git a/src/modules/reader/utils/ReaderSettings.utils.tsx b/src/modules/reader/utils/ReaderSettings.utils.tsx index 324ce44a..3a7f1990 100644 --- a/src/modules/reader/utils/ReaderSettings.utils.tsx +++ b/src/modules/reader/utils/ReaderSettings.utils.tsx @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { ComponentType, MemoExoticComponent } from 'react'; +import { ForwardRefExoticComponent, MemoExoticComponent, RefAttributes } from 'react'; import { IReaderSettings, IReaderSettingsWithDefaultFlag, @@ -59,7 +59,7 @@ export const isAutoWebtoonMode = ( export const getPagerForReadingMode = ( readingMode: ReadingMode, -): MemoExoticComponent> => { +): MemoExoticComponent>> => { switch (readingMode) { case ReadingMode.SINGLE_PAGE: return ReaderPagedPager;