Fixed a bug with double clicking manga reader on mobile no longer triggering bookmarking

This commit is contained in:
Joseph Milazzo 2024-12-08 11:09:43 -06:00
parent 2a43deea24
commit 0a7bc4b3f6
16 changed files with 250 additions and 203 deletions

View file

@ -0,0 +1,27 @@
import {Directive, EventEmitter, HostListener, Output} from '@angular/core';
@Directive({
selector: '[appDblClick]',
standalone: true
})
export class DblClickDirective {
@Output() doubleClick = new EventEmitter<Event>();
private lastTapTime = 0;
private tapTimeout = 300; // Time threshold for a double tap (in milliseconds)
@HostListener('click', ['$event'])
handleClick(event: Event): void {
event.stopPropagation();
event.preventDefault();
const currentTime = new Date().getTime();
if (currentTime - this.lastTapTime < this.tapTimeout) {
// Detected a double click/tap
this.doubleClick.emit(event);
}
this.lastTapTime = currentTime;
}
}

View file

@ -48,7 +48,7 @@ import {FilterUtilitiesService} from "../shared/_services/filter-utilities.servi
import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {ReadingList} from "../_models/reading-list"; import {ReadingList} from "../_models/reading-list";
import {ReadingListService} from "../_services/reading-list.service"; import {ReadingListService} from "../_services/reading-list.service";
import {RelatedTabComponent} from "../_single-modules/related-tab/related-tab.component"; import {RelatedTabComponent} from "../_single-module/related-tab/related-tab.component";
import {BadgeExpanderComponent} from "../shared/badge-expander/badge-expander.component"; import {BadgeExpanderComponent} from "../shared/badge-expander/badge-expander.component";
import { import {
MetadataDetailRowComponent MetadataDetailRowComponent

View file

@ -3,13 +3,14 @@
@if(debugMode) { @if(debugMode) {
<div class="fixed-top overlay"> <div class="fixed-top overlay">
@for(img of cachedImages; track img.src) { @for(img of cachedImages; track img.src) {
<ng-container *ngIf="this.readerService.imageUrlToPageNum(img.src) as imageNum"> @if (this.readerService.imageUrlToPageNum(img.src); as imageNum) {
<span class="me-1" [ngClass]="{'current': imageNum === this.pageNum, 'loaded': img.complete}">{{this.readerService.imageUrlToPageNum(img.src)}}</span> <span class="me-1" [ngClass]="{'current': imageNum === this.pageNum, 'loaded': img.complete}">{{this.readerService.imageUrlToPageNum(img.src)}}</span>
</ng-container> }
} }
</div> </div>
} }
<div class="fixed-top overlay" *ngIf="menuOpen" [@slideFromTop]="menuOpen"> @if (menuOpen) {
<div class="fixed-top overlay" [@slideFromTop]="menuOpen">
<div style="display: flex; margin-top: 5px;"> <div style="display: flex; margin-top: 5px;">
<button class="btn btn-icon" style="height: 100%" [title]="t('back')" (click)="closeReader()"> <button class="btn btn-icon" style="height: 100%" [title]="t('back')" (click)="closeReader()">
<i class="fa fa-arrow-left" aria-hidden="true"></i> <i class="fa fa-arrow-left" aria-hidden="true"></i>
@ -17,9 +18,16 @@
</button> </button>
<div> <div>
<div style="font-weight: bold;">{{title}} <span class="clickable" *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" [attr.aria-label]="t('incognito-alt')">(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">{{t('incognito-title')}}</span>)</span></div> <div style="font-weight: bold;">{{title}}
@if (incognitoMode) {
<span class="clickable" (click)="turnOffIncognito()" role="button" [attr.aria-label]="t('incognito-alt')">(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">{{t('incognito-title')}}</span>)</span>
}
</div>
<div class="subtitle"> <div class="subtitle">
{{subtitle}} <span *ngIf="totalSeriesPages > 0">{{t('series-progress', {percentage: (Math.min(1, ((totalSeriesPagesRead + pageNum) / totalSeriesPages)) | percent)}) }}</span> {{subtitle}}
@if (totalSeriesPages > 0) {
<span>{{t('series-progress', {percentage: (Math.min(1, ((totalSeriesPagesRead + pageNum) / totalSeriesPages)) | percent)}) }}</span>
}
</div> </div>
</div> </div>
@ -38,12 +46,15 @@
</div> </div>
</div> </div>
</div> </div>
}
<app-loading [loading]="isLoading || (!(currentImage$ | async)?.complete && this.readerMode !== ReaderMode.Webtoon)" [absolute]="true"></app-loading> <app-loading [loading]="isLoading || (!(currentImage$ | async)?.complete && this.readerMode !== ReaderMode.Webtoon)" [absolute]="true"></app-loading>
<div class="reading-area" <div class="reading-area"
ngSwipe (swipeEnd)="onSwipeEnd($event)" (swipeMove)="onSwipeMove($event)" ngSwipe (swipeEnd)="onSwipeEnd($event)" (swipeMove)="onSwipeMove($event)"
[ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : '100dvh'}" #readingArea> [ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : '100dvh'}" #readingArea>
<ng-container *ngIf="readerMode !== ReaderMode.Webtoon; else webtoon"> @if (readerMode !== ReaderMode.Webtoon) {
<div (dblclick)="bookmarkPage($event)"> <div (dblclick)="bookmarkPage($event)">
<app-canvas-renderer <app-canvas-renderer
[readerSettings$]="readerSettings$" [readerSettings$]="readerSettings$"
@ -57,24 +68,28 @@
<div class="pagination-area"> <div class="pagination-area">
<div class="{{readerMode === ReaderMode.LeftRight ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, KeyDirection.Left)" <div class="{{readerMode === ReaderMode.LeftRight ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, KeyDirection.Left)"
[ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? MaxHeight: '25%'), 'max-height': MaxHeight}"> [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? MaxHeight: '25%'), 'max-height': MaxHeight}">
<div *ngIf="showClickOverlay"> @if (showClickOverlay) {
<div>
<i class="fa fa-angle-{{readingDirection === ReadingDirection.RightToLeft ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'left' : 'up'}}" <i class="fa fa-angle-{{readingDirection === ReadingDirection.RightToLeft ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'left' : 'up'}}"
[title]="t('prev-page-tooltip')" aria-hidden="true"></i> [title]="t('prev-page-tooltip')" aria-hidden="true"></i>
</div> </div>
}
</div> </div>
<div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, KeyDirection.Right)" <div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, KeyDirection.Right)"
[ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? MaxHeight: '25%'), [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? MaxHeight: '25%'),
'left': 'inherit', 'left': 'inherit',
'right': RightPaginationOffset + 'px', 'right': RightPaginationOffset + 'px',
'max-height': MaxHeight}"> 'max-height': MaxHeight}">
<div *ngIf="showClickOverlay"> @if (showClickOverlay) {
<div>
<i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}" <i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}"
[title]="t('next-page-tooltip')" aria-hidden="true"></i> [title]="t('next-page-tooltip')" aria-hidden="true"></i>
</div> </div>
}
</div> </div>
</div> </div>
<div (dblclick)="bookmarkPage($event)"> <div appDblClick (doubleClick)="bookmarkPage($event)">
<app-single-renderer [image$]="currentImage$" <app-single-renderer [image$]="currentImage$"
[readerSettings$]="readerSettings$" [readerSettings$]="readerSettings$"
[bookmark$]="showBookmarkEffect$" [bookmark$]="showBookmarkEffect$"
@ -106,11 +121,9 @@
[getPage]="getPageFn"> [getPage]="getPageFn">
</app-double-no-cover-renderer> </app-double-no-cover-renderer>
</div> </div>
} @else {
</ng-container> @if (!isLoading && !inSetup) {
<div class="webtoon-images">
<ng-template #webtoon>
<div class="webtoon-images" *ngIf="!isLoading && !inSetup">
<app-infinite-scroller [pageNum]="pageNum" <app-infinite-scroller [pageNum]="pageNum"
[bufferPages]="5" [bufferPages]="5"
[goToPage]="goToPageEvent" [goToPage]="goToPageEvent"
@ -124,30 +137,32 @@
[readerSettings$]="readerSettings$"> [readerSettings$]="readerSettings$">
</app-infinite-scroller> </app-infinite-scroller>
</div> </div>
</ng-template> }
}
</div> </div>
<div class="fixed-bottom overlay" *ngIf="menuOpen" [@slideFromBottom]="menuOpen"> @if (menuOpen) {
<div class="fixed-bottom overlay" [@slideFromBottom]="menuOpen">
<div class="mb-3" *ngIf="pageOptions !== undefined && pageOptions.ceil !== undefined"> @if (pageOptions !== undefined && pageOptions.ceil !== undefined) {
<div class="mb-3">
<span class="visually-hidden" id="slider-info"></span> <span class="visually-hidden" id="slider-info"></span>
<div class="row g-0"> <div class="row g-0">
<button class="btn btn-icon col-1" [disabled]="prevChapterDisabled" (click)="loadPrevChapter();resetMenuCloseTimer();" [title]="t('prev-chapter-tooltip')"><i class="fa fa-fast-backward" aria-hidden="true"></i></button> <button class="btn btn-icon col-1" [disabled]="prevChapterDisabled" (click)="loadPrevChapter();resetMenuCloseTimer();" [title]="t('prev-chapter-tooltip')"><i class="fa fa-fast-backward" aria-hidden="true"></i></button>
<button class="btn btn-icon col-2" [disabled]="prevPageDisabled || pageNum === 0" (click)="goToPage(0);resetMenuCloseTimer();" [title]="t('first-page-tooltip')"><i class="fa fa-step-backward" aria-hidden="true"></i></button> <button class="btn btn-icon col-2" [disabled]="prevPageDisabled || pageNum === 0" (click)="goToPage(0);resetMenuCloseTimer();" [title]="t('first-page-tooltip')"><i class="fa fa-step-backward" aria-hidden="true"></i></button>
<div class="col custom-slider" *ngIf="pageOptions.ceil > 0; else noSlider"> @if (pageOptions.ceil > 0) {
<div class="col custom-slider">
<ngx-slider [options]="pageOptions" [value]="pageNum" aria-describedby="slider-info" [manualRefresh]="refreshSlider" (userChangeEnd)="sliderPageUpdate($event);startMenuCloseTimer()" (userChange)="sliderDragUpdate($event)" (userChangeStart)="cancelMenuCloseTimer();"></ngx-slider> <ngx-slider [options]="pageOptions" [value]="pageNum" aria-describedby="slider-info" [manualRefresh]="refreshSlider" (userChangeEnd)="sliderPageUpdate($event);startMenuCloseTimer()" (userChange)="sliderDragUpdate($event)" (userChangeStart)="cancelMenuCloseTimer();"></ngx-slider>
</div> </div>
<ng-template #noSlider> } @else {
<div class="col custom-slider"> <div class="col custom-slider">
<ngx-slider [options]="pageOptions" [value]="pageNum" aria-describedby="slider-info" (userChangeEnd)="startMenuCloseTimer()" (userChangeStart)="cancelMenuCloseTimer();"></ngx-slider> <ngx-slider [options]="pageOptions" [value]="pageNum" aria-describedby="slider-info" (userChangeEnd)="startMenuCloseTimer()" (userChangeStart)="cancelMenuCloseTimer();"></ngx-slider>
</div> </div>
</ng-template> }
<button class="btn btn-icon col-2" [disabled]="nextPageDisabled || pageNum >= maxPages - 1" (click)="goToPage(this.maxPages);resetMenuCloseTimer();" [title]="t('last-page-tooltip')"><i class="fa fa-step-forward" aria-hidden="true"></i></button> <button class="btn btn-icon col-2" [disabled]="nextPageDisabled || pageNum >= maxPages - 1" (click)="goToPage(this.maxPages);resetMenuCloseTimer();" [title]="t('last-page-tooltip')"><i class="fa fa-step-forward" aria-hidden="true"></i></button>
<button class="btn btn-icon col-1" [disabled]="nextChapterDisabled" (click)="loadNextChapter();resetMenuCloseTimer();" [title]="t('next-chapter-tooltip')"><i class="fa fa-fast-forward" aria-hidden="true"></i></button> <button class="btn btn-icon col-1" [disabled]="nextChapterDisabled" (click)="loadNextChapter();resetMenuCloseTimer();" [title]="t('next-chapter-tooltip')"><i class="fa fa-fast-forward" aria-hidden="true"></i></button>
</div> </div>
</div> </div>
}
<div class="row pt-4 ms-2 me-2 mb-2"> <div class="row pt-4 ms-2 me-2 mb-2">
<div class="col"> <div class="col">
<button class="btn btn-icon" (click)="setReadingDirection();resetMenuCloseTimer();" [disabled]="readerMode === ReaderMode.Webtoon || readerMode === ReaderMode.UpDown" aria-describedby="reading-direction" [title]="t('reading-direction-tooltip') + readingDirection === ReadingDirection.LeftToRight ? t('left-to-right-alt') : t('right-to-left-alt')"> <button class="btn btn-icon" (click)="setReadingDirection();resetMenuCloseTimer();" [disabled]="readerMode === ReaderMode.Webtoon || readerMode === ReaderMode.UpDown" aria-describedby="reading-direction" [title]="t('reading-direction-tooltip') + readingDirection === ReadingDirection.LeftToRight ? t('left-to-right-alt') : t('right-to-left-alt')">
@ -174,7 +189,8 @@
</button> </button>
</div> </div>
</div> </div>
<div class="bottom-menu" *ngIf="settingsOpen && generalSettingsForm"> @if (settingsOpen && generalSettingsForm) {
<div class="bottom-menu">
<form [formGroup]="generalSettingsForm"> <form [formGroup]="generalSettingsForm">
<div class="row mb-2"> <div class="row mb-2">
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12">
@ -295,7 +311,10 @@
</div> </div>
</form> </form>
</div> </div>
}
</div> </div>
}
</div> </div>
</ng-container> </ng-container>

View file

@ -13,7 +13,7 @@ import {
OnInit, OnInit,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import {AsyncPipe, DOCUMENT, NgClass, NgFor, NgIf, NgStyle, NgSwitch, NgSwitchCase, PercentPipe} from '@angular/common'; import {AsyncPipe, DOCUMENT, NgClass, NgFor, NgStyle, NgSwitch, NgSwitchCase, PercentPipe} from '@angular/common';
import {ActivatedRoute, Router} from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import { import {
BehaviorSubject, BehaviorSubject,
@ -70,6 +70,7 @@ import {SwipeDirective} from '../../../ng-swipe/ng-swipe.directive';
import {LoadingComponent} from '../../../shared/loading/loading.component'; import {LoadingComponent} from '../../../shared/loading/loading.component';
import {translate, TranslocoDirective} from "@jsverse/transloco"; import {translate, TranslocoDirective} from "@jsverse/transloco";
import {shareReplay} from "rxjs/operators"; import {shareReplay} from "rxjs/operators";
import {DblClickDirective} from "../../../_directives/dbl-click.directive";
const PREFETCH_PAGES = 10; const PREFETCH_PAGES = 10;
@ -123,10 +124,10 @@ enum KeyDirection {
]) ])
], ],
standalone: true, standalone: true,
imports: [NgStyle, NgIf, LoadingComponent, SwipeDirective, CanvasRendererComponent, SingleRendererComponent, imports: [NgStyle, LoadingComponent, SwipeDirective, CanvasRendererComponent, SingleRendererComponent,
DoubleRendererComponent, DoubleReverseRendererComponent, DoubleNoCoverRendererComponent, InfiniteScrollerComponent, DoubleRendererComponent, DoubleReverseRendererComponent, DoubleNoCoverRendererComponent, InfiniteScrollerComponent,
NgxSliderModule, ReactiveFormsModule, NgFor, NgSwitch, NgSwitchCase, FittingIconPipe, ReaderModeIconPipe, NgxSliderModule, ReactiveFormsModule, NgFor, NgSwitch, NgSwitchCase, FittingIconPipe, ReaderModeIconPipe,
FullscreenIconPipe, TranslocoDirective, NgbProgressbar, PercentPipe, NgClass, AsyncPipe] FullscreenIconPipe, TranslocoDirective, PercentPipe, NgClass, AsyncPipe, DblClickDirective]
}) })
export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
@ -1656,7 +1657,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
/** /**
* Bookmarks the current page for the chapter * Bookmarks the current page for the chapter
*/ */
bookmarkPage(event: MouseEvent | undefined = undefined) { bookmarkPage(event: Event | undefined = undefined) {
if (event) { if (event) {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();

View file

@ -1,5 +1,5 @@
import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core';
import {AgeRatingImageComponent} from "../../../_single-modules/age-rating-image/age-rating-image.component"; import {AgeRatingImageComponent} from "../../../_single-module/age-rating-image/age-rating-image.component";
import {CompactNumberPipe} from "../../../_pipes/compact-number.pipe"; import {CompactNumberPipe} from "../../../_pipes/compact-number.pipe";
import {ReadTimeLeftPipe} from "../../../_pipes/read-time-left.pipe"; import {ReadTimeLeftPipe} from "../../../_pipes/read-time-left.pipe";
import {ReadTimePipe} from "../../../_pipes/read-time.pipe"; import {ReadTimePipe} from "../../../_pipes/read-time.pipe";
@ -17,7 +17,7 @@ import {FilterComparison} from "../../../_models/metadata/v2/filter-comparison";
import {FilterField} from "../../../_models/metadata/v2/filter-field"; import {FilterField} from "../../../_models/metadata/v2/filter-field";
import {MangaFormat} from "../../../_models/manga-format"; import {MangaFormat} from "../../../_models/manga-format";
import {SeriesFormatComponent} from "../../../shared/series-format/series-format.component"; import {SeriesFormatComponent} from "../../../shared/series-format/series-format.component";
import {PublisherFlipperComponent} from "../../../_single-modules/publisher-flipper/publisher-flipper.component"; import {PublisherFlipperComponent} from "../../../_single-module/publisher-flipper/publisher-flipper.component";
@Component({ @Component({
selector: 'app-metadata-detail-row', selector: 'app-metadata-detail-row',

View file

@ -115,7 +115,7 @@ import {DownloadButtonComponent} from "../download-button/download-button.compon
import {hasAnyCast} from "../../../_models/common/i-has-cast"; import {hasAnyCast} from "../../../_models/common/i-has-cast";
import {EditVolumeModalComponent} from "../../../_single-module/edit-volume-modal/edit-volume-modal.component"; import {EditVolumeModalComponent} from "../../../_single-module/edit-volume-modal/edit-volume-modal.component";
import {CoverUpdateEvent} from "../../../_models/events/cover-update-event"; import {CoverUpdateEvent} from "../../../_models/events/cover-update-event";
import {RelatedSeriesPair, RelatedTabComponent} from "../../../_single-modules/related-tab/related-tab.component"; import {RelatedSeriesPair, RelatedTabComponent} from "../../../_single-module/related-tab/related-tab.component";
import {CollectionTagService} from "../../../_services/collection-tag.service"; import {CollectionTagService} from "../../../_services/collection-tag.service";
import {UserCollection} from "../../../_models/collection-tag"; import {UserCollection} from "../../../_models/collection-tag";
import {CoverImageComponent} from "../../../_single-module/cover-image/cover-image.component"; import {CoverImageComponent} from "../../../_single-module/cover-image/cover-image.component";

View file

@ -59,7 +59,7 @@ import {
} from "../_single-module/edit-volume-modal/edit-volume-modal.component"; } from "../_single-module/edit-volume-modal/edit-volume-modal.component";
import {Genre} from "../_models/metadata/genre"; import {Genre} from "../_models/metadata/genre";
import {Tag} from "../_models/tag"; import {Tag} from "../_models/tag";
import {RelatedTabComponent} from "../_single-modules/related-tab/related-tab.component"; import {RelatedTabComponent} from "../_single-module/related-tab/related-tab.component";
import {ReadingList} from "../_models/reading-list"; import {ReadingList} from "../_models/reading-list";
import {ReadingListService} from "../_services/reading-list.service"; import {ReadingListService} from "../_services/reading-list.service";
import {BadgeExpanderComponent} from "../shared/badge-expander/badge-expander.component"; import {BadgeExpanderComponent} from "../shared/badge-expander/badge-expander.component";