Ignore tap zone clicks without window focus

This commit is contained in:
schroda
2026-06-02 15:46:52 +02:00
parent 78e2a99847
commit f42d7fdbda
2 changed files with 59 additions and 26 deletions

View File

@@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- (**Migration**) Show the exclude/include button only for an entry with a selected match - (**Migration**) Show the exclude/include button only for an entry with a selected match
- (**Source/Extension**) Rename language "All" to "Multi" - (**Source/Extension**) Rename language "All" to "Multi"
- (**Reader**) Simplify changing settings in desktop sidebar - (**Reader**) Simplify changing settings in desktop sidebar
- (**Reader**) Ignore tap zone clicks while window does not have focus
### Fixed ### Fixed

View File

@@ -82,10 +82,44 @@ const getScrollDirectionInvert = (
return 1; return 1;
}; };
export class ReaderControls { class ReaderControlsClass {
private static updateCurrentPageTimeout: NodeJS.Timeout; private updateCurrentPageTimeout: NodeJS.Timeout | undefined;
static scroll( private windowFocus: boolean = true;
private windowFocusUpdateImminent = false;
constructor() {
let focusTimeout: NodeJS.Timeout | undefined;
window.addEventListener('blur', () => {
clearTimeout(focusTimeout);
this.windowFocus = false;
this.windowFocusUpdateImminent = false;
});
window.addEventListener('focus', () => {
this.windowFocusUpdateImminent = true;
clearTimeout(focusTimeout);
focusTimeout = setTimeout(() => {
this.windowFocus = true;
this.windowFocusUpdateImminent = false;
}, d(250).milliseconds.inWholeMilliseconds);
});
}
private checkAndUpdateWindowsFocus(): boolean {
const hasFocus = this.windowFocus;
if (this.windowFocusUpdateImminent) {
this.windowFocus = true;
this.windowFocusUpdateImminent = false;
}
return hasFocus;
}
scroll(
offset: ScrollOffset, offset: ScrollOffset,
direction: ScrollDirection, direction: ScrollDirection,
readingMode: ReadingMode, readingMode: ReadingMode,
@@ -124,12 +158,12 @@ export class ReaderControls {
scrollToOptions: ScrollToOptions, scrollToOptions: ScrollToOptions,
) => { ) => {
if (isAtStartForDirection && offset === ScrollOffset.BACKWARD && isContinuousReadingModeActive) { if (isAtStartForDirection && offset === ScrollOffset.BACKWARD && isContinuousReadingModeActive) {
ReaderControls.openChapter('previous'); this.openChapter('previous');
return; return;
} }
if (isAtEndForDirection && offset === ScrollOffset.FORWARD && isContinuousReadingModeActive) { if (isAtEndForDirection && offset === ScrollOffset.FORWARD && isContinuousReadingModeActive) {
ReaderControls.openChapter('next'); this.openChapter('next');
return; return;
} }
@@ -153,7 +187,7 @@ export class ReaderControls {
} }
} }
static openChapter( openChapter(
offset: 'previous' | 'next' | ChapterIdInfo['id'], offset: 'previous' | 'next' | ChapterIdInfo['id'],
doTransitionCheck: boolean = true, doTransitionCheck: boolean = true,
scrollIntoView: boolean = true, scrollIntoView: boolean = true,
@@ -205,7 +239,7 @@ export class ReaderControls {
try { try {
if (doTransitionCheck) { if (doTransitionCheck) {
await ReaderControls.checkNextChapterConsistency( await this.checkNextChapterConsistency(
isPreviousChapter ? 'previous' : 'next', isPreviousChapter ? 'previous' : 'next',
currentChapter, currentChapter,
chapterToOpen, chapterToOpen,
@@ -247,7 +281,7 @@ export class ReaderControls {
doOpenChapter().catch(defaultPromiseErrorHandler('ReaderControls#useOpenChapter')); doOpenChapter().catch(defaultPromiseErrorHandler('ReaderControls#useOpenChapter'));
} }
private static async checkNextChapterConsistency( private async checkNextChapterConsistency(
offset: 'previous' | 'next', offset: 'previous' | 'next',
currentChapter: TChapterReader | null | undefined, currentChapter: TChapterReader | null | undefined,
chapterToOpen: TChapterReader | null | undefined, chapterToOpen: TChapterReader | null | undefined,
@@ -314,7 +348,7 @@ export class ReaderControls {
}); });
} }
static openPage(page: number | 'previous' | 'next', forceDirection?: Direction, hideOverlay: boolean = true): void { openPage(page: number | 'previous' | 'next', forceDirection?: Direction, hideOverlay: boolean = true): void {
const { currentPageIndex, setPageToScrollToIndex, pages, transitionPageMode, setTransitionPageMode } = const { currentPageIndex, setPageToScrollToIndex, pages, transitionPageMode, setTransitionPageMode } =
getReaderPagesStore(); getReaderPagesStore();
const { readingDirection, readingMode, shouldShowTransitionPage } = getReaderSettingsStore(); const { readingDirection, readingMode, shouldShowTransitionPage } = getReaderSettingsStore();
@@ -365,7 +399,7 @@ export class ReaderControls {
convertedPage === 'previous' && convertedPage === 'previous' &&
!!getReaderChaptersStore().previousChapter; !!getReaderChaptersStore().previousChapter;
if (shouldOpenPreviousChapter) { if (shouldOpenPreviousChapter) {
ReaderControls.openChapter('previous'); this.openChapter('previous');
return; return;
} }
@@ -375,7 +409,7 @@ export class ReaderControls {
convertedPage === 'next' && convertedPage === 'next' &&
!!getReaderChaptersStore().nextChapter; !!getReaderChaptersStore().nextChapter;
if (shouldOpenNextChapter) { if (shouldOpenNextChapter) {
ReaderControls.openChapter('next'); this.openChapter('next');
return; return;
} }
@@ -406,11 +440,7 @@ export class ReaderControls {
setPageToScrollToIndex(isPreviousMode ? previousPageIndex : nextPageIndex); setPageToScrollToIndex(isPreviousMode ? previousPageIndex : nextPageIndex);
} }
static useUpdateCurrentPageIndex(): ( useUpdateCurrentPageIndex(): (pageIndex: number, debounceChapterUpdate?: boolean, endReached?: boolean) => void {
pageIndex: number,
debounceChapterUpdate?: boolean,
endReached?: boolean,
) => void {
const updateChapter = ReaderService.useUpdateChapter(); const updateChapter = ReaderService.useUpdateChapter();
const { const {
settings: { downloadAheadLimit }, settings: { downloadAheadLimit },
@@ -463,9 +493,9 @@ export class ReaderControls {
}); });
}; };
clearTimeout(ReaderControls.updateCurrentPageTimeout); clearTimeout(this.updateCurrentPageTimeout);
if (debounceChapterUpdate) { if (debounceChapterUpdate) {
ReaderControls.updateCurrentPageTimeout = setTimeout( this.updateCurrentPageTimeout = setTimeout(
handleCurrentPageIndexChange, handleCurrentPageIndexChange,
d(1).seconds.inWholeMilliseconds, d(1).seconds.inWholeMilliseconds,
); );
@@ -478,10 +508,10 @@ export class ReaderControls {
); );
} }
static updateCurrentPageOnScroll( updateCurrentPageOnScroll(
imageRefs: MutableRefObject<(HTMLElement | null)[]>, imageRefs: MutableRefObject<(HTMLElement | null)[]>,
lastPageIndex: number, lastPageIndex: number,
updateCurrentPageIndex: ReturnType<typeof ReaderControls.useUpdateCurrentPageIndex>, updateCurrentPageIndex: ReturnType<typeof this.useUpdateCurrentPageIndex>,
type: PageInViewportType, type: PageInViewportType,
readingDirection: ReadingDirection, readingDirection: ReadingDirection,
) { ) {
@@ -529,8 +559,8 @@ export class ReaderControls {
updateCurrentPageIndex(firstVisibleImageIndex, firstVisibleImageIndex !== lastPageIndex); updateCurrentPageIndex(firstVisibleImageIndex, firstVisibleImageIndex !== lastPageIndex);
} }
static handleClick(scrollElement: HTMLElement | null, e: React.MouseEvent<HTMLDivElement, MouseEvent>): void { handleClick(scrollElement: HTMLElement | null, e: React.MouseEvent<HTMLDivElement, MouseEvent>): void {
if (!scrollElement) { if (!scrollElement || !this.checkAndUpdateWindowsFocus()) {
return; return;
} }
@@ -554,7 +584,7 @@ export class ReaderControls {
case TapZoneRegionType.PREVIOUS: case TapZoneRegionType.PREVIOUS:
case TapZoneRegionType.NEXT: case TapZoneRegionType.NEXT:
if (isContinuousReadingModeActive) { if (isContinuousReadingModeActive) {
ReaderControls.scroll( this.scroll(
action === TapZoneRegionType.PREVIOUS ? ScrollOffset.BACKWARD : ScrollOffset.FORWARD, action === TapZoneRegionType.PREVIOUS ? ScrollOffset.BACKWARD : ScrollOffset.FORWARD,
scrollDirection, scrollDirection,
readingMode.value, readingMode.value,
@@ -563,7 +593,7 @@ export class ReaderControls {
scrollAmount, scrollAmount,
); );
} else { } else {
ReaderControls.openPage(action === TapZoneRegionType.PREVIOUS ? 'previous' : 'next', 'ltr'); this.openPage(action === TapZoneRegionType.PREVIOUS ? 'previous' : 'next', 'ltr');
} }
break; break;
default: default:
@@ -571,7 +601,7 @@ export class ReaderControls {
} }
} }
static useHandleProgressDragging( useHandleProgressDragging(
progressBarRef: RefObject<HTMLDivElement | null>, progressBarRef: RefObject<HTMLDivElement | null>,
isDragging: boolean, isDragging: boolean,
currentPage: TReaderProgressCurrentPage, currentPage: TReaderProgressCurrentPage,
@@ -608,7 +638,7 @@ export class ReaderControls {
return; return;
} }
ReaderControls.openPage(newPageIndex, undefined, false); this.openPage(newPageIndex, undefined, false);
}; };
const handleMouseMove = (e: MouseEvent) => { const handleMouseMove = (e: MouseEvent) => {
@@ -631,3 +661,5 @@ export class ReaderControls {
}, [isDragging, currentPage, pages, progressBarPosition]); }, [isDragging, currentPage, pages, progressBarPosition]);
} }
} }
export const ReaderControls = new ReaderControlsClass();