Manga Reader Work (#1729)

* Instead of augmenting prefetcher to move across chapter bounds, let's try to instead just load 5 images (which the browser will cache) from next/prev so when it loads, it's much faster.

* Trialing loading next/prev chapters 5 pages to have better next page loading experience.

* Tweaked GetChapterInfo API to actually apply conditional includeDimensions parameter.

* added a basic language file for upcoming work

* Moved the bottom menu up a bit for iOS devices with handlebars.

* Fixed fit to width on phones still having a horizontal scrollbar

* Fixed a bug where there is extra space under the image when fit to width and on a phone due to pagination going to far.

* Changed which variable we use for right pagination calculation

* Fixing fit to height

- Fixing height calc to account for horizontal scroll bar height.

* Added a comment for the height scrollbar fix

* Adding screenfull package

# Added:
- Added screenfull package to handle cross-platform browser fullscreen code

# Removed:
- Removed custom fullscreen code

* Fixed a bug where switching from webtoon reader to other layout modes wouldn't render anything. Webtoon continuous scroll down is now broken.

* Fixed it back to how it was and all is good. Need to call detectChanges explicitly.

* Removed an additional undeeded save progress call on loadPage

* Laid out the test case to move the page snapping to the backend with full unit tests. Current code is broken just like UI layer.

* Refactored the snap points into the backend and ensure that it works correctly.

* Fixed a broken unit test

* Filter out spammy hubs/messages calls in the logs

* Swallow all noisy messages that are from RequestLoggingMiddleware when the log level is on Information or above.

* Added a common loading component to the app. Have yet to refactor all screens to use this.

* Bump json5 from 2.2.0 to 2.2.3 in /UI/Web

Bumps [json5](https://github.com/json5/json5) from 2.2.0 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.0...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Alrigned all the loading messages and styles throughout the app

* Webtoon reader will use max width of all images to ensure images align well.

* On Original scaling mode, users can use the keyboard to scroll around the images without pagination kicking off.

* Removed console logs

* Fixed a public vs private issue

* Fixed an issue around some cached files getting locked due to NetVips holding them during file size calculations.

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Joe Milazzo 2023-01-07 09:14:22 -06:00 committed by GitHub
parent 22442d745c
commit 2464a30bc2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 367 additions and 390 deletions

View file

@ -7,13 +7,12 @@ img {
align-items: center;
&.full-width {
width: 100vw;
height: calc(var(--vh)*100);
display: grid;
}
&.full-height {
height: 100vh;
height: calc(100vh - 34px); // 34px is the height of the horizontal scrollbar that will appear
display: flex; // changed from inline-block to fix the centering on tablets not working
}

View file

@ -14,6 +14,7 @@ import { SeriesFilter } from '../_models/metadata/series-filter';
import { UtilityService } from '../shared/_services/utility.service';
import { FilterUtilitiesService } from '../shared/_services/filter-utilities.service';
import { FileDimension } from '../manga-reader/_models/file-dimension';
import screenfull from 'screenfull';
export const CHAPTER_ID_DOESNT_EXIST = -1;
export const CHAPTER_ID_NOT_FETCHED = -2;
@ -235,25 +236,10 @@ export class ReaderService {
return params;
}
enterFullscreen(el: Element, callback?: VoidFunction) {
if (!document.fullscreenElement) {
if (el.requestFullscreen) {
el.requestFullscreen().then(() => {
if (callback) {
callback();
}
});
}
}
}
toggleFullscreen(el: Element, callback?: VoidFunction) {
exitFullscreen(callback?: VoidFunction) {
if (document.exitFullscreen && this.checkFullscreenMode()) {
document.exitFullscreen().then(() => {
if (callback) {
callback();
}
});
if (screenfull.isEnabled) {
screenfull.toggle();
}
}

View file

@ -6,7 +6,7 @@
</button>
</div>
<div class="modal-body">
<div class="list-group" *ngIf="!isLoading">
<div class="list-group">
<div class="form-check">
<input id="selectall" type="checkbox" class="form-check-input"
[ngModel]="selectAll" (change)="toggleAll()" [indeterminate]="hasSomeSelected">

View file

@ -5,6 +5,7 @@ import { Member } from 'src/app/_models/auth/member';
import { LibraryService } from 'src/app/_services/library.service';
import { SelectionModel } from 'src/app/typeahead/_components/typeahead.component';
// TODO: Change to OnPush
@Component({
selector: 'app-library-access-modal',
templateUrl: './library-access-modal.component.html',
@ -17,7 +18,6 @@ export class LibraryAccessModalComponent implements OnInit {
selectedLibraries: Array<{selected: boolean, data: Library}> = [];
selections!: SelectionModel<Library>;
selectAll: boolean = false;
isLoading: boolean = false;
get hasSomeSelected() {
return this.selections != null && this.selections.hasSomeSelected();
@ -49,7 +49,6 @@ export class LibraryAccessModalComponent implements OnInit {
setupSelections() {
this.selections = new SelectionModel<Library>(false, this.allLibraries);
this.isLoading = false;
// If a member is passed in, then auto-select their libraries
if (this.member !== undefined) {

View file

@ -26,7 +26,7 @@
</div>
</li>
<li *ngIf="loading" class="list-group-item">
<div class="spinner-border text-secondary" role="status">
<div class="spinner-border text-primary" role="status">
<span class="invisible">Loading...</span>
</div>
</li>

View file

@ -21,6 +21,4 @@
</div>
<div class="spinner-border text-secondary" *ngIf="isLoading" role="status">
<span class="invisible">Loading...</span>
</div>
<app-loading [loading]="isLoading"></app-loading>

View file

@ -473,7 +473,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.navService.showNavBar();
this.navService.showSideNav();
this.readerService.exitFullscreen();
this.onDestroy.next();
this.onDestroy.complete();
@ -1203,13 +1202,13 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
toggleFullscreen() {
this.isFullscreen = this.readerService.checkFullscreenMode();
if (this.isFullscreen) {
this.readerService.exitFullscreen(() => {
this.readerService.toggleFullscreen(this.reader.nativeElement, () => {
this.isFullscreen = false;
this.cdRef.markForCheck();
this.renderer.removeStyle(this.reader.nativeElement, 'background');
});
} else {
this.readerService.enterFullscreen(this.reader.nativeElement, () => {
this.readerService.toggleFullscreen(this.reader.nativeElement, () => {
this.isFullscreen = true;
this.cdRef.markForCheck();
// HACK: This is a bug with how browsers change the background color for fullscreen mode

View file

@ -20,7 +20,7 @@
</li>
<li class="list-group-item" *ngIf="lists.length === 0 && !loading">No collections created yet</li>
<li class="list-group-item" *ngIf="loading">
<div class="spinner-border text-secondary" role="status">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</li>

View file

@ -47,11 +47,7 @@
</div>
</ng-template>
<div class="mx-auto" *ngIf="isLoading" style="width: 200px;">
<div class="spinner-border text-secondary loading" role="status">
<span class="invisible">Loading...</span>
</div>
</div>
<app-loading [loading]="true"></app-loading>
<ng-template #jumpBar>
<div class="jump-bar">

View file

@ -6,6 +6,7 @@ import { ScrollService } from 'src/app/_services/scroll.service';
import { ReaderService } from '../../../_services/reader.service';
import { PAGING_DIRECTION } from '../../_models/reader-enums';
import { WebtoonImage } from '../../_models/webtoon-image';
import { ManagaReaderService } from '../../_series/managa-reader.service';
/**
* How much additional space should pass, past the original bottom of the document height before we trigger the next chapter load
@ -129,7 +130,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
/**
* Debug mode. Will show extra information. Use bitwise (|) operators between different modes to enable different output
*/
debugMode: DEBUG_MODES = DEBUG_MODES.None;
debugMode: DEBUG_MODES = DEBUG_MODES.Logs;
/**
* Debug mode. Will filter out any messages in here so they don't hit the log
*/
@ -153,7 +154,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
constructor(private readerService: ReaderService, private renderer: Renderer2,
@Inject(DOCUMENT) private document: Document, private scrollService: ScrollService,
private readonly cdRef: ChangeDetectorRef) {
private readonly cdRef: ChangeDetectorRef, private mangaReaderService: ManagaReaderService) {
// This will always exist at this point in time since this is used within manga reader
const reader = document.querySelector('.reading-area');
if (reader !== null) {
@ -453,7 +454,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
this.webtoonImageWidth = event.target.width;
}
this.renderer.setAttribute(event.target, 'width', this.webtoonImageWidth + '');
this.renderer.setAttribute(event.target, 'width', this.mangaReaderService.maxWidth() + '');
this.renderer.setAttribute(event.target, 'height', event.target.height + '');
this.attachIntersectionObserverElem(event.target);

View file

@ -26,11 +26,7 @@
</div>
</div>
</div>
<!-- <ng-container *ngIf="isLoading">
<div class="spinner-border text-secondary loading" role="status">
<span class="invisible">Loading...</span>
</div>
</ng-container> -->
<app-loading [loading]="isLoading"></app-loading>
<div class="reading-area"
appSwipe (swipeEvent)="onSwipeEvent($event)"
[ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : 'calc(var(--vh)*100)'}" #readingArea>
@ -57,7 +53,7 @@
<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'}">
'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>
@ -93,7 +89,7 @@
</ng-container>
<ng-template #webtoon>
<div class="webtoon-images" *ngIf="readerMode === ReaderMode.Webtoon && !isLoading && !inSetup">
<div class="webtoon-images" *ngIf="!isLoading && !inSetup">
<app-infinite-scroller [pageNum]="pageNum"
[bufferPages]="5"
[goToPage]="goToPageEvent"
@ -130,7 +126,7 @@
<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="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="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>

View file

@ -176,6 +176,7 @@ $pointer-offset: 5px;
top: 0px;
width: $side-width;
background: $pagination-bg;
max-height: calc(var(--vh)*100);
z-index: 100;
}
@ -194,6 +195,7 @@ $pointer-offset: 5px;
top: 0px;
width: $side-width;
background: $pagination-bg;
max-height: calc(var(--vh)*100);
z-index: 100;
}

View file

@ -1,7 +1,7 @@
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, debounceTime, distinctUntilChanged, forkJoin, fromEvent, map, merge, Observable, ReplaySubject, Subject, take, takeUntil, tap } from 'rxjs';
import { BehaviorSubject, combineLatest, debounceTime, distinctUntilChanged, filter, forkJoin, fromEvent, map, merge, Observable, of, ReplaySubject, Subject, take, takeUntil, tap } from 'rxjs';
import { LabelType, ChangeContext, Options } from '@angular-slider/ngx-slider';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { FormGroup, FormBuilder, FormControl } from '@angular/forms';
@ -30,6 +30,7 @@ import { CanvasRendererComponent } from '../canvas-renderer/canvas-renderer.comp
import { DoubleRendererComponent } from '../double-renderer/double-renderer.component';
import { DoubleReverseRendererComponent } from '../double-reverse-renderer/double-reverse-renderer.component';
import { SingleRendererComponent } from '../single-renderer/single-renderer.component';
import { ChapterInfo } from '../../_models/chapter-info';
const PREFETCH_PAGES = 10;
@ -41,6 +42,19 @@ const ANIMATION_SPEED = 200;
const OVERLAY_AUTO_CLOSE_TIME = 3000;
const CLICK_OVERLAY_TIMEOUT = 3000;
enum ChapterInfoPosition {
Previous = 0,
Current = 1,
Next = 2
}
enum KeyDirection {
Right = 0,
Left = 1,
Up = 2,
Down = 3
}
@Component({
selector: 'app-manga-reader',
templateUrl: './manga-reader.component.html',
@ -138,6 +152,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
isLoading = true;
hasBookmarkRights: boolean = false; // TODO: This can be an observable
getPageFn!: (pageNum: number) => HTMLImageElement;
@ -165,6 +180,8 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
*/
continuousChaptersStack: Stack<number> = new Stack();
continuousChapterInfos: Array<ChapterInfo | undefined> = [undefined, undefined, undefined];
/**
* An event emitter when a page change occurs. Used solely by the webtoon reader.
*/
@ -511,7 +528,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.onDestroy.next();
this.onDestroy.complete();
this.showBookmarkEffectEvent.complete();
this.readerService.exitFullscreen();
if (this.goToPageEvent !== undefined) this.goToPageEvent.complete();
}
@ -526,14 +542,22 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
switch (this.readerMode) {
case ReaderMode.LeftRight:
if (event.key === KEY_CODES.RIGHT_ARROW) {
//if (!this.checkIfPaginationAllowed()) return;
if (!this.checkIfPaginationAllowed(KeyDirection.Right)) return;
this.readingDirection === ReadingDirection.LeftToRight ? this.nextPage() : this.prevPage();
} else if (event.key === KEY_CODES.LEFT_ARROW) {
//if (!this.checkIfPaginationAllowed()) return;
if (!this.checkIfPaginationAllowed(KeyDirection.Left)) return;
this.readingDirection === ReadingDirection.LeftToRight ? this.prevPage() : this.nextPage();
}
break;
case ReaderMode.UpDown:
if (event.key === KEY_CODES.UP_ARROW) {
if (!this.checkIfPaginationAllowed(KeyDirection.Up)) return;
this.prevPage();
} else if (event.key === KEY_CODES.DOWN_ARROW) {
if (!this.checkIfPaginationAllowed(KeyDirection.Down)) return;
this.nextPage();
}
break;
case ReaderMode.Webtoon:
if (event.key === KEY_CODES.DOWN_ARROW) {
this.nextPage()
@ -624,17 +648,36 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
// if there is scroll room and on original, then don't paginate
checkIfPaginationAllowed() {
checkIfPaginationAllowed(direction: KeyDirection) {
// This is not used atm due to the complexity it adds with keyboard.
if (this.readingArea === undefined || this.readingArea.nativeElement === undefined) return true;
const scrollLeft = this.readingArea?.nativeElement?.scrollLeft || 0;
const totalScrollWidth = this.readingArea?.nativeElement?.scrollWidth;
// need to also check if there is scroll needed
const scrollTop = this.readingArea?.nativeElement?.scrollTop || 0;
if (this.FittingOption === FITTING_OPTION.ORIGINAL && scrollLeft < totalScrollWidth) {
return false;
switch (direction) {
case KeyDirection.Right:
if (this.FittingOption === FITTING_OPTION.ORIGINAL && scrollLeft < this.readingArea?.nativeElement.scrollWidth - this.readingArea?.nativeElement.clientWidth) {
return false;
}
break;
case KeyDirection.Left:
if (this.FittingOption === FITTING_OPTION.ORIGINAL && scrollLeft > 0) {
return false;
}
break;
case KeyDirection.Up:
if (this.FittingOption === FITTING_OPTION.ORIGINAL && scrollTop > 0) {
return false;
}
break;
case KeyDirection.Down:
if (this.FittingOption === FITTING_OPTION.ORIGINAL && scrollTop < this.readingArea?.nativeElement.scrollHeight - this.readingArea?.nativeElement.clientHeight) {
return false;
}
break;
}
return true;
}
@ -712,8 +755,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return;
}
this.mangaReaderService.loadPageDimensions(results.chapterInfo.pageDimensions);
this.mangaReaderService.load(results.chapterInfo);
this.continuousChapterInfos[ChapterInfoPosition.Current] = results.chapterInfo;
this.volumeId = results.chapterInfo.volumeId;
this.maxPages = results.chapterInfo.pages;
let page = results.progress.pageNum;
@ -755,6 +799,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} else {
// Fetch the first page of next chapter
this.getPage(0, this.nextChapterId);
}
});
this.readerService.getPrevChapter(this.seriesId, this.volumeId, this.chapterId, this.readingListId).pipe(take(1)).subscribe(chapterId => {
@ -984,6 +1029,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
renderPage() {
const page = [this.canvasImage];
// After switching from webtoon mode, these are all undefined
this.canvasRenderer?.renderPage(page);
this.singleRenderer?.renderPage(page);
this.doubleRenderer?.renderPage(page);
@ -1027,28 +1075,24 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
prefetch() {
// NOTE: This doesn't allow for any directionality
// NOTE: This doesn't maintain 1 image behind at all times
// NOTE: I may want to provide a different prefetcher for double renderer
for(let i = 0; i <= PREFETCH_PAGES - 3; i++) {
const numOffset = this.pageNum + i;
//console.log('numOffset: ', numOffset);
if (numOffset > this.maxPages - 1) continue;
let numOffset = this.pageNum + i;
if (numOffset > this.maxPages - 1) {
continue;
}
const index = (numOffset % this.cachedImages.length + this.cachedImages.length) % this.cachedImages.length;
const cachedImagePageNum = this.readerService.imageUrlToPageNum(this.cachedImages[index].src);
const cachedImageChapterId = this.readerService.imageUrlToChapterId(this.cachedImages[index].src);
//console.log('chapter id for ', cachedImagePageNum, ' = ', cachedImageChapterId)
if (cachedImagePageNum !== numOffset) { // && cachedImageChapterId === this.chapterId
if (cachedImagePageNum !== numOffset) {
this.cachedImages[index] = new Image();
this.cachedImages[index].src = this.getPageUrl(numOffset);
}
}
const pages = this.cachedImages.map(img => this.readerService.imageUrlToPageNum(img.src));
const pagesBefore = pages.filter(p => p >= 0 && p < this.pageNum).length;
const pagesAfter = pages.filter(p => p >= 0 && p > this.pageNum).length;
//console.log('Buffer Health: Before: ', pagesBefore, ' After: ', pagesAfter);
// const pages = this.cachedImages.map(img => [this.readerService.imageUrlToChapterId(img.src), this.readerService.imageUrlToPageNum(img.src)]);
// console.log(this.pageNum, ' Prefetched pages: ', pages.map(p => {
// if (this.pageNum === p) return '[' + p + ']';
// if (this.pageNum === p[1]) return '[' + p + ']';
// return '' + p
// }));
}
@ -1061,7 +1105,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (this.readerMode === ReaderMode.Webtoon) return;
this.isLoading = true;
this.setPageNum(this.pageNum);
this.setCanvasImage();
this.cdRef.markForCheck();
@ -1094,6 +1137,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
// This will update the value for value except when in webtoon due to how the webtoon reader
// responds to page changes
if (this.readerMode !== ReaderMode.Webtoon) {
console.log('Setting Page Number as slider drag occured');
this.setPageNum(context.value);
}
}
@ -1107,6 +1151,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.pagingDirectionSubject.next(PAGING_DIRECTION.BACKWARDS);
}
console.log('Setting Page Number as slider page update occurred');
this.setPageNum(this.adjustPagesForDoubleRenderer(page));
this.refreshSlider.emit();
this.goToPageEvent.next(this.pageNum);
@ -1122,13 +1167,17 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
// Tell server to cache the next chapter
if (this.nextChapterId > 0 && !this.nextChapterPrefetched) {
this.readerService.getChapterInfo(this.nextChapterId).pipe(take(1)).subscribe(res => {
this.continuousChapterInfos[ChapterInfoPosition.Next] = res;
this.nextChapterPrefetched = true;
this.prefetchStartOfChapter(this.nextChapterId, PAGING_DIRECTION.FORWARD);
});
}
} else if (this.pageNum <= 10) {
if (this.prevChapterId > 0 && !this.prevChapterPrefetched) {
this.readerService.getChapterInfo(this.prevChapterId).pipe(take(1)).subscribe(res => {
this.continuousChapterInfos[ChapterInfoPosition.Previous] = res;
this.prevChapterPrefetched = true;
this.prefetchStartOfChapter(this.nextChapterId, PAGING_DIRECTION.BACKWARDS);
});
}
}
@ -1144,6 +1193,30 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
}
/**
* Loads the first 5 images (throwaway cache) from the given chapterId
* @param chapterId
* @param direction Used to indicate if the chapter is behind or ahead of curent chapter
*/
prefetchStartOfChapter(chapterId: number, direction: PAGING_DIRECTION) {
let pages = [];
if (direction === PAGING_DIRECTION.BACKWARDS) {
if (this.continuousChapterInfos[ChapterInfoPosition.Previous] === undefined) return;
const n = this.continuousChapterInfos[ChapterInfoPosition.Previous]!.pages;
pages = Array.from({length: n + 1}, (v, k) => n - k);
} else {
pages = [0, 1, 2, 3, 4];
}
let images = [];
pages.forEach((_, i: number) => {
let img = new Image();
img.src = this.getPageUrl(i, chapterId);
images.push(img)
});
}
goToPage(pageNum: number) {
let page = pageNum;
@ -1165,6 +1238,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.pagingDirectionSubject.next(PAGING_DIRECTION.BACKWARDS);
}
console.log('Setting Page Number as goto page');
this.setPageNum(this.adjustPagesForDoubleRenderer(page));
this.goToPageEvent.next(page);
this.render();
@ -1179,20 +1253,11 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
// This is menu only code
toggleFullscreen() {
this.isFullscreen = this.readerService.checkFullscreenMode();
if (this.isFullscreen) {
this.readerService.exitFullscreen(() => {
this.isFullscreen = false;
this.fullscreenEvent.next(false);
this.render();
});
} else {
this.readerService.enterFullscreen(this.reader.nativeElement, () => {
this.readerService.toggleFullscreen(this.reader.nativeElement, () => {
this.isFullscreen = true;
this.fullscreenEvent.next(true);
this.render();
});
}
}
// This is menu only code
@ -1212,10 +1277,11 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
// We must set this here because loadPage from render doesn't call if we aren't page splitting
if (this.readerMode !== ReaderMode.Webtoon) {
this.canvasImage = this.cachedImages[this.pageNum & this.cachedImages.length];
this.canvasImage = this.getPage(this.pageNum);
this.currentImage.next(this.canvasImage);
this.pageNumSubject.next({pageNum: this.pageNum, maxPages: this.maxPages});
this.isLoading = true;
//this.isLoading = true;
this.cdRef.detectChanges(); // Must use detectChanges to ensure ViewChildren get updated again
}
this.updateForm();
@ -1243,6 +1309,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
handleWebtoonPageChange(updatedPageNum: number) {
console.log('Setting Page Number as webtoon page changed');
this.setPageNum(updatedPageNum);
}

View file

@ -20,5 +20,9 @@ export interface ChapterInfo {
/**
* This will not always be present. Depends on if asked from backend.
*/
pageDimensions: Array<FileDimension>;
pageDimensions?: Array<FileDimension>;
/**
* This will not always be present. Depends on if asked from backend.
*/
doublePairs?: {[key: number]: number};
}

View file

@ -2,6 +2,7 @@ export interface FileDimension {
pageNumber: number;
width: number;
height: number;
isWide: boolean;
}
export type DimensionMap = {[key: number]: {width: number, height: number, isWide: boolean}};

View file

@ -2,6 +2,7 @@ import { ElementRef, Injectable, Renderer2, RendererFactory2 } from '@angular/co
import { PageSplitOption } from 'src/app/_models/preferences/page-split-option';
import { ScalingOption } from 'src/app/_models/preferences/scaling-option';
import { ReaderService } from 'src/app/_services/reader.service';
import { ChapterInfo } from '../_models/chapter-info';
import { DimensionMap, FileDimension } from '../_models/file-dimension';
import { FITTING_OPTION } from '../_models/reader-enums';
@ -13,41 +14,22 @@ export class ManagaReaderService {
private pageDimensions: DimensionMap = {};
private pairs: {[key: number]: number} = {};
private renderer: Renderer2;
constructor(rendererFactory: RendererFactory2, private readerService: ReaderService) {
this.renderer = rendererFactory.createRenderer(null, null);
}
loadPageDimensions(dims: Array<FileDimension>) {
this.pageDimensions = {};
let counter = 0;
let i = 0;
dims.forEach(d => {
const isWide = (d.width > d.height);
load(chapterInfo: ChapterInfo) {
chapterInfo.pageDimensions!.forEach(d => {
this.pageDimensions[d.pageNumber] = {
height: d.height,
width: d.width,
isWide: isWide
isWide: d.isWide
};
//console.log('Page Number: ', d.pageNumber);
if (isWide) {
console.log('\tPage is wide, counter: ', counter, 'i: ', i);
this.pairs[d.pageNumber] = d.pageNumber;
//this.pairs[d.pageNumber] = this.pairs[d.pageNumber - 1] + 1;
} else {
//console.log('\tPage is single, counter: ', counter, 'i: ', i);
this.pairs[d.pageNumber] = counter % 2 === 0 ? Math.max(i - 1, 0) : counter;
counter++;
}
//console.log('\t\tMapped to ', this.pairs[d.pageNumber]);
i++;
});
//console.log('pairs: ', this.pairs);
this.pairs = chapterInfo.doublePairs!;
}
adjustForDoubleReader(page: number) {
if (!this.pairs.hasOwnProperty(page)) return page;
return this.pairs[page];
@ -67,6 +49,14 @@ export class ManagaReaderService {
return this.pageDimensions[pageNum].isWide;
}
maxHeight() {
return Object.values(this.pageDimensions).reduce((max, obj) => Math.max(max, obj.height), 0);
}
maxWidth() {
return Object.values(this.pageDimensions).reduce((max, obj) => Math.max(max, obj.width), 0);
}
/**

View file

@ -101,7 +101,6 @@ export class PdfReaderComponent implements OnInit, OnDestroy {
this.navService.showNavBar();
this.navService.showSideNav();
this.readerService.exitFullscreen();
this.onDestroy.next();
this.onDestroy.complete();

View file

@ -59,11 +59,7 @@
<ng-container *ngIf="items.length === 0 && !isLoading">
Nothing added
</ng-container>
<ng-container *ngIf="isLoading">
<div class="spinner-border text-secondary" role="status">
<span class="invisible">Loading...</span>
</div>
</ng-container>
<app-loading [loading]="isLoading"></app-loading>
</div>
<!-- TODO: This needs virtualization -->

View file

@ -20,7 +20,7 @@
</li>
<li class="list-group-item" *ngIf="lists.length === 0 && !loading">No lists created yet</li>
<li class="list-group-item" *ngIf="loading">
<div class="spinner-border text-secondary" role="status">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</li>

View file

@ -308,9 +308,5 @@
<div [ngbNavOutlet]="nav"></div>
</ng-container>
<div class="mx-auto" *ngIf="isLoading" style="width: 200px;">
<div class="spinner-border text-secondary loading" role="status">
<span class="invisible">Loading...</span>
</div>
</div>
<app-loading [loading]="isLoading"></app-loading>
</div>

View file

@ -0,0 +1,7 @@
<ng-container *ngIf="loading">
<div class="d-flex justify-content-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</ng-container>

View file

@ -0,0 +1,19 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'app-loading',
templateUrl: './loading.component.html',
styleUrls: ['./loading.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LoadingComponent implements OnInit {
@Input() loading: boolean = false;
@Input() message: string = '';
constructor(private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
}
}

View file

@ -17,6 +17,7 @@ import { BadgeExpanderComponent } from './badge-expander/badge-expander.componen
import { ImageComponent } from './image/image.component';
import { PipeModule } from '../pipe/pipe.module';
import { IconAndTitleComponent } from './icon-and-title/icon-and-title.component';
import { LoadingComponent } from './loading/loading.component';
@NgModule({
declarations: [
@ -32,6 +33,7 @@ import { IconAndTitleComponent } from './icon-and-title/icon-and-title.component
BadgeExpanderComponent,
ImageComponent,
IconAndTitleComponent,
LoadingComponent,
],
imports: [
CommonModule,
@ -54,6 +56,8 @@ import { IconAndTitleComponent } from './icon-and-title/icon-and-title.component
PersonBadgeComponent, // Used Series Detail
BadgeExpanderComponent, // Used Series Detail/Metadata
IconAndTitleComponent, // Used in Series Detail/Metadata
LoadingComponent
],
})
export class SharedModule { }

View file

@ -0,0 +1,3 @@
{
"login": "Test"
}