Merged v0.5.1 develop into main.
This commit is contained in:
commit
150479e755
256 changed files with 6898 additions and 1833 deletions
|
|
@ -4,7 +4,7 @@
|
|||
<strong>Is Scrolling:</strong> {{isScrollingForwards() ? 'Forwards' : 'Backwards'}} {{this.isScrolling}}
|
||||
<strong>All Images Loaded:</strong> {{this.allImagesLoaded}}
|
||||
<strong>Prefetched</strong> {{minPageLoaded}}-{{maxPageLoaded}}
|
||||
<strong>Pages:</strong> {{pageNum}} / {{totalPages}}
|
||||
<strong>Pages:</strong> {{pageNum}} / {{totalPages - 1}}
|
||||
<strong>At Top:</strong> {{atTop}}
|
||||
<strong>At Bottom:</strong> {{atBottom}}
|
||||
<strong>Total Height:</strong> {{getTotalHeight()}}
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
</div>
|
||||
<ng-container *ngFor="let item of webtoonImages | async; let index = index;">
|
||||
<img src="{{item.src}}" style="display: block"
|
||||
class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}} {{areImagesWiderThanWindow ? 'full-width' : ''}}"
|
||||
class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}} {{areImagesWiderThanWindow ? 'full-width' : ''}} {{initFinished ? '' : 'full-opacity'}}"
|
||||
*ngIf="pageNum >= pageNum - bufferPages && pageNum <= pageNum + bufferPages" rel="nofollow" alt="image"
|
||||
(load)="onImageLoad($event)" id="page-{{item.page}}" [attr.page]="item.page" ondragstart="return false;" onselectstart="return false;">
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@
|
|||
border: 2px solid red;
|
||||
}
|
||||
|
||||
.full-opacity {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
@Output() loadNextChapter: EventEmitter<void> = new EventEmitter<void>();
|
||||
@Output() loadPrevChapter: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
@Input() goToPage: ReplaySubject<number> = new ReplaySubject<number>();
|
||||
@Input() goToPage: BehaviorSubject<number> | undefined;
|
||||
@Input() bookmarkPage: ReplaySubject<number> = new ReplaySubject<number>();
|
||||
@Input() fullscreenToggled: ReplaySubject<boolean> = new ReplaySubject<boolean>();
|
||||
|
||||
|
|
@ -121,10 +121,18 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* Keeps track of the previous scrolling height for restoring scroll position after we inject spacer block
|
||||
*/
|
||||
previousScrollHeightMinusTop: number = 0;
|
||||
/**
|
||||
* Tracks the first load, until all the initial prefetched images are loaded. We use this to reduce opacity so images can load without jerk.
|
||||
*/
|
||||
initFinished: boolean = false;
|
||||
/**
|
||||
* Debug mode. Will show extra information. Use bitwise (|) operators between different modes to enable different output
|
||||
*/
|
||||
debugMode: DEBUG_MODES = DEBUG_MODES.None;
|
||||
/**
|
||||
* Debug mode. Will filter out any messages in here so they don't hit the log
|
||||
*/
|
||||
debugLogFilter: Array<string> = ['[PREFETCH]', '[Intersection]', '[Visibility]', '[Image Load]'];
|
||||
|
||||
get minPageLoaded() {
|
||||
return Math.min(...Object.values(this.imagesLoaded));
|
||||
|
|
@ -135,7 +143,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
}
|
||||
|
||||
get areImagesWiderThanWindow() {
|
||||
let [innerWidth, _] = this.getInnerDimensions();
|
||||
let [_, innerWidth] = this.getInnerDimensions();
|
||||
return this.webtoonImageWidth > (innerWidth || document.documentElement.clientWidth);
|
||||
}
|
||||
|
||||
|
|
@ -173,18 +181,18 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
fromEvent(this.isFullscreenMode ? this.readerElemRef.nativeElement : window, 'scroll')
|
||||
.pipe(debounceTime(20), takeUntil(this.onDestroy))
|
||||
.subscribe((event) => this.handleScrollEvent(event));
|
||||
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initScrollHandler();
|
||||
|
||||
this.recalculateImageWidth();
|
||||
|
||||
if (this.goToPage) {
|
||||
this.goToPage.pipe(takeUntil(this.onDestroy)).subscribe(page => {
|
||||
this.debugLog('[GoToPage] jump has occured from ' + this.pageNum + ' to ' + page);
|
||||
const isSamePage = this.pageNum === page;
|
||||
if (isSamePage) { return; }
|
||||
this.debugLog('[GoToPage] jump has occured from ' + this.pageNum + ' to ' + page);
|
||||
|
||||
if (this.pageNum < page) {
|
||||
this.scrollingDirection = PAGING_DIRECTION.FORWARD;
|
||||
|
|
@ -212,14 +220,18 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.fullscreenToggled.pipe(takeUntil(this.onDestroy)).subscribe(isFullscreen => {
|
||||
this.debugLog('[FullScreen] Fullscreen mode: ', isFullscreen);
|
||||
this.isFullscreenMode = isFullscreen;
|
||||
const [innerWidth, _] = this.getInnerDimensions();
|
||||
this.webtoonImageWidth = innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
||||
this.recalculateImageWidth();
|
||||
this.initScrollHandler();
|
||||
this.setPageNum(this.pageNum, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
recalculateImageWidth() {
|
||||
const [_, innerWidth] = this.getInnerDimensions();
|
||||
this.webtoonImageWidth = innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
||||
}
|
||||
|
||||
getVerticalOffset() {
|
||||
const reader = this.isFullscreenMode ? this.readerElemRef.nativeElement : window;
|
||||
|
||||
|
|
@ -252,10 +264,6 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
}
|
||||
this.prevScrollPosition = verticalOffset;
|
||||
|
||||
console.log('CurrentPageElem: ', this.currentPageElem);
|
||||
if (this.currentPageElem != null) {
|
||||
console.log('Element Visible: ', this.isElementVisible(this.currentPageElem));
|
||||
}
|
||||
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;
|
||||
|
|
@ -336,6 +344,10 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns Height, Width
|
||||
*/
|
||||
getInnerDimensions() {
|
||||
let innerHeight = window.innerHeight;
|
||||
let innerWidth = window.innerWidth;
|
||||
|
|
@ -356,15 +368,12 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
isElementVisible(elem: Element) {
|
||||
if (elem === null || elem === undefined) { return false; }
|
||||
|
||||
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();
|
||||
|
||||
let [innerHeight, innerWidth] = this.getInnerDimensions();
|
||||
|
||||
|
||||
console.log('innerHeight: ', innerHeight);
|
||||
console.log('innerWidth: ', innerWidth);
|
||||
|
||||
return (rect.bottom >= 0 &&
|
||||
rect.right >= 0 &&
|
||||
rect.top <= (innerHeight || document.documentElement.clientHeight) &&
|
||||
|
|
@ -399,8 +408,8 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
|
||||
initWebtoonReader() {
|
||||
const [innerWidth, _] = this.getInnerDimensions();
|
||||
this.webtoonImageWidth = innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
||||
this.initFinished = false;
|
||||
this.recalculateImageWidth();
|
||||
this.imagesLoaded = {};
|
||||
this.webtoonImages.next([]);
|
||||
this.atBottom = false;
|
||||
|
|
@ -437,11 +446,14 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
.filter((img: any) => !img.complete)
|
||||
.map((img: any) => new Promise(resolve => { img.onload = img.onerror = resolve; })))
|
||||
.then(() => {
|
||||
this.debugLog('[Initialization] All images have loaded from initial prefetch, initFinished = true');
|
||||
this.debugLog('[Image Load] ! Loaded current page !', this.pageNum);
|
||||
this.currentPageElem = document.querySelector('img#page-' + this.pageNum);
|
||||
|
||||
// There needs to be a bit of time before we scroll
|
||||
if (this.currentPageElem && !this.isElementVisible(this.currentPageElem)) {
|
||||
this.scrollToCurrentPage();
|
||||
} else {
|
||||
this.initFinished = true;
|
||||
}
|
||||
|
||||
this.allImagesLoaded = true;
|
||||
|
|
@ -471,8 +483,8 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* @param scrollToPage Optional (default false) parameter to trigger scrolling to the newly set page
|
||||
*/
|
||||
setPageNum(pageNum: number, scrollToPage: boolean = false) {
|
||||
if (pageNum > this.totalPages) {
|
||||
pageNum = this.totalPages;
|
||||
if (pageNum >= this.totalPages) {
|
||||
pageNum = this.totalPages - 1;
|
||||
} else if (pageNum < 0) {
|
||||
pageNum = 0;
|
||||
}
|
||||
|
|
@ -482,9 +494,6 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.prefetchWebtoonImages();
|
||||
|
||||
if (scrollToPage) {
|
||||
const currentImage = document.querySelector('img#page-' + this.pageNum);
|
||||
if (currentImage === null) return;
|
||||
this.debugLog('[GoToPage] Scrolling to page', this.pageNum);
|
||||
this.scrollToCurrentPage();
|
||||
}
|
||||
}
|
||||
|
|
@ -499,6 +508,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
scrollToCurrentPage() {
|
||||
this.currentPageElem = document.querySelector('img#page-' + this.pageNum);
|
||||
if (!this.currentPageElem) { return; }
|
||||
this.debugLog('[GoToPage] Scrolling to page', this.pageNum);
|
||||
|
||||
// Update prevScrollPosition, so the next scroll event properly calculates direction
|
||||
this.prevScrollPosition = this.currentPageElem.getBoundingClientRect().top;
|
||||
|
|
@ -508,6 +518,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
if (this.currentPageElem) {
|
||||
this.debugLog('[Scroll] Scrolling to page ', this.pageNum);
|
||||
this.currentPageElem.scrollIntoView({behavior: 'smooth'});
|
||||
this.initFinished = true;
|
||||
}
|
||||
}, 600);
|
||||
}
|
||||
|
|
@ -540,7 +551,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
attachIntersectionObserverElem(elem: HTMLImageElement) {
|
||||
if (elem !== null) {
|
||||
this.intersectionObserver.observe(elem);
|
||||
this.debugLog('Attached Intersection Observer to page', this.readerService.imageUrlToPageNum(elem.src));
|
||||
this.debugLog('[Intersection] Attached Intersection Observer to page', this.readerService.imageUrlToPageNum(elem.src));
|
||||
} else {
|
||||
console.error('Could not attach observer on elem'); // This never happens
|
||||
}
|
||||
|
|
@ -610,6 +621,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
debugLog(message: string, extraData?: any) {
|
||||
if (!(this.debugMode & DEBUG_MODES.Logs)) return;
|
||||
|
||||
if (this.debugLogFilter.filter(str => message.replace('\t', '').startsWith(str)).length > 0) return;
|
||||
if (extraData !== undefined) {
|
||||
console.log(message, extraData);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
<canvas #content class="{{getFittingOptionClass()}} {{readerMode === READER_MODE.MANGA_LR || readerMode === READER_MODE.MANGA_UD ? '' : 'd-none'}} {{showClickOverlay ? 'blur' : ''}}"
|
||||
ondragstart="return false;" onselectstart="return false;">
|
||||
</canvas>
|
||||
<div class="webtoon-images" *ngIf="readerMode === READER_MODE.WEBTOON && !isLoading">
|
||||
<div class="webtoon-images" *ngIf="readerMode === READER_MODE.WEBTOON && !isLoading && !inSetup">
|
||||
<app-infinite-scroller [pageNum]="pageNum"
|
||||
[bufferPages]="5"
|
||||
[goToPage]="goToPageEvent"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { NavService } from '../_services/nav.service';
|
|||
import { ReadingDirection } from '../_models/preferences/reading-direction';
|
||||
import { ScalingOption } from '../_models/preferences/scaling-option';
|
||||
import { PageSplitOption } from '../_models/preferences/page-split-option';
|
||||
import { forkJoin, ReplaySubject, Subject } from 'rxjs';
|
||||
import { BehaviorSubject, forkJoin, ReplaySubject, Subject } from 'rxjs';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { KEY_CODES, UtilityService, Breakpoint } from '../shared/_services/utility.service';
|
||||
import { CircularArray } from '../shared/data-structures/circular-array';
|
||||
|
|
@ -126,7 +126,8 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
/**
|
||||
* An event emiter when a page change occurs. Used soley by the webtoon reader.
|
||||
*/
|
||||
goToPageEvent: ReplaySubject<number> = new ReplaySubject<number>();
|
||||
goToPageEvent!: BehaviorSubject<number>;
|
||||
|
||||
/**
|
||||
* An event emiter when a bookmark on a page change occurs. Used soley by the webtoon reader.
|
||||
*/
|
||||
|
|
@ -221,6 +222,10 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
* Library Type used for rendering chapter or issue
|
||||
*/
|
||||
libraryType: LibraryType = LibraryType.Manga;
|
||||
/**
|
||||
* Used for webtoon reader. When loading pages or data, this will disable the reader
|
||||
*/
|
||||
inSetup: boolean = true;
|
||||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
|
|
@ -400,6 +405,8 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.goToPage(parseInt(goToPageNum.trim(), 10));
|
||||
} else if (event.key === KEY_CODES.B) {
|
||||
this.bookmarkPage();
|
||||
} else if (event.key === KEY_CODES.F) {
|
||||
this.toggleFullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -422,6 +429,13 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.nextChapterPrefetched = false;
|
||||
this.pageNum = 0;
|
||||
this.pagingDirection = PAGING_DIRECTION.FORWARD;
|
||||
this.inSetup = true;
|
||||
|
||||
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
|
||||
// and we use a BehaviourSubject to ensure only latest value is sent
|
||||
this.goToPageEvent.complete();
|
||||
}
|
||||
|
||||
forkJoin({
|
||||
progress: this.readerService.getProgress(this.chapterId),
|
||||
|
|
@ -443,6 +457,8 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
page = this.maxPages - 1;
|
||||
}
|
||||
this.setPageNum(page);
|
||||
this.goToPageEvent = new BehaviorSubject<number>(this.pageNum);
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -451,11 +467,14 @@ 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;
|
||||
|
||||
// TODO: Move this into ChapterInfo
|
||||
this.libraryService.getLibraryType(results.chapterInfo.libraryId).pipe(take(1)).subscribe(type => {
|
||||
this.libraryType = type;
|
||||
this.updateTitle(results.chapterInfo, type);
|
||||
});
|
||||
|
||||
this.inSetup = false;
|
||||
|
||||
|
||||
|
||||
// From bookmarks, create map of pages to make lookup time O(1)
|
||||
|
|
@ -1017,7 +1036,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
this.setPageNum(page);
|
||||
this.refreshSlider.emit();
|
||||
this.goToPageEvent.next(page);
|
||||
this.goToPageEvent.next(page);
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue