From 965aa7215ce77ca6d26104c85a0c350449cbc4f8 Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sat, 7 Dec 2024 03:43:18 +0100 Subject: [PATCH] Update chapter --- .../reader/components/viewer/ReaderViewer.tsx | 14 ++- .../state/ReaderStateChaptersContext.tsx | 1 + .../ReaderStateChaptersContextProvider.tsx | 2 +- src/modules/reader/services/ReaderControls.ts | 102 ++++++++++++++-- src/modules/reader/services/ReaderService.ts | 111 +++++++++++++++++- src/modules/reader/types/Reader.types.ts | 11 ++ src/modules/reader/utils/Reader.utils.ts | 81 +++++++++++++ .../reader/utils/ReaderPager.utils.tsx | 50 +++++++- .../reader/utils/ReaderProgressBar.utils.tsx | 11 +- 9 files changed, 365 insertions(+), 18 deletions(-) diff --git a/src/modules/reader/components/viewer/ReaderViewer.tsx b/src/modules/reader/components/viewer/ReaderViewer.tsx index f9ffd2f2..2b19e6d1 100644 --- a/src/modules/reader/components/viewer/ReaderViewer.tsx +++ b/src/modules/reader/components/viewer/ReaderViewer.tsx @@ -28,7 +28,7 @@ import { createPagesData, getDoublePageModePages, isSpreadPage } from '@/modules import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrollbarContext.tsx'; import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx'; import { ReaderControls } from '@/modules/reader/services/ReaderControls.ts'; -import { getPage } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx'; +import { getNextIndexFromPage, getPage } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx'; import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx'; import { useMouseDragScroll } from '@/modules/core/hooks/useMouseDragScroll.tsx'; @@ -42,7 +42,6 @@ const READING_MODE_TO_IN_VIEWPORT_TYPE: Record export const ReaderViewer = forwardRef((_, ref: ForwardedRef) => { const { currentPageIndex, - setCurrentPageIndex, pageToScrollToIndex, pages, setPages, @@ -53,6 +52,7 @@ export const ReaderViewer = forwardRef((_, ref: ForwardedRef(null); useImperativeHandle(ref, () => scrollElementRef.current!); @@ -143,7 +143,10 @@ export const ReaderViewer = forwardRef((_, ref: ForwardedRef ReaderControls.updateCurrentPageOnScroll( imageRefs, - currentPageIndex, - setCurrentPageIndex, + totalPages - 1, + updateCurrentPageIndex, inViewportType, + readingDirection.value, ) } > diff --git a/src/modules/reader/contexts/state/ReaderStateChaptersContext.tsx b/src/modules/reader/contexts/state/ReaderStateChaptersContext.tsx index a1042caa..dbf4f04d 100644 --- a/src/modules/reader/contexts/state/ReaderStateChaptersContext.tsx +++ b/src/modules/reader/contexts/state/ReaderStateChaptersContext.tsx @@ -10,6 +10,7 @@ import { createContext, useContext } from 'react'; import { ReaderStateChapters } from '@/modules/reader/types/Reader.types.ts'; export const ReaderStateChaptersContext = createContext({ + mangaChapters: [], chapters: [], setReaderStateChapters: () => {}, }); diff --git a/src/modules/reader/contexts/state/ReaderStateChaptersContextProvider.tsx b/src/modules/reader/contexts/state/ReaderStateChaptersContextProvider.tsx index 8e435ee4..e731af08 100644 --- a/src/modules/reader/contexts/state/ReaderStateChaptersContextProvider.tsx +++ b/src/modules/reader/contexts/state/ReaderStateChaptersContextProvider.tsx @@ -12,7 +12,7 @@ import { ReaderStateChaptersContext } from '@/modules/reader/contexts/state/Read type TContext = ContextType; export const ReaderStateChaptersContextProvider = ({ children }: { children: ReactNode }) => { - const [state, setState] = useState>({ chapters: [] }); + const [state, setState] = useState>({ mangaChapters: [], chapters: [] }); const value = useMemo( () => ({ diff --git a/src/modules/reader/services/ReaderControls.ts b/src/modules/reader/services/ReaderControls.ts index 623c650f..43610c1a 100644 --- a/src/modules/reader/services/ReaderControls.ts +++ b/src/modules/reader/services/ReaderControls.ts @@ -9,7 +9,7 @@ import { MutableRefObject, useCallback, useMemo } from 'react'; import { Direction } from '@mui/material/styles'; import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/ReaderStatePagesContext.tsx'; -import { getNextPageIndex, getPage } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx'; +import { getNextIndexFromPage, getNextPageIndex, getPage } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx'; import { getOptionForDirection } from '@/theme.tsx'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx'; @@ -22,12 +22,16 @@ import { } 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'; +import { isEndOfPageInViewport, isPageInViewport } from '@/modules/reader/utils/ReaderPager.utils.tsx'; import { useReaderOverlayContext } from '@/modules/reader/contexts/ReaderOverlayContext.tsx'; import { useReaderTapZoneContext } from '@/modules/reader/contexts/ReaderTapZoneContext.tsx'; import { TapZoneRegionType } from '@/modules/reader/types/TapZoneLayout.types.ts'; import { ReaderTapZoneService } from '@/modules/reader/services/ReaderTapZoneService.ts'; import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx'; +import { getReaderChapterFromCache } from '@/modules/reader/utils/Reader.utils.ts'; +import { Chapters } from '@/modules/chapter/services/Chapters.ts'; +import { DirectionOffset } from '@/Base.types.ts'; +import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts'; const READING_DIRECTION_TO_DIRECTION: Record = { [ReadingDirection.LTR]: 'ltr', @@ -46,6 +50,8 @@ const SCROLL_DIRECTION_BY_SCROLL_OFFSET_BY_READING_DIRECTION: Record getNextPageIndex('next', currentPage.pagesIndex, pages), [currentPage, pages], ); + const indexOfLastPage = getNextIndexFromPage(pages[pages.length - 1]); const direction = READING_DIRECTION_TO_DIRECTION[readingDirection.value]; const isFirstPage = currentPageIndex === 0; - const isLastPage = currentPageIndex === pages[pages.length - 1].primary.index; + const isLastPage = currentPageIndex === indexOfLastPage; const isATransitionPageVisible = transitionPageMode !== ReaderTransitionPageMode.NONE; const isContinuousReadingModeActive = isContinuousReadingMode(readingMode.value); @@ -225,6 +232,7 @@ export class ReaderControls { direction, previousPageIndex, nextPageIndex, + indexOfLastPage, isATransitionPageVisible, isContinuousReadingModeActive, isFirstPage, @@ -236,16 +244,96 @@ export class ReaderControls { ); } + static useUpdateCurrentPageIndex(): ( + pageIndex: number, + debounceChapterUpdate?: boolean, + endReached?: boolean, + ) => void { + const { currentPageIndex, setCurrentPageIndex } = userReaderStatePagesContext(); + const { initialChapter, currentChapter, nextChapter, mangaChapters } = useReaderStateChaptersContext(); + const updateChapter = ReaderService.useUpdateChapter(); + const { shouldSkipDupChapters } = ReaderService.useSettings(); + const { + settings: { downloadAheadLimit }, + } = useMetadataServerSettings(); + + const nextChapters = useMemo(() => { + if (!initialChapter || !currentChapter) { + return []; + } + + return Chapters.getNextChapters(currentChapter, mangaChapters, { + offset: DirectionOffset.NEXT, + skipDupe: shouldSkipDupChapters, + skipDupeChapter: initialChapter, + }); + }, [initialChapter?.id, currentChapter?.id, mangaChapters, shouldSkipDupChapters]); + + return useCallback( + (pageIndex, debounceChapterUpdate = true, endReached = false) => { + setCurrentPageIndex(pageIndex); + + if (!currentChapter) { + return; + } + + const hasPageIndexChanged = pageIndex !== currentPageIndex; + if (!hasPageIndexChanged) { + return; + } + + ReaderService.downloadAhead(currentChapter, nextChapter, nextChapters, pageIndex, downloadAheadLimit); + + const handleCurrentPageIndexChange = () => { + const currentChapterUpToDate = getReaderChapterFromCache(currentChapter.id); + if (!currentChapterUpToDate) { + return; + } + + const hasLastPageReadChanged = pageIndex !== currentChapterUpToDate.lastPageRead; + const isLasPage = endReached || pageIndex === currentChapterUpToDate.pageCount - 1; + const shouldUpdateChapter = hasLastPageReadChanged || isLasPage; + if (!shouldUpdateChapter) { + return; + } + + updateChapter({ + lastPageRead: hasLastPageReadChanged ? pageIndex : undefined, + isRead: isLasPage ? true : undefined, + }); + }; + + clearTimeout(this.updateCurrentPageTimeout); + if (debounceChapterUpdate) { + this.updateCurrentPageTimeout = setTimeout(handleCurrentPageIndexChange, 1000); + return; + } + + handleCurrentPageIndexChange(); + }, + [currentChapter?.id, nextChapter?.id, nextChapters, currentPageIndex, downloadAheadLimit], + ); + } + static updateCurrentPageOnScroll( imageRefs: MutableRefObject<(HTMLElement | null)[]>, - currentPageIndex: number, - setCurrentPageIndex: (index: number) => void, + lastPageIndex: number, + updateCurrentPageIndex: ReturnType, type: PageInViewportType, + readingDirection: ReadingDirection, ) { const firstVisibleImageIndex = imageRefs.current.findIndex((image) => image && isPageInViewport(image, type)); + const lastPage = imageRefs.current?.[imageRefs.current.length - 1]; + const isEndReached = lastPage && isEndOfPageInViewport(lastPage, type, readingDirection); - if (firstVisibleImageIndex !== -1 && firstVisibleImageIndex !== currentPageIndex) { - setCurrentPageIndex(firstVisibleImageIndex); + // handle cases where the last page is too small to ever be the "firstVisibleImageIndex" + if (isEndReached) { + updateCurrentPageIndex(lastPageIndex, false); + return; + } + + if (firstVisibleImageIndex !== -1) { + updateCurrentPageIndex(firstVisibleImageIndex, firstVisibleImageIndex !== lastPageIndex); } } diff --git a/src/modules/reader/services/ReaderService.ts b/src/modules/reader/services/ReaderService.ts index 7cb2b829..3d65351b 100644 --- a/src/modules/reader/services/ReaderService.ts +++ b/src/modules/reader/services/ReaderService.ts @@ -6,7 +6,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { Direction, useTheme } from '@mui/material/styles'; import { t as translate } from 'i18next'; @@ -33,6 +33,16 @@ import { updateMetadataList } from '@/modules/metadata/services/MetadataApolloCa import { useBackButton } from '@/modules/core/hooks/useBackButton.ts'; import { GLOBAL_READER_SETTING_KEYS } from '@/modules/reader/constants/ReaderSettings.constants.tsx'; import { useReaderStateSettingsContext } from '@/modules/reader/contexts/state/ReaderStateSettingsContext.tsx'; +import { UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts'; +import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx'; +import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts'; +import { DirectionOffset } from '@/Base.types.ts'; +import { + getChapterIdsForDownloadAhead, + getChapterIdsToDeleteForChapterUpdate, +} from '@/modules/reader/utils/Reader.utils.ts'; +import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; +import { Queue } from '@/lib/Queue.ts'; const DIRECTION_TO_INVERTED: Record = { ltr: 'rtl', @@ -45,6 +55,8 @@ const DIRECTION_TO_READING_DIRECTION: Record = { }; export class ReaderService { + private static downloadAheadQueue: Queue = new Queue(1); + static useNavigateToChapter(chapter?: TChapterReader, resumeMode?: ReaderResumeMode): () => void { const navigate = useNavigate(); return useCallback( @@ -60,6 +72,103 @@ export class ReaderService { ); } + static downloadAhead( + currentChapter: TChapterReader, + nextChapter: TChapterReader | undefined, + nextChapters: TChapterReader[], + pageIndex: number, + downloadAheadLimit: number, + ): void { + const key = `${currentChapter.id}_${nextChapter?.id}_${pageIndex}_${downloadAheadLimit}`; + + this.downloadAheadQueue.enqueue(key, async () => { + const chapterIdsForDownloadAhead = getChapterIdsForDownloadAhead( + currentChapter, + nextChapter, + nextChapters, + pageIndex, + downloadAheadLimit, + ); + + if (!chapterIdsForDownloadAhead.length) { + return; + } + + try { + await Chapters.download(chapterIdsForDownloadAhead); + } catch (e) { + defaultPromiseErrorHandler('ReaderService::useUpdateCurrentPageIndex: download ahead'); + } + }); + } + + static useUpdateChapter(): (patch: UpdateChapterPatchInput) => void { + const { manga } = useReaderStateMangaContext(); + const { initialChapter, currentChapter, mangaChapters } = useReaderStateChaptersContext(); + const { shouldSkipDupChapters } = this.useSettings(); + const { + settings: { deleteChaptersWhileReading, deleteChaptersWithBookmark, updateProgressAfterReading }, + } = useMetadataServerSettings(); + + const previousChapters = useMemo(() => { + if (!initialChapter || !currentChapter) { + return []; + } + + return Chapters.getNextChapters(currentChapter, mangaChapters, { + offset: DirectionOffset.PREVIOUS, + skipDupe: shouldSkipDupChapters, + skipDupeChapter: initialChapter, + }); + }, [initialChapter?.id, currentChapter?.id, mangaChapters, shouldSkipDupChapters]); + + return useCallback( + (patch) => { + if (!manga || !currentChapter) { + return; + } + + const chapterIdsToUpdate = Chapters.getIds( + shouldSkipDupChapters ? Chapters.addDuplicates([currentChapter], mangaChapters) : [currentChapter], + ); + const chapterIdsToDelete = getChapterIdsToDeleteForChapterUpdate( + currentChapter, + mangaChapters, + previousChapters, + patch, + deleteChaptersWhileReading, + deleteChaptersWithBookmark, + shouldSkipDupChapters, + ); + + requestManager + .updateChapters( + chapterIdsToUpdate, + { + ...patch, + chapterIdsToDelete, + trackProgressMangaId: + updateProgressAfterReading && patch.isRead && manga.trackRecords.totalCount + ? manga.id + : undefined, + }, + { errorPolicy: 'all' }, + ) + .response.catch(defaultPromiseErrorHandler('ReaderService::useUpdateChapter')); + }, + [ + manga?.id, + currentChapter?.id, + mangaChapters, + previousChapters, + shouldSkipDupChapters, + deleteChaptersWhileReading, + deleteChaptersWithBookmark, + updateProgressAfterReading, + ], + ); + } + static useSettings(): IReaderSettingsWithDefaultFlag { return useReaderStateSettingsContext().settings; } diff --git a/src/modules/reader/types/Reader.types.ts b/src/modules/reader/types/Reader.types.ts index 1c03ea7a..69791e23 100644 --- a/src/modules/reader/types/Reader.types.ts +++ b/src/modules/reader/types/Reader.types.ts @@ -154,7 +154,18 @@ export interface IReaderSettingsWithDefaultFlag TransformRecordToWithDefaultFlag {} export interface ReaderStateChapters { + /** + * all chapters of the manga + */ + mangaChapters: TChapterReader[]; + /** + * actual chapters that have been filtered + * + * optional filters: + * - removed duplicate chapters + */ chapters: TChapterReader[]; + initialChapter?: TChapterReader | null; currentChapter?: TChapterReader | null; nextChapter?: TChapterReader; previousChapter?: TChapterReader; diff --git a/src/modules/reader/utils/Reader.utils.ts b/src/modules/reader/utils/Reader.utils.ts index a91cc216..a82efd55 100644 --- a/src/modules/reader/utils/Reader.utils.ts +++ b/src/modules/reader/utils/Reader.utils.ts @@ -7,6 +7,10 @@ */ import { ReaderResumeMode } from '@/modules/reader/types/Reader.types.ts'; +import { UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts'; +import { TChapterReader } from '@/modules/chapter/Chapter.types.ts'; +import { ChapterIdInfo, Chapters } from '@/modules/chapter/services/Chapters.ts'; +import { CHAPTER_READER_FIELDS } from '@/lib/graphql/fragments/ChapterFragments.ts'; export const getInitialReaderPageIndex = ( resumeMode: ReaderResumeMode, @@ -23,3 +27,80 @@ export const getInitialReaderPageIndex = ( return Math.max(0, Math.min(lastPageIndex, lastReadPageIndex)); }; + +export const getReaderChapterFromCache = (id: ChapterIdInfo['id']): TChapterReader | null => + Chapters.getFromCache(id, CHAPTER_READER_FIELDS, 'CHAPTER_READER_FIELDS')!; + +export const getChapterIdsToDeleteForChapterUpdate = ( + chapter: TChapterReader, + chapters: TChapterReader[], + previousChapters: TChapterReader[], + patch: UpdateChapterPatchInput, + deleteChaptersWhileReading: number, + deleteChaptersWithBookmark: boolean, + shouldSkipDupChapters: boolean, +): TChapterReader['id'][] => { + const isAutoDeletionEnabled = !!patch.isRead && !!deleteChaptersWhileReading; + if (!isAutoDeletionEnabled) { + return []; + } + + const chapterToDelete = [chapter, ...previousChapters][deleteChaptersWhileReading - 1]; + if (!chapterToDelete) { + return []; + } + + const chapterToDeleteUpToDateData = getReaderChapterFromCache(chapterToDelete.id); + if (!chapterToDeleteUpToDateData) { + return []; + } + + const shouldDeleteChapter = + chapterToDeleteUpToDateData.isRead && + Chapters.isDeletable(chapterToDeleteUpToDateData, deleteChaptersWithBookmark); + if (!shouldDeleteChapter) { + return []; + } + + if (!shouldSkipDupChapters) { + return Chapters.getIds([chapterToDelete]); + } + + return Chapters.getIds(Chapters.addDuplicates([chapterToDelete], chapters)); +}; + +export const getChapterIdsForDownloadAhead = ( + chapter: TChapterReader, + nextChapter: TChapterReader | undefined, + nextChapters: TChapterReader[], + currentPageIndex: number, + downloadAheadLimit: number, +): TChapterReader['id'][] => { + const chapterUpToDateData = getReaderChapterFromCache(chapter.id); + if (!chapterUpToDateData || !nextChapter) { + return []; + } + + const isDownloadAheadEnabled = !!downloadAheadLimit; + const inDownloadRange = currentPageIndex / chapterUpToDateData.pageCount > 0.25; + const shouldCheckDownloadAhead = isDownloadAheadEnabled && chapterUpToDateData.isDownloaded && inDownloadRange; + if (!shouldCheckDownloadAhead) { + return []; + } + + const nextChapterUpToDateData = getReaderChapterFromCache(nextChapter.id); + + if (!nextChapterUpToDateData?.isDownloaded) { + return []; + } + + const unreadNextChaptersUpToDateData = Chapters.getNonRead(nextChapters) + .map((unreadNextChapter) => getReaderChapterFromCache(unreadNextChapter.id)) + .filter((unreadNextChapterUpToDateData) => !!unreadNextChapterUpToDateData); + + return unreadNextChaptersUpToDateData + .slice(-downloadAheadLimit) + .filter((unreadNextChapter) => !unreadNextChapter.isDownloaded) + .map((unreadUnDownloadedNextChapter) => unreadUnDownloadedNextChapter.id) + .filter((id) => !Chapters.isDownloading(id)); +}; diff --git a/src/modules/reader/utils/ReaderPager.utils.tsx b/src/modules/reader/utils/ReaderPager.utils.tsx index 6c1e362e..2b0defc3 100644 --- a/src/modules/reader/utils/ReaderPager.utils.tsx +++ b/src/modules/reader/utils/ReaderPager.utils.tsx @@ -12,8 +12,8 @@ import { IReaderSettings, PageInViewportType, ReaderPageScaleMode, - ReadingDirection, ReaderTransitionPageMode, + ReadingDirection, ReadingMode, } from '@/modules/reader/types/Reader.types.ts'; import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; @@ -224,7 +224,16 @@ export const createReaderPage = ( /> ); -export const isPageInViewport = (element: HTMLElement, type: PageInViewportType): boolean => { +const getIsPageInViewportInfo = ( + element: HTMLElement, +): { + isLeftInViewport: boolean; + isRightInViewport: boolean; + isFillingWidthViewportCompletely: boolean; + isTopInViewport: boolean; + isBottomInViewport: boolean; + isFillingHeightViewportCompletely: boolean; +} => { const { top, bottom, left, right } = element.getBoundingClientRect(); // for some reason using "scrollIntoView" to scroll a page into view does not always result in the page to be at top, @@ -239,6 +248,26 @@ export const isPageInViewport = (element: HTMLElement, type: PageInViewportType) const isBottomInViewport = bottom >= MIN_VISIBLE_PX && bottom <= window.innerHeight; const isFillingHeightViewportCompletely = top <= MIN_VISIBLE_PX && bottom >= window.innerHeight; + return { + isLeftInViewport, + isRightInViewport, + isFillingWidthViewportCompletely, + isTopInViewport, + isBottomInViewport, + isFillingHeightViewportCompletely, + }; +}; + +export const isPageInViewport = (element: HTMLElement, type: PageInViewportType): boolean => { + const { + isLeftInViewport, + isRightInViewport, + isFillingWidthViewportCompletely, + isTopInViewport, + isBottomInViewport, + isFillingHeightViewportCompletely, + } = getIsPageInViewportInfo(element); + const isInViewportX = isLeftInViewport || isRightInViewport || isFillingWidthViewportCompletely; const isInViewportY = isTopInViewport || isBottomInViewport || isFillingHeightViewportCompletely; @@ -252,6 +281,23 @@ export const isPageInViewport = (element: HTMLElement, type: PageInViewportType) } }; +export const isEndOfPageInViewport = ( + element: HTMLElement, + type: PageInViewportType, + direction: ReadingDirection, +): boolean => { + const { isLeftInViewport, isRightInViewport, isBottomInViewport } = getIsPageInViewportInfo(element); + + switch (type) { + case PageInViewportType.X: + return direction === ReadingDirection.LTR ? isRightInViewport : isLeftInViewport; + case PageInViewportType.Y: + return isBottomInViewport; + default: + throw new Error(`unexpected "type" (${type})`); + } +}; + export const getDoublePageModePages = ( pageUrls: ReaderStatePages['pageUrls'], pagesToSpreadState: boolean[], diff --git a/src/modules/reader/utils/ReaderProgressBar.utils.tsx b/src/modules/reader/utils/ReaderProgressBar.utils.tsx index 05056877..634dcf3e 100644 --- a/src/modules/reader/utils/ReaderProgressBar.utils.tsx +++ b/src/modules/reader/utils/ReaderProgressBar.utils.tsx @@ -23,6 +23,13 @@ export const getPage = (pageIndex: number, pages: ReaderProgressBarProps['pages' }; }; +/** + * for the double page mode the secondary page index has to be used to be able to correctly detect if the last page is visible + * + */ +export const getNextIndexFromPage = (page: ReaderProgressBarProps['pages'][number]) => + page.secondary?.index ?? page.primary.index; + export const getNextPageIndex = ( offset: 'previous' | 'next', pagesIndex: number, @@ -30,9 +37,9 @@ export const getNextPageIndex = ( ): number => { switch (offset) { case 'previous': - return pages[Math.max(0, pagesIndex - 1)].primary.index; + return getNextIndexFromPage(pages[Math.max(0, pagesIndex - 1)]); case 'next': - return pages[Math.min(pages.length - 1, pagesIndex + 1)].primary.index; + return getNextIndexFromPage(pages[Math.min(pages.length - 1, pagesIndex + 1)]); default: throw new Error(`Unexpected offset "${offset}"`); }