mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
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:
parent
adb38a83ed
commit
f97e2a2122
@ -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}">
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
@ -18246,4 +18246,4 @@
|
|||||||
"description": "Responsible for all things Want To Read"
|
"description": "Responsible for all things Want To Read"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user