Show chapter transition warning

- on scanlator change
- on skipped chapters
This commit is contained in:
schroda
2024-12-11 22:33:04 +01:00
parent 8ca0af8d02
commit 6dd2c8e2ff
4 changed files with 96 additions and 35 deletions

View File

@@ -323,6 +323,7 @@
"log_out": "Log out", "log_out": "Log out",
"migrate": "Migrate", "migrate": "Migrate",
"ok": "Ok", "ok": "Ok",
"open": "Open",
"open_site": "Open Site", "open_site": "Open Site",
"options": "Options", "options": "Options",
"popular": "Popular", "popular": "Popular",
@@ -804,6 +805,21 @@
"previous_page": "Previous page", "previous_page": "Previous page",
"retry_load_pages": "Retry errored pages" "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": { "error": {
"label": { "label": {
"chapter_not_found": "Chapter does not exist", "chapter_not_found": "Chapter does not exist",

View File

@@ -25,7 +25,7 @@ export const awaitConfirmation = async (
if (accepted) { if (accepted) {
confirmationPromise.resolve(); confirmationPromise.resolve();
} else { } else {
confirmationPromise.reject(); confirmationPromise.reject(new Error('Confirmation declined'));
} }
root.unmount(); root.unmount();

View File

@@ -17,13 +17,12 @@ import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel'; import InputLabel from '@mui/material/InputLabel';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Select } from '@/modules/core/components/inputs/Select.tsx'; 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 { ReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { ReaderChapterList } from '@/modules/reader/components/overlay/navigation/ReaderChapterList.tsx'; import { ReaderChapterList } from '@/modules/reader/components/overlay/navigation/ReaderChapterList.tsx';
import { ReaderNavBarDesktopNextPreviousButton } from '@/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopNextPreviousButton.tsx'; import { ReaderNavBarDesktopNextPreviousButton } from '@/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopNextPreviousButton.tsx';
import { getOptionForDirection } from '@/modules/theme/services/ThemeCreator.ts'; import { getOptionForDirection } from '@/modules/theme/services/ThemeCreator.ts';
import { ReaderService } from '@/modules/reader/services/ReaderService.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 = ({ export const ReaderNavBarDesktopChapterNavigation = ({
currentChapter, currentChapter,
@@ -36,6 +35,7 @@ export const ReaderNavBarDesktopChapterNavigation = ({
>) => { >) => {
const { t } = useTranslation(); const { t } = useTranslation();
const readerThemeDirection = ReaderService.useGetThemeDirection(); const readerThemeDirection = ReaderService.useGetThemeDirection();
const openChapter = ReaderControls.useOpenChapter();
const popupState = usePopupState({ variant: 'popover', popupId: 'reader-nav-bar-desktop-chapter-list' }); const popupState = usePopupState({ variant: 'popover', popupId: 'reader-nav-bar-desktop-chapter-list' });
@@ -46,7 +46,6 @@ export const ReaderNavBarDesktopChapterNavigation = ({
return ( return (
<Stack sx={{ flexDirection: 'row', gap: 1 }} dir="ltr"> <Stack sx={{ flexDirection: 'row', gap: 1 }} dir="ltr">
<ReaderNavBarDesktopNextPreviousButton <ReaderNavBarDesktopNextPreviousButton
component={Link}
type="previous" type="previous"
title={t( title={t(
getOptionForDirection( getOptionForDirection(
@@ -55,20 +54,10 @@ export const ReaderNavBarDesktopChapterNavigation = ({
readerThemeDirection, readerThemeDirection,
), ),
)} )}
disabled={getOptionForDirection(!previousChapter, !nextChapter, readerThemeDirection)} onClick={() => {
to={getOptionForDirection( openChapter(getOptionForDirection('previous', 'next', readerThemeDirection));
previousChapter && Chapters.getReaderUrl(previousChapter),
nextChapter && Chapters.getReaderUrl(nextChapter),
readerThemeDirection,
)}
replace
state={{
resumeMode: getOptionForDirection(
ReaderResumeMode.END,
ReaderResumeMode.START,
readerThemeDirection,
),
}} }}
disabled={getOptionForDirection(!previousChapter, !nextChapter, readerThemeDirection)}
/> />
<FormControl sx={{ flexBasis: '70%', flexGrow: 0, flexShrink: 0 }}> <FormControl sx={{ flexBasis: '70%', flexGrow: 0, flexShrink: 0 }}>
<InputLabel id="reader-nav-bar-desktop-chapter-select">{t('chapter.title_one')}</InputLabel> <InputLabel id="reader-nav-bar-desktop-chapter-select">{t('chapter.title_one')}</InputLabel>
@@ -97,20 +86,10 @@ export const ReaderNavBarDesktopChapterNavigation = ({
readerThemeDirection, readerThemeDirection,
), ),
)} )}
disabled={getOptionForDirection(!nextChapter, !previousChapter, readerThemeDirection)} onClick={() => {
to={getOptionForDirection( openChapter(getOptionForDirection('next', 'previous', readerThemeDirection));
nextChapter && Chapters.getReaderUrl(nextChapter),
previousChapter && Chapters.getReaderUrl(previousChapter),
readerThemeDirection,
)}
replace
state={{
resumeMode: getOptionForDirection(
ReaderResumeMode.START,
ReaderResumeMode.END,
readerThemeDirection,
),
}} }}
disabled={getOptionForDirection(!nextChapter, !previousChapter, readerThemeDirection)}
/> />
<Popover <Popover
{...bindPopover(popupState)} {...bindPopover(popupState)}

View File

@@ -8,6 +8,8 @@
import { ContextType, MutableRefObject, useCallback, useMemo } from 'react'; import { ContextType, MutableRefObject, useCallback, useMemo } from 'react';
import { Direction, useTheme } from '@mui/material/styles'; import { Direction, useTheme } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import { TFunction } from 'i18next';
import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/ReaderStatePagesContext.tsx'; import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/ReaderStatePagesContext.tsx';
import { getNextIndexFromPage, getNextPageIndex, getPage } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx'; import { getNextIndexFromPage, getNextPageIndex, getPage } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx';
import { getOptionForDirection } from '@/modules/theme/services/ThemeCreator.ts'; import { getOptionForDirection } from '@/modules/theme/services/ThemeCreator.ts';
@@ -33,8 +35,10 @@ import { ReaderTapZoneService } from '@/modules/reader/services/ReaderTapZoneSer
import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx'; import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
import { getReaderChapterFromCache } from '@/modules/reader/utils/Reader.utils.ts'; import { getReaderChapterFromCache } from '@/modules/reader/utils/Reader.utils.ts';
import { Chapters } from '@/modules/chapter/services/Chapters.ts'; import { Chapters } from '@/modules/chapter/services/Chapters.ts';
import { DirectionOffset } from '@/Base.types.ts'; import { DirectionOffset, TranslationKey } from '@/Base.types.ts';
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts'; import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
import { TChapterReader } from '@/modules/chapter/Chapter.types.ts';
import { awaitConfirmation } from '@/modules/core/utils/AwaitableDialog.tsx';
const getScrollDirectionInvert = ( const getScrollDirectionInvert = (
scrollDirection: ScrollDirection, scrollDirection: ScrollDirection,
@@ -147,8 +151,9 @@ export class ReaderControls {
} }
static useOpenChapter(): (offset: 'previous' | 'next') => void { static useOpenChapter(): (offset: 'previous' | 'next') => void {
const { t } = useTranslation();
const { readingMode } = ReaderService.useSettings(); const { readingMode } = ReaderService.useSettings();
const { previousChapter, nextChapter } = useReaderStateChaptersContext(); const { currentChapter, previousChapter, nextChapter } = useReaderStateChaptersContext();
const openPreviousChapter = ReaderService.useNavigateToChapter(previousChapter, ReaderResumeMode.END); const openPreviousChapter = ReaderService.useNavigateToChapter(previousChapter, ReaderResumeMode.END);
const openNextChapter = ReaderService.useNavigateToChapter(nextChapter, ReaderResumeMode.START); const openNextChapter = ReaderService.useNavigateToChapter(nextChapter, ReaderResumeMode.START);
@@ -157,19 +162,80 @@ export class ReaderControls {
(offset) => { (offset) => {
switch (offset) { switch (offset) {
case 'previous': case 'previous':
openPreviousChapter(); ReaderControls.checkNextChapterConsistency(t, offset, currentChapter, previousChapter).then(
openPreviousChapter,
);
break; break;
case 'next': case 'next':
openNextChapter(); ReaderControls.checkNextChapterConsistency(t, offset, currentChapter, nextChapter).then(
openNextChapter,
);
break; break;
default: default:
throw new Error(`Unexpected "offset" (${offset})`); 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<void> {
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<typeof offset, Record<'scanlator' | 'chapter_number', TranslationKey>> = {
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 { static useOpenPage(): (page: number | 'previous' | 'next', forceDirection?: Direction) => void {
const { currentPageIndex, setPageToScrollToIndex, pages, transitionPageMode, setTransitionPageMode } = const { currentPageIndex, setPageToScrollToIndex, pages, transitionPageMode, setTransitionPageMode } =
userReaderStatePagesContext(); userReaderStatePagesContext();