Reworked tap-to-paginate (#2061)

* Reworked tap-to-paginate

* Addressing review comments and issues

* More PR review changes

* Minor indentation in reader component HTML

* Removed no-longer-used prop

* Bring back checking last click

---------

Co-authored-by: Joe Milazzo <josephmajora@gmail.com>
This commit is contained in:
Gazy Mahomar 2023-08-28 21:04:34 +02:00 committed by GitHub
parent adb38a83ed
commit f97e2a2122
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 109 additions and 22 deletions

View File

@ -101,20 +101,24 @@
<div #readingSection class="reading-section {{ColumnLayout}} {{WritingStyleClass}}" [ngStyle]="{'width': PageWidthForPagination}" [ngClass]="{'immersive' : immersiveMode || !actionBarVisible}" [@isLoading]="isLoading"> <div #readingSection class="reading-section {{ColumnLayout}} {{WritingStyleClass}}" [ngStyle]="{'width': PageWidthForPagination}" [ngClass]="{'immersive' : immersiveMode || !actionBarVisible}" [@isLoading]="isLoading">
<ng-container *ngIf="clickToPaginate"> <ng-container *ngIf="clickToPaginate">
<div class="left {{clickOverlayClass('left')}} no-observe" [ngClass]="{'immersive' : immersiveMode}" <div class="left {{clickOverlayClass('left')}} no-pointer-events no-observe"
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
tabindex="-1" [ngStyle]="{height: PageHeightForPagination}"></div>
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe"
[ngClass]="{'immersive' : immersiveMode}" [ngClass]="{'immersive' : immersiveMode}"
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)" tabindex="-1"
tabindex="-1" [ngStyle]="{height: PageHeightForPagination}"></div> [ngStyle]="{height: PageHeightForPagination}"></div>
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-pointer-events no-observe"
[ngClass]="{'immersive' : immersiveMode}"
tabindex="-1"
[ngStyle]="{height: PageHeightForPagination}"></div>
</ng-container> </ng-container>
<div #bookContainer class="book-container {{WritingStyleClass}}" [ngClass]="{'immersive' : immersiveMode}"> <div #bookContainer class="book-container {{WritingStyleClass}}"
[ngClass]="{'immersive' : immersiveMode, 'pointer' : cursorIsPointer}"
(click)="handleReaderClick($event)"
(mousedown)="mouseDown($event)">
<div #readingHtml class="book-content {{ColumnLayout}} {{WritingStyleClass}}" <div #readingHtml class="book-content {{ColumnLayout}} {{WritingStyleClass}}"
[ngStyle]="{'max-height': ColumnHeight, 'max-width': VerticalBookContentWidth, 'width': VerticalBookContentWidth, 'column-width': ColumnWidth}" [ngStyle]="{'max-height': ColumnHeight, 'max-width': VerticalBookContentWidth, 'width': VerticalBookContentWidth, 'column-width': ColumnWidth}"
[ngClass]="{'immersive': immersiveMode && actionBarVisible}" [ngClass]="{'immersive': immersiveMode && actionBarVisible}"
[innerHtml]="page" *ngIf="page !== undefined" (click)="toggleMenu($event)" (mousedown)="mouseDown($event)" (wheel)="onWheel($event)"></div> [innerHtml]="page" *ngIf="page !== undefined" (wheel)="onWheel($event)"></div>
<div *ngIf="page !== undefined && (scrollbarNeeded || layoutMode !== BookPageLayoutMode.Default) && !(writingStyle === WritingStyle.Vertical && layoutMode === BookPageLayoutMode.Default)" <div *ngIf="page !== undefined && (scrollbarNeeded || layoutMode !== BookPageLayoutMode.Default) && !(writingStyle === WritingStyle.Vertical && layoutMode === BookPageLayoutMode.Default)"
(click)="$event.stopPropagation();" (click)="$event.stopPropagation();"
[ngClass]="{'bottom-bar': layoutMode !== BookPageLayoutMode.Default}"> [ngClass]="{'bottom-bar': layoutMode !== BookPageLayoutMode.Default}">

View File

@ -199,6 +199,9 @@ $action-bar-height: 38px;
overflow: auto; overflow: auto;
} }
&.pointer {
cursor: pointer;
}
} }
.book-content { .book-content {
@ -339,8 +342,8 @@ $action-bar-height: 38px;
top: 0px; top: 0px;
} }
&:hover { &.no-pointer-events {
cursor: pointer; pointer-events: none;
} }
} }
@ -360,8 +363,9 @@ $action-bar-height: 38px;
&.immersive { &.immersive {
top: 0px; top: 0px;
} }
&:hover {
cursor: pointer; &.no-pointer-events {
pointer-events: none;
} }
} }
@ -381,8 +385,8 @@ $action-bar-height: 38px;
top: 0px; top: 0px;
} }
&:hover { &.no-pointer-events {
cursor: pointer; pointer-events: none;
} }
} }

View File

@ -17,7 +17,7 @@ import { DOCUMENT, Location, NgTemplateOutlet, NgIf, NgStyle, NgClass } from '@a
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { forkJoin, fromEvent, of } from 'rxjs'; import { forkJoin, fromEvent, of } from 'rxjs';
import { catchError, debounceTime, take } from 'rxjs/operators'; import { catchError, debounceTime, distinctUntilChanged, map, take } from 'rxjs/operators';
import { Chapter } from 'src/app/_models/chapter'; import { Chapter } from 'src/app/_models/chapter';
import { AccountService } from 'src/app/_services/account.service'; import { AccountService } from 'src/app/_services/account.service';
import { NavService } from 'src/app/_services/nav.service'; import { NavService } from 'src/app/_services/nav.service';
@ -292,6 +292,19 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
writingStyle: WritingStyle = WritingStyle.Horizontal; writingStyle: WritingStyle = WritingStyle.Horizontal;
/**
* Controls whether the cursor is a pointer when tap to paginate is on and the cursor is over
* either of the pagination areas
*/
cursorIsPointer: boolean = false;
/**
* Used to keep track of the last time a paginator area was clicked to prevent the default
* browser behavior of selecting words or lines when double- or triple-clicking, respectively,
* triggered by repeated click pagination (when enabled).
*/
lastPaginatorClickTime: number = 0;
/** /**
* Used to refresh the Personal PoC * Used to refresh the Personal PoC
*/ */
@ -497,8 +510,14 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.handleScrollEvent(); this.handleScrollEvent();
}); });
// TODO: In order to allow people to highlight content under pagination overlays, apply pointer-events: none while fromEvent<MouseEvent>(this.bookContainerElemRef.nativeElement, 'mousemove')
// a highlight operation is in effect .pipe(
takeUntilDestroyed(this.destroyRef),
map(this.isCursorOverPaginationArea.bind(this)),
distinctUntilChanged())
.subscribe((isPointer) => {
this.changeCursor(isPointer);
});
} }
handleScrollEvent() { handleScrollEvent() {
@ -823,6 +842,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
const links = this.readingSectionElemRef.nativeElement.querySelectorAll('a'); const links = this.readingSectionElemRef.nativeElement.querySelectorAll('a');
links.forEach((link: any) => { links.forEach((link: any) => {
link.addEventListener('click', (e: any) => { link.addEventListener('click', (e: any) => {
e.stopPropagation();
let targetElem = e.target; let targetElem = e.target;
if (e.target.nodeName !== 'A' && e.target.parentNode.nodeName === 'A') { if (e.target.nodeName !== 'A' && e.target.parentNode.nodeName === 'A') {
// Certain combos like <a><sup>text</sup></a> can cause the target to be the sup tag and not the anchor // Certain combos like <a><sup>text</sup></a> can cause the target to be the sup tag and not the anchor
@ -1568,7 +1588,49 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return side === 'right' ? 'highlight-2' : 'highlight'; return side === 'right' ? 'highlight-2' : 'highlight';
} }
private isCursorOverLeftPaginationArea(event: MouseEvent): boolean {
const leftPaginationAreaEnd = window.innerWidth * 0.2;
return event.clientX <= leftPaginationAreaEnd;
}
private isCursorOverRightPaginationArea(event: MouseEvent): boolean {
const rightPaginationAreaStart = event.clientX >= window.innerWidth * 0.8;
return rightPaginationAreaStart;
}
private isCursorOverPaginationArea(event: MouseEvent): boolean {
if (this.isLoading || !this.clickToPaginate) {
return false;
}
return this.isCursorOverLeftPaginationArea(event) || this.isCursorOverRightPaginationArea(event);
}
private changeCursor(isPointer: boolean) {
this.cursorIsPointer = isPointer;
this.cdRef.markForCheck();
}
handleReaderClick(event: MouseEvent) {
if (!this.clickToPaginate) {
this.toggleMenu(event);
return;
}
const isHighlighting = window.getSelection()?.toString() != '';
if (isHighlighting) {
return;
}
if (this.isCursorOverLeftPaginationArea(event)) {
this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD);
} else if (this.isCursorOverRightPaginationArea(event)) {
this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)
} else {
this.toggleMenu(event);
}
}
toggleMenu(event: MouseEvent) { toggleMenu(event: MouseEvent) {
const targetElement = (event.target as Element); const targetElement = (event.target as Element);
@ -1581,8 +1643,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
if ( if (
Math.abs(this.mousePosition.x - event.screenX) <= mouseOffset && Math.abs(this.mousePosition.x - event.clientX) <= mouseOffset &&
Math.abs(this.mousePosition.y - event.screenY) <= mouseOffset Math.abs(this.mousePosition.y - event.clientY) <= mouseOffset
) { ) {
this.actionBarVisible = !this.actionBarVisible; this.actionBarVisible = !this.actionBarVisible;
this.cdRef.markForCheck(); this.cdRef.markForCheck();
@ -1590,8 +1652,25 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
mouseDown($event: MouseEvent) { mouseDown($event: MouseEvent) {
this.mousePosition.x = $event.screenX; this.mousePosition.x = $event.clientX;
this.mousePosition.y = $event.screenY; this.mousePosition.y = $event.clientY;
if (!this.clickToPaginate || !this.isCursorOverPaginationArea($event)) {
return
}
// This value is completely arbitrary and should cover most
// double-click speeds
const halfASecond = 500;
const now = Date.now();
// This is to prevent selecting text when clicking repeatedly to switch pages,
// while also allowing selections to begin in a pagination area
if (now - this.lastPaginatorClickTime < halfASecond) {
$event.preventDefault();
}
this.lastPaginatorClickTime = now;
} }
refreshPersonalToC() { refreshPersonalToC() {