Remove "forwardRef" usage

This commit is contained in:
schroda
2025-09-24 00:51:13 +02:00
parent bcb5e391f0
commit a2d7091712
19 changed files with 1055 additions and 1101 deletions

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { MutableRefObject } from 'react';
import { MutableRefObject, Ref } from 'react';
import { TapZoneInvertMode, TapZoneLayouts } from '@/features/reader/tap-zones/TapZoneLayout.types.ts';
import { TChapterReader } from '@/features/chapter/Chapter.types.ts';
import { ReaderService } from '@/features/reader/services/ReaderService.ts';
@@ -349,6 +349,7 @@ export interface ReaderPagerProps
isPreloadMode: boolean;
resumeMode: ReaderResumeMode;
handleAsInitialRender: boolean;
ref?: Ref<HTMLDivElement>;
}
export enum PageInViewportType {

View File

@@ -17,7 +17,7 @@ import Link from '@mui/material/Link';
import { Link as RouterLink } from 'react-router-dom';
import { alpha } from '@mui/material/styles';
import Slide from '@mui/material/Slide';
import { forwardRef, memo } from 'react';
import { memo, Ref } from 'react';
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
import { TypographyMaxLines } from '@/base/components/texts/TypographyMaxLines.tsx';
import { makeToast } from '@/base/utils/Toast.ts';
@@ -38,7 +38,7 @@ import {
const DEFAULT_MANGA = { ...FALLBACK_MANGA, title: '' };
const BaseReaderOverlayHeaderMobile = forwardRef<HTMLDivElement, MobileHeaderProps>(({ isVisible }, ref) => {
const BaseReaderOverlayHeaderMobile = ({ isVisible, ref }: MobileHeaderProps & { ref?: Ref<HTMLDivElement> }) => {
const { t } = useTranslation();
const popupState = usePopupState({ popupId: 'reader-overlay-more-menu', variant: 'popover' });
const currentChapter = useReaderChaptersStore((state) => state.chapters.currentChapter);
@@ -126,6 +126,6 @@ const BaseReaderOverlayHeaderMobile = forwardRef<HTMLDivElement, MobileHeaderPro
</Stack>
</Slide>
);
});
};
export const ReaderOverlayHeaderMobile = memo(BaseReaderOverlayHeaderMobile);

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { forwardRef, useMemo } from 'react';
import { useMemo } from 'react';
import { CacheProvider } from '@emotion/react';
import { ThemeProvider } from '@mui/material/styles';
import Box, { BoxProps } from '@mui/material/Box';
@@ -17,12 +17,12 @@ import { ReaderService } from '@/features/reader/services/ReaderService.ts';
import { DIRECTION_TO_CACHE } from '@/features/theme/ThemeDirectionCache.ts';
import { withPropsFrom } from '@/base/hoc/withPropsFrom.tsx';
const BaseReaderProgressBarDirectionWrapper = forwardRef<
HTMLElement,
BoxProps & {
direction: ReturnType<typeof ReaderService.useGetThemeDirection>;
}
>(({ direction, ...boxProps }, ref) => {
const BaseReaderProgressBarDirectionWrapper = ({
direction,
...boxProps
}: BoxProps & {
direction: ReturnType<typeof ReaderService.useGetThemeDirection>;
}) => {
const {
settings: { customThemes, appTheme, themeMode, shouldUsePureBlackMode },
} = useMetadataServerSettings();
@@ -35,11 +35,11 @@ const BaseReaderProgressBarDirectionWrapper = forwardRef<
return (
<CacheProvider value={DIRECTION_TO_CACHE[direction]}>
<ThemeProvider theme={readerTheme}>
<Box {...boxProps} ref={ref} dir={direction} />
<Box {...boxProps} dir={direction} />
</ThemeProvider>
</CacheProvider>
);
});
};
export const ReaderProgressBarDirectionWrapper = withPropsFrom(
BaseReaderProgressBarDirectionWrapper,

View File

@@ -6,17 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import {
ForwardedRef,
forwardRef,
memo,
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react';
import { ForwardedRef, memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import Stack from '@mui/material/Stack';
import { useTheme } from '@mui/material/styles';
import { useLocation } from 'react-router-dom';
@@ -74,362 +64,356 @@ const READING_MODE_TO_IN_VIEWPORT_TYPE: Record<ReadingMode, PageInViewportType>
[ReadingMode.WEBTOON]: PageInViewportType.Y,
};
const BaseReaderViewer = forwardRef(
(
{
readerNavBarWidth,
updateCurrentPageIndex,
}: Pick<NavbarContextType, 'readerNavBarWidth'> & {
updateCurrentPageIndex: ReturnType<typeof ReaderControls.useUpdateCurrentPageIndex>;
const BaseReaderViewer = ({
readerNavBarWidth,
updateCurrentPageIndex,
ref,
}: Pick<NavbarContextType, 'readerNavBarWidth'> & {
updateCurrentPageIndex: ReturnType<typeof ReaderControls.useUpdateCurrentPageIndex>;
ref?: ForwardedRef<HTMLDivElement | null>;
}) => {
const { direction: themeDirection } = useTheme();
const isOverlayVisible = useReaderOverlayStore((state) => state.overlay.isVisible);
const {
currentPageIndex,
pageToScrollToIndex,
setPageToScrollToIndex,
pages,
totalPages,
setPages,
setPageLoadStates,
setTotalPages,
setCurrentPageIndex,
transitionPageMode,
retryFailedPagesKeyPrefix,
setTransitionPageMode,
} = useReaderPagesStore((state) => ({
currentPageIndex: state.pages.currentPageIndex,
pageToScrollToIndex: state.pages.pageToScrollToIndex,
setPageToScrollToIndex: state.pages.setPageToScrollToIndex,
pages: state.pages.pages,
totalPages: state.pages.totalPages,
setPages: state.pages.setPages,
setPageLoadStates: state.pages.setPageLoadStates,
setTotalPages: state.pages.setTotalPages,
setCurrentPageIndex: state.pages.setCurrentPageIndex,
transitionPageMode: state.pages.transitionPageMode,
retryFailedPagesKeyPrefix: state.pages.retryFailedPagesKeyPrefix,
setTransitionPageMode: state.pages.setTransitionPageMode,
}));
const { initialChapter, currentChapter, chapters, visibleChapters, isCurrentChapterReady } = useReaderChaptersStore(
(state) => ({
initialChapter: state.chapters.initialChapter,
currentChapter: state.chapters.currentChapter,
chapters: state.chapters.chapters,
visibleChapters: state.chapters.visibleChapters,
setReaderStateChapters: state.chapters.setReaderStateChapters,
isCurrentChapterReady: state.chapters.isCurrentChapterReady,
}),
);
const {
readingMode,
readingDirection,
readerWidth,
pageScaleMode,
shouldOffsetDoubleSpreads,
imagePreLoadAmount,
pageGap,
customFilter,
shouldStretchPage,
isStaticNav,
} = useReaderSettingsStore((state) => ({
readingMode: state.settings.readingMode.value,
readingDirection: state.settings.readingDirection.value,
readerWidth: state.settings.readerWidth.value,
pageScaleMode: state.settings.pageScaleMode.value,
shouldOffsetDoubleSpreads: state.settings.shouldOffsetDoubleSpreads.value,
imagePreLoadAmount: state.settings.imagePreLoadAmount,
pageGap: state.settings.pageGap.value,
customFilter: state.settings.customFilter,
shouldStretchPage: state.settings.shouldStretchPage.value,
isStaticNav: state.settings.isStaticNav,
}));
const { showPreview, setShowPreview } = useReaderTapZoneStore((state) => ({
showPreview: state.tapZone.showPreview,
setShowPreview: state.tapZone.setShowPreview,
}));
const { resumeMode = ReaderResumeMode.START } = useLocation<ReaderOpenChapterLocationState>().state ?? {
resumeMode: ReaderResumeMode.START,
};
const scrollElementRef = useRef<HTMLDivElement | null>(null);
const mergedRef = useMergedRef(ref, scrollElementRef);
const isContinuousVerticalReadingModeActive = isContinuousVerticalReadingMode(readingMode);
const isContinuousReadingModeActive = isContinuousReadingMode(readingMode);
const isDragging = useMouseDragScroll(scrollElementRef);
const automaticScrolling = useReaderAutoScrollStore((state) => ({
isPaused: state.autoScroll.isPaused,
pause: state.autoScroll.pause,
resume: state.autoScroll.resume,
setScrollRef: state.autoScroll.setScrollRef,
}));
useEffect(() => automaticScrolling.setScrollRef(scrollElementRef.current), []);
const scrollbarXSize = MediaQuery.useGetScrollbarSize('width', scrollElementRef.current);
const scrollbarYSize = MediaQuery.useGetScrollbarSize('height', scrollElementRef.current);
useLayoutEffect(() => {
const { scrollbar } = getReaderStore();
scrollbar.setXSize(scrollbarXSize);
scrollbar.setYSize(scrollbarYSize);
}, [scrollbarXSize, scrollbarYSize]);
const imageRefs = useRef<(HTMLElement | null)[]>(pages.map(() => null));
const [{ minChapterViewWidth, minChapterViewHeight, minChapterSizeSourceChapterId }, setChapterViewerSize] =
useState({
minChapterViewWidth: 0,
minChapterViewHeight: 0,
minChapterSizeSourceChapterId: -1,
});
const [, setTriggerReRender] = useState({});
const inViewportType = READING_MODE_TO_IN_VIEWPORT_TYPE[readingMode];
const isLtrReadingDirection = readingDirection === ReadingDirection.LTR;
const initialChapterIndex = useMemo(
() => chapters.findIndex((chapter) => chapter.id === initialChapter?.id),
[chapters, initialChapter?.id],
);
const chaptersToRender = useMemo(
() =>
chapters.slice(
Math.max(0, initialChapterIndex - visibleChapters.trailing),
Math.min(chapters.length, initialChapterIndex + visibleChapters.leading + 1),
),
[chapters, initialChapterIndex, visibleChapters.trailing, visibleChapters.leading],
);
const currentChapterIndex = useMemo(
() => chaptersToRender.findIndex((chapter) => chapter.id === currentChapter?.id),
[currentChapter, chaptersToRender],
);
const onChapterViewSizeChange = useCallback(
(width: number, height: number, chapterId: ChapterIdInfo['id']) => {
if (!isContinuousReadingModeActive) {
return;
}
const isSameChapterId = chapterId === minChapterSizeSourceChapterId;
if (isContinuousVerticalReadingModeActive) {
if (!isSameChapterId && minChapterViewWidth >= width) {
return;
}
setChapterViewerSize({
minChapterViewWidth: width,
minChapterViewHeight: 0,
minChapterSizeSourceChapterId: chapterId,
});
return;
}
if (isSameChapterId || minChapterViewHeight < height) {
setChapterViewerSize({
minChapterViewWidth: 0,
minChapterViewHeight: height,
minChapterSizeSourceChapterId: chapterId,
});
}
},
[
isContinuousReadingModeActive,
isContinuousVerticalReadingModeActive,
minChapterViewWidth,
minChapterViewHeight,
minChapterSizeSourceChapterId,
],
);
ref: ForwardedRef<HTMLDivElement | null>,
) => {
const { direction: themeDirection } = useTheme();
const isOverlayVisible = useReaderOverlayStore((state) => state.overlay.isVisible);
const {
currentPageIndex,
pageToScrollToIndex,
setPageToScrollToIndex,
pages,
totalPages,
setPages,
setPageLoadStates,
setTotalPages,
setCurrentPageIndex,
transitionPageMode,
retryFailedPagesKeyPrefix,
setTransitionPageMode,
} = useReaderPagesStore((state) => ({
currentPageIndex: state.pages.currentPageIndex,
pageToScrollToIndex: state.pages.pageToScrollToIndex,
setPageToScrollToIndex: state.pages.setPageToScrollToIndex,
pages: state.pages.pages,
totalPages: state.pages.totalPages,
setPages: state.pages.setPages,
setPageLoadStates: state.pages.setPageLoadStates,
setTotalPages: state.pages.setTotalPages,
setCurrentPageIndex: state.pages.setCurrentPageIndex,
transitionPageMode: state.pages.transitionPageMode,
retryFailedPagesKeyPrefix: state.pages.retryFailedPagesKeyPrefix,
setTransitionPageMode: state.pages.setTransitionPageMode,
}));
const { initialChapter, currentChapter, chapters, visibleChapters, isCurrentChapterReady } =
useReaderChaptersStore((state) => ({
initialChapter: state.chapters.initialChapter,
currentChapter: state.chapters.currentChapter,
chapters: state.chapters.chapters,
visibleChapters: state.chapters.visibleChapters,
setReaderStateChapters: state.chapters.setReaderStateChapters,
isCurrentChapterReady: state.chapters.isCurrentChapterReady,
}));
const {
readingMode,
readingDirection,
readerWidth,
pageScaleMode,
shouldOffsetDoubleSpreads,
imagePreLoadAmount,
pageGap,
customFilter,
shouldStretchPage,
isStaticNav,
} = useReaderSettingsStore((state) => ({
readingMode: state.settings.readingMode.value,
readingDirection: state.settings.readingDirection.value,
readerWidth: state.settings.readerWidth.value,
pageScaleMode: state.settings.pageScaleMode.value,
shouldOffsetDoubleSpreads: state.settings.shouldOffsetDoubleSpreads.value,
imagePreLoadAmount: state.settings.imagePreLoadAmount,
pageGap: state.settings.pageGap.value,
customFilter: state.settings.customFilter,
shouldStretchPage: state.settings.shouldStretchPage.value,
isStaticNav: state.settings.isStaticNav,
}));
const { showPreview, setShowPreview } = useReaderTapZoneStore((state) => ({
showPreview: state.tapZone.showPreview,
setShowPreview: state.tapZone.setShowPreview,
}));
const { resumeMode = ReaderResumeMode.START } = useLocation<ReaderOpenChapterLocationState>().state ?? {
resumeMode: ReaderResumeMode.START,
};
useReaderHandlePageSelection(
pageToScrollToIndex,
currentPageIndex,
pages,
totalPages,
setPageToScrollToIndex,
updateCurrentPageIndex,
isContinuousReadingModeActive,
imageRefs,
themeDirection,
readingDirection,
);
useReaderScrollToStartOnPageChange(
currentPageIndex,
isContinuousReadingModeActive,
themeDirection,
readingDirection,
scrollElementRef,
);
useReaderHideCursorOnInactivity(scrollElementRef);
useReaderHorizontalModeInvertXYScrolling(readingMode, readingDirection, scrollElementRef);
useReaderHideOverlayOnUserScroll(isOverlayVisible, showPreview, setShowPreview, scrollElementRef);
useReaderAutoScroll(
isOverlayVisible,
automaticScrolling.isPaused,
automaticScrolling.pause,
automaticScrolling.resume,
isStaticNav,
);
useReaderPreserveScrollPosition(
scrollElementRef,
currentChapter?.id,
currentChapterIndex,
currentPageIndex,
chaptersToRender,
visibleChapters,
readingMode,
readingDirection,
setPageToScrollToIndex,
pageScaleMode,
);
const scrollElementRef = useRef<HTMLDivElement | null>(null);
const mergedRef = useMergedRef(ref, scrollElementRef);
useLayoutEffect(() => {
setChapterViewerSize({
minChapterViewWidth: 0,
minChapterViewHeight: 0,
minChapterSizeSourceChapterId: -1,
});
setTriggerReRender({});
}, [readingMode]);
const isContinuousVerticalReadingModeActive = isContinuousVerticalReadingMode(readingMode);
const isContinuousReadingModeActive = isContinuousReadingMode(readingMode);
const isDragging = useMouseDragScroll(scrollElementRef);
if (!initialChapter || !currentChapter) {
throw new Error('ReaderViewer: illegal state - initialChapter and currentChapter should not be undefined');
}
const automaticScrolling = useReaderAutoScrollStore((state) => ({
isPaused: state.autoScroll.isPaused,
pause: state.autoScroll.pause,
resume: state.autoScroll.resume,
setScrollRef: state.autoScroll.setScrollRef,
}));
useEffect(() => automaticScrolling.setScrollRef(scrollElementRef.current), []);
const scrollbarXSize = MediaQuery.useGetScrollbarSize('width', scrollElementRef.current);
const scrollbarYSize = MediaQuery.useGetScrollbarSize('height', scrollElementRef.current);
useLayoutEffect(() => {
const { scrollbar } = getReaderStore();
scrollbar.setXSize(scrollbarXSize);
scrollbar.setYSize(scrollbarYSize);
}, [scrollbarXSize, scrollbarYSize]);
const imageRefs = useRef<(HTMLElement | null)[]>(pages.map(() => null));
const [{ minChapterViewWidth, minChapterViewHeight, minChapterSizeSourceChapterId }, setChapterViewerSize] =
useState({
minChapterViewWidth: 0,
minChapterViewHeight: 0,
minChapterSizeSourceChapterId: -1,
});
const [, setTriggerReRender] = useState({});
const inViewportType = READING_MODE_TO_IN_VIEWPORT_TYPE[readingMode];
const isLtrReadingDirection = readingDirection === ReadingDirection.LTR;
const initialChapterIndex = useMemo(
() => chapters.findIndex((chapter) => chapter.id === initialChapter?.id),
[chapters, initialChapter?.id],
);
const chaptersToRender = useMemo(
() =>
chapters.slice(
Math.max(0, initialChapterIndex - visibleChapters.trailing),
Math.min(chapters.length, initialChapterIndex + visibleChapters.leading + 1),
return (
<Stack
ref={mergedRef}
sx={{
width: '100%',
height: '100%',
overflow: 'auto',
flexWrap: 'nowrap',
...applyStyles(
isContinuousVerticalReadingModeActive && shouldApplyReaderWidth(readerWidth, pageScaleMode),
{ alignItems: 'center' },
),
[chapters, initialChapterIndex, visibleChapters.trailing, visibleChapters.leading],
);
const currentChapterIndex = useMemo(
() => chaptersToRender.findIndex((chapter) => chapter.id === currentChapter?.id),
[currentChapter, chaptersToRender],
);
const onChapterViewSizeChange = useCallback(
(width: number, height: number, chapterId: ChapterIdInfo['id']) => {
if (!isContinuousReadingModeActive) {
return;
}
const isSameChapterId = chapterId === minChapterSizeSourceChapterId;
if (isContinuousVerticalReadingModeActive) {
if (!isSameChapterId && minChapterViewWidth >= width) {
return;
}
setChapterViewerSize({
minChapterViewWidth: width,
minChapterViewHeight: 0,
minChapterSizeSourceChapterId: chapterId,
});
return;
}
if (isSameChapterId || minChapterViewHeight < height) {
setChapterViewerSize({
minChapterViewWidth: 0,
minChapterViewHeight: height,
minChapterSizeSourceChapterId: chapterId,
});
}
},
[
isContinuousReadingModeActive,
isContinuousVerticalReadingModeActive,
minChapterViewWidth,
minChapterViewHeight,
minChapterSizeSourceChapterId,
],
);
useReaderHandlePageSelection(
pageToScrollToIndex,
currentPageIndex,
pages,
totalPages,
setPageToScrollToIndex,
updateCurrentPageIndex,
isContinuousReadingModeActive,
imageRefs,
themeDirection,
readingDirection,
);
useReaderScrollToStartOnPageChange(
currentPageIndex,
isContinuousReadingModeActive,
themeDirection,
readingDirection,
scrollElementRef,
);
useReaderHideCursorOnInactivity(scrollElementRef);
useReaderHorizontalModeInvertXYScrolling(readingMode, readingDirection, scrollElementRef);
useReaderHideOverlayOnUserScroll(isOverlayVisible, showPreview, setShowPreview, scrollElementRef);
useReaderAutoScroll(
isOverlayVisible,
automaticScrolling.isPaused,
automaticScrolling.pause,
automaticScrolling.resume,
isStaticNav,
);
useReaderPreserveScrollPosition(
scrollElementRef,
currentChapter?.id,
currentChapterIndex,
currentPageIndex,
chaptersToRender,
visibleChapters,
readingMode,
readingDirection,
setPageToScrollToIndex,
pageScaleMode,
);
useLayoutEffect(() => {
setChapterViewerSize({
minChapterViewWidth: 0,
minChapterViewHeight: 0,
minChapterSizeSourceChapterId: -1,
});
setTriggerReRender({});
}, [readingMode]);
if (!initialChapter || !currentChapter) {
throw new Error('ReaderViewer: illegal state - initialChapter and currentChapter should not be undefined');
}
return (
<Stack
ref={mergedRef}
sx={{
width: '100%',
height: '100%',
overflow: 'auto',
flexWrap: 'nowrap',
...applyStyles(
isContinuousVerticalReadingModeActive && shouldApplyReaderWidth(readerWidth, pageScaleMode),
{ alignItems: 'center' },
),
...applyStyles(!isContinuousVerticalReadingModeActive, {
...applyStyles(themeDirection === 'ltr', {
flexDirection: isLtrReadingDirection ? 'row' : 'row-reverse',
}),
...applyStyles(themeDirection === 'rtl', {
flexDirection: isLtrReadingDirection ? 'row-reverse' : 'row',
}),
...applyStyles(!isContinuousVerticalReadingModeActive, {
...applyStyles(themeDirection === 'ltr', {
flexDirection: isLtrReadingDirection ? 'row' : 'row-reverse',
}),
}}
onClick={(e) => !isDragging && ReaderControls.handleClick(scrollElementRef.current, e)}
onScroll={() =>
ReaderControls.updateCurrentPageOnScroll(
imageRefs,
totalPages - 1,
updateCurrentPageIndex,
inViewportType,
readingDirection,
)
}
>
{chaptersToRender.map((_, index) => {
// chapters are sorted by latest to oldest, thus, loop over it in reversed order
const chapterIndex = Math.max(0, chaptersToRender.length - index - 1);
const chapter = chaptersToRender[chapterIndex];
...applyStyles(themeDirection === 'rtl', {
flexDirection: isLtrReadingDirection ? 'row-reverse' : 'row',
}),
}),
}}
onClick={(e) => !isDragging && ReaderControls.handleClick(scrollElementRef.current, e)}
onScroll={() =>
ReaderControls.updateCurrentPageOnScroll(
imageRefs,
totalPages - 1,
updateCurrentPageIndex,
inViewportType,
readingDirection,
)
}
>
{chaptersToRender.map((_, index) => {
// chapters are sorted by latest to oldest, thus, loop over it in reversed order
const chapterIndex = Math.max(0, chaptersToRender.length - index - 1);
const chapter = chaptersToRender[chapterIndex];
const previousChapter =
chaptersToRender[chapterIndex + 1] ??
chapters[initialChapterIndex + visibleChapters.leading + 1];
const nextChapter =
chaptersToRender[chapterIndex - 1] ??
chapters[initialChapterIndex - visibleChapters.trailing - 1];
const previousChapter =
chaptersToRender[chapterIndex + 1] ?? chapters[initialChapterIndex + visibleChapters.leading + 1];
const nextChapter =
chaptersToRender[chapterIndex - 1] ?? chapters[initialChapterIndex - visibleChapters.trailing - 1];
const isInitialChapter = chapter.id === initialChapter.id;
const isCurrentChapter = chapter.id === currentChapter.id;
const isPreviousChapter = chapter.id === chaptersToRender[currentChapterIndex + 1]?.id;
const isNextChapter = chapter.id === chaptersToRender[currentChapterIndex - 1]?.id;
const isLeadingChapter = initialChapter.sourceOrder > chapter.sourceOrder;
const isTrailingChapter = initialChapter.sourceOrder < chapter.sourceOrder;
const isLastLeadingChapter = visibleChapters.lastLeadingChapterSourceOrder === chapter.sourceOrder;
const isLastTrailingChapter =
visibleChapters.lastTrailingChapterSourceOrder === chapter.sourceOrder;
const isPreloadMode =
(isLastLeadingChapter && visibleChapters.isLeadingChapterPreloadMode) ||
(isLastTrailingChapter && visibleChapters.isTrailingChapterPreloadMode);
const isInitialChapter = chapter.id === initialChapter.id;
const isCurrentChapter = chapter.id === currentChapter.id;
const isPreviousChapter = chapter.id === chaptersToRender[currentChapterIndex + 1]?.id;
const isNextChapter = chapter.id === chaptersToRender[currentChapterIndex - 1]?.id;
const isLeadingChapter = initialChapter.sourceOrder > chapter.sourceOrder;
const isTrailingChapter = initialChapter.sourceOrder < chapter.sourceOrder;
const isLastLeadingChapter = visibleChapters.lastLeadingChapterSourceOrder === chapter.sourceOrder;
const isLastTrailingChapter = visibleChapters.lastTrailingChapterSourceOrder === chapter.sourceOrder;
const isPreloadMode =
(isLastLeadingChapter && visibleChapters.isLeadingChapterPreloadMode) ||
(isLastTrailingChapter && visibleChapters.isTrailingChapterPreloadMode);
const previousNextChapterVisibility = getPreviousNextChapterVisibility(
chapterIndex,
chaptersToRender,
visibleChapters,
);
const previousNextChapterVisibility = getPreviousNextChapterVisibility(
chapterIndex,
chaptersToRender,
visibleChapters,
);
const isChapterSizeSourceChapter = chapter.id === minChapterSizeSourceChapterId;
const isChapterSizeSourceChapter = chapter.id === minChapterSizeSourceChapterId;
return (
<ReaderChapterViewer
key={chapter.id}
chapterId={chapter.id}
previousChapterId={previousChapter?.id}
nextChapterId={nextChapter?.id}
isPreviousChapterVisible={previousNextChapterVisibility.previous}
isNextChapterVisible={previousNextChapterVisibility.next}
lastPageRead={coerceIn(chapter.lastPageRead, 0, chapter.pageCount - 1)}
currentPageIndex={getReaderChapterViewerCurrentPageIndex(
currentPageIndex,
chapter,
currentChapter,
isCurrentChapter,
isCurrentChapterReady,
isLeadingChapter,
isTrailingChapter,
visibleChapters,
)}
isInitialChapter={isInitialChapter}
isCurrentChapter={isCurrentChapter}
isPreviousChapter={isPreviousChapter}
isNextChapter={isNextChapter}
isLeadingChapter={isLeadingChapter}
isTrailingChapter={isTrailingChapter}
isPreloadMode={isPreloadMode}
imageRefs={imageRefs}
setPages={setPages}
setPageLoadStates={setPageLoadStates}
setTotalPages={setTotalPages}
setCurrentPageIndex={setCurrentPageIndex}
setPageToScrollToIndex={setPageToScrollToIndex}
transitionPageMode={transitionPageMode}
retryFailedPagesKeyPrefix={retryFailedPagesKeyPrefix}
readingMode={readingMode}
readerWidth={readerWidth}
pageScaleMode={pageScaleMode}
shouldOffsetDoubleSpreads={shouldOffsetDoubleSpreads}
readingDirection={readingDirection}
updateCurrentPageIndex={isCurrentChapter ? updateCurrentPageIndex : noOp}
scrollIntoView={isCurrentChapter && visibleChapters.scrollIntoView}
resumeMode={getReaderChapterViewResumeMode(
isCurrentChapter,
isInitialChapter,
isLeadingChapter,
isTrailingChapter,
visibleChapters.resumeMode,
resumeMode,
)}
setTransitionPageMode={setTransitionPageMode}
pageGap={pageGap}
imagePreLoadAmount={imagePreLoadAmount}
customFilter={customFilter}
shouldStretchPage={shouldStretchPage}
readerNavBarWidth={readerNavBarWidth}
onSizeChange={onChapterViewSizeChange}
minWidth={isChapterSizeSourceChapter ? 0 : minChapterViewWidth}
minHeight={isChapterSizeSourceChapter ? 0 : minChapterViewHeight}
scrollElement={scrollElementRef.current}
/>
);
})}
</Stack>
);
},
);
return (
<ReaderChapterViewer
key={chapter.id}
chapterId={chapter.id}
previousChapterId={previousChapter?.id}
nextChapterId={nextChapter?.id}
isPreviousChapterVisible={previousNextChapterVisibility.previous}
isNextChapterVisible={previousNextChapterVisibility.next}
lastPageRead={coerceIn(chapter.lastPageRead, 0, chapter.pageCount - 1)}
currentPageIndex={getReaderChapterViewerCurrentPageIndex(
currentPageIndex,
chapter,
currentChapter,
isCurrentChapter,
isCurrentChapterReady,
isLeadingChapter,
isTrailingChapter,
visibleChapters,
)}
isInitialChapter={isInitialChapter}
isCurrentChapter={isCurrentChapter}
isPreviousChapter={isPreviousChapter}
isNextChapter={isNextChapter}
isLeadingChapter={isLeadingChapter}
isTrailingChapter={isTrailingChapter}
isPreloadMode={isPreloadMode}
imageRefs={imageRefs}
setPages={setPages}
setPageLoadStates={setPageLoadStates}
setTotalPages={setTotalPages}
setCurrentPageIndex={setCurrentPageIndex}
setPageToScrollToIndex={setPageToScrollToIndex}
transitionPageMode={transitionPageMode}
retryFailedPagesKeyPrefix={retryFailedPagesKeyPrefix}
readingMode={readingMode}
readerWidth={readerWidth}
pageScaleMode={pageScaleMode}
shouldOffsetDoubleSpreads={shouldOffsetDoubleSpreads}
readingDirection={readingDirection}
updateCurrentPageIndex={isCurrentChapter ? updateCurrentPageIndex : noOp}
scrollIntoView={isCurrentChapter && visibleChapters.scrollIntoView}
resumeMode={getReaderChapterViewResumeMode(
isCurrentChapter,
isInitialChapter,
isLeadingChapter,
isTrailingChapter,
visibleChapters.resumeMode,
resumeMode,
)}
setTransitionPageMode={setTransitionPageMode}
pageGap={pageGap}
imagePreLoadAmount={imagePreLoadAmount}
customFilter={customFilter}
shouldStretchPage={shouldStretchPage}
readerNavBarWidth={readerNavBarWidth}
onSizeChange={onChapterViewSizeChange}
minWidth={isChapterSizeSourceChapter ? 0 : minChapterViewWidth}
minHeight={isChapterSizeSourceChapter ? 0 : minChapterViewHeight}
scrollElement={scrollElementRef.current}
/>
);
})}
</Stack>
);
};
export const ReaderViewer = withPropsFrom(
memo(BaseReaderViewer),

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { forwardRef, memo, ReactNode, useCallback, useEffect, useMemo, useRef } from 'react';
import { memo, ReactNode, useCallback, useEffect, useMemo, useRef } from 'react';
import Box, { BoxProps } from '@mui/material/Box';
import { getPageIndexesToLoad, isATransitionPageVisible } from '@/features/reader/viewer/pager/ReaderPager.utils.tsx';
import {
@@ -22,133 +22,117 @@ import { isContinuousReadingMode } from '@/features/reader/settings/ReaderSettin
const getPreviousCurrentPageIndex = (resumeMode: ReaderResumeMode): number =>
resumeMode === ReaderResumeMode.END ? Number.MAX_SAFE_INTEGER : -1;
const BaseBasePager = forwardRef<
HTMLDivElement,
Omit<ReaderPagerProps, 'pageLoadStates' | 'retryFailedPagesKeyPrefix' | 'isPreloadMode'> &
Pick<IReaderSettings, 'readingMode' | 'imagePreLoadAmount'> & {
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'],
readerNavBarWidth: ReaderPagerProps['readerNavBarWidth'],
) => ReactNode;
slots?: { boxProps?: BoxProps };
}
>(
(
{
currentPageIndex,
pages,
transitionPageMode,
imageRefs,
createPage,
slots,
readingMode,
imagePreLoadAmount,
isCurrentChapter,
isPreviousChapter,
isNextChapter,
customFilter,
pageScaleMode,
shouldStretchPage,
readerWidth,
readerNavBarWidth,
resumeMode,
handleAsInitialRender,
},
ref,
) => {
const previousCurrentPageIndex = useRef(getPreviousCurrentPageIndex(resumeMode));
const BaseBasePager = ({
currentPageIndex,
pages,
transitionPageMode,
imageRefs,
createPage,
slots,
readingMode,
imagePreLoadAmount,
isCurrentChapter,
isPreviousChapter,
isNextChapter,
customFilter,
pageScaleMode,
shouldStretchPage,
readerWidth,
readerNavBarWidth,
resumeMode,
handleAsInitialRender,
ref,
}: Omit<ReaderPagerProps, 'pageLoadStates' | 'retryFailedPagesKeyPrefix' | 'isPreloadMode'> &
Pick<IReaderSettings, 'readingMode' | 'imagePreLoadAmount'> & {
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'],
readerNavBarWidth: ReaderPagerProps['readerNavBarWidth'],
) => ReactNode;
slots?: { boxProps?: BoxProps };
}) => {
const previousCurrentPageIndex = useRef(getPreviousCurrentPageIndex(resumeMode));
if (handleAsInitialRender) {
previousCurrentPageIndex.current = getPreviousCurrentPageIndex(resumeMode);
}
if (handleAsInitialRender) {
previousCurrentPageIndex.current = getPreviousCurrentPageIndex(resumeMode);
}
const pagesIndexesToRender = useMemo(
() =>
getPageIndexesToLoad(
currentPageIndex,
pages,
previousCurrentPageIndex.current,
imagePreLoadAmount,
readingMode,
isCurrentChapter,
isPreviousChapter,
isNextChapter,
),
[
const pagesIndexesToRender = useMemo(
() =>
getPageIndexesToLoad(
currentPageIndex,
pages,
previousCurrentPageIndex.current,
imagePreLoadAmount,
readingMode,
isCurrentChapter,
isPreviousChapter,
isNextChapter,
],
);
useEffect(() => {
if (isCurrentChapter) {
previousCurrentPageIndex.current = currentPageIndex;
}
}, [pagesIndexesToRender, isCurrentChapter]);
),
[currentPageIndex, pages, imagePreLoadAmount, readingMode, isCurrentChapter, isPreviousChapter, isNextChapter],
);
useEffect(() => {
if (isCurrentChapter) {
previousCurrentPageIndex.current = currentPageIndex;
}
}, [pagesIndexesToRender, isCurrentChapter]);
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 (
<Box
ref={ref}
{...slots?.boxProps}
sx={[
return (
<Box
ref={ref}
{...slots?.boxProps}
sx={[
{
width: 'fit-content',
height: 'fit-content',
},
...(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),
{
width: 'fit-content',
height: 'fit-content',
visibility: 'hidden',
width: 0,
height: 0,
m: 0,
p: 0,
},
...(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,
readerNavBarWidth,
),
)}
</Box>
);
},
);
),
]}
>
{pages.map((page, pagesIndex) =>
createPage(
page,
pagesIndex,
pagesIndexesToRender.includes(pagesIndex),
[ReaderTransitionPageMode.NONE, ReaderTransitionPageMode.BOTH].includes(transitionPageMode),
setRef,
readingMode,
customFilter,
pageScaleMode,
shouldStretchPage,
readerWidth,
readerNavBarWidth,
),
)}
</Box>
);
};
export const BasePager = memo(BaseBasePager);

View File

@@ -7,7 +7,7 @@
*/
import { Direction, useTheme } from '@mui/material/styles';
import { forwardRef, Fragment, memo, useMemo } from 'react';
import { Fragment, memo, useMemo } from 'react';
import { BasePager } from '@/features/reader/viewer/pager/components/BasePager.tsx';
import {
IReaderSettings,
@@ -41,10 +41,14 @@ const getPagePosition = (
return isLtrReadingDirection ? 'right' : 'left';
};
const BaseReaderDoublePagedPager = forwardRef<
HTMLDivElement,
ReaderPagerProps & Pick<IReaderSettings, 'readingDirection' | 'pageScaleMode'>
>(({ onLoad, onError, pageLoadStates, retryFailedPagesKeyPrefix, isPreloadMode, ...props }, ref) => {
const BaseReaderDoublePagedPager = ({
onLoad,
onError,
pageLoadStates,
retryFailedPagesKeyPrefix,
isPreloadMode,
...props
}: ReaderPagerProps & Pick<IReaderSettings, 'readingDirection' | 'pageScaleMode'>) => {
const { currentPageIndex, pages, totalPages, readingDirection, pageScaleMode } = props;
const { direction: themeDirection } = useTheme();
@@ -54,7 +58,6 @@ const BaseReaderDoublePagedPager = forwardRef<
return (
<BasePager
ref={ref}
{...props}
createPage={(page, pagesIndex, shouldLoad, shouldDisplay, _setRef, ...baseProps) => {
const { primary, secondary } = page;
@@ -129,6 +132,6 @@ const BaseReaderDoublePagedPager = forwardRef<
}}
/>
);
});
};
export const ReaderDoublePagedPager = memo(BaseReaderDoublePagedPager);

View File

@@ -7,16 +7,20 @@
*/
import { useTheme } from '@mui/material/styles';
import { forwardRef, memo } from 'react';
import { memo } from 'react';
import { BasePager } from '@/features/reader/viewer/pager/components/BasePager.tsx';
import { applyStyles } from '@/base/utils/ApplyStyles.ts';
import { IReaderSettings, ReaderPagerProps, ReadingDirection } from '@/features/reader/Reader.types.ts';
import { createReaderPage } from '@/features/reader/viewer/pager/ReaderPager.utils.tsx';
const BaseReaderHorizontalPager = forwardRef<
HTMLDivElement,
ReaderPagerProps & Pick<IReaderSettings, 'pageGap' | 'readingDirection'>
>(({ onLoad, onError, pageLoadStates, retryFailedPagesKeyPrefix, isPreloadMode, ...props }, ref) => {
const BaseReaderHorizontalPager = ({
onLoad,
onError,
pageLoadStates,
retryFailedPagesKeyPrefix,
isPreloadMode,
...props
}: ReaderPagerProps & Pick<IReaderSettings, 'pageGap' | 'readingDirection'>) => {
const { currentPageIndex, totalPages, pageGap, readingDirection } = props;
const { direction: themeDirection } = useTheme();
@@ -25,7 +29,6 @@ const BaseReaderHorizontalPager = forwardRef<
return (
<BasePager
ref={ref}
{...props}
createPage={(page, pagesIndex, shouldLoad, _, setRef, ...baseProps) =>
createReaderPage(
@@ -69,6 +72,6 @@ const BaseReaderHorizontalPager = forwardRef<
}}
/>
);
});
};
export const ReaderHorizontalPager = memo(BaseReaderHorizontalPager);

View File

@@ -6,46 +6,50 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { forwardRef, memo } from 'react';
import { memo } from 'react';
import { BasePager } from '@/features/reader/viewer/pager/components/BasePager.tsx';
import { ReaderPagerProps } from '@/features/reader/Reader.types.ts';
import { createReaderPage } from '@/features/reader/viewer/pager/ReaderPager.utils.tsx';
const BaseReaderPagedPager = forwardRef<HTMLDivElement, ReaderPagerProps>(
({ onLoad, onError, pageLoadStates, retryFailedPagesKeyPrefix, isPreloadMode, ...props }, ref) => {
const { currentPageIndex, totalPages } = props;
const BaseReaderPagedPager = ({
onLoad,
onError,
pageLoadStates,
retryFailedPagesKeyPrefix,
isPreloadMode,
...props
}: ReaderPagerProps) => {
const { currentPageIndex, totalPages } = props;
return (
<BasePager
ref={ref}
{...props}
createPage={(page, pagesIndex, shouldLoad, shouldDisplay, _setRef, ...baseProps) =>
createReaderPage(
page,
pagesIndex,
true,
pageLoadStates[page.primary.index].loaded,
isPreloadMode,
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 (
<BasePager
{...props}
createPage={(page, pagesIndex, shouldLoad, shouldDisplay, _setRef, ...baseProps) =>
createReaderPage(
page,
pagesIndex,
true,
pageLoadStates[page.primary.index].loaded,
isPreloadMode,
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);

View File

@@ -6,47 +6,51 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { forwardRef, memo } from 'react';
import { memo } from 'react';
import { BasePager } from '@/features/reader/viewer/pager/components/BasePager.tsx';
import { ReaderPagerProps, ReadingMode } from '@/features/reader/Reader.types.ts';
import { createReaderPage } from '@/features/reader/viewer/pager/ReaderPager.utils.tsx';
const BaseReaderVerticalPager = forwardRef<HTMLDivElement, ReaderPagerProps>(
({ onLoad, onError, pageLoadStates, retryFailedPagesKeyPrefix, isPreloadMode, ...props }, ref) => {
const { currentPageIndex, totalPages, readingMode, pageGap } = props;
const BaseReaderVerticalPager = ({
onLoad,
onError,
pageLoadStates,
retryFailedPagesKeyPrefix,
isPreloadMode,
...props
}: ReaderPagerProps) => {
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 (
<BasePager
ref={ref}
{...props}
createPage={(page, pagesIndex, shouldLoad, _, setRef, ...baseProps) =>
createReaderPage(
page,
pagesIndex,
true,
pageLoadStates[page.primary.index].loaded,
isPreloadMode,
onLoad,
onError,
shouldLoad,
!isPreloadMode,
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 (
<BasePager
{...props}
createPage={(page, pagesIndex, shouldLoad, _, setRef, ...baseProps) =>
createReaderPage(
page,
pagesIndex,
true,
pageLoadStates[page.primary.index].loaded,
isPreloadMode,
onLoad,
onError,
shouldLoad,
!isPreloadMode,
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);