2025-01-18 22:41:54 +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-11-29 16:23:12 +01:00
|
|
|
import { useEffect, useMemo, useRef } from 'react';
|
2025-02-20 13:16:39 +01:00
|
|
|
import { useLocation, useNavigate } from 'react-router-dom';
|
2025-08-15 22:02:58 +02:00
|
|
|
import { Chapters } from '@/features/chapter/services/Chapters.ts';
|
2025-01-18 22:41:54 +01:00
|
|
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
|
|
|
|
import { GetChaptersReaderQuery } from '@/lib/graphql/generated/graphql.ts';
|
2025-02-20 13:16:39 +01:00
|
|
|
import {
|
|
|
|
|
IReaderSettings,
|
|
|
|
|
ReaderOpenChapterLocationState,
|
|
|
|
|
ReaderStateChapters,
|
2025-08-16 02:02:13 +02:00
|
|
|
} from '@/features/reader/Reader.types.ts';
|
2025-08-15 22:02:58 +02:00
|
|
|
import { filterChapters } from '@/features/chapter/utils/ChapterList.util.tsx';
|
|
|
|
|
import { ChapterListOptions } from '@/features/chapter/Chapter.types.ts';
|
2025-08-16 02:02:13 +02:00
|
|
|
import { getReaderChapterFromCache } from '@/features/reader/Reader.utils.ts';
|
2025-08-16 02:48:46 +02:00
|
|
|
import { DirectionOffset } from '@/base/Base.types.ts';
|
2025-09-21 00:50:19 +02:00
|
|
|
import { getReaderChaptersStore } from '@/features/reader/stores/ReaderStore.ts';
|
2025-09-20 17:58:51 +02:00
|
|
|
|
|
|
|
|
import { READER_DEFAULT_CHAPTERS_STATE } from '@/features/reader/stores/ReaderChaptersStore.ts';
|
2025-01-18 22:41:54 +01:00
|
|
|
|
|
|
|
|
export const useReaderSetChaptersState = (
|
|
|
|
|
chaptersResponse: ReturnType<typeof requestManager.useGetMangaChapters<GetChaptersReaderQuery>>,
|
|
|
|
|
chapterSourceOrder: number,
|
2025-07-01 00:37:29 +02:00
|
|
|
mangaChapters: ReaderStateChapters['mangaChapters'],
|
2025-01-28 02:16:00 +01:00
|
|
|
initialChapter: ReaderStateChapters['initialChapter'],
|
2025-02-01 20:03:02 +01:00
|
|
|
chapterForDuplicatesHandling: ReaderStateChapters['chapterForDuplicatesHandling'],
|
2025-01-18 22:41:54 +01:00
|
|
|
shouldSkipDupChapters: IReaderSettings['shouldSkipDupChapters'],
|
2025-07-01 00:37:29 +02:00
|
|
|
shouldSkipFilteredChapters: IReaderSettings['shouldSkipFilteredChapters'],
|
|
|
|
|
chapterListOptions: ChapterListOptions,
|
2025-01-18 22:41:54 +01:00
|
|
|
) => {
|
2025-02-11 02:09:28 +01:00
|
|
|
const navigate = useNavigate();
|
2025-02-20 13:16:39 +01:00
|
|
|
const locationState = useLocation<ReaderOpenChapterLocationState>().state;
|
|
|
|
|
|
|
|
|
|
const { updateInitialChapter } = locationState ?? {};
|
|
|
|
|
const finalInitialChapter = updateInitialChapter ? undefined : initialChapter;
|
2025-02-11 02:09:28 +01:00
|
|
|
|
2025-11-29 16:23:12 +01:00
|
|
|
const newMangaChapters = chaptersResponse.data?.chapters.nodes;
|
|
|
|
|
const newCurrentChapter = useMemo(
|
|
|
|
|
() => (newMangaChapters ? (newMangaChapters[newMangaChapters.length - chapterSourceOrder] ?? null) : undefined),
|
|
|
|
|
[newMangaChapters, chapterSourceOrder],
|
|
|
|
|
);
|
|
|
|
|
const newInitialChapter = finalInitialChapter ?? newCurrentChapter;
|
|
|
|
|
const newChapterForDuplicatesHandling = chapterForDuplicatesHandling ?? newCurrentChapter;
|
|
|
|
|
|
|
|
|
|
const filteredChapters = useMemo(() => {
|
|
|
|
|
if (!newMangaChapters) {
|
|
|
|
|
return newMangaChapters;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return shouldSkipFilteredChapters
|
|
|
|
|
? filterChapters(mangaChapters ?? newMangaChapters, chapterListOptions)
|
|
|
|
|
: newMangaChapters;
|
|
|
|
|
}, [newMangaChapters, shouldSkipFilteredChapters, mangaChapters, chapterListOptions]);
|
|
|
|
|
|
|
|
|
|
const uniqueChapters = useMemo(() => {
|
|
|
|
|
if (!filteredChapters || !newChapterForDuplicatesHandling) {
|
|
|
|
|
return filteredChapters;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return shouldSkipDupChapters
|
|
|
|
|
? Chapters.removeDuplicates(newChapterForDuplicatesHandling, filteredChapters)
|
|
|
|
|
: filteredChapters;
|
|
|
|
|
}, [filteredChapters, shouldSkipDupChapters, newChapterForDuplicatesHandling]);
|
|
|
|
|
|
|
|
|
|
const visibleChapters = useMemo(() => {
|
|
|
|
|
if (!uniqueChapters) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return uniqueChapters.map((chapter) => getReaderChapterFromCache(chapter.id)!);
|
|
|
|
|
}, [uniqueChapters]);
|
|
|
|
|
|
|
|
|
|
const prevVisibleChaptersRef = useRef<typeof visibleChapters>([]);
|
|
|
|
|
|
2025-01-18 22:41:54 +01:00
|
|
|
useEffect(() => {
|
2025-11-29 16:23:12 +01:00
|
|
|
// Check if visibleChapters actually changed by comparing chapter IDs
|
|
|
|
|
const hasChaptersChanged =
|
|
|
|
|
visibleChapters.length !== prevVisibleChaptersRef.current.length ||
|
|
|
|
|
visibleChapters.some((chapter, index) => chapter.id !== prevVisibleChaptersRef.current[index]?.id);
|
|
|
|
|
|
|
|
|
|
// Use the previous reference if chapters haven't actually changed
|
|
|
|
|
const stableVisibleChapters = hasChaptersChanged ? visibleChapters : prevVisibleChaptersRef.current;
|
|
|
|
|
|
|
|
|
|
// Update the ref for next comparison
|
|
|
|
|
if (hasChaptersChanged) {
|
|
|
|
|
prevVisibleChaptersRef.current = visibleChapters;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-28 02:16:00 +01:00
|
|
|
const nextChapter =
|
|
|
|
|
newCurrentChapter &&
|
2025-11-29 16:23:12 +01:00
|
|
|
Chapters.getNextChapter(newCurrentChapter, stableVisibleChapters, {
|
2025-01-28 02:16:00 +01:00
|
|
|
offset: DirectionOffset.NEXT,
|
|
|
|
|
});
|
|
|
|
|
const previousChapter =
|
|
|
|
|
newCurrentChapter &&
|
2025-11-29 16:23:12 +01:00
|
|
|
Chapters.getNextChapter(newCurrentChapter, stableVisibleChapters, {
|
2025-01-28 02:16:00 +01:00
|
|
|
offset: DirectionOffset.PREVIOUS,
|
|
|
|
|
});
|
|
|
|
|
|
2025-02-20 13:16:39 +01:00
|
|
|
const hasInitialChapterChanged = newInitialChapter != null && newInitialChapter.id !== finalInitialChapter?.id;
|
2025-01-28 02:16:00 +01:00
|
|
|
|
2025-02-11 02:09:28 +01:00
|
|
|
if (hasInitialChapterChanged) {
|
2025-02-20 13:16:39 +01:00
|
|
|
navigate('', { replace: true, state: { ...locationState, updateInitialChapter: undefined } });
|
2025-02-11 02:09:28 +01:00
|
|
|
}
|
|
|
|
|
|
2025-09-21 00:50:19 +02:00
|
|
|
getReaderChaptersStore().setReaderStateChapters((prevState) => {
|
2025-01-28 02:16:00 +01:00
|
|
|
const hasCurrentChapterChanged = newCurrentChapter?.id !== prevState.currentChapter?.id;
|
2025-07-01 00:37:29 +02:00
|
|
|
|
2025-01-28 02:16:00 +01:00
|
|
|
return {
|
|
|
|
|
...prevState,
|
2025-07-01 00:37:29 +02:00
|
|
|
mangaChapters: prevState.mangaChapters ?? newMangaChapters,
|
2025-11-29 16:23:12 +01:00
|
|
|
chapters: stableVisibleChapters,
|
2025-01-28 02:16:00 +01:00
|
|
|
initialChapter: newInitialChapter,
|
|
|
|
|
chapterForDuplicatesHandling: newChapterForDuplicatesHandling,
|
|
|
|
|
currentChapter: newCurrentChapter,
|
|
|
|
|
nextChapter,
|
|
|
|
|
previousChapter,
|
|
|
|
|
isCurrentChapterReady: hasCurrentChapterChanged ? false : prevState.isCurrentChapterReady,
|
|
|
|
|
visibleChapters: hasInitialChapterChanged
|
|
|
|
|
? {
|
2025-09-20 01:33:27 +02:00
|
|
|
...READER_DEFAULT_CHAPTERS_STATE.chapters.visibleChapters,
|
2025-01-28 02:16:00 +01:00
|
|
|
lastLeadingChapterSourceOrder:
|
|
|
|
|
newInitialChapter?.sourceOrder ??
|
2025-09-20 01:33:27 +02:00
|
|
|
READER_DEFAULT_CHAPTERS_STATE.chapters.visibleChapters.lastLeadingChapterSourceOrder,
|
2025-01-28 02:16:00 +01:00
|
|
|
lastTrailingChapterSourceOrder:
|
|
|
|
|
newInitialChapter?.sourceOrder ??
|
2025-09-20 01:33:27 +02:00
|
|
|
READER_DEFAULT_CHAPTERS_STATE.chapters.visibleChapters.lastTrailingChapterSourceOrder,
|
2025-02-10 01:05:35 +01:00
|
|
|
isLeadingChapterPreloadMode: false,
|
|
|
|
|
isTrailingChapterPreloadMode: false,
|
2025-04-26 23:08:05 +02:00
|
|
|
// do not set "scrollIntoView" to "true" for the initial render
|
|
|
|
|
scrollIntoView: !!prevState.initialChapter,
|
2025-01-28 02:16:00 +01:00
|
|
|
}
|
|
|
|
|
: prevState.visibleChapters,
|
|
|
|
|
};
|
2025-01-18 22:41:54 +01:00
|
|
|
});
|
2025-07-01 00:37:29 +02:00
|
|
|
}, [
|
2025-11-29 16:23:12 +01:00
|
|
|
visibleChapters,
|
|
|
|
|
newCurrentChapter,
|
|
|
|
|
newInitialChapter,
|
2025-07-01 00:37:29 +02:00
|
|
|
finalInitialChapter,
|
2025-11-29 16:23:12 +01:00
|
|
|
newMangaChapters,
|
|
|
|
|
newChapterForDuplicatesHandling,
|
|
|
|
|
locationState,
|
2025-07-01 00:37:29 +02:00
|
|
|
]);
|
2025-01-18 22:41:54 +01:00
|
|
|
};
|