Bookmark and Reader bugs (#1632)

* Updated swiper and some packages for reported security issues

* Fixed reading lists promotion not working

* Refactor RenameFileForCopy to use iterative recursion, rather than functional.

* Ensured that bookmarks are fetched and ordered by Created date.

* Fixed a bug where bookmarks were coming back in the correct order, but due to filenames, would not sort correctly.

* Default installs to Debug log level given errors users have and Debug not being too noisy

* Added jumpbar to bookmarks page

* Now added jumpbar to bookmarks

* Refactored some code into pipes and added some debug messaging for prefetcher

* Try loading next and prev chapter's first/last page to cache so it renders faster

* Updated GetImage to do a bound check on max page.

Fixed a critical bug in how manga reader updates image elements src to prefetch/load pages. I was not creating a new reference which broke Angular's ability to update DOM on changes.

* Refactored the image setting code to use a single method which tries to use a cached image always.

* Refactored code to use getPage which favors cache and simplifies image creation code
This commit is contained in:
Joe Milazzo 2022-11-02 21:10:19 -04:00 committed by GitHub
parent dab42041d5
commit 38a169818b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 317 additions and 166 deletions

View file

@ -12469,9 +12469,9 @@
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": {
"brace-expansion": "^1.1.7"
}
@ -15417,9 +15417,9 @@
"dev": true
},
"swiper": {
"version": "8.0.6",
"resolved": "https://registry.npmjs.org/swiper/-/swiper-8.0.6.tgz",
"integrity": "sha512-Ssyu1+FeNATF/G8e84QG+ZUNtUOAZ5vngdgxzczh0oWZPhGUVgkdv+BoePUuaCXLAFXnwVpNjgLIcGnxMdmWPA==",
"version": "8.4.4",
"resolved": "https://registry.npmjs.org/swiper/-/swiper-8.4.4.tgz",
"integrity": "sha512-jA/8BfOZwT8PqPSnMX0TENZYitXEhNa7ZSNj1Diqh5LZyUJoBQaZcqAiPQ/PIg1+IPaRn/V8ZYVb0nxHMh51yw==",
"requires": {
"dom7": "^4.0.4",
"ssr-window": "^4.0.2"

View file

@ -44,7 +44,7 @@
"ngx-toastr": "^14.2.1",
"requires": "^1.0.2",
"rxjs": "~7.5.4",
"swiper": "^8.0.6",
"swiper": "^8.4.4",
"tslib": "^2.3.1",
"webpack-bundle-analyzer": "^4.5.0",
"zone.js": "~0.11.4"

View file

@ -36,15 +36,15 @@ export class JumpbarService {
const removalTimes = Math.ceil(removeCount / 2);
const midPoint = Math.floor(jumpBarKeys.length / 2);
jumpBarKeysToRender.push(jumpBarKeys[0]);
this.removeFirstPartOfJumpBar(midPoint, removalTimes, jumpBarKeys, jumpBarKeysToRender);
this._removeFirstPartOfJumpBar(midPoint, removalTimes, jumpBarKeys, jumpBarKeysToRender);
jumpBarKeysToRender.push(jumpBarKeys[midPoint]);
this.removeSecondPartOfJumpBar(midPoint, removalTimes, jumpBarKeys, jumpBarKeysToRender);
this._removeSecondPartOfJumpBar(midPoint, removalTimes, jumpBarKeys, jumpBarKeysToRender);
jumpBarKeysToRender.push(jumpBarKeys[jumpBarKeys.length - 1]);
return jumpBarKeysToRender;
}
removeSecondPartOfJumpBar(midPoint: number, numberOfRemovals: number = 1, jumpBarKeys: Array<JumpKey>, jumpBarKeysToRender: Array<JumpKey>) {
_removeSecondPartOfJumpBar(midPoint: number, numberOfRemovals: number = 1, jumpBarKeys: Array<JumpKey>, jumpBarKeysToRender: Array<JumpKey>) {
const removedIndexes: Array<number> = [];
for(let removal = 0; removal < numberOfRemovals; removal++) {
let min = 100000000;
@ -62,7 +62,7 @@ export class JumpbarService {
}
}
removeFirstPartOfJumpBar(midPoint: number, numberOfRemovals: number = 1, jumpBarKeys: Array<JumpKey>, jumpBarKeysToRender: Array<JumpKey>) {
_removeFirstPartOfJumpBar(midPoint: number, numberOfRemovals: number = 1, jumpBarKeys: Array<JumpKey>, jumpBarKeysToRender: Array<JumpKey>) {
const removedIndexes: Array<number> = [];
for(let removal = 0; removal < numberOfRemovals; removal++) {
let min = 100000000;
@ -80,4 +80,35 @@ export class JumpbarService {
if (!removedIndexes.includes(i)) jumpBarKeysToRender.push(jumpBarKeys[i]);
}
}
/**
*
* @param data An array of objects
* @param keySelector A method to fetch a string from the object, which is used to classify the JumpKey
* @returns
*/
getJumpKeys(data :Array<any>, keySelector: (data: any) => string) {
const keys: {[key: string]: number} = {};
data.forEach(obj => {
let ch = keySelector(obj).charAt(0);
if (/\d|\#|!|%|@|\(|\)|\^|\.|_|\*/g.test(ch)) {
ch = '#';
}
if (!keys.hasOwnProperty(ch)) {
keys[ch] = 0;
}
keys[ch] += 1;
});
return Object.keys(keys).map(k => {
return {
key: k,
size: keys[k],
title: k.toUpperCase()
}
}).sort((a, b) => {
if (a.key < b.key) return -1;
if (a.key > b.key) return 1;
return 0;
});
}
}

View file

@ -11,6 +11,7 @@
[filterSettings]="filterSettings"
[trackByIdentity]="trackByIdentity"
[refresh]="refresh"
[jumpBarKeys]="jumpbarKeys"
(applyFilter)="updateFilter($event)"
>
<ng-template #cardItem let-item let-position="idx">

View file

@ -8,12 +8,14 @@ import { ConfirmService } from 'src/app/shared/confirm.service';
import { DownloadService } from 'src/app/shared/_services/download.service';
import { FilterUtilitiesService } from 'src/app/shared/_services/filter-utilities.service';
import { KEY_CODES } from 'src/app/shared/_services/utility.service';
import { JumpKey } from 'src/app/_models/jumpbar/jump-key';
import { PageBookmark } from 'src/app/_models/page-bookmark';
import { Pagination } from 'src/app/_models/pagination';
import { Series } from 'src/app/_models/series';
import { FilterEvent, SeriesFilter } from 'src/app/_models/series-filter';
import { Action, ActionFactoryService, ActionItem } from 'src/app/_services/action-factory.service';
import { ImageService } from 'src/app/_services/image.service';
import { JumpbarService } from 'src/app/_services/jumpbar.service';
import { ReaderService } from 'src/app/_services/reader.service';
import { SeriesService } from 'src/app/_services/series.service';
@ -32,6 +34,7 @@ export class BookmarksComponent implements OnInit, OnDestroy {
downloadingSeries: {[id: number]: boolean} = {};
clearingSeries: {[id: number]: boolean} = {};
actions: ActionItem<Series>[] = [];
jumpbarKeys: Array<JumpKey> = [];
pagination!: Pagination;
filter: SeriesFilter | undefined = undefined;
@ -50,7 +53,8 @@ export class BookmarksComponent implements OnInit, OnDestroy {
private confirmService: ConfirmService, public bulkSelectionService: BulkSelectionService,
public imageService: ImageService, private actionFactoryService: ActionFactoryService,
private router: Router, private readonly cdRef: ChangeDetectorRef,
private filterUtilityService: FilterUtilitiesService, private route: ActivatedRoute) {
private filterUtilityService: FilterUtilitiesService, private route: ActivatedRoute,
private jumpbarService: JumpbarService) {
this.filterSettings.ageRatingDisabled = true;
this.filterSettings.collectionDisabled = true;
this.filterSettings.formatDisabled = true;
@ -158,6 +162,7 @@ export class BookmarksComponent implements OnInit, OnDestroy {
const ids = Object.keys(this.seriesIds).map(k => parseInt(k, 10));
this.seriesService.getAllSeriesByIds(ids).subscribe(series => {
this.jumpbarKeys = this.jumpbarService.getJumpKeys(series, (t: Series) => t.name);
this.series = series;
this.loadingBookmarks = false;
this.cdRef.markForCheck();

View file

@ -3,13 +3,13 @@ import { Title } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { EditCollectionTagsComponent } from 'src/app/cards/_modals/edit-collection-tags/edit-collection-tags.component';
import { UtilityService } from 'src/app/shared/_services/utility.service';
import { CollectionTag } from 'src/app/_models/collection-tag';
import { JumpKey } from 'src/app/_models/jumpbar/jump-key';
import { Tag } from 'src/app/_models/tag';
import { ActionItem, ActionFactoryService, Action } from 'src/app/_services/action-factory.service';
import { CollectionTagService } from 'src/app/_services/collection-tag.service';
import { ImageService } from 'src/app/_services/image.service';
import { JumpbarService } from 'src/app/_services/jumpbar.service';
@Component({
@ -30,7 +30,7 @@ export class AllCollectionsComponent implements OnInit {
constructor(private collectionService: CollectionTagService, private router: Router,
private actionFactoryService: ActionFactoryService, private modalService: NgbModal,
private titleService: Title, private utilityService: UtilityService,
private titleService: Title, private jumpbarService: JumpbarService,
private readonly cdRef: ChangeDetectorRef, public imageSerivce: ImageService) {
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
this.titleService.setTitle('Kavita - Collections');
@ -54,7 +54,7 @@ export class AllCollectionsComponent implements OnInit {
this.collectionService.allTags().subscribe(tags => {
this.collections = tags;
this.isLoading = false;
this.jumpbarKeys = this.utilityService.getJumpKeys(tags, (t: Tag) => t.title);
this.jumpbarKeys = this.jumpbarService.getJumpKeys(tags, (t: Tag) => t.title);
this.cdRef.markForCheck();
});
}

View file

@ -21,6 +21,7 @@ import { Action, ActionFactoryService, ActionItem } from 'src/app/_services/acti
import { ActionService } from 'src/app/_services/action.service';
import { CollectionTagService } from 'src/app/_services/collection-tag.service';
import { ImageService } from 'src/app/_services/image.service';
import { JumpbarService } from 'src/app/_services/jumpbar.service';
import { EVENTS, MessageHubService } from 'src/app/_services/message-hub.service';
import { ScrollService } from 'src/app/_services/scroll.service';
import { SeriesService } from 'src/app/_services/series.service';
@ -124,7 +125,7 @@ export class CollectionDetailComponent implements OnInit, OnDestroy, AfterConten
constructor(public imageService: ImageService, private collectionService: CollectionTagService, private router: Router, private route: ActivatedRoute,
private seriesService: SeriesService, private toastr: ToastrService, private actionFactoryService: ActionFactoryService,
private modalService: NgbModal, private titleService: Title,
private modalService: NgbModal, private titleService: Title, private jumpbarService: JumpbarService,
public bulkSelectionService: BulkSelectionService, private actionService: ActionService, private messageHub: MessageHubService,
private filterUtilityService: FilterUtilitiesService, private utilityService: UtilityService, @Inject(DOCUMENT) private document: Document,
private readonly cdRef: ChangeDetectorRef, private scrollService: ScrollService) {
@ -210,7 +211,7 @@ export class CollectionDetailComponent implements OnInit, OnDestroy, AfterConten
this.seriesService.getAllSeries(undefined, undefined, this.filter).pipe(take(1)).subscribe(series => {
this.series = series.result;
this.seriesPagination = series.pagination;
this.jumpbarKeys = this.utilityService.getJumpKeys(this.series, (series: Series) => series.name);
this.jumpbarKeys = this.jumpbarService.getJumpKeys(this.series, (series: Series) => series.name);
this.isLoading = false;
window.scrollTo(0, 0);
this.cdRef.markForCheck();

View file

@ -0,0 +1,20 @@
import { Pipe, PipeTransform } from '@angular/core';
import { LayoutMode } from '../_models/layout-mode';
@Pipe({
name: 'layoutModeIcon'
})
export class LayoutModeIconPipe implements PipeTransform {
transform(layoutMode: LayoutMode): string {
switch (layoutMode) {
case LayoutMode.Single:
return 'none';
case LayoutMode.Double:
return 'double';
case LayoutMode.DoubleReversed:
return 'double-reversed';
}
}
}

View file

@ -0,0 +1,22 @@
import { Pipe, PipeTransform } from '@angular/core';
import { ReaderMode } from 'src/app/_models/preferences/reader-mode';
@Pipe({
name: 'readerModeIcon'
})
export class ReaderModeIconPipe implements PipeTransform {
transform(readerMode: ReaderMode): string {
switch(readerMode) {
case ReaderMode.LeftRight:
return 'fa-exchange-alt';
case ReaderMode.UpDown:
return 'fa-exchange-alt fa-rotate-90';
case ReaderMode.Webtoon:
return 'fa-arrows-alt-v';
default:
return '';
}
}
}

View file

@ -121,7 +121,7 @@
</div>
<div class="col">
<button class="btn btn-icon" title="Reading Mode" (click)="toggleReaderMode();resetMenuCloseTimer();">
<i class="fa {{ReaderModeIcon}}" aria-hidden="true"></i>
<i class="fa {{this.readerMode | readerModeIcon}}" aria-hidden="true"></i>
<span class="visually-hidden">Reading Mode</span>
</button>
</div>
@ -197,7 +197,6 @@
</span>
</div>
</ng-container>
<!-- <div class="{{LayoutModeIconClass}}"></div> -->
</ng-container>
<select class="form-control" id="page-fitting" formControlName="layoutMode">
<option [value]="opt.value" *ngFor="let opt of layoutModes">{{opt.text}}</option>

View file

@ -1,4 +1,4 @@
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Inject, NgZone, 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';
@ -290,9 +290,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
bookmarkPageHandler = this.bookmarkPage.bind(this);
getPageUrl = (pageNum: number) => {
getPageUrl = (pageNum: number, chapterId: number = this.chapterId) => {
if (this.bookmarkMode) return this.readerService.getBookmarkPageUrl(this.seriesId, this.user.apiKey, pageNum);
return this.readerService.getPageUrl(this.chapterId, pageNum);
return this.readerService.getPageUrl(chapterId, pageNum);
}
private readonly onDestroy = new Subject<void>();
@ -377,29 +377,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return 'right-side';
}
get LayoutModeIconClass() {
switch (this.layoutMode) {
case LayoutMode.Single:
return 'none';
case LayoutMode.Double:
return 'double';
case LayoutMode.DoubleReversed:
return 'double-reversed';
}
}
get ReaderModeIcon() {
switch(this.readerMode) {
case ReaderMode.LeftRight:
return 'fa-exchange-alt';
case ReaderMode.UpDown:
return 'fa-exchange-alt fa-rotate-90';
case ReaderMode.Webtoon:
return 'fa-arrows-alt-v';
default:
return '';
}
}
get ReaderMode() {
return ReaderMode;
@ -433,7 +410,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
private toastr: ToastrService, private memberService: MemberService,
public utilityService: UtilityService, private renderer: Renderer2,
@Inject(DOCUMENT) private document: Document, private modalService: NgbModal,
private readonly cdRef: ChangeDetectorRef) {
private readonly cdRef: ChangeDetectorRef, private readonly ngZone: NgZone) {
this.navService.hideNavBar();
this.navService.hideSideNav();
this.cdRef.markForCheck();
@ -626,6 +603,23 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
}
/**
* Gets a page from cache else gets a brand new Image
* @param pageNum Page Number to load
* @param forceNew Forces to fetch a new image
* @param chapterId ChapterId to fetch page from. Defaults to current chapterId
* @returns
*/
getPage(pageNum: number, chapterId: number = this.chapterId, forceNew: boolean = false) {
let img = this.cachedImages.find(img => this.readerService.imageUrlToPageNum(img.src) === pageNum);
if (!img || forceNew) {
img = new Image();
img.src = this.getPageUrl(this.pageNum, chapterId);
}
return img;
}
// if there is scroll room and on original, then don't paginate
checkIfPaginationAllowed() {
// This is not used atm due to the complexity it adds with keyboard.
@ -748,6 +742,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (chapterId === CHAPTER_ID_DOESNT_EXIST || chapterId === this.chapterId) {
this.nextChapterDisabled = true;
this.cdRef.markForCheck();
} else {
// Fetch the first page of next chapter
this.getPage(0, this.nextChapterId);
}
});
this.readerService.getPrevChapter(this.seriesId, this.volumeId, this.chapterId, this.readingListId).pipe(take(1)).subscribe(chapterId => {
@ -755,6 +752,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (chapterId === CHAPTER_ID_DOESNT_EXIST || chapterId === this.chapterId) {
this.prevChapterDisabled = true;
this.cdRef.markForCheck();
} else {
// Fetch the last page of prev chapter
this.getPage(1000000, this.nextChapterId);
}
});
@ -961,6 +961,10 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
}
onSwipeEvent(event: any) {
console.log('Swipe event occured: ', event);
}
handlePageChange(event: any, direction: string) {
if (this.readerMode === ReaderMode.Webtoon) {
if (direction === 'right') {
@ -1073,25 +1077,15 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
* Sets canvasImage's src to current page, but first attempts to use a pre-fetched image
*/
setCanvasImage() {
if (this.layoutMode === LayoutMode.Single) {
const img = this.cachedImages.find(img => this.readerService.imageUrlToPageNum(img.src) === this.pageNum);
if (img) {
this.canvasImage = img; // If we tried to use this for double, then the loadPage might not render correctly when switching layout mode
} else {
this.canvasImage.src = this.getPageUrl(this.pageNum);
}
} else {
this.canvasImage.src = this.getPageUrl(this.pageNum);
}
this.canvasImage = this.getPage(this.pageNum);
this.canvasImage.onload = () => {
this.cdRef.markForCheck();
this.renderPage();
};
this.cdRef.markForCheck();
}
loadNextChapter() {
if (this.nextPageDisabled || this.nextChapterDisabled || this.bookmarkMode) {
this.toastr.info('No Next Chapter');
@ -1301,18 +1295,32 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
* and also maintains page info (wide image, etc) due to onload event.
*/
prefetch() {
// NOTE: This doesn't allow for any directionality
// NOTE: This doesn't maintain 1 image behind at all times
// NOTE: I may want to provide a different prefetcher for double renderer
for(let i = 0; i <= PREFETCH_PAGES - 3; i++) {
const numOffset = this.pageNum + i;
if (numOffset > this.maxPages - 1) continue;
const index = (numOffset % this.cachedImages.length + this.cachedImages.length) % this.cachedImages.length;
if (this.readerService.imageUrlToPageNum(this.cachedImages[index].src) !== numOffset) {
this.cachedImages[index] = new Image();
this.cachedImages[index].src = this.getPageUrl(numOffset);
this.cachedImages[index].onload = () => this.cdRef.markForCheck();
this.cachedImages[index].onload = () => {
//console.log('Page ', numOffset, ' loaded');
//this.cdRef.markForCheck();
};
}
}
//console.log(this.pageNum, ' Prefetched pages: ', this.cachedImages.map(img => this.readerService.imageUrlToPageNum(img.src)));
const pages = this.cachedImages.map(img => this.readerService.imageUrlToPageNum(img.src));
const pagesBefore = pages.filter(p => p >= 0 && p < this.pageNum).length;
const pagesAfter = pages.filter(p => p >= 0 && p > this.pageNum).length;
console.log('Buffer Health: Before: ', pagesBefore, ' After: ', pagesAfter);
console.log(this.pageNum, ' Prefetched pages: ', pages.map(p => {
if (this.pageNum === p) return '[' + p + ']';
return '' + p
}));
}
@ -1327,30 +1335,54 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.setCanvasImage();
// ?! This logic is hella complex and confusing to read
// ?! We need to refactor into separate methods and keep it clean
// ?! In addition, we shouldn't update canvasImage outside of this code
if (this.layoutMode !== LayoutMode.Single) {
this.canvasImageNext.src = this.getPageUrl(this.pageNum + 1); // This needs to be capped at maxPages !this.isLastImage()
this.canvasImagePrev.src = this.getPageUrl(this.pageNum - 1);
this.canvasImageNext = new Image();
// If prev page was a spread, then we don't do + 1
console.log('Current canvas image page: ', this.readerService.imageUrlToPageNum(this.canvasImage.src));
console.log('Prev canvas image page: ', this.readerService.imageUrlToPageNum(this.canvasImage2.src));
// if (this.isWideImage(this.canvasImage2)) {
// this.canvasImagePrev = this.getPage(this.pageNum); // this.getPageUrl(this.pageNum);
// console.log('Setting Prev to ', this.pageNum);
// } else {
// this.canvasImagePrev = this.getPage(this.pageNum - 1); //this.getPageUrl(this.pageNum - 1);
// console.log('Setting Prev to ', this.pageNum - 1);
// }
// TODO: Validate this statement: This needs to be capped at maxPages !this.isLastImage()
this.canvasImageNext = this.getPage(this.pageNum + 1);
console.log('Setting Next to ', this.pageNum + 1);
this.canvasImagePrev = this.getPage(this.pageNum - 1);
console.log('Setting Prev to ', this.pageNum - 1);
if (this.pageNum + 2 < this.maxPages - 1) {
this.canvasImageAheadBy2.src = this.getPageUrl(this.pageNum + 2);
this.canvasImageAheadBy2 = this.getPage(this.pageNum + 2);
}
if (this.pageNum - 2 >= 0) {
this.canvasImageBehindBy2.src = this.getPageUrl(this.pageNum - 2 || 0);
this.canvasImageBehindBy2 = this.getPage(this.pageNum - 2 || 0);
}
if (this.ShouldRenderDoublePage || this.ShouldRenderReverseDouble) {
console.log('Rendering Double Page');
if (this.layoutMode === LayoutMode.Double) {
this.canvasImage2.src = this.canvasImageNext.src;
this.canvasImage2 = this.canvasImageNext;
} else {
this.canvasImage2.src = this.canvasImagePrev.src;
this.canvasImage2 = this.canvasImagePrev;
}
}
}
this.cdRef.markForCheck();
this.cdRef.markForCheck();
this.renderPage();
this.prefetch();
this.isLoading = false;
this.cdRef.markForCheck();
}

View file

@ -8,14 +8,18 @@ import { SharedModule } from '../shared/shared.module';
import { NgxSliderModule } from '@angular-slider/ngx-slider';
import { InfiniteScrollerComponent } from './infinite-scroller/infinite-scroller.component';
import { ReaderSharedModule } from '../reader-shared/reader-shared.module';
import { FullscreenIconPipe } from './fullscreen-icon.pipe';
import { PipeModule } from '../pipe/pipe.module';
import { FullscreenIconPipe } from './_pipes/fullscreen-icon.pipe';
import { LayoutModeIconPipe } from './_pipes/layout-mode-icon.pipe';
import { ReaderModeIconPipe } from './_pipes/reader-mode-icon.pipe';
@NgModule({
declarations: [
MangaReaderComponent,
InfiniteScrollerComponent,
FullscreenIconPipe
FullscreenIconPipe,
ReaderModeIconPipe,
LayoutModeIconPipe,
],
imports: [
CommonModule,

View file

@ -56,9 +56,7 @@ export class EditReadingListModalComponent implements OnInit {
save() {
if (this.reviewGroup.value.title.trim() === '') return;
const model = {...this.reviewGroup.value, readingListId: this.readingList.id, promoted: this.readingList.promoted, coverImageLocked: this.coverImageLocked};
const apis = [this.readingListService.update(model)];
if (this.selectedCover !== '') {
@ -77,7 +75,7 @@ export class EditReadingListModalComponent implements OnInit {
togglePromotion() {
const originalPromotion = this.readingList.promoted;
this.readingList.promoted = !this.readingList.promoted;
const model = {readingListId: this.readingList.id, promoted: this.readingList.promoted};
const model = {...this.reviewGroup.value, readingListId: this.readingList.id, promoted: this.readingList.promoted, coverImageLocked: this.coverImageLocked};
this.readingListService.update(model).subscribe(res => {
/* No Operation */
}, err => {

View file

@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { take } from 'rxjs/operators';
import { UtilityService } from 'src/app/shared/_services/utility.service';
import { JumpKey } from 'src/app/_models/jumpbar/jump-key';
import { PaginatedResult, Pagination } from 'src/app/_models/pagination';
import { ReadingList } from 'src/app/_models/reading-list';
@ -10,6 +9,7 @@ import { AccountService } from 'src/app/_services/account.service';
import { Action, ActionFactoryService, ActionItem } from 'src/app/_services/action-factory.service';
import { ActionService } from 'src/app/_services/action.service';
import { ImageService } from 'src/app/_services/image.service';
import { JumpbarService } from 'src/app/_services/jumpbar.service';
import { ReadingListService } from 'src/app/_services/reading-list.service';
@Component({
@ -28,7 +28,7 @@ export class ReadingListsComponent implements OnInit {
constructor(private readingListService: ReadingListService, public imageService: ImageService, private actionFactoryService: ActionFactoryService,
private accountService: AccountService, private toastr: ToastrService, private router: Router, private actionService: ActionService,
private utilityService: UtilityService, private readonly cdRef: ChangeDetectorRef) { }
private jumpbarService: JumpbarService, private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
@ -84,7 +84,7 @@ export class ReadingListsComponent implements OnInit {
this.readingListService.getReadingLists(true).pipe(take(1)).subscribe((readingLists: PaginatedResult<ReadingList[]>) => {
this.lists = readingLists.result;
this.pagination = readingLists.pagination;
this.jumpbarKeys = this.utilityService.getJumpKeys(readingLists.result, (rl: ReadingList) => rl.title);
this.jumpbarKeys = this.jumpbarService.getJumpKeys(readingLists.result, (rl: ReadingList) => rl.title);
this.loadingLists = false;
window.scrollTo(0, 0);
this.cdRef.markForCheck();

View file

@ -203,36 +203,4 @@ export class UtilityService {
|| document.body.clientHeight;
return [windowWidth, windowHeight];
}
/**
*
* @param data An array of objects
* @param keySelector A method to fetch a string from the object, which is used to classify the JumpKey
* @returns
*/
getJumpKeys(data :Array<any>, keySelector: (data: any) => string) {
const keys: {[key: string]: number} = {};
data.forEach(obj => {
let ch = keySelector(obj).charAt(0);
if (/\d|\#|!|%|@|\(|\)|\^|\.|_|\*/g.test(ch)) {
ch = '#';
}
if (!keys.hasOwnProperty(ch)) {
keys[ch] = 0;
}
keys[ch] += 1;
});
return Object.keys(keys).map(k => {
return {
key: k,
size: keys[k],
title: k.toUpperCase()
}
}).sort((a, b) => {
if (a.key < b.key) return -1;
if (a.key > b.key) return 1;
return 0;
});
}
}

View file

@ -15,6 +15,7 @@ import { SeriesFilter, FilterEvent } from 'src/app/_models/series-filter';
import { Action, ActionItem } from 'src/app/_services/action-factory.service';
import { ActionService } from 'src/app/_services/action.service';
import { ImageService } from 'src/app/_services/image.service';
import { JumpbarService } from 'src/app/_services/jumpbar.service';
import { MessageHubService, EVENTS } from 'src/app/_services/message-hub.service';
import { ScrollService } from 'src/app/_services/scroll.service';
import { SeriesService } from 'src/app/_services/series.service';
@ -80,7 +81,8 @@ export class WantToReadComponent implements OnInit, OnDestroy {
private seriesService: SeriesService, private titleService: Title,
public bulkSelectionService: BulkSelectionService, private actionService: ActionService, private messageHub: MessageHubService,
private filterUtilityService: FilterUtilitiesService, private utilityService: UtilityService, @Inject(DOCUMENT) private document: Document,
private readonly cdRef: ChangeDetectorRef, private scrollService: ScrollService, private hubService: MessageHubService) {
private readonly cdRef: ChangeDetectorRef, private scrollService: ScrollService, private hubService: MessageHubService,
private jumpbarService: JumpbarService) {
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
this.titleService.setTitle('Want To Read');
@ -152,7 +154,7 @@ export class WantToReadComponent implements OnInit, OnDestroy {
this.seriesService.getWantToRead(undefined, undefined, this.filter).pipe(take(1)).subscribe(paginatedList => {
this.series = paginatedList.result;
this.seriesPagination = paginatedList.pagination;
this.jumpbarKeys = this.utilityService.getJumpKeys(this.series, (series: Series) => series.name);
this.jumpbarKeys = this.jumpbarService.getJumpKeys(this.series, (series: Series) => series.name);
this.isLoading = false;
window.scrollTo(0, 0);
this.cdRef.markForCheck();