2025-02-17 20:19:28 +01:00
|
|
|
/*
|
|
|
|
|
* Copyright (C) Contributors to the Suwayomi project
|
|
|
|
|
*
|
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-03-24 23:45:06 +01:00
|
|
|
import { RefObject, useCallback, useEffect, useLayoutEffect, useRef } from 'react';
|
2025-02-17 20:19:28 +01:00
|
|
|
import { ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts';
|
2025-03-24 23:24:36 +01:00
|
|
|
import { ReaderStateChapters, ReadingDirection, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
|
2025-02-17 20:19:28 +01:00
|
|
|
import { getOptionForDirection } from '@/modules/theme/services/ThemeCreator.ts';
|
|
|
|
|
import { READING_DIRECTION_TO_THEME_DIRECTION } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
|
|
|
|
import { getPreviousNextChapterVisibility } from '@/modules/reader/utils/Reader.utils.ts';
|
|
|
|
|
import { TChapterReader } from '@/modules/chapter/Chapter.types.ts';
|
2025-03-24 23:24:36 +01:00
|
|
|
import { ReaderStatePages } from '@/modules/reader/types/ReaderProgressBar.types';
|
2025-02-17 20:19:28 +01:00
|
|
|
|
|
|
|
|
export const useReaderPreserveScrollPosition = (
|
|
|
|
|
scrollElementRef: RefObject<HTMLElement | null>,
|
|
|
|
|
currentChapterId: ChapterIdInfo['id'] | undefined,
|
|
|
|
|
chapterIndex: number,
|
2025-03-24 23:45:06 +01:00
|
|
|
pageIndex: number,
|
2025-02-17 20:19:28 +01:00
|
|
|
chaptersToRender: TChapterReader[],
|
|
|
|
|
visibleChapters: ReaderStateChapters['visibleChapters'],
|
2025-03-24 23:24:36 +01:00
|
|
|
readingMode: ReadingMode,
|
2025-02-17 20:19:28 +01:00
|
|
|
isContinuousReadingModeActive: boolean,
|
|
|
|
|
readingDirection: ReadingDirection,
|
2025-03-24 23:45:06 +01:00
|
|
|
readerNavBarWidth: number,
|
2025-03-24 23:24:36 +01:00
|
|
|
setPageToScrollToIndex: ReaderStatePages['setPageToScrollToIndex'],
|
2025-02-17 20:19:28 +01:00
|
|
|
) => {
|
|
|
|
|
const scrollPosition = useRef({ left: 0, top: 0, scrollWidth: 0, scrollHeight: 0 });
|
2025-03-24 23:45:06 +01:00
|
|
|
const readerNavBarWidthRef = useRef<number>(readerNavBarWidth);
|
2025-02-17 20:19:28 +01:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const element = scrollElementRef.current;
|
|
|
|
|
|
|
|
|
|
if (!element) {
|
|
|
|
|
return () => {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onScroll = () => {
|
|
|
|
|
scrollPosition.current = {
|
|
|
|
|
...scrollPosition.current,
|
|
|
|
|
left: element.scrollLeft,
|
|
|
|
|
top: element.scrollTop,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
element.addEventListener('scroll', onScroll);
|
|
|
|
|
|
|
|
|
|
return () => element.removeEventListener('scroll', onScroll);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
|
const scrollElement = scrollElementRef.current;
|
|
|
|
|
const { left, top, scrollWidth, scrollHeight } = scrollPosition.current;
|
|
|
|
|
|
|
|
|
|
if (!scrollElement || !isContinuousReadingModeActive) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scrollPosition.current = {
|
|
|
|
|
...scrollPosition.current,
|
|
|
|
|
scrollWidth: scrollElement.scrollWidth,
|
|
|
|
|
scrollHeight: scrollElement.scrollHeight,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const themeDirectionForReadingDirection = READING_DIRECTION_TO_THEME_DIRECTION[readingDirection];
|
|
|
|
|
|
|
|
|
|
const previousNextChapterVisibility = getPreviousNextChapterVisibility(
|
|
|
|
|
chapterIndex,
|
|
|
|
|
chaptersToRender,
|
|
|
|
|
visibleChapters,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const wasScrolledBackwardHorizontal = Math.abs(left) < window.innerWidth * 1.5;
|
|
|
|
|
const wasScrolledBackwardVertical = top < window.innerHeight * 1.5;
|
|
|
|
|
|
|
|
|
|
const widthOfPrependedContent = scrollElement.scrollWidth - scrollWidth;
|
|
|
|
|
const heightOfPrependedContent = scrollElement.scrollHeight - scrollHeight;
|
|
|
|
|
|
|
|
|
|
const newLeft = wasScrolledBackwardHorizontal
|
|
|
|
|
? getOptionForDirection(
|
|
|
|
|
widthOfPrependedContent,
|
|
|
|
|
-widthOfPrependedContent,
|
|
|
|
|
themeDirectionForReadingDirection,
|
|
|
|
|
) + left
|
|
|
|
|
: left;
|
|
|
|
|
const newTop = wasScrolledBackwardVertical ? heightOfPrependedContent + top : top;
|
|
|
|
|
|
|
|
|
|
// only relevant when prepending content to the dom due to the resulting layout shift
|
|
|
|
|
const isFirstRenderOfPreviousChapter =
|
|
|
|
|
!previousNextChapterVisibility.previous && (wasScrolledBackwardHorizontal || wasScrolledBackwardVertical);
|
|
|
|
|
if (!isFirstRenderOfPreviousChapter) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scrollElement.scrollTo(newLeft, newTop);
|
|
|
|
|
}, [currentChapterId]);
|
2025-03-24 23:24:36 +01:00
|
|
|
|
2025-03-24 23:45:06 +01:00
|
|
|
const onSizeReset = useCallback(() => {
|
|
|
|
|
if (!isContinuousReadingModeActive) return;
|
|
|
|
|
setPageToScrollToIndex(pageIndex);
|
|
|
|
|
}, [isContinuousReadingModeActive, pageIndex]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
window.addEventListener('resize', onSizeReset);
|
|
|
|
|
return () => window.removeEventListener('resize', onSizeReset);
|
|
|
|
|
}, [onSizeReset]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (readerNavBarWidthRef.current === readerNavBarWidth) return;
|
|
|
|
|
onSizeReset();
|
|
|
|
|
readerNavBarWidthRef.current = readerNavBarWidth;
|
|
|
|
|
}, [onSizeReset, readerNavBarWidth]);
|
|
|
|
|
|
2025-03-24 23:24:36 +01:00
|
|
|
useLayoutEffect(() => {
|
2025-03-24 23:45:06 +01:00
|
|
|
setPageToScrollToIndex(pageIndex);
|
2025-03-24 23:24:36 +01:00
|
|
|
}, [readingMode]);
|
2025-02-17 20:19:28 +01:00
|
|
|
};
|