diff --git a/public/locales/en.json b/public/locales/en.json index f64e07cf..c2eb8ab4 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -323,6 +323,7 @@ "log_out": "Log out", "migrate": "Migrate", "ok": "Ok", + "open": "Open", "open_site": "Open Site", "options": "Options", "popular": "Popular", @@ -804,6 +805,21 @@ "previous_page": "Previous page", "retry_load_pages": "Retry errored pages" }, + "chapter_transition": { + "warning": { + "next": { + "chapter_number_one": "There is one missing chapter.\nCurrent: {{currentChapter}}\nNext: {{nextChapter}}", + "chapter_number_other": "There are {{count}} missing chapters.\nCurrent: {{currentChapter}}\nNext: {{nextChapter}}", + "scanlator": "The next chapter has a different scanlator then the current one.\nCurrent: {{currentScanlator}}\nNext: {{nextScanlator}}" + }, + "previous": { + "chapter_number_one": "There is one missing chapter.\nPrevious: {{nextChapter}}\nCurrent: {{currentChapter}}", + "chapter_number_other": "There are {{count}} missing chapters.\nPrevious: {{nextChapter}}\nCurrent: {{currentChapter}}", + "scanlator": "The previous chapter has a different scanlator then the current one.\nPrevious: {{nextScanlator}}\nCurrent: {{currentScanlator}}" + }, + "title": "Chapter transition warning" + } + }, "error": { "label": { "chapter_not_found": "Chapter does not exist", diff --git a/src/modules/core/utils/AwaitableDialog.tsx b/src/modules/core/utils/AwaitableDialog.tsx index 1ef82108..bdd73520 100644 --- a/src/modules/core/utils/AwaitableDialog.tsx +++ b/src/modules/core/utils/AwaitableDialog.tsx @@ -25,7 +25,7 @@ export const awaitConfirmation = async ( if (accepted) { confirmationPromise.resolve(); } else { - confirmationPromise.reject(); + confirmationPromise.reject(new Error('Confirmation declined')); } root.unmount(); diff --git a/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopChapterNavigation.tsx b/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopChapterNavigation.tsx index d081589e..6c0c986e 100644 --- a/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopChapterNavigation.tsx +++ b/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopChapterNavigation.tsx @@ -17,13 +17,12 @@ import FormControl from '@mui/material/FormControl'; import InputLabel from '@mui/material/InputLabel'; import { Link } from 'react-router-dom'; import { Select } from '@/modules/core/components/inputs/Select.tsx'; -import { Chapters } from '@/modules/chapter/services/Chapters.ts'; import { ReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx'; import { ReaderChapterList } from '@/modules/reader/components/overlay/navigation/ReaderChapterList.tsx'; import { ReaderNavBarDesktopNextPreviousButton } from '@/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopNextPreviousButton.tsx'; import { getOptionForDirection } from '@/modules/theme/services/ThemeCreator.ts'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; -import { ReaderResumeMode } from '@/modules/reader/types/Reader.types.ts'; +import { ReaderControls } from '@/modules/reader/services/ReaderControls.ts'; export const ReaderNavBarDesktopChapterNavigation = ({ currentChapter, @@ -36,6 +35,7 @@ export const ReaderNavBarDesktopChapterNavigation = ({ >) => { const { t } = useTranslation(); const readerThemeDirection = ReaderService.useGetThemeDirection(); + const openChapter = ReaderControls.useOpenChapter(); const popupState = usePopupState({ variant: 'popover', popupId: 'reader-nav-bar-desktop-chapter-list' }); @@ -46,7 +46,6 @@ export const ReaderNavBarDesktopChapterNavigation = ({ return ( { + openChapter(getOptionForDirection('previous', 'next', readerThemeDirection)); }} + disabled={getOptionForDirection(!previousChapter, !nextChapter, readerThemeDirection)} /> {t('chapter.title_one')} @@ -97,20 +86,10 @@ export const ReaderNavBarDesktopChapterNavigation = ({ readerThemeDirection, ), )} - disabled={getOptionForDirection(!nextChapter, !previousChapter, readerThemeDirection)} - to={getOptionForDirection( - nextChapter && Chapters.getReaderUrl(nextChapter), - previousChapter && Chapters.getReaderUrl(previousChapter), - readerThemeDirection, - )} - replace - state={{ - resumeMode: getOptionForDirection( - ReaderResumeMode.START, - ReaderResumeMode.END, - readerThemeDirection, - ), + onClick={() => { + openChapter(getOptionForDirection('next', 'previous', readerThemeDirection)); }} + disabled={getOptionForDirection(!nextChapter, !previousChapter, readerThemeDirection)} /> void { + const { t } = useTranslation(); const { readingMode } = ReaderService.useSettings(); - const { previousChapter, nextChapter } = useReaderStateChaptersContext(); + const { currentChapter, previousChapter, nextChapter } = useReaderStateChaptersContext(); const openPreviousChapter = ReaderService.useNavigateToChapter(previousChapter, ReaderResumeMode.END); const openNextChapter = ReaderService.useNavigateToChapter(nextChapter, ReaderResumeMode.START); @@ -157,19 +162,80 @@ export class ReaderControls { (offset) => { switch (offset) { case 'previous': - openPreviousChapter(); + ReaderControls.checkNextChapterConsistency(t, offset, currentChapter, previousChapter).then( + openPreviousChapter, + ); break; case 'next': - openNextChapter(); + ReaderControls.checkNextChapterConsistency(t, offset, currentChapter, nextChapter).then( + openNextChapter, + ); break; default: throw new Error(`Unexpected "offset" (${offset})`); } }, - [openPreviousChapter, openNextChapter, readingMode.value], + [t, currentChapter?.id, openPreviousChapter, openNextChapter, readingMode.value], ); } + private static async checkNextChapterConsistency( + t: TFunction, + offset: 'previous' | 'next', + currentChapter?: TChapterReader | null, + chapterToOpen?: TChapterReader | null, + ): Promise { + if (!currentChapter || !chapterToOpen) { + return; + } + + const missingChapters = Math.abs(currentChapter.chapterNumber - chapterToOpen.chapterNumber); + const isSameScanlator = currentChapter.scanlator === chapterToOpen.scanlator; + const isContinuousChapter = missingChapters === 1; + + const showWarning = !isSameScanlator || !isContinuousChapter; + if (!showWarning) { + return; + } + + const offsetToTranslationKeys: Record> = { + previous: { + scanlator: 'reader.chapter_transition.warning.previous.scanlator', + chapter_number: 'reader.chapter_transition.warning.previous.chapter_number', + }, + next: { + scanlator: 'reader.chapter_transition.warning.next.scanlator', + chapter_number: 'reader.chapter_transition.warning.next.chapter_number', + }, + }; + + const sameScanlator = isSameScanlator + ? '' + : t(offsetToTranslationKeys[offset].scanlator, { + nextScanlator: chapterToOpen.scanlator, + currentScanlator: currentChapter.scanlator, + }); + const continuousChapter = isContinuousChapter + ? '' + : t(offsetToTranslationKeys[offset].chapter_number, { + count: missingChapters, + nextChapter: `#${chapterToOpen.chapterNumber} ${chapterToOpen.name}`, + currentChapter: `#${currentChapter.chapterNumber} ${currentChapter.name}`, + }); + const warningLineBreak = !isSameScanlator && !isContinuousChapter ? '\n\n' : ''; + const warning = `${sameScanlator}${warningLineBreak}${continuousChapter}`; + + await awaitConfirmation({ + title: t('reader.chapter_transition.warning.title'), + message: warning, + actions: { + confirm: { + title: t('global.button.open'), + }, + }, + }); + } + static useOpenPage(): (page: number | 'previous' | 'next', forceDirection?: Direction) => void { const { currentPageIndex, setPageToScrollToIndex, pages, transitionPageMode, setTransitionPageMode } = userReaderStatePagesContext();