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
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue