* Introduced a lock for DB work during the scan to hopefully reduce the concurrency issues * Don't allow multiple theme scans to occur * Fixed bulk actions not having all actions due to nested actionable menu changes * Refactored the Scan loop to be synchronous to avoid any issues. After first loop, no real performance issues. * Updated the LibraryWatcher when under many internal buffer full issues, to suspend watching for a full hour, to allow whatever downloading to complete. * Removed Semaphore as it's not needed anymore * Updated the output for logger to explicitly say from Kavita (if you're pushing to Seq) * Fixed a broken test * Fixed ReleaseYear not populating due to a change from a contributor around how to populate ReleaseYear. * Ensure when scan folder runs, that we don't double enqueue the same tasks. * Fixed user settings not loading the correct tab * Changed the Release Year -> Release * Added more refresh hooks in reader to hopefully ensure faster refreshes * Reset images between chapter loads to help flush image faster. Don't show broken image icon when an image is still loading. * Fixed the prefetcher not properly loading the correct images and hence, allowing a bit of lag between chapter loads. * Code smells
228 lines
15 KiB
HTML
228 lines
15 KiB
HTML
<div class="reader" #reader [ngStyle]="{overflow: (isFullscreen ? 'auto' : 'visible')}">
|
|
<div class="fixed-top overlay" *ngIf="menuOpen" [@slideFromTop]="menuOpen">
|
|
<div style="display: flex; margin-top: 5px;">
|
|
<button class="btn btn-icon" style="height: 100%" title="Back" (click)="closeReader()">
|
|
<i class="fa fa-arrow-left" aria-hidden="true"></i>
|
|
<span class="visually-hidden">Back</span>
|
|
</button>
|
|
|
|
<div>
|
|
<div style="font-weight: bold;">{{title}} <span class="clickable" *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" aria-label="Incognito mode is on. Toggle to turn off.">(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">Incognito Mode:</span>)</span></div>
|
|
<div class="subtitle">
|
|
{{subtitle}}
|
|
</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">Keyboard Shortcuts Modal</span>
|
|
</button>
|
|
<button *ngIf="!bookmarkMode && hasBookmarkRights" class="btn btn-icon" role="checkbox" [attr.aria-checked]="CurrentPageBookmarked"
|
|
title="{{CurrentPageBookmarked ? 'Unbookmark Page' : 'Bookmark Page'}}" (click)="bookmarkPage()">
|
|
<i class="{{CurrentPageBookmarked ? 'fa' : 'far'}} fa-bookmark" aria-hidden="true"></i>
|
|
<span class="visually-hidden">{{CurrentPageBookmarked ? 'Unbookmark Page' : 'Bookmark Page'}}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<ng-container *ngIf="isLoading">
|
|
<div class="spinner-border text-secondary loading" role="status">
|
|
<span class="invisible">Loading...</span>
|
|
</div>
|
|
</ng-container>
|
|
|
|
<div class="reading-area"
|
|
[ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : 'calc(var(--vh)*100)'}" #readingArea>
|
|
<ng-container *ngIf="readerMode !== ReaderMode.Webtoon; else webtoon">
|
|
<div class="image-container" [ngClass]="{'d-none': !renderWithCanvas }" [style.filter]="'brightness(' + generalSettingsForm.get('darkness')?.value + '%)'">
|
|
<canvas #content class="{{getFittingOptionClass()}}"
|
|
ondragstart="return false;" onselectstart="return false;">
|
|
</canvas>
|
|
</div>
|
|
|
|
<div class="pagination-area">
|
|
<!-- Pagination controls and screen hints-->
|
|
<div class="{{readerMode === ReaderMode.LeftRight ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, 'left')"
|
|
[ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? ImageHeight: '25%')}">
|
|
<div *ngIf="showClickOverlay">
|
|
<i class="fa fa-angle-{{readingDirection === ReadingDirection.RightToLeft ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'left' : 'up'}}"
|
|
title="Previous Page" aria-hidden="true"></i>
|
|
</div>
|
|
</div>
|
|
<div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')"
|
|
[ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? ImageHeight: '25%'),
|
|
'left': 'inherit',
|
|
'right': rightPaginationOffset + 'px'}">
|
|
<div *ngIf="showClickOverlay">
|
|
<i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}"
|
|
title="Next Page" aria-hidden="true"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="image-container {{getFittingOptionClass()}}" [ngClass]="{'d-none': renderWithCanvas, 'center-double': ShouldRenderDoublePage,
|
|
'fit-to-width-double-offset' : FittingOption === FITTING_OPTION.WIDTH && ShouldRenderDoublePage,
|
|
'fit-to-height-double-offset': FittingOption === FITTING_OPTION.HEIGHT && ShouldRenderDoublePage,
|
|
'original-double-offset' : FittingOption === FITTING_OPTION.ORIGINAL && ShouldRenderDoublePage}"
|
|
[style.filter]="'brightness(' + generalSettingsForm.get('darkness')?.value + '%)' | safeStyle" (dblclick)="bookmarkPage($event)">
|
|
<img alt=" " #image [src]="canvasImage.src" id="image-1"
|
|
class="{{getFittingOptionClass()}} {{readerMode === ReaderMode.LeftRight || readerMode === ReaderMode.UpDown ? '' : 'd-none'}} {{showClickOverlay ? 'blur' : ''}}">
|
|
|
|
<ng-container *ngIf="(this.canvasImage2.src !== '') && (readerService.imageUrlToPageNum(canvasImage2.src) <= maxPages - 1 && !isCoverImage())">
|
|
<img alt=" " [src]="canvasImage2.src" id="image-2" class="image-2 {{getFittingOptionClass()}} {{readerMode === ReaderMode.LeftRight || readerMode === ReaderMode.UpDown ? '' : 'd-none'}} {{showClickOverlay ? 'blur' : ''}}"> <!-- {{ShouldRenderReverseDouble ? 'reverse' : ''}} -->
|
|
</ng-container>
|
|
</div>
|
|
|
|
</ng-container>
|
|
|
|
<ng-template #webtoon>
|
|
<div class="webtoon-images" *ngIf="readerMode === ReaderMode.Webtoon && !isLoading && !inSetup">
|
|
<app-infinite-scroller [pageNum]="pageNum"
|
|
[bufferPages]="5"
|
|
[goToPage]="goToPageEvent"
|
|
(pageNumberChange)="handleWebtoonPageChange($event)"
|
|
[totalPages]="maxPages"
|
|
[urlProvider]="getPageUrl"
|
|
(loadNextChapter)="loadNextChapter()"
|
|
(loadPrevChapter)="loadPrevChapter()"
|
|
[bookmarkPage]="showBookmarkEffectEvent"
|
|
[fullscreenToggled]="fullscreenEvent"></app-infinite-scroller>
|
|
</div>
|
|
</ng-template>
|
|
|
|
|
|
</div>
|
|
|
|
<div class="fixed-bottom overlay" *ngIf="menuOpen" [@slideFromBottom]="menuOpen">
|
|
<div class="mb-3" *ngIf="pageOptions != undefined && pageOptions.ceil != undefined">
|
|
<span class="visually-hidden" id="slider-info"></span>
|
|
<div class="row g-0">
|
|
<button class="btn btn-icon col-1" [disabled]="prevChapterDisabled" (click)="loadPrevChapter();resetMenuCloseTimer();" title="Prev Chapter/Volume"><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="First Page"><i class="fa fa-step-backward" aria-hidden="true"></i></button>
|
|
<div class="col custom-slider" *ngIf="pageOptions.ceil > 0; else 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>
|
|
<ng-template #noSlider>
|
|
<div class="col custom-slider">
|
|
<ngx-slider [options]="pageOptions" [value]="pageNum" aria-describedby="slider-info" (userChangeEnd)="startMenuCloseTimer()" (userChangeStart)="cancelMenuCloseTimer();"></ngx-slider>
|
|
</div>
|
|
</ng-template>
|
|
<button class="btn btn-icon col-2" [disabled]="nextPageDisabled || pageNum >= maxPages - 1" (click)="goToPage(this.maxPages);resetMenuCloseTimer();" title="Last Page"><i class="fa fa-step-forward" aria-hidden="true"></i></button>
|
|
<button class="btn btn-icon col-1" [disabled]="nextChapterDisabled" (click)="loadNextChapter();resetMenuCloseTimer();" title="Next Chapter/Volume"><i class="fa fa-fast-forward" aria-hidden="true"></i></button>
|
|
</div>
|
|
</div>
|
|
<div class="row pt-4 ms-2 me-2">
|
|
<div class="col">
|
|
<button class="btn btn-icon" (click)="setReadingDirection();resetMenuCloseTimer();" [disabled]="readerMode === ReaderMode.Webtoon || readerMode === ReaderMode.UpDown" aria-describedby="reading-direction" title="Reading Direction: {{readingDirection === ReadingDirection.LeftToRight ? 'Left to Right' : 'Right to Left'}}">
|
|
<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 ? 'Left to Right' : 'Right to Left'}}</span>
|
|
</button>
|
|
</div>
|
|
<div class="col">
|
|
<button class="btn btn-icon" title="Reading Mode" (click)="toggleReaderMode();resetMenuCloseTimer();">
|
|
<i class="fa {{ReaderModeIcon}}" aria-hidden="true"></i>
|
|
<span class="visually-hidden">Reading Mode</span>
|
|
</button>
|
|
</div>
|
|
<div class="col">
|
|
<button class="btn btn-icon" title="{{this.isFullscreen ? 'Collapse' : 'Fullscreen'}}" (click)="toggleFullscreen();resetMenuCloseTimer();">
|
|
<i class="fa {{this.isFullscreen | fullscreenIcon}}" aria-hidden="true"></i>
|
|
<span class="visually-hidden">{{this.isFullscreen ? 'Collapse' : 'Fullscreen'}}</span>
|
|
</button>
|
|
</div>
|
|
<div class="col">
|
|
<button class="btn btn-icon" title="Settings" (click)="settingsOpen = !settingsOpen;resetMenuCloseTimer();">
|
|
<i class="fa fa-sliders-h" aria-hidden="true"></i>
|
|
<span class="visually-hidden">Settings</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">Image Splitting</label>
|
|
<div class="split fa fa-image">
|
|
<div class="{{SplitIconClass}}"></div>
|
|
</div>
|
|
<select class="form-control" id="page-splitting" formControlName="pageSplitOption">
|
|
<option *ngFor="let opt of pageSplitOptions" [value]="opt.value">{{opt.text}}</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-md-6 col-sm-12">
|
|
<label for="page-fitting" class="form-label">Image Scaling</label> <i class="fa {{getFittingIcon()}}" aria-hidden="true"></i>
|
|
<select class="form-control" id="page-fitting" formControlName="fittingOption">
|
|
<option value="full-height">Height</option>
|
|
<option value="full-width">Width</option>
|
|
<option value="original">Original</option>
|
|
</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>
|
|
<ng-container [ngSwitch]="layoutMode">
|
|
<ng-container *ngSwitchCase="LayoutMode.Single">
|
|
<div class="split-double">
|
|
<span class="fa-stack fa-1x">
|
|
<i class="fa-regular fa-square-full fa-stack-2x"></i>
|
|
<i class="fa fa-image fa-stack-1x"></i>
|
|
</span>
|
|
</div>
|
|
</ng-container>
|
|
<ng-container *ngSwitchCase="LayoutMode.Double">
|
|
<div class="split-double">
|
|
<span class="fa-stack fa-1x">
|
|
<i class="fa-regular fa-square-full fa-stack-2x"></i>
|
|
<i class="fab fa-1 fa-stack-1x"></i>
|
|
</span>
|
|
<span class="fa-stack fa right">
|
|
<i class="fa-regular fa-square-full fa-stack-2x"></i>
|
|
<i class="fab fa-2 fa-stack-1x"></i>
|
|
</span>
|
|
</div>
|
|
</ng-container>
|
|
<ng-container *ngSwitchCase="LayoutMode.DoubleReversed">
|
|
<div class="split-double">
|
|
<span class="fa-stack fa-1x">
|
|
<i class="fa-regular fa-square-full fa-stack-2x"></i>
|
|
<i class="fab fa-2 fa-stack-1x"></i>
|
|
</span>
|
|
<span class="fa-stack fa right">
|
|
<i class="fa-regular fa-square-full fa-stack-2x"></i>
|
|
<i class="fab fa-1 fa-stack-1x"></i>
|
|
</span>
|
|
</div>
|
|
</ng-container>
|
|
<!-- <div class="{{LayoutModeIconClass}}"></div> -->
|
|
</ng-container>
|
|
<select class="form-control" id="page-fitting" formControlName="layoutMode">
|
|
<option [value]="opt.value" *ngFor="let opt of layoutModes">{{opt.text}}</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6 col-sm-12">
|
|
<div class="mb-3">
|
|
<label id="auto-close-label" class="form-label"></label>
|
|
<div class="mb-3">
|
|
<div class="form-check form-switch">
|
|
<input type="checkbox" id="auto-close" formControlName="autoCloseMenu" class="form-check-input" [value]="true" aria-labelledby="auto-close-label">
|
|
<label class="form-check-label" for="auto-close">Auto Close Menu</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row mb-2">
|
|
<div class="col-md-6 col-sm-12">
|
|
<label for="darkness" class="form-label range-label">Brightness</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>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|