mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Webtoon Polish (#734)
* Removed some code for a feature I'm not taking further * Webtoon reader will now use fit to width instead of original to prevent overflow for high res images * Added an effect when a user bookmarks an image in the reader * Split the prefetching from the current page calculation code. Dynamically set width on images if they are wider than device screen. * Dragging on the slider now shows the value on tooltip * Slider now updates for non-webtoon reader mode when you drag it. * Fixed a bunch of package security issues * Updated dependencies for security * Tweaked the current page check to use top 25% of screen to trigger the page change.
This commit is contained in:
parent
93e5e0a68f
commit
50429777d0
133
UI/Web/package-lock.json
generated
133
UI/Web/package-lock.json
generated
@ -89,6 +89,36 @@
|
||||
"webpack-sources": "2.0.1",
|
||||
"webpack-subresource-integrity": "1.5.1",
|
||||
"worker-plugin": "5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"postcss": {
|
||||
"version": "7.0.32",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz",
|
||||
"integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^2.4.2",
|
||||
"source-map": "^0.6.1",
|
||||
"supports-color": "^6.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
|
||||
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@angular-devkit/build-optimizer": {
|
||||
@ -3252,9 +3282,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
@ -4591,9 +4621,9 @@
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||
},
|
||||
"color-string": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz",
|
||||
"integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz",
|
||||
"integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "^1.0.0",
|
||||
@ -6820,9 +6850,9 @@
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
|
||||
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
@ -9620,9 +9650,9 @@
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz",
|
||||
"integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==",
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz",
|
||||
"integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@ -9710,9 +9740,9 @@
|
||||
}
|
||||
},
|
||||
"jszip": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz",
|
||||
"integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==",
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz",
|
||||
"integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lie": "~3.3.0",
|
||||
@ -11437,6 +11467,12 @@
|
||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
|
||||
"dev": true
|
||||
},
|
||||
"picocolors": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
|
||||
"integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==",
|
||||
"dev": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
|
||||
@ -11520,14 +11556,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "7.0.32",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz",
|
||||
"integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==",
|
||||
"version": "7.0.39",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
|
||||
"integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^2.4.2",
|
||||
"source-map": "^0.6.1",
|
||||
"supports-color": "^6.1.0"
|
||||
"picocolors": "^0.2.1",
|
||||
"source-map": "^0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
@ -11535,15 +11570,6 @@
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
|
||||
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -15376,9 +15402,9 @@
|
||||
}
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
|
||||
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz",
|
||||
"integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==",
|
||||
"requires": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
@ -16664,45 +16690,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
|
||||
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^1.0.2 || 2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^3.0.0"
|
||||
}
|
||||
}
|
||||
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||
}
|
||||
},
|
||||
"wildcard": {
|
||||
|
@ -26,7 +26,7 @@
|
||||
</div>
|
||||
</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': ''}}" *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;">
|
||||
<img src="{{item.src}}" style="display: block" class="mx-auto {{pageNum === item.page && showDebugOutline() ? 'active': ''}} {{areImagesWiderThanWindow ? 'full-width' : ''}}" *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>
|
||||
<div *ngIf="atBottom" class="spacer bottom" role="alert" (click)="loadPrevChapter.emit()">
|
||||
<div>
|
||||
|
@ -21,6 +21,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
|
||||
@keyframes move-up-down {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
@ -28,4 +33,17 @@
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
.bookmark-effect {
|
||||
animation: bookmark 1s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
}
|
||||
|
||||
@keyframes bookmark {
|
||||
0%, 100% {
|
||||
filter: opacity(1);
|
||||
}
|
||||
50% {
|
||||
filter: opacity(0.25);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, R
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { BehaviorSubject, fromEvent, ReplaySubject, Subject } from 'rxjs';
|
||||
import { debounceTime, takeUntil } from 'rxjs/operators';
|
||||
import { UtilityService } from 'src/app/shared/_services/utility.service';
|
||||
import { ReaderService } from '../../_services/reader.service';
|
||||
import { PAGING_DIRECTION } from '../_models/reader-enums';
|
||||
import { WebtoonImage } from '../_models/webtoon-image';
|
||||
@ -63,6 +64,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Output() loadPrevChapter: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
@Input() goToPage: ReplaySubject<number> = new ReplaySubject<number>();
|
||||
@Input() bookmarkPage: ReplaySubject<number> = new ReplaySubject<number>();
|
||||
|
||||
/**
|
||||
* Stores and emits all the src urls
|
||||
@ -117,7 +119,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.Outline;
|
||||
|
||||
get minPageLoaded() {
|
||||
return Math.min(...Object.values(this.imagesLoaded));
|
||||
@ -127,12 +129,16 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return Math.max(...Object.values(this.imagesLoaded));
|
||||
}
|
||||
|
||||
get areImagesWiderThanWindow() {
|
||||
return this.webtoonImageWidth > (window.innerWidth || document.documentElement.clientWidth);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
constructor(private readerService: ReaderService, private renderer: Renderer2, private toastr: ToastrService) {}
|
||||
constructor(private readerService: ReaderService, private renderer: Renderer2, private utilityService: UtilityService) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.hasOwnProperty('totalPages') && changes['totalPages'].previousValue != changes['totalPages'].currentValue) {
|
||||
@ -167,6 +173,18 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.setPageNum(page, true);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.bookmarkPage) {
|
||||
this.bookmarkPage.pipe(takeUntil(this.onDestroy)).subscribe(page => {
|
||||
const image = document.querySelector('img[id^="page-' + page + '"]');
|
||||
if (image) {
|
||||
this.renderer.addClass(image, 'bookmark-effect');
|
||||
setTimeout(() => {
|
||||
this.renderer.removeClass(image, 'bookmark-effect');
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -191,6 +209,15 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
this.prevScrollPosition = verticalOffset;
|
||||
|
||||
// Use offset of the image against the scroll container to test if the most of the image is visible on the screen. We can use this
|
||||
// to mark the current page and separate the prefetching code.
|
||||
const midlineImages = Array.from(document.querySelectorAll('img[id^="page-"]'))
|
||||
.filter(entry => this.shouldElementCountAsCurrentPage(entry));
|
||||
|
||||
if (midlineImages.length > 0) {
|
||||
this.setPageNum(parseInt(midlineImages[0].getAttribute('page') || this.pageNum + '', 10));
|
||||
}
|
||||
|
||||
// Check if we hit the last page
|
||||
this.checkIfShouldTriggerContinuousReader();
|
||||
|
||||
@ -224,7 +251,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.atBottom = true;
|
||||
this.setPageNum(this.totalPages);
|
||||
// Scroll user back to original location
|
||||
this.previousScrollHeightMinusTop = document.documentElement.scrollTop;
|
||||
this.previousScrollHeightMinusTop = this.getScrollTop();
|
||||
requestAnimationFrame(() => document.documentElement.scrollTop = this.previousScrollHeightMinusTop + (SPACER_SCROLL_INTO_PX / 2));
|
||||
} else if (totalScroll >= totalHeight + SPACER_SCROLL_INTO_PX && this.atBottom) {
|
||||
// This if statement will fire once we scroll into the spacer at all
|
||||
@ -266,6 +293,41 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is any part of the element visible in the scrollport and is it above the midline trigger.
|
||||
* The midline trigger does not mean it is half of the screen. It may be top 25%.
|
||||
* @param elem HTML Element
|
||||
* @returns If above midline
|
||||
*/
|
||||
shouldElementCountAsCurrentPage(elem: Element) {
|
||||
if (elem === null || elem === undefined) { return false; }
|
||||
|
||||
var rect = elem.getBoundingClientRect();
|
||||
|
||||
if (rect.bottom >= 0 &&
|
||||
rect.right >= 0 &&
|
||||
rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
|
||||
rect.left <= (window.innerWidth || document.documentElement.clientWidth)
|
||||
) {
|
||||
const topX = (window.innerHeight || document.documentElement.clientHeight);
|
||||
const bottomX = this.getScrollTop();
|
||||
|
||||
// Check if the image is mostly above the cuttoff point
|
||||
const cuttoffPoint = bottomX - ((bottomX - topX) / 2);
|
||||
// without this, it will only trigger once you get the top of the image to the top of the screen: && rect.bottom > cuttoffPoint
|
||||
// with it, it will trigger at half way
|
||||
//console.log('Cutoff point: ', cuttoffPoint);
|
||||
//return rect.top <= cuttoffPoint ; // && rect.bottom > cuttoffPoint
|
||||
// console.log('device height: ', topX);
|
||||
// console.log('image ' + elem.getAttribute('page') + ' top: ', rect.top);
|
||||
// console.log('amount scrolled down: ', rect.top / topX);
|
||||
// console.log('cutoff point: ', cuttoffPoint);
|
||||
|
||||
return Math.abs(rect.top / topX) <= 0.25;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
initWebtoonReader() {
|
||||
this.imagesLoaded = {};
|
||||
@ -327,7 +389,9 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.debugLog('[Intersection] Page ' + imagePage + ' is visible: ', entry.isIntersecting);
|
||||
if (entry.isIntersecting) {
|
||||
this.debugLog('[Intersection] ! Page ' + imagePage + ' just entered screen');
|
||||
this.setPageNum(imagePage);
|
||||
//this.setPageNum(imagePage);
|
||||
// ?! Changing this so that this just triggers a prefetch
|
||||
this.prefetchWebtoonImages(imagePage);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -414,19 +478,23 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
calculatePrefetchIndecies() {
|
||||
calculatePrefetchIndecies(pageNum: number = -1) {
|
||||
if (pageNum == -1) {
|
||||
pageNum = this.pageNum;
|
||||
}
|
||||
|
||||
let startingIndex = 0;
|
||||
let endingIndex = 0;
|
||||
if (this.isScrollingForwards()) {
|
||||
startingIndex = Math.min(Math.max(this.pageNum - this.bufferPages, 0), this.totalPages);
|
||||
endingIndex = Math.min(Math.max(this.pageNum + this.bufferPages, 0), this.totalPages);
|
||||
startingIndex = Math.min(Math.max(pageNum - this.bufferPages, 0), this.totalPages);
|
||||
endingIndex = Math.min(Math.max(pageNum + this.bufferPages, 0), this.totalPages);
|
||||
|
||||
if (startingIndex === this.totalPages) {
|
||||
return [0, 0];
|
||||
}
|
||||
} else {
|
||||
startingIndex = Math.min(Math.max(this.pageNum - this.bufferPages, 0), this.totalPages);
|
||||
endingIndex = Math.min(Math.max(this.pageNum + this.bufferPages, 0), this.totalPages);
|
||||
startingIndex = Math.min(Math.max(pageNum - this.bufferPages, 0), this.totalPages);
|
||||
endingIndex = Math.min(Math.max(pageNum + this.bufferPages, 0), this.totalPages);
|
||||
}
|
||||
|
||||
|
||||
@ -443,8 +511,13 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return [...Array(size).keys()].map(i => i + startAt);
|
||||
}
|
||||
|
||||
prefetchWebtoonImages() {
|
||||
const [startingIndex, endingIndex] = this.calculatePrefetchIndecies();
|
||||
prefetchWebtoonImages(pageNum: number = -1) {
|
||||
|
||||
if (pageNum === -1) {
|
||||
pageNum = this.pageNum;
|
||||
}
|
||||
|
||||
const [startingIndex, endingIndex] = this.calculatePrefetchIndecies(pageNum);
|
||||
if (startingIndex === 0 && endingIndex === 0) { return; }
|
||||
|
||||
this.debugLog('\t[PREFETCH] prefetching pages: ' + startingIndex + ' to ' + endingIndex);
|
||||
|
@ -24,11 +24,19 @@
|
||||
</ng-container>
|
||||
|
||||
<div (click)="toggleMenu()" class="reading-area">
|
||||
<canvas #content class="{{getFittingOptionClass()}} {{this.colorMode}} {{readerMode === READER_MODE.MANGA_LR || readerMode === READER_MODE.MANGA_UD ? '' : 'd-none'}}"
|
||||
<canvas #content class="{{getFittingOptionClass()}} {{this.colorMode}} {{readerMode === READER_MODE.MANGA_LR || readerMode === READER_MODE.MANGA_UD ? '' : 'd-none'}} "
|
||||
ondragstart="return false;" onselectstart="return false;">
|
||||
</canvas>
|
||||
<div class="webtoon-images" *ngIf="readerMode === READER_MODE.WEBTOON && !isLoading">
|
||||
<app-infinite-scroller [pageNum]="pageNum" [bufferPages]="5" [goToPage]="goToPageEvent" (pageNumberChange)="handleWebtoonPageChange($event)" [totalPages]="maxPages - 1" [urlProvider]="getPageUrl" (loadNextChapter)="loadNextChapter()" (loadPrevChapter)="loadPrevChapter()"></app-infinite-scroller>
|
||||
<app-infinite-scroller [pageNum]="pageNum"
|
||||
[bufferPages]="5"
|
||||
[goToPage]="goToPageEvent"
|
||||
(pageNumberChange)="handleWebtoonPageChange($event)"
|
||||
[totalPages]="maxPages - 1"
|
||||
[urlProvider]="getPageUrl"
|
||||
(loadNextChapter)="loadNextChapter()"
|
||||
(loadPrevChapter)="loadPrevChapter()"
|
||||
[bookmarkPage]="showBookmarkEffectEvent"></app-infinite-scroller>
|
||||
</div>
|
||||
<ng-container *ngIf="readerMode === READER_MODE.MANGA_LR || readerMode === READER_MODE.MANGA_UD">
|
||||
<div class="{{readerMode === READER_MODE.MANGA_LR ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')"></div>
|
||||
@ -43,7 +51,7 @@
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="prevChapterDisabled" (click)="loadPrevChapter();resetMenuCloseTimer();" title="Prev Chapter/Volume"><i class="fa fa-fast-backward" aria-hidden="true"></i></button>
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="prevPageDisabled || pageNum === 0" (click)="goToPage(0);resetMenuCloseTimer();" title="First Page"><i class="fa fa-step-backward" aria-hidden="true"></i></button>
|
||||
<div class="col custom-slider" *ngIf="pageOptions.ceil > 0; else noSlider">
|
||||
<ngx-slider [options]="pageOptions" [value]="pageNum" aria-describedby="slider-info" [manualRefresh]="refreshSlider" (userChangeEnd)="sliderPageUpdate($event);startMenuCloseTimer()" (userChangeStart)="cancelMenuCloseTimer();"></ngx-slider>
|
||||
<ngx-slider [options]="pageOptions" [value]="pageNum" aria-describedby="slider-info" [manualRefresh]="refreshSlider" (userChangeEnd)="sliderPageUpdate($event);startMenuCloseTimer()" (userChange)="sliderDragUpdate($event)" (userChangeStart)="cancelMenuCloseTimer();"></ngx-slider>
|
||||
</div>
|
||||
<ng-template #noSlider>
|
||||
<div class="col custom-slider">
|
||||
|
@ -223,6 +223,7 @@ canvas {
|
||||
|
||||
.webtoon-images {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
@ -234,6 +235,20 @@ canvas {
|
||||
animation: fadein .5s both;
|
||||
}
|
||||
|
||||
|
||||
.bookmark-effect {
|
||||
animation: bookmark 1s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
}
|
||||
|
||||
@keyframes bookmark {
|
||||
0%, 100% {
|
||||
filter: opacity(1);
|
||||
}
|
||||
50% {
|
||||
filter: opacity(0.25);
|
||||
}
|
||||
}
|
||||
|
||||
// DEBUG
|
||||
.active-image {
|
||||
border: 5px solid red;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, OnDestroy, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { take, takeUntil } from 'rxjs/operators';
|
||||
@ -124,7 +124,10 @@ 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>();
|
||||
|
||||
/**
|
||||
* An event emiter when a bookmark on a page change occurs. Used soley by the webtoon reader.
|
||||
*/
|
||||
showBookmarkEffectEvent: ReplaySubject<number> = new ReplaySubject<number>();
|
||||
/**
|
||||
* If the menu is open/visible.
|
||||
*/
|
||||
@ -267,7 +270,8 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
public readerService: ReaderService, private location: Location,
|
||||
private formBuilder: FormBuilder, private navService: NavService,
|
||||
private toastr: ToastrService, private memberService: MemberService,
|
||||
private libraryService: LibraryService, private utilityService: UtilityService) {
|
||||
private libraryService: LibraryService, private utilityService: UtilityService,
|
||||
private renderer: Renderer2) {
|
||||
this.navService.hideNavBar();
|
||||
}
|
||||
|
||||
@ -354,6 +358,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
this.goToPageEvent.complete();
|
||||
this.showBookmarkEffectEvent.complete();
|
||||
}
|
||||
|
||||
@HostListener('window:keyup', ['$event'])
|
||||
@ -942,6 +947,14 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
return side === 'right' ? 'highlight-2' : 'highlight';
|
||||
}
|
||||
|
||||
sliderDragUpdate(context: ChangeContext) {
|
||||
// This will update the value for value except when in webtoon due to how the webtoon reader
|
||||
// responds to page changes
|
||||
if (this.readerMode != READER_MODE.WEBTOON) {
|
||||
this.setPageNum(context.value);
|
||||
}
|
||||
}
|
||||
|
||||
sliderPageUpdate(context: ChangeContext) {
|
||||
const page = context.value;
|
||||
|
||||
@ -1057,50 +1070,10 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.readerService.saveProgress(this.seriesId, this.volumeId, this.chapterId, this.pageNum).pipe(take(1)).subscribe(() => {/* No operation */});
|
||||
}
|
||||
|
||||
saveSettings() {
|
||||
// NOTE: This is not called anywhere
|
||||
if (this.user === undefined) return;
|
||||
|
||||
const data: Preferences = {
|
||||
readingDirection: this.readingDirection,
|
||||
scalingOption: this.scalingOption,
|
||||
pageSplitOption: this.pageSplitOption,
|
||||
autoCloseMenu: this.autoCloseMenu,
|
||||
readerMode: this.readerMode,
|
||||
|
||||
bookReaderDarkMode: this.user.preferences.bookReaderDarkMode,
|
||||
bookReaderFontFamily: this.user.preferences.bookReaderFontFamily,
|
||||
bookReaderFontSize: this.user.preferences.bookReaderFontSize,
|
||||
bookReaderLineSpacing: this.user.preferences.bookReaderLineSpacing,
|
||||
bookReaderMargin: this.user.preferences.bookReaderMargin,
|
||||
bookReaderTapToPaginate: this.user.preferences.bookReaderTapToPaginate,
|
||||
bookReaderReadingDirection: this.readingDirection,
|
||||
|
||||
siteDarkMode: this.user.preferences.siteDarkMode,
|
||||
};
|
||||
this.accountService.updatePreferences(data).pipe(take(1)).subscribe((updatedPrefs) => {
|
||||
this.toastr.success('User settings updated');
|
||||
if (this.user) {
|
||||
this.user.preferences = updatedPrefs;
|
||||
}
|
||||
this.resetSettings();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
resetSettings() {
|
||||
this.generalSettingsForm.get('fittingOption')?.value.get('fittingOption')?.setValue(this.translateScalingOption(this.user.preferences.scalingOption));
|
||||
this.generalSettingsForm.get('pageSplitOption')?.setValue(this.user.preferences.pageSplitOption + '');
|
||||
this.generalSettingsForm.get('autoCloseMenu')?.setValue(this.autoCloseMenu);
|
||||
|
||||
this.updateForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bookmarks the current page for the chapter
|
||||
*/
|
||||
bookmarkPage() {
|
||||
// TODO: Show some sort of UI visual to show that a page was bookmarked
|
||||
const pageNum = this.pageNum;
|
||||
if (this.pageBookmarked) {
|
||||
this.readerService.unbookmark(this.seriesId, this.volumeId, this.chapterId, pageNum).pipe(take(1)).subscribe(() => {
|
||||
@ -1111,7 +1084,17 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.bookmarks[pageNum] = 1;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Show an effect on the image to show that it was bookmarked
|
||||
this.showBookmarkEffectEvent.next(pageNum);
|
||||
if (this.readerMode != READER_MODE.WEBTOON) {
|
||||
if (this.canvas) {
|
||||
this.renderer.addClass(this.canvas?.nativeElement, 'bookmark-effect');
|
||||
setTimeout(() => {
|
||||
this.renderer.removeClass(this.canvas?.nativeElement, 'bookmark-effect');
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user