Change Detection: On Push aka UI Smoothness (#1369)
* Updated Series Info Cards to use OnPush and hooked in progress events when we do a mark as read/unread on entities. These events update progress bars but also will now trigger a re-calculation on Read Time Left. * Removed Library Card Component * Refactored manga reader title and subtitle calculation to the backend. * Coverted card actionables to onPush * Series Card on push cleanup * Updated edit collection tags for on push * Update cover image chooser for on push * Cleaned up carsouel reel * Updated cover image to allow for uploading gif and webp files * Bulk add to collection on push * Updated bulk operation to use on push. Updated bulk operation to have mark as unread and read buttons explicitly. Updated so add to collection is visible and delete. Fixed a bug where manage library component wasn't invoking the trackBy function * Updating entity title for on push * Removed file info component * Updated Mange Library for on push * Entity info cards on push * List item on push * Updated icon and title for on push and fixed some missing change detection on series detail * Restricted the typeahead interface to simplify the design * Edit Series Relation now shows a value in the dropdown for Parent relationships and disables the field. * Updated edit series relation to focus on new typeahead when adding a new relationship * Added some documentation and when Scanning a library, don't allow the user to enqueue the same job multiple times. * Applied the No-enqueue if already enqueued logic to other tasks * Library detail on push * Updated events widget to onpush * Card detail drawer on push. Card detail cover chooser now will show all chapter's covers for selection in cover chooser. * Chapter metadata detail on push * Removed Card Detail modal * All collections on push * Removed some comments * Updated bulk selection to use an observable rather than function calls so new on push strategy works * collection detail now uses on push and scroller is placed on correct element * Updated library recommended to on push. Ensure that when mark as read occurs, the appropriate streams are refreshed. * Updated library detail to on push * Update metadata fiter to onpush. Bugs found and reported to Project * person badge on push * Read more on push * Updated tag badge to on push * User login on push * When initing side nav, don't call an authenticated api until we are sure a user is logged in * Updated splash container to on push * Dashboard on push * Side nav slight refactor around some api calls * Cleaned up series card on push to use same cdRef naming convention * Updated Static Files to use caching * Added width and height to logo image * shortcuts modal on push * reading lists on push * Reading list detail on push * draggable ordered list on push * Refactored reading-list-detail to use a new item which drastically reduces renders on operations * series format on push * circular loader on push * Badge Expander on push * update notification modal on push * drawer on push * Edit Series Modal on push * reset password on push * review series modal on push * series metadata detail on push * theme manager on push * confirm reset password on push * register on push * confirm migration email on push * confirm email on push * add email to account migration on push * user preferences on push. Made global settings default open * edit series relation on push * Fixed an edge case bug for next chapter where if the current volume had a single chapter of 1 and the next volume had a chapter number of 0, it would say there are no more chapters. * Updated infinite scroller with on push support * Moved some animations over to typeahead, not integrated yet. * Manga reader is now on push * Reader settings on push * refactored how we close the book * Updated table of contents for on push * Updated book reader for on push. Fixed a bug where table of contents wasn't showing current page anchor due to a scroll calulation bug * Small code tweak * Icon and title on push * nav header on push * grouped typeahead on push * typeahead on push and added a new trackby identity function to allow even faster rendering of big lists * pdf reader on push * code cleanup
This commit is contained in:
parent
f5be0fac58
commit
4e49aa47ce
126 changed files with 1658 additions and 1674 deletions
|
|
@ -14,4 +14,6 @@ export interface ChapterInfo {
|
|||
isSpecial: boolean;
|
||||
volumeId: number;
|
||||
pages: number;
|
||||
subtitle: string;
|
||||
title: string;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { DOCUMENT } from '@angular/common';
|
||||
import { Component, ElementRef, EventEmitter, Inject, Input, OnChanges, OnDestroy, OnInit, Output, Renderer2, SimpleChanges } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Inject, Input, OnChanges, OnDestroy, OnInit, Output, Renderer2, SimpleChanges } from '@angular/core';
|
||||
import { BehaviorSubject, fromEvent, ReplaySubject, Subject } from 'rxjs';
|
||||
import { debounceTime, takeUntil } from 'rxjs/operators';
|
||||
import { ScrollService } from 'src/app/_services/scroll.service';
|
||||
|
|
@ -39,7 +39,8 @@ const enum DEBUG_MODES {
|
|||
@Component({
|
||||
selector: 'app-infinite-scroller',
|
||||
templateUrl: './infinite-scroller.component.html',
|
||||
styleUrls: ['./infinite-scroller.component.scss']
|
||||
styleUrls: ['./infinite-scroller.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
|
|
@ -150,11 +151,11 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
constructor(private readerService: ReaderService, private renderer: Renderer2, @Inject(DOCUMENT) private document: Document, private scrollService: ScrollService) {
|
||||
constructor(private readerService: ReaderService, private renderer: Renderer2,
|
||||
@Inject(DOCUMENT) private document: Document, private scrollService: ScrollService,
|
||||
private readonly cdRef: ChangeDetectorRef) {
|
||||
// This will always exist at this point in time since this is used within manga reader
|
||||
const reader = document.querySelector('.reader');
|
||||
if (reader !== null) {
|
||||
|
|
@ -165,6 +166,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.hasOwnProperty('totalPages') && changes['totalPages'].previousValue != changes['totalPages'].currentValue) {
|
||||
this.totalPages = changes['totalPages'].currentValue;
|
||||
this.cdRef.markForCheck();
|
||||
this.initWebtoonReader();
|
||||
}
|
||||
}
|
||||
|
|
@ -211,6 +213,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
const image = document.querySelector('img[id^="page-' + page + '"]');
|
||||
if (image) {
|
||||
this.renderer.addClass(image, 'bookmark-effect');
|
||||
|
||||
setTimeout(() => {
|
||||
this.renderer.removeClass(image, 'bookmark-effect');
|
||||
}, 1000);
|
||||
|
|
@ -222,6 +225,8 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.fullscreenToggled.pipe(takeUntil(this.onDestroy)).subscribe(isFullscreen => {
|
||||
this.debugLog('[FullScreen] Fullscreen mode: ', isFullscreen);
|
||||
this.isFullscreenMode = isFullscreen;
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
this.recalculateImageWidth();
|
||||
this.initScrollHandler();
|
||||
this.setPageNum(this.pageNum, true);
|
||||
|
|
@ -232,6 +237,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
recalculateImageWidth() {
|
||||
const [_, innerWidth] = this.getInnerDimensions();
|
||||
this.webtoonImageWidth = innerWidth || document.body.clientWidth || document.documentElement.clientWidth;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
getVerticalOffset() {
|
||||
|
|
@ -256,9 +262,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* @param event Scroll Event
|
||||
*/
|
||||
handleScrollEvent(event?: any) {
|
||||
// Need a fullscreen handler here too
|
||||
let verticalOffset = this.getVerticalOffset();
|
||||
|
||||
const verticalOffset = this.getVerticalOffset();
|
||||
|
||||
if (verticalOffset > this.prevScrollPosition) {
|
||||
this.scrollingDirection = PAGING_DIRECTION.FORWARD;
|
||||
|
|
@ -270,6 +274,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
if (this.isScrolling && this.currentPageElem != null && this.isElementVisible(this.currentPageElem)) {
|
||||
this.debugLog('[Scroll] Image is visible from scroll, isScrolling is now false');
|
||||
this.isScrolling = false;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
if (!this.isScrolling) {
|
||||
|
|
@ -282,11 +287,9 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.setPageNum(parseInt(midlineImages[0].getAttribute('page') || this.pageNum + '', 10));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check if we hit the last page
|
||||
this.checkIfShouldTriggerContinuousReader();
|
||||
|
||||
}
|
||||
|
||||
getTotalHeight() {
|
||||
|
|
@ -294,17 +297,18 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
document.querySelectorAll('img[id^="page-"]').forEach(img => totalHeight += img.getBoundingClientRect().height);
|
||||
return Math.round(totalHeight);
|
||||
}
|
||||
|
||||
getTotalScroll() {
|
||||
if (this.isFullscreenMode) {
|
||||
return this.readerElemRef.nativeElement.offsetHeight + this.readerElemRef.nativeElement.scrollTop;
|
||||
}
|
||||
return document.body.offsetHeight + document.body.scrollTop;
|
||||
}
|
||||
|
||||
getScrollTop() {
|
||||
if (this.isFullscreenMode) {
|
||||
return this.readerElemRef.nativeElement.scrollTop;
|
||||
}
|
||||
|
||||
return document.body.scrollTop;
|
||||
}
|
||||
|
||||
|
|
@ -318,25 +322,32 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
// If we were at top but have started scrolling down past page 0, remove top spacer
|
||||
if (this.atTop && this.pageNum > 0) {
|
||||
this.atTop = false;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
if (totalScroll === totalHeight && !this.atBottom) {
|
||||
this.atBottom = true;
|
||||
this.cdRef.markForCheck();
|
||||
this.setPageNum(this.totalPages);
|
||||
|
||||
// Scroll user back to original location
|
||||
this.previousScrollHeightMinusTop = this.getScrollTop();
|
||||
requestAnimationFrame(() => document.body.scrollTop = this.previousScrollHeightMinusTop + (SPACER_SCROLL_INTO_PX / 2));
|
||||
requestAnimationFrame(() => {
|
||||
document.body.scrollTop = this.previousScrollHeightMinusTop + (SPACER_SCROLL_INTO_PX / 2);
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
} else if (totalScroll >= totalHeight + SPACER_SCROLL_INTO_PX && this.atBottom) {
|
||||
// This if statement will fire once we scroll into the spacer at all
|
||||
this.loadNextChapter.emit();
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
} else {
|
||||
// < 5 because debug mode and FF (mobile) can report non 0, despite being at 0
|
||||
if (this.getScrollTop() < 5 && this.pageNum === 0 && !this.atTop) {
|
||||
this.atBottom = false;
|
||||
|
||||
this.atTop = true;
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
// Scroll user back to original location
|
||||
this.previousScrollHeightMinusTop = document.body.scrollHeight - document.body.scrollTop;
|
||||
|
||||
|
|
@ -345,9 +356,9 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
} else if (this.getScrollTop() < 5 && this.pageNum === 0 && this.atTop) {
|
||||
// If already at top, then we moving on
|
||||
this.loadPrevChapter.emit();
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -376,9 +387,9 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
this.debugLog('[Visibility] Checking if Page ' + elem.getAttribute('id') + ' is visible');
|
||||
// NOTE: This will say an element is visible if it is 1 px offscreen on top
|
||||
var rect = elem.getBoundingClientRect();
|
||||
const rect = elem.getBoundingClientRect();
|
||||
|
||||
let [innerHeight, innerWidth] = this.getInnerDimensions();
|
||||
const [innerHeight, innerWidth] = this.getInnerDimensions();
|
||||
|
||||
return (rect.bottom >= 0 &&
|
||||
rect.right >= 0 &&
|
||||
|
|
@ -396,9 +407,9 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
shouldElementCountAsCurrentPage(elem: Element) {
|
||||
if (elem === null || elem === undefined) { return false; }
|
||||
|
||||
var rect = elem.getBoundingClientRect();
|
||||
const rect = elem.getBoundingClientRect();
|
||||
|
||||
let [innerHeight, innerWidth] = this.getInnerDimensions();
|
||||
const [innerHeight, innerWidth] = this.getInnerDimensions();
|
||||
|
||||
|
||||
if (rect.bottom >= 0 &&
|
||||
|
|
@ -420,13 +431,14 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.webtoonImages.next([]);
|
||||
this.atBottom = false;
|
||||
this.checkIfShouldTriggerContinuousReader();
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
const [startingIndex, endingIndex] = this.calculatePrefetchIndecies();
|
||||
|
||||
this.debugLog('[INIT] Prefetching pages ' + startingIndex + ' to ' + endingIndex + '. Current page: ', this.pageNum);
|
||||
for(let i = startingIndex; i <= endingIndex; i++) {
|
||||
this.loadWebtoonImage(i);
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -460,9 +472,11 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.scrollToCurrentPage();
|
||||
} else {
|
||||
this.initFinished = true;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
this.allImagesLoaded = true;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -496,6 +510,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
}
|
||||
this.pageNum = pageNum;
|
||||
this.pageNumberChange.emit(this.pageNum);
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
this.prefetchWebtoonImages();
|
||||
|
||||
|
|
@ -519,26 +534,27 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
// Update prevScrollPosition, so the next scroll event properly calculates direction
|
||||
this.prevScrollPosition = this.currentPageElem.getBoundingClientRect().top;
|
||||
this.isScrolling = true;
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.currentPageElem) {
|
||||
this.debugLog('[Scroll] Scrolling to page ', this.pageNum);
|
||||
this.currentPageElem.scrollIntoView({behavior: 'smooth'});
|
||||
this.initFinished = true;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
}, 600);
|
||||
}
|
||||
|
||||
loadWebtoonImage(page: number) {
|
||||
let data = this.webtoonImages.value;
|
||||
|
||||
if (this.imagesLoaded.hasOwnProperty(page)) {
|
||||
this.debugLog('\t[PREFETCH] Skipping prefetch of ', page);
|
||||
return;
|
||||
}
|
||||
this.debugLog('\t[PREFETCH] Prefetching ', page);
|
||||
|
||||
data = data.concat({src: this.urlProvider(page), page});
|
||||
this.debugLog('\t[PREFETCH] Prefetching ', page);
|
||||
|
||||
const data = this.webtoonImages.value.concat({src: this.urlProvider(page), page});
|
||||
|
||||
data.sort((a: WebtoonImage, b: WebtoonImage) => {
|
||||
if (a.page < b.page) { return -1; }
|
||||
|
|
@ -547,6 +563,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
});
|
||||
|
||||
this.allImagesLoaded = false;
|
||||
this.cdRef.markForCheck();
|
||||
this.webtoonImages.next(data);
|
||||
|
||||
if (!this.imagesLoaded.hasOwnProperty(page)) {
|
||||
|
|
@ -603,7 +620,6 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
}
|
||||
|
||||
prefetchWebtoonImages(pageNum: number = -1) {
|
||||
|
||||
if (pageNum === -1) {
|
||||
pageNum = this.pageNum;
|
||||
}
|
||||
|
|
@ -621,6 +637,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
.map((img: any) => new Promise(resolve => { img.onload = img.onerror = resolve; })))
|
||||
.then(() => {
|
||||
this.allImagesLoaded = true;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { DOCUMENT, Location } from '@angular/common';
|
||||
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 { debounceTime, take, takeUntil } from 'rxjs/operators';
|
||||
import { User } from '../_models/user';
|
||||
|
|
@ -18,7 +18,6 @@ import { MemberService } from '../_services/member.service';
|
|||
import { Stack } from '../shared/data-structures/stack';
|
||||
import { ChangeContext, LabelType, Options } from '@angular-slider/ngx-slider';
|
||||
import { trigger, state, style, transition, animate } from '@angular/animations';
|
||||
import { ChapterInfo } from './_models/chapter-info';
|
||||
import { FITTING_OPTION, PAGING_DIRECTION, SPLIT_PAGE_PART } from './_models/reader-enums';
|
||||
import { layoutModes, pageSplitOptions, scalingOptions } from '../_models/preferences/preferences';
|
||||
import { ReaderMode } from '../_models/preferences/reader-mode';
|
||||
|
|
@ -43,6 +42,7 @@ const CLICK_OVERLAY_TIMEOUT = 3000;
|
|||
selector: 'app-manga-reader',
|
||||
templateUrl: './manga-reader.component.html',
|
||||
styleUrls: ['./manga-reader.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: [
|
||||
trigger('slideFromTop', [
|
||||
state('in', style({ transform: 'translateY(0)'})),
|
||||
|
|
@ -342,20 +342,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
);
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
@HostListener('window:orientationchange', ['$event'])
|
||||
onResize() {
|
||||
if (window.innerWidth > window.innerHeight) {
|
||||
this.generalSettingsForm.get('layoutMode')?.enable();
|
||||
return;
|
||||
};
|
||||
if (this.layoutMode === LayoutMode.Single || this.readerMode === ReaderMode.Webtoon) return;
|
||||
|
||||
this.generalSettingsForm.get('layoutMode')?.setValue(LayoutMode.Single);
|
||||
this.generalSettingsForm.get('layoutMode')?.disable();
|
||||
this.toastr.info('Layout mode switched to Single due to insufficient space to render double layout');
|
||||
}
|
||||
|
||||
get CurrentPageBookmarked() {
|
||||
return this.bookmarks.hasOwnProperty(this.pageNum);
|
||||
}
|
||||
|
|
@ -448,13 +434,14 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService,
|
||||
public readerService: ReaderService, private location: Location,
|
||||
private formBuilder: FormBuilder, private navService: NavService,
|
||||
public readerService: ReaderService, private formBuilder: FormBuilder, private navService: NavService,
|
||||
private toastr: ToastrService, private memberService: MemberService,
|
||||
public utilityService: UtilityService, private renderer: Renderer2,
|
||||
@Inject(DOCUMENT) private document: Document, private modalService: NgbModal) {
|
||||
@Inject(DOCUMENT) private document: Document, private modalService: NgbModal,
|
||||
private readonly cdRef: ChangeDetectorRef) {
|
||||
this.navService.hideNavBar();
|
||||
this.navService.hideSideNav();
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
|
@ -519,6 +506,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.generalSettingsForm.get('fittingOption')?.setValue(this.translateScalingOption(ScalingOption.FitToHeight));
|
||||
this.generalSettingsForm.get('fittingOption')?.disable();
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
// Re-render the current page when we switch layouts
|
||||
if (changeOccurred) {
|
||||
|
|
@ -527,7 +515,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
});
|
||||
|
||||
this.generalSettingsForm.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe((changes: SimpleChanges) => {
|
||||
this.autoCloseMenu = this.generalSettingsForm.get('autoCloseMenu')?.value;
|
||||
this.autoCloseMenu = this.generalSettingsForm.get('autoCloseMenu')?.value; // TODO: Do I need cd check here?
|
||||
const needsSplitting = this.isWideImage();
|
||||
// If we need to split on a menu change, then we need to re-render.
|
||||
if (needsSplitting) {
|
||||
|
|
@ -551,9 +539,11 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
if (this.readerMode === ReaderMode.Webtoon) return;
|
||||
if (this.readerMode === ReaderMode.LeftRight && this.FittingOption === FITTING_OPTION.HEIGHT) {
|
||||
this.rightPaginationOffset = (this.readingArea.nativeElement.scrollLeft) * -1;
|
||||
this.cdRef.markForCheck();
|
||||
return;
|
||||
}
|
||||
this.rightPaginationOffset = 0;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
if (this.canvas) {
|
||||
|
|
@ -573,6 +563,22 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
if (this.goToPageEvent !== undefined) this.goToPageEvent.complete();
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
@HostListener('window:orientationchange', ['$event'])
|
||||
onResize() {
|
||||
if (window.innerWidth > window.innerHeight) {
|
||||
this.generalSettingsForm.get('layoutMode')?.enable();
|
||||
this.cdRef.markForCheck();
|
||||
return;
|
||||
};
|
||||
if (this.layoutMode === LayoutMode.Single || this.readerMode === ReaderMode.Webtoon) return;
|
||||
|
||||
this.generalSettingsForm.get('layoutMode')?.setValue(LayoutMode.Single);
|
||||
this.generalSettingsForm.get('layoutMode')?.disable();
|
||||
this.toastr.info('Layout mode switched to Single due to insufficient space to render double layout');
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
@HostListener('window:keyup', ['$event'])
|
||||
handleKeyPress(event: KeyboardEvent) {
|
||||
switch (this.readerMode) {
|
||||
|
|
@ -651,6 +657,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.pageNum = 0;
|
||||
this.pagingDirection = PAGING_DIRECTION.FORWARD;
|
||||
this.inSetup = true;
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
if (this.goToPageEvent) {
|
||||
// There was a bug where goToPage was emitting old values into infinite scroller between chapter loads. We explicity clear it out between loads
|
||||
|
|
@ -670,8 +677,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
newOptions.ceil = this.maxPages - 1; // We -1 so that the slider UI shows us hitting the end, since visually we +1 everything.
|
||||
this.pageOptions = newOptions;
|
||||
this.inSetup = false;
|
||||
this.prevChapterDisabled = true;
|
||||
this.nextChapterDisabled = true;
|
||||
|
||||
const images = [];
|
||||
for (let i = 0; i < PREFETCH_PAGES + 2; i++) {
|
||||
|
|
@ -691,8 +696,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
chapterInfo: this.readerService.getChapterInfo(this.chapterId),
|
||||
bookmarks: this.readerService.getBookmarks(this.chapterId),
|
||||
}).pipe(take(1)).subscribe(results => {
|
||||
|
||||
|
||||
if (this.readingListMode && (results.chapterInfo.seriesFormat === MangaFormat.EPUB || results.chapterInfo.seriesFormat === MangaFormat.PDF)) {
|
||||
// Redirect to the book reader.
|
||||
const params = this.readerService.getQueryParamsObject(this.incognitoMode, this.readingListMode, this.readingListId);
|
||||
|
|
@ -718,7 +721,8 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.pageOptions = newOptions;
|
||||
|
||||
this.libraryType = results.chapterInfo.libraryType;
|
||||
this.updateTitle(results.chapterInfo, this.libraryType);
|
||||
this.title = results.chapterInfo.title;
|
||||
this.subtitle = results.chapterInfo.subtitle;
|
||||
|
||||
this.inSetup = false;
|
||||
|
||||
|
|
@ -729,17 +733,20 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
results.bookmarks.forEach(bookmark => {
|
||||
this.bookmarks[bookmark.page] = 1;
|
||||
});
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
this.readerService.getNextChapter(this.seriesId, this.volumeId, this.chapterId, this.readingListId).pipe(take(1)).subscribe(chapterId => {
|
||||
this.nextChapterId = chapterId;
|
||||
if (chapterId === CHAPTER_ID_DOESNT_EXIST || chapterId === this.chapterId) {
|
||||
this.nextChapterDisabled = true;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
});
|
||||
this.readerService.getPrevChapter(this.seriesId, this.volumeId, this.chapterId, this.readingListId).pipe(take(1)).subscribe(chapterId => {
|
||||
this.prevChapterId = chapterId;
|
||||
if (chapterId === CHAPTER_ID_DOESNT_EXIST || chapterId === this.chapterId) {
|
||||
this.prevChapterDisabled = true;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -760,43 +767,19 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
closeReader() {
|
||||
this.readerService.closeReader(this.readingListMode, this.readingListId);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.readerMode === ReaderMode.Webtoon) {
|
||||
this.isLoading = false;
|
||||
this.cdRef.markForCheck();
|
||||
} else {
|
||||
this.loadPage();
|
||||
}
|
||||
}
|
||||
|
||||
closeReader() {
|
||||
if (this.readingListMode) {
|
||||
this.router.navigateByUrl('lists/' + this.readingListId);
|
||||
} else {
|
||||
this.location.back();
|
||||
}
|
||||
}
|
||||
|
||||
updateTitle(chapterInfo: ChapterInfo, type: LibraryType) {
|
||||
this.title = chapterInfo.seriesName;
|
||||
if (chapterInfo.chapterTitle != null && chapterInfo.chapterTitle.length > 0) {
|
||||
this.title += ' - ' + chapterInfo.chapterTitle;
|
||||
}
|
||||
|
||||
// TODO: Move this to the backend
|
||||
this.subtitle = '';
|
||||
if (chapterInfo.isSpecial && chapterInfo.volumeNumber === '0') {
|
||||
this.subtitle = chapterInfo.fileName;
|
||||
} else if (!chapterInfo.isSpecial && chapterInfo.volumeNumber === '0') {
|
||||
this.subtitle = this.utilityService.formatChapterName(type, true, true) + chapterInfo.chapterNumber;
|
||||
} else {
|
||||
this.subtitle = 'Volume ' + chapterInfo.volumeNumber;
|
||||
|
||||
if (chapterInfo.chapterNumber !== '0') {
|
||||
this.subtitle += ' ' + this.utilityService.formatChapterName(type, true, true) + chapterInfo.chapterNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
translateScalingOption(option: ScalingOption) {
|
||||
switch (option) {
|
||||
case (ScalingOption.Automatic):
|
||||
|
|
@ -907,6 +890,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
toggleMenu() {
|
||||
this.menuOpen = !this.menuOpen;
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
if (this.menuTimeout) {
|
||||
clearTimeout(this.menuTimeout);
|
||||
|
|
@ -917,6 +901,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
} else {
|
||||
this.showClickOverlay = false;
|
||||
this.settingsOpen = false;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1084,8 +1069,13 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
loadNextChapter() {
|
||||
if (this.nextPageDisabled) { return; }
|
||||
if (this.nextChapterDisabled) { return; }
|
||||
if (this.nextChapterDisabled) {
|
||||
this.toastr.info('No Next Chapter');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
this.cdRef.markForCheck();
|
||||
if (this.nextChapterId === CHAPTER_ID_NOT_FETCHED || this.nextChapterId === this.chapterId) {
|
||||
this.readerService.getNextChapter(this.seriesId, this.volumeId, this.chapterId, this.readingListId).pipe(take(1)).subscribe(chapterId => {
|
||||
this.nextChapterId = chapterId;
|
||||
|
|
@ -1098,8 +1088,12 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
loadPrevChapter() {
|
||||
if (this.prevPageDisabled) { return; }
|
||||
if (this.prevChapterDisabled) { return; }
|
||||
if (this.prevChapterDisabled) {
|
||||
this.toastr.info('No Previous Chapter');
|
||||
return;
|
||||
}
|
||||
this.isLoading = true;
|
||||
this.cdRef.markForCheck();
|
||||
this.continuousChaptersStack.pop();
|
||||
const prevChapter = this.continuousChaptersStack.peek();
|
||||
if (prevChapter != this.chapterId) {
|
||||
|
|
@ -1138,7 +1132,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
} else {
|
||||
this.nextPageDisabled = true;
|
||||
}
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1170,44 +1164,55 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.canvas.nativeElement.width = this.canvasImage.width;
|
||||
this.canvas.nativeElement.height = this.canvasImage.height;
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
renderPage() {
|
||||
const pageSplit = this.generalSettingsForm.get('pageSplitOption')?.value;
|
||||
if (!this.ctx || !this.canvas || pageSplit !== PageSplitOption.FitSplit || pageSplit !== PageSplitOption.NoSplit) {
|
||||
const pageSplit = parseInt(this.generalSettingsForm.get('pageSplitOption')?.value, 10);
|
||||
if (!this.ctx || !this.canvas || (pageSplit === PageSplitOption.FitSplit || pageSplit === PageSplitOption.NoSplit)) {
|
||||
if (this.getFit() !== FITTING_OPTION.HEIGHT) {
|
||||
this.readingArea.nativeElement.scroll(0,0);
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.cdRef.markForCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
const needsSplitting = this.isWideImage();
|
||||
if (!needsSplitting) {
|
||||
this.renderWithCanvas = false;
|
||||
if (this.getFit() !== FITTING_OPTION.HEIGHT) {
|
||||
this.readingArea.nativeElement.scroll(0,0);
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.cdRef.markForCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
this.canvasImage.onload = null;
|
||||
|
||||
this.setCanvasSize();
|
||||
|
||||
const needsSplitting = this.isWideImage();
|
||||
this.updateSplitPage();
|
||||
|
||||
if (needsSplitting && this.currentImageSplitPart === SPLIT_PAGE_PART.LEFT_PART) {
|
||||
this.canvas.nativeElement.width = this.canvasImage.width / 2;
|
||||
this.ctx.drawImage(this.canvasImage, 0, 0, this.canvasImage.width, this.canvasImage.height, 0, 0, this.canvasImage.width, this.canvasImage.height);
|
||||
this.renderWithCanvas = true;
|
||||
this.cdRef.markForCheck();
|
||||
} else if (needsSplitting && this.currentImageSplitPart === SPLIT_PAGE_PART.RIGHT_PART) {
|
||||
this.canvas.nativeElement.width = this.canvasImage.width / 2;
|
||||
this.ctx.drawImage(this.canvasImage, 0, 0, this.canvasImage.width, this.canvasImage.height, -this.canvasImage.width / 2, 0, this.canvasImage.width, this.canvasImage.height);
|
||||
this.renderWithCanvas = true;
|
||||
} else {
|
||||
this.renderWithCanvas = false;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
// Reset scroll on non HEIGHT Fits
|
||||
if (this.getFit() !== FITTING_OPTION.HEIGHT) {
|
||||
this.readingArea.nativeElement.scroll(0,0);
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
updateScalingForFirstPageRender() {
|
||||
|
|
@ -1233,6 +1238,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
this.firstPageRendered = true;
|
||||
this.generalSettingsForm.get('fittingOption')?.setValue(newScale, {emitEvent: false});
|
||||
//this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1330,9 +1336,12 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
this.renderPage();
|
||||
this.prefetch();
|
||||
this.isLoading = false;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
setReadingDirection() {
|
||||
|
|
@ -1376,18 +1385,21 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
setPageNum(pageNum: number) {
|
||||
this.pageNum = Math.max(Math.min(pageNum, this.maxPages - 1), 0);
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
if (this.pageNum >= this.maxPages - 10) {
|
||||
// Tell server to cache the next chapter
|
||||
if (this.nextChapterId > 0 && !this.nextChapterPrefetched) {
|
||||
this.readerService.getChapterInfo(this.nextChapterId).pipe(take(1)).subscribe(res => {
|
||||
this.nextChapterPrefetched = true;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
} else if (this.pageNum <= 10) {
|
||||
if (this.prevChapterId > 0 && !this.prevChapterPrefetched) {
|
||||
this.readerService.getChapterInfo(this.prevChapterId).pipe(take(1)).subscribe(res => {
|
||||
this.prevChapterPrefetched = true;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1481,7 +1493,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
updateForm() {
|
||||
|
||||
if ( this.readerMode === ReaderMode.Webtoon) {
|
||||
this.generalSettingsForm.get('pageSplitOption')?.disable()
|
||||
this.generalSettingsForm.get('fittingOption')?.disable()
|
||||
|
|
@ -1496,6 +1507,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.generalSettingsForm.get('fittingOption')?.disable();
|
||||
}
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
handleWebtoonPageChange(updatedPageNum: number) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue