Show chapter transition warning
- on scanlator change - on skipped chapters
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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)}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user