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,47 +3,58 @@
@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 style="display: flex; margin-top: 5px;"> <div class="fixed-top overlay" [@slideFromTop]="menuOpen">
<button class="btn btn-icon" style="height: 100%" [title]="t('back')" (click)="closeReader()"> <div style="display: flex; margin-top: 5px;">
<i class="fa fa-arrow-left" aria-hidden="true"></i> <button class="btn btn-icon" style="height: 100%" [title]="t('back')" (click)="closeReader()">
<span class="visually-hidden">{{t('back')}}</span> <i class="fa fa-arrow-left" aria-hidden="true"></i>
</button> <span class="visually-hidden">{{t('back')}}</span>
</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}}
<div class="subtitle"> @if (incognitoMode) {
{{subtitle}} <span *ngIf="totalSeriesPages > 0">{{t('series-progress', {percentage: (Math.min(1, ((totalSeriesPagesRead + pageNum) / totalSeriesPages)) | percent)}) }}</span> <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">
{{subtitle}}
@if (totalSeriesPages > 0) {
<span>{{t('series-progress', {percentage: (Math.min(1, ((totalSeriesPagesRead + pageNum) / totalSeriesPages)) | percent)}) }}</span>
}
</div>
</div>
<div style="margin-left: auto; padding-right: 3%;">
<button class="btn btn-icon" title="Shortcuts" (click)="openShortcutModal()">
<i class="fa-regular fa-rectangle-list" aria-hidden="true"></i>
<span class="visually-hidden">{{t('shortcuts-menu-alt')}}</span>
</button>
@if (!bookmarkMode && hasBookmarkRights) {
<button class="btn btn-icon" role="checkbox" [attr.aria-checked]="CurrentPageBookmarked"
title="{{t(CurrentPageBookmarked ? 'unbookmark-page-tooltip' : 'bookmark-page-tooltip')}}" (click)="bookmarkPage()">
<i class="{{CurrentPageBookmarked ? 'fa' : 'far'}} fa-bookmark" aria-hidden="true"></i>
<span class="visually-hidden">{{t(CurrentPageBookmarked ? 'unbookmark-page-tooltip' : 'bookmark-page-tooltip')}}</span>
</button>
}
</div> </div>
</div> </div>
<div style="margin-left: auto; padding-right: 3%;">
<button class="btn btn-icon" title="Shortcuts" (click)="openShortcutModal()">
<i class="fa-regular fa-rectangle-list" aria-hidden="true"></i>
<span class="visually-hidden">{{t('shortcuts-menu-alt')}}</span>
</button>
@if (!bookmarkMode && hasBookmarkRights) {
<button class="btn btn-icon" role="checkbox" [attr.aria-checked]="CurrentPageBookmarked"
title="{{t(CurrentPageBookmarked ? 'unbookmark-page-tooltip' : 'bookmark-page-tooltip')}}" (click)="bookmarkPage()">
<i class="{{CurrentPageBookmarked ? 'fa' : 'far'}} fa-bookmark" aria-hidden="true"></i>
<span class="visually-hidden">{{t(CurrentPageBookmarked ? 'unbookmark-page-tooltip' : 'bookmark-page-tooltip')}}</span>
</button>
}
</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) {
<i class="fa fa-angle-{{readingDirection === ReadingDirection.RightToLeft ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'left' : 'up'}}" <div>
[title]="t('prev-page-tooltip')" aria-hidden="true"></i> <i class="fa fa-angle-{{readingDirection === ReadingDirection.RightToLeft ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'left' : 'up'}}"
</div> [title]="t('prev-page-tooltip')" aria-hidden="true"></i>
</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) {
<i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}" <div>
[title]="t('next-page-tooltip')" aria-hidden="true"></i> <i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}"
</div> [title]="t('next-page-tooltip')" aria-hidden="true"></i>
</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,196 +121,200 @@
[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> <app-infinite-scroller [pageNum]="pageNum"
<div class="webtoon-images" *ngIf="!isLoading && !inSetup"> [bufferPages]="5"
<app-infinite-scroller [pageNum]="pageNum" [goToPage]="goToPageEvent"
[bufferPages]="5" (pageNumberChange)="handleWebtoonPageChange($event)"
[goToPage]="goToPageEvent" [totalPages]="maxPages"
(pageNumberChange)="handleWebtoonPageChange($event)" [urlProvider]="getPageUrl"
[totalPages]="maxPages" (loadNextChapter)="loadNextChapter()"
[urlProvider]="getPageUrl" (loadPrevChapter)="loadPrevChapter()"
(loadNextChapter)="loadNextChapter()" [bookmarkPage]="showBookmarkEffectEvent"
(loadPrevChapter)="loadPrevChapter()" [fullscreenToggled]="fullscreenEvent"
[bookmarkPage]="showBookmarkEffectEvent" [readerSettings$]="readerSettings$">
[fullscreenToggled]="fullscreenEvent" </app-infinite-scroller>
[readerSettings$]="readerSettings$"> </div>
</app-infinite-scroller> }
</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) {
<span class="visually-hidden" id="slider-info"></span> <div class="mb-3">
<div class="row g-0"> <span class="visually-hidden" id="slider-info"></span>
<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> <div class="row g-0">
<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-1" [disabled]="prevChapterDisabled" (click)="loadPrevChapter();resetMenuCloseTimer();" [title]="t('prev-chapter-tooltip')"><i class="fa fa-fast-backward" aria-hidden="true"></i></button>
<div class="col custom-slider" *ngIf="pageOptions.ceil > 0; else noSlider"> <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>
<ngx-slider [options]="pageOptions" [value]="pageNum" aria-describedby="slider-info" [manualRefresh]="refreshSlider" (userChangeEnd)="sliderPageUpdate($event);startMenuCloseTimer()" (userChange)="sliderDragUpdate($event)" (userChangeStart)="cancelMenuCloseTimer();"></ngx-slider> @if (pageOptions.ceil > 0) {
</div> <div class="col custom-slider">
<ng-template #noSlider> <ngx-slider [options]="pageOptions" [value]="pageNum" aria-describedby="slider-info" [manualRefresh]="refreshSlider" (userChangeEnd)="sliderPageUpdate($event);startMenuCloseTimer()" (userChange)="sliderDragUpdate($event)" (userChangeStart)="cancelMenuCloseTimer();"></ngx-slider>
<div class="col custom-slider"> </div>
<ngx-slider [options]="pageOptions" [value]="pageNum" aria-describedby="slider-info" (userChangeEnd)="startMenuCloseTimer()" (userChangeStart)="cancelMenuCloseTimer();"></ngx-slider> } @else {
<div class="col custom-slider">
<ngx-slider [options]="pageOptions" [value]="pageNum" aria-describedby="slider-info" (userChangeEnd)="startMenuCloseTimer()" (userChangeStart)="cancelMenuCloseTimer();"></ngx-slider>
</div>
}
<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>
</div> </div>
</ng-template> </div>
<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> <div class="row pt-4 ms-2 me-2 mb-2">
<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')">
<i class="fa fa-angle-double-{{readingDirection === ReadingDirection.LeftToRight ? 'right' : 'left'}}" aria-hidden="true"></i>
<span id="reading-direction" class="visually-hidden">{{readingDirection === ReadingDirection.LeftToRight ? t('left-to-right-alt') : t('right-to-left-alt')}}</span>
</button>
</div>
<div class="col">
<button class="btn btn-icon" [title]="t('reading-mode-tooltip')" (click)="toggleReaderMode();resetMenuCloseTimer();">
<i class="fa {{this.readerMode | readerModeIcon}}" aria-hidden="true"></i>
<span class="visually-hidden">{{t('reading-mode-tooltip')}}</span>
</button>
</div>
<div class="col">
<button class="btn btn-icon" title="{{this.isFullscreen ? t('collapse') : t('fullscreen')}}" (click)="toggleFullscreen();resetMenuCloseTimer();">
<i class="fa {{this.isFullscreen | fullscreenIcon}}" aria-hidden="true"></i>
<span class="visually-hidden">{{this.isFullscreen ? t('collapse') : t('fullscreen')}}</span>
</button>
</div>
<div class="col">
<button class="btn btn-icon" [title]="t('settings-tooltip')" (click)="settingsOpen = !settingsOpen;resetMenuCloseTimer();">
<i class="fa fa-sliders-h" aria-hidden="true"></i>
<span class="visually-hidden">{{t('settings-tooltip')}}</span>
</button>
</div>
</div> </div>
</div> @if (settingsOpen && generalSettingsForm) {
<div class="row pt-4 ms-2 me-2 mb-2"> <div class="bottom-menu">
<div class="col"> <form [formGroup]="generalSettingsForm">
<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')"> <div class="row mb-2">
<i class="fa fa-angle-double-{{readingDirection === ReadingDirection.LeftToRight ? 'right' : 'left'}}" aria-hidden="true"></i> <div class="col-md-6 col-sm-12">
<span id="reading-direction" class="visually-hidden">{{readingDirection === ReadingDirection.LeftToRight ? t('left-to-right-alt') : t('right-to-left-alt')}}</span> <label for="page-splitting" class="form-label">{{t('image-splitting-label')}}</label>&nbsp;
</button> <div class="split fa fa-image">
</div> <div class="{{SplitIconClass}}"></div>
<div class="col"> </div>
<button class="btn btn-icon" [title]="t('reading-mode-tooltip')" (click)="toggleReaderMode();resetMenuCloseTimer();"> <select class="form-control" id="page-splitting" formControlName="pageSplitOption">
<i class="fa {{this.readerMode | readerModeIcon}}" aria-hidden="true"></i> <option *ngFor="let opt of pageSplitOptionsTranslated" [value]="opt.value">{{opt.text}}</option>
<span class="visually-hidden">{{t('reading-mode-tooltip')}}</span> </select>
</button> </div>
</div>
<div class="col"> <div class="col-md-6 col-sm-12">
<button class="btn btn-icon" title="{{this.isFullscreen ? t('collapse') : t('fullscreen')}}" (click)="toggleFullscreen();resetMenuCloseTimer();"> <label for="page-fitting" class="form-label">{{t('image-scaling-label')}}</label>&nbsp;<i class="{{FittingOption | fittingIcon}}" aria-hidden="true"></i>
<i class="fa {{this.isFullscreen | fullscreenIcon}}" aria-hidden="true"></i> <select class="form-control" id="page-fitting" formControlName="fittingOption">
<span class="visually-hidden">{{this.isFullscreen ? t('collapse') : t('fullscreen')}}</span> <option value="full-height">{{t('height')}}</option>
</button> <option value="full-width">{{t('width')}}</option>
</div> <option value="original">{{t('original')}}</option>
<div class="col"> </select>
<button class="btn btn-icon" [title]="t('settings-tooltip')" (click)="settingsOpen = !settingsOpen;resetMenuCloseTimer();"> </div>
<i class="fa fa-sliders-h" aria-hidden="true"></i>
<span class="visually-hidden">{{t('settings-tooltip')}}</span>
</button>
</div>
</div>
<div class="bottom-menu" *ngIf="settingsOpen && generalSettingsForm">
<form [formGroup]="generalSettingsForm">
<div class="row mb-2">
<div class="col-md-6 col-sm-12">
<label for="page-splitting" class="form-label">{{t('image-splitting-label')}}</label>&nbsp;
<div class="split fa fa-image">
<div class="{{SplitIconClass}}"></div>
</div> </div>
<select class="form-control" id="page-splitting" formControlName="pageSplitOption">
<option *ngFor="let opt of pageSplitOptionsTranslated" [value]="opt.value">{{opt.text}}</option>
</select>
</div>
<div class="col-md-6 col-sm-12"> <div class="row mb-2">
<label for="page-fitting" class="form-label">{{t('image-scaling-label')}}</label>&nbsp;<i class="{{FittingOption | fittingIcon}}" aria-hidden="true"></i> <div class="col-md-6 col-sm-12">
<select class="form-control" id="page-fitting" formControlName="fittingOption"> <label for="layout-mode" class="form-label">Layout Mode</label>&nbsp;
<option value="full-height">{{t('height')}}</option> <ng-container [ngSwitch]="layoutMode">
<option value="full-width">{{t('width')}}</option> <ng-container *ngSwitchCase="LayoutMode.Single">
<option value="original">{{t('original')}}</option> <div class="split-double">
</select>
</div>
</div>
<div class="row mb-2">
<div class="col-md-6 col-sm-12">
<label for="layout-mode" class="form-label">Layout Mode</label>&nbsp;
<ng-container [ngSwitch]="layoutMode">
<ng-container *ngSwitchCase="LayoutMode.Single">
<div class="split-double">
<span class="fa-stack fa-1x"> <span class="fa-stack fa-1x">
<i class="fa-regular fa-square-full fa-stack-2x"></i> <i class="fa-regular fa-square-full fa-stack-2x"></i>
<i class="fa fa-image fa-stack-1x"></i> <i class="fa fa-image fa-stack-1x"></i>
</span> </span>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="LayoutMode.Double"> <ng-container *ngSwitchCase="LayoutMode.Double">
<div class="split-double"> <div class="split-double">
<span class="fa-stack fa-1x"> <span class="fa-stack fa-1x">
<i class="fa-regular fa-square-full fa-stack-2x"></i> <i class="fa-regular fa-square-full fa-stack-2x"></i>
<i class="fab fa-1 fa-stack-1x"></i> <i class="fab fa-1 fa-stack-1x"></i>
</span> </span>
<span class="fa-stack fa right"> <span class="fa-stack fa right">
<i class="fa-regular fa-square-full fa-stack-2x"></i> <i class="fa-regular fa-square-full fa-stack-2x"></i>
<i class="fab fa-2 fa-stack-1x"></i> <i class="fab fa-2 fa-stack-1x"></i>
</span> </span>
</div> </div>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="LayoutMode.DoubleReversed"> <ng-container *ngSwitchCase="LayoutMode.DoubleReversed">
<div class="split-double"> <div class="split-double">
<span class="fa-stack fa-1x"> <span class="fa-stack fa-1x">
<i class="fa-regular fa-square-full fa-stack-2x"></i> <i class="fa-regular fa-square-full fa-stack-2x"></i>
<i class="fab fa-2 fa-stack-1x"></i> <i class="fab fa-2 fa-stack-1x"></i>
</span> </span>
<span class="fa-stack fa right"> <span class="fa-stack fa right">
<i class="fa-regular fa-square-full fa-stack-2x"></i> <i class="fa-regular fa-square-full fa-stack-2x"></i>
<i class="fab fa-1 fa-stack-1x"></i> <i class="fab fa-1 fa-stack-1x"></i>
</span> </span>
</div> </div>
</ng-container> </ng-container>
</ng-container> </ng-container>
<select class="form-control" id="layout-mode" formControlName="layoutMode"> <select class="form-control" id="layout-mode" formControlName="layoutMode">
<option [value]="opt.value" *ngFor="let opt of layoutModesTranslated">{{opt.text}}</option> <option [value]="opt.value" *ngFor="let opt of layoutModesTranslated">{{opt.text}}</option>
</select> </select>
</div>
<div class="col-md-3 col-sm-12">
<div class="mb-3">
<div class="mb-3">
<div class="form-check form-switch">
<input type="checkbox" id="auto-close" formControlName="autoCloseMenu" class="form-check-input" >
<label class="form-check-label" for="auto-close">{{t('auto-close-menu-label')}}</label>
</div>
</div> </div>
</div> <div class="col-md-3 col-sm-12">
<div class="mb-3">
<div class="mb-3">
<div class="form-check form-switch">
<input type="checkbox" id="auto-close" formControlName="autoCloseMenu" class="form-check-input" >
<label class="form-check-label" for="auto-close">{{t('auto-close-menu-label')}}</label>
</div>
</div>
</div>
<div class="mb-3"> <div class="mb-3">
<div class="mb-3"> <div class="mb-3">
<div class="form-check form-switch"> <div class="form-check form-switch">
<input type="checkbox" id="swipe-to-paginate" formControlName="swipeToPaginate" class="form-check-input" > <input type="checkbox" id="swipe-to-paginate" formControlName="swipeToPaginate" class="form-check-input" >
<label class="form-check-label" for="swipe-to-paginate">{{t('swipe-enabled-label')}}</label> <label class="form-check-label" for="swipe-to-paginate">{{t('swipe-enabled-label')}}</label>
</div>
</div>
</div>
</div>
<div class="col-md-3 col-sm-12">
<div class="mb-3">
<div class="mb-3">
<div class="form-check form-switch">
<input type="checkbox" id="emulate-book" formControlName="emulateBook" class="form-check-input">
<label class="form-check-label" for="emulate-book">{{t('emulate-comic-book-label')}}</label>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="row mb-2">
<div class="col-md-3 col-sm-12"> <div class="col-md-6 col-sm-12">
<div class="mb-3"> <label for="darkness" class="form-label range-label">{{t('brightness-label')}}</label>
<div class="mb-3"> <span class="ms-1 range-text">{{generalSettingsForm.get('darkness')?.value + '%'}}</span>
<div class="form-check form-switch"> <input type="range" class="form-range" id="darkness"
<input type="checkbox" id="emulate-book" formControlName="emulateBook" class="form-check-input"> min="10" max="100" step="1" formControlName="darkness">
<label class="form-check-label" for="emulate-book">{{t('emulate-comic-book-label')}}</label> </div>
</div>
<div class="col-md-6 col-sm-12">
<label for="width-override-slider" class="form-label">{{t('width-override-label')}}:
@if (widthOverrideLabel$ | async; as widthOverrideLabel) {
{{ widthOverrideLabel ? widthOverrideLabel : t('off') }}
}
@else {
{{t('off')}}
}
</label>
<input id="width-override-slider" type="range" min="0" max="100" class="form-range" formControlName="widthSlider">
</div>
<div class="col-md-6 col-sm-12">
<button class="btn btn-primary" (click)="savePref()">{{t('save-globally')}}</button>
</div> </div>
</div> </div>
</div> </form>
</div> </div>
<div class="row mb-2"> }
<div class="col-md-6 col-sm-12">
<label for="darkness" class="form-label range-label">{{t('brightness-label')}}</label>
<span class="ms-1 range-text">{{generalSettingsForm.get('darkness')?.value + '%'}}</span>
<input type="range" class="form-range" id="darkness"
min="10" max="100" step="1" formControlName="darkness">
</div>
<div class="col-md-6 col-sm-12">
<label for="width-override-slider" class="form-label">{{t('width-override-label')}}:
@if (widthOverrideLabel$ | async; as widthOverrideLabel) {
{{ widthOverrideLabel ? widthOverrideLabel : t('off') }}
}
@else {
{{t('off')}}
}
</label>
<input id="width-override-slider" type="range" min="0" max="100" class="form-range" formControlName="widthSlider">
</div>
<div class="col-md-6 col-sm-12">
<button class="btn btn-primary" (click)="savePref()">{{t('save-globally')}}</button>
</div>
</div>
</form>
</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";