Fix click/scroll/hotkey open previous/next chapter

Properly handle all combinations of theme and reader reading directions
This commit is contained in:
schroda
2024-12-09 13:58:17 +01:00
parent 3839b438eb
commit f654d95804
3 changed files with 57 additions and 32 deletions

View File

@@ -23,6 +23,7 @@ import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import { HotkeyScope } from '@/modules/hotkeys/Hotkeys.types.ts'; import { HotkeyScope } from '@/modules/hotkeys/Hotkeys.types.ts';
import { ReaderControls } from '@/modules/reader/services/ReaderControls.ts'; import { ReaderControls } from '@/modules/reader/services/ReaderControls.ts';
import { ScrollDirection, ScrollOffset } from '@/modules/core/Core.types.ts'; import { ScrollDirection, ScrollOffset } from '@/modules/core/Core.types.ts';
import { getOptionForDirection } from '@/theme.tsx';
const useHotkeys = (...args: Parameters<typeof useHotKeysHook>): ReturnType<typeof useHotKeysHook> => { const useHotkeys = (...args: Parameters<typeof useHotKeysHook>): ReturnType<typeof useHotKeysHook> => {
const [keys, callback, options, dependencies] = args; const [keys, callback, options, dependencies] = args;
@@ -44,6 +45,7 @@ export const ReaderHotkeys = ({
scrollElementRef: React.MutableRefObject<HTMLElement | null>; scrollElementRef: React.MutableRefObject<HTMLElement | null>;
}) => { }) => {
const { direction: themeDirection } = useTheme(); const { direction: themeDirection } = useTheme();
const readerThemeDirection = ReaderService.useGetThemeDirection();
const { enableScope, disableScope } = useHotkeysContext(); const { enableScope, disableScope } = useHotkeysContext();
const { manga } = useReaderStateMangaContext(); const { manga } = useReaderStateMangaContext();
const { isVisible, setIsVisible } = useReaderOverlayContext(); const { isVisible, setIsVisible } = useReaderOverlayContext();
@@ -92,8 +94,16 @@ export const ReaderHotkeys = ({
{ preventDefault: true }, { preventDefault: true },
[readingMode.value, readingDirection.value, themeDirection, openChapter], [readingMode.value, readingDirection.value, themeDirection, openChapter],
); );
useHotkeys(hotkeys[ReaderHotkey.PREVIOUS_CHAPTER], () => openChapter('previous'), [openChapter]); useHotkeys(
useHotkeys(hotkeys[ReaderHotkey.NEXT_CHAPTER], () => openChapter('next'), [openChapter]); hotkeys[ReaderHotkey.PREVIOUS_CHAPTER],
() => openChapter(getOptionForDirection('previous', 'next', readerThemeDirection)),
[openChapter, readerThemeDirection],
);
useHotkeys(
hotkeys[ReaderHotkey.NEXT_CHAPTER],
() => openChapter(getOptionForDirection('next', 'previous', readerThemeDirection)),
[openChapter, readerThemeDirection],
);
useHotkeys(hotkeys[ReaderHotkey.TOGGLE_MENU], () => setIsVisible(!isVisible), [isVisible]); useHotkeys(hotkeys[ReaderHotkey.TOGGLE_MENU], () => setIsVisible(!isVisible), [isVisible]);
useHotkeys( useHotkeys(
hotkeys[ReaderHotkey.CYCLE_SCALE_TYPE], hotkeys[ReaderHotkey.CYCLE_SCALE_TYPE],

View File

@@ -21,9 +21,8 @@ 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 { useGetOptionForDirection } from '@/theme.tsx'; import { getOptionForDirection } from '@/theme.tsx';
import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
import { READING_DIRECTION_TO_THEME_DIRECTION } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
import { ReaderResumeMode } from '@/modules/reader/types/Reader.types.ts'; import { ReaderResumeMode } from '@/modules/reader/types/Reader.types.ts';
export const ReaderNavBarDesktopChapterNavigation = ({ export const ReaderNavBarDesktopChapterNavigation = ({
@@ -36,13 +35,10 @@ export const ReaderNavBarDesktopChapterNavigation = ({
'chapters' | 'currentChapter' | 'previousChapter' | 'nextChapter' 'chapters' | 'currentChapter' | 'previousChapter' | 'nextChapter'
>) => { >) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { readingDirection } = ReaderService.useSettings(); const readerThemeDirection = ReaderService.useGetThemeDirection();
const getOptionForDirection = useGetOptionForDirection();
const popupState = usePopupState({ variant: 'popover', popupId: 'reader-nav-bar-desktop-chapter-list' }); const popupState = usePopupState({ variant: 'popover', popupId: 'reader-nav-bar-desktop-chapter-list' });
const direction = READING_DIRECTION_TO_THEME_DIRECTION[readingDirection.value];
useLayoutEffect(() => { useLayoutEffect(() => {
popupState.close(); popupState.close();
}, [currentChapter?.id]); }, [currentChapter?.id]);
@@ -53,16 +49,26 @@ export const ReaderNavBarDesktopChapterNavigation = ({
component={Link} component={Link}
type="previous" type="previous"
title={t( title={t(
getOptionForDirection('reader.button.previous_chapter', 'reader.button.next_chapter', direction), getOptionForDirection(
'reader.button.previous_chapter',
'reader.button.next_chapter',
readerThemeDirection,
),
)} )}
disabled={getOptionForDirection(!previousChapter, !nextChapter, direction)} disabled={getOptionForDirection(!previousChapter, !nextChapter, readerThemeDirection)}
to={getOptionForDirection( to={getOptionForDirection(
previousChapter && Chapters.getReaderUrl(previousChapter), previousChapter && Chapters.getReaderUrl(previousChapter),
nextChapter && Chapters.getReaderUrl(nextChapter), nextChapter && Chapters.getReaderUrl(nextChapter),
direction, readerThemeDirection,
)} )}
replace replace
state={{ resumeMode: getOptionForDirection(ReaderResumeMode.END, ReaderResumeMode.START) }} state={{
resumeMode: getOptionForDirection(
ReaderResumeMode.END,
ReaderResumeMode.START,
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>
@@ -85,16 +91,26 @@ export const ReaderNavBarDesktopChapterNavigation = ({
component={Link} component={Link}
type="next" type="next"
title={t( title={t(
getOptionForDirection('reader.button.next_chapter', 'reader.button.previous_chapter', direction), getOptionForDirection(
'reader.button.next_chapter',
'reader.button.previous_chapter',
readerThemeDirection,
),
)} )}
disabled={getOptionForDirection(!nextChapter, !previousChapter, direction)} disabled={getOptionForDirection(!nextChapter, !previousChapter, readerThemeDirection)}
to={getOptionForDirection( to={getOptionForDirection(
nextChapter && Chapters.getReaderUrl(nextChapter), nextChapter && Chapters.getReaderUrl(nextChapter),
previousChapter && Chapters.getReaderUrl(previousChapter), previousChapter && Chapters.getReaderUrl(previousChapter),
direction, readerThemeDirection,
)} )}
replace replace
state={{ resumeMode: getOptionForDirection(ReaderResumeMode.START, ReaderResumeMode.END) }} state={{
resumeMode: getOptionForDirection(
ReaderResumeMode.START,
ReaderResumeMode.END,
readerThemeDirection,
),
}}
/> />
<Popover <Popover
{...bindPopover(popupState)} {...bindPopover(popupState)}

View File

@@ -88,14 +88,14 @@ export class ReaderControls {
const areReadingDirectionsEqual = themeDirection === themeDirectionOfReadingDirection; const areReadingDirectionsEqual = themeDirection === themeDirectionOfReadingDirection;
const isContinuousReadingModeActive = isContinuousReadingMode(readingMode); const isContinuousReadingModeActive = isContinuousReadingMode(readingMode);
const isAtStartY = element.scrollTop === 0; const isAtStartY = Math.abs(element.scrollTop) <= 1;
const isAtEndY = const isAtEndY =
Math.floor(element.scrollTop) === element.scrollHeight - element.clientHeight || Math.floor(element.scrollTop) === element.scrollHeight - element.clientHeight ||
Math.ceil(element.scrollTop) === element.scrollHeight - element.clientHeight; Math.ceil(element.scrollTop) === element.scrollHeight - element.clientHeight;
const isAtStartX = element.scrollLeft === 0; const isAtStartX = Math.abs(element.scrollLeft) <= 1;
const isAtEndX = const isAtEndX =
Math.floor(Math.abs(element.scrollLeft)) === element.scrollWidth - element.clientWidth || element.scrollWidth - element.clientWidth - Math.floor(Math.abs(element.scrollLeft)) <= 1 ||
Math.ceil(Math.abs(element.scrollLeft)) === element.scrollWidth - element.clientWidth; element.scrollWidth - element.clientWidth - Math.ceil(Math.abs(element.scrollLeft)) <= 1;
const isAtStartXForDirection = areReadingDirectionsEqual ? isAtStartX : isAtEndX; const isAtStartXForDirection = areReadingDirectionsEqual ? isAtStartX : isAtEndX;
const isAtEndXForDirection = areReadingDirectionsEqual ? isAtEndX : isAtStartX; const isAtEndXForDirection = areReadingDirectionsEqual ? isAtEndX : isAtStartX;
@@ -108,12 +108,12 @@ export class ReaderControls {
switch (direction) { switch (direction) {
case ScrollDirection.X: case ScrollDirection.X:
if (isAtStartXForDirection && offset === ScrollOffset.BACKWARD && isContinuousReadingModeActive) { if (isAtStartXForDirection && offset === ScrollOffset.BACKWARD && isContinuousReadingModeActive) {
openChapter('previous', 'ltr'); openChapter('previous');
return; return;
} }
if (isAtEndXForDirection && offset === ScrollOffset.FORWARD && isContinuousReadingModeActive) { if (isAtEndXForDirection && offset === ScrollOffset.FORWARD && isContinuousReadingModeActive) {
openChapter('next', 'ltr'); openChapter('next');
return; return;
} }
@@ -124,12 +124,12 @@ export class ReaderControls {
break; break;
case ScrollDirection.Y: case ScrollDirection.Y:
if (isAtStartY && offset === ScrollOffset.BACKWARD && isContinuousReadingModeActive) { if (isAtStartY && offset === ScrollOffset.BACKWARD && isContinuousReadingModeActive) {
openChapter('previous', 'ltr'); openChapter('previous');
return; return;
} }
if (isAtEndY && offset === ScrollOffset.FORWARD && isContinuousReadingModeActive) { if (isAtEndY && offset === ScrollOffset.FORWARD && isContinuousReadingModeActive) {
openChapter('next', 'ltr'); openChapter('next');
return; return;
} }
@@ -143,28 +143,27 @@ export class ReaderControls {
} }
} }
static useOpenChapter(): (offset: 'previous' | 'next', forceDirection?: Direction) => void { static useOpenChapter(): (offset: 'previous' | 'next') => void {
const { readingDirection, readingMode } = ReaderService.useSettings(); const { readingMode } = ReaderService.useSettings();
const { previousChapter, nextChapter } = useReaderStateChaptersContext(); const { previousChapter, nextChapter } = useReaderStateChaptersContext();
const direction = READING_DIRECTION_TO_THEME_DIRECTION[readingDirection.value];
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);
return useCallback( return useCallback(
(offset, forceDirection = direction) => { (offset) => {
switch (offset) { switch (offset) {
case 'previous': case 'previous':
getOptionForDirection(openPreviousChapter, openNextChapter, forceDirection)(); openPreviousChapter();
break; break;
case 'next': case 'next':
getOptionForDirection(openNextChapter, openPreviousChapter, forceDirection)(); openNextChapter();
break; break;
default: default:
throw new Error(`Unexpected "offset" (${offset})`); throw new Error(`Unexpected "offset" (${offset})`);
} }
}, },
[direction, openPreviousChapter, openNextChapter, readingMode.value], [openPreviousChapter, openNextChapter, readingMode.value],
); );
} }
@@ -390,7 +389,7 @@ export class ReaderControls {
const isContinuousReadingModeActive = isContinuousReadingMode(readingMode.value); const isContinuousReadingModeActive = isContinuousReadingMode(readingMode.value);
const scrollDirection = const scrollDirection =
readingMode.value === ReadingMode.CONTINUOUS_HORIZONTAL ? ScrollDirection.X : ScrollDirection.Y; readingMode.value === ReadingMode.CONTINUOUS_HORIZONTAL ? ScrollDirection.X : ScrollDirection.Y;
console.log('click', action);
switch (action) { switch (action) {
case TapZoneRegionType.MENU: case TapZoneRegionType.MENU:
setIsOverlayVisible((isVisible) => isStaticNav || !isVisible); setIsOverlayVisible((isVisible) => isStaticNav || !isVisible);