Reader transition page

This commit is contained in:
schroda
2024-12-03 18:00:46 +01:00
parent 7cf78e771a
commit fb31a92cd7
19 changed files with 423 additions and 59 deletions

View File

@@ -13,7 +13,12 @@ import { getNextPageIndex, getPage } from '@/modules/reader/utils/ReaderProgress
import { getOptionForDirection } from '@/theme.tsx';
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { PageInViewportType, ReadingDirection, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
import {
PageInViewportType,
ReadingDirection,
ReadingMode,
ReaderTransitionPageMode,
} from '@/modules/reader/types/Reader.types.ts';
import { ScrollDirection, ScrollOffset } from '@/modules/core/Core.types.ts';
import { ReaderScrollAmount } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
import { isPageInViewport } from '@/modules/reader/utils/ReaderPager.utils.tsx';
@@ -45,12 +50,22 @@ export class ReaderControls {
direction: ScrollDirection,
readingDirection: ReadingDirection,
element: HTMLElement,
openChapter: ReturnType<(typeof ReaderControls)['useOpenChapter']>,
scrollAmountPercentage: number = ReaderScrollAmount.LARGE,
): void {
if (!element) {
return;
}
const isAtStartY = element.scrollTop === 0;
const isAtEndY =
Math.floor(element.scrollTop) === element.scrollHeight - element.clientHeight ||
Math.ceil(element.scrollTop) === element.scrollHeight - element.clientHeight;
const isAtStartX = element.scrollLeft === 0;
const isAtEndX =
Math.floor(element.scrollLeft) === element.scrollWidth - element.clientWidth ||
Math.ceil(element.scrollLeft) === element.scrollWidth - element.clientWidth;
const scrollAmount = scrollAmountPercentage / 100;
const scrollDirection = SCROLL_DIRECTION_BY_SCROLL_OFFSET_BY_READING_DIRECTION[readingDirection][offset];
@@ -59,12 +74,32 @@ export class ReaderControls {
switch (direction) {
case ScrollDirection.X:
if (isAtStartX && offset === ScrollOffset.BACKWARD) {
openChapter('previous');
return;
}
if (isAtEndX && offset === ScrollOffset.FORWARD) {
openChapter('next');
return;
}
element.scroll({
left: getNewScrollPosition(element.scrollLeft, element.clientWidth),
behavior: 'smooth',
});
break;
case ScrollDirection.Y:
if (isAtStartY && offset === ScrollOffset.BACKWARD) {
openChapter('previous');
return;
}
if (isAtEndY && offset === ScrollOffset.FORWARD) {
openChapter('next');
return;
}
element.scroll({
top: getNewScrollPosition(element.scrollTop, element.clientHeight),
behavior: 'smooth',
@@ -76,7 +111,7 @@ export class ReaderControls {
}
static useOpenChapter(): (offset: 'previous' | 'next') => void {
const { readingDirection } = ReaderService.useSettings();
const { readingDirection, readingMode } = ReaderService.useSettings();
const { previousChapter, nextChapter } = useReaderStateChaptersContext();
const direction = READING_DIRECTION_TO_DIRECTION[readingDirection.value];
@@ -96,13 +131,16 @@ export class ReaderControls {
throw new Error(`Unexpected "offset" (${offset})`);
}
},
[direction, openPreviousChapter, openNextChapter],
[direction, openPreviousChapter, openNextChapter, readingMode.value],
);
}
static useOpenPage(): (offset: 'previous' | 'next') => void {
const { currentPageIndex, setPageToScrollToIndex, pages } = userReaderStatePagesContext();
const { readingDirection } = ReaderService.useSettings();
static useOpenPage(): (page: number | 'previous' | 'next', forceDirection?: Direction) => void {
const { currentPageIndex, setPageToScrollToIndex, pages, transitionPageMode, setTransitionPageMode } =
userReaderStatePagesContext();
const { previousChapter, nextChapter } = useReaderStateChaptersContext();
const { readingDirection, readingMode } = ReaderService.useSettings();
const openChapter = ReaderControls.useOpenChapter();
const currentPage = useMemo(() => getPage(currentPageIndex, pages), [currentPageIndex, pages]);
const previousPageIndex = useMemo(
@@ -115,20 +153,85 @@ export class ReaderControls {
);
const direction = READING_DIRECTION_TO_DIRECTION[readingDirection.value];
const isFirstPage = currentPageIndex === 0;
const isLastPage = currentPageIndex === pages[pages.length - 1].primary.index;
const isATransitionPageVisible = transitionPageMode !== ReaderTransitionPageMode.NONE;
const isContinuousReadingModeActive = isContinuousReadingMode(readingMode.value);
return useCallback(
(offset) => {
switch (offset) {
(page, forceDirection = direction) => {
const convertedPage = getOptionForDirection(
page,
page === 'previous' ? 'next' : 'previous',
forceDirection,
);
const shouldOpenPreviousChapter =
isFirstPage && isATransitionPageVisible && convertedPage === 'previous' && !!previousChapter;
if (shouldOpenPreviousChapter) {
openChapter('previous');
return;
}
const shouldOpenNextChapter =
isLastPage && isATransitionPageVisible && convertedPage === 'next' && !!nextChapter;
if (shouldOpenNextChapter) {
openChapter('next');
return;
}
const hideTransitionPage = () => setTransitionPageMode(ReaderTransitionPageMode.NONE);
if (typeof page === 'number') {
setPageToScrollToIndex(page);
hideTransitionPage();
return;
}
const needToHideTransitionPage = isATransitionPageVisible && !isContinuousReadingModeActive;
switch (convertedPage) {
case 'previous':
setPageToScrollToIndex(getOptionForDirection(previousPageIndex, nextPageIndex, direction));
if (isFirstPage) {
setTransitionPageMode(ReaderTransitionPageMode.PREVIOUS);
return;
}
if (needToHideTransitionPage) {
hideTransitionPage();
return;
}
setPageToScrollToIndex(previousPageIndex);
break;
case 'next':
setPageToScrollToIndex(getOptionForDirection(nextPageIndex, previousPageIndex, direction));
if (isLastPage) {
setTransitionPageMode(ReaderTransitionPageMode.NEXT);
return;
}
if (needToHideTransitionPage) {
hideTransitionPage();
return;
}
setPageToScrollToIndex(nextPageIndex);
break;
default:
throw new Error(`Unexpected "offset" (${offset})`);
throw new Error(`Unexpected "offset" (${page})`);
}
},
[direction, previousPageIndex, nextPageIndex],
[
direction,
previousPageIndex,
nextPageIndex,
isATransitionPageVisible,
isContinuousReadingModeActive,
isFirstPage,
isLastPage,
openChapter,
!!previousChapter,
!!nextChapter,
],
);
}
@@ -153,6 +256,7 @@ export class ReaderControls {
const { setShowPreview } = useReaderTapZoneContext();
const { readingMode, readingDirection, isStaticNav } = ReaderService.useSettings();
const openPage = ReaderControls.useOpenPage();
const openChapter = ReaderControls.useOpenChapter();
return useCallback(
(e) => {
@@ -177,14 +281,26 @@ export class ReaderControls {
break;
case TapZoneRegionType.PREVIOUS:
if (isContinuousReadingModeActive) {
this.scroll(ScrollOffset.BACKWARD, scrollDirection, readingDirection.value, scrollElement);
this.scroll(
ScrollOffset.BACKWARD,
scrollDirection,
readingDirection.value,
scrollElement,
openChapter,
);
} else {
openPage('previous');
}
break;
case TapZoneRegionType.NEXT:
if (isContinuousReadingModeActive) {
this.scroll(ScrollOffset.FORWARD, scrollDirection, readingDirection.value, scrollElement);
this.scroll(
ScrollOffset.FORWARD,
scrollDirection,
readingDirection.value,
scrollElement,
openChapter,
);
} else {
openPage('next');
}
@@ -193,7 +309,7 @@ export class ReaderControls {
throw new Error(`Unexpected "TapZoneRegionType" (${action})`);
}
},
[scrollElement, currentPageIndex, pages, readingMode.value, openPage, readingDirection.value],
[scrollElement, currentPageIndex, pages, readingMode.value, openPage, readingDirection.value, openChapter],
);
}
}