Angular 16 (#2007)

* Removed adv, which isn't needed.

* Updated zone

* Updated to angular 16

* Updated to angular 16 (partially)

* Updated to angular 16

* Package update for Angular 16 (and other dependencies) is complete.

* Replaced all takeUntil(this.onDestroy) with new takeUntilDestroyed()

* Updated all inputs that have ! to be required and deleted all unit tests.

* Corrected how takeUntilDestroyed() is supposed to be implemented.
This commit is contained in:
Joe Milazzo 2023-05-21 12:30:32 -05:00 committed by GitHub
parent 9bc8361381
commit 9c06cccd35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
87 changed files with 3964 additions and 20426 deletions

View file

@ -15,7 +15,7 @@ import { CollectionTagService } from 'src/app/_services/collection-tag.service';
})
export class BulkAddToCollectionComponent implements OnInit, AfterViewInit {
@Input() title!: string;
@Input({required: true}) title!: string;
/**
* Series Ids to add to Collection Tag
*/
@ -33,14 +33,14 @@ export class BulkAddToCollectionComponent implements OnInit, AfterViewInit {
@ViewChild('title') inputElem!: ElementRef<HTMLInputElement>;
constructor(private modal: NgbActiveModal, private collectionService: CollectionTagService,
constructor(private modal: NgbActiveModal, private collectionService: CollectionTagService,
private toastr: ToastrService, private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
this.listForm.addControl('title', new FormControl(this.title, []));
this.listForm.addControl('filterQuery', new FormControl('', []));
this.loading = true;
this.cdRef.markForCheck();
this.collectionService.allTags().subscribe(tags => {
@ -77,7 +77,7 @@ export class BulkAddToCollectionComponent implements OnInit, AfterViewInit {
this.toastr.success('Series added to ' + tag.title + ' collection');
this.modal.close();
});
}
filterList = (listItem: ReadingList) => {

View file

@ -1,4 +1,13 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
inject,
Input,
OnDestroy,
OnInit
} from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
@ -14,6 +23,7 @@ import { ImageService } from 'src/app/_services/image.service';
import { LibraryService } from 'src/app/_services/library.service';
import { SeriesService } from 'src/app/_services/series.service';
import { UploadService } from 'src/app/_services/upload.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
enum TabID {
@ -28,9 +38,9 @@ enum TabID {
styleUrls: ['./edit-collection-tags.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EditCollectionTagsComponent implements OnInit, OnDestroy {
export class EditCollectionTagsComponent implements OnInit {
@Input() tag!: CollectionTag;
@Input({required: true}) tag!: CollectionTag;
series: Array<Series> = [];
selections!: SelectionModel<Series>;
isLoading: boolean = true;
@ -42,8 +52,7 @@ export class EditCollectionTagsComponent implements OnInit, OnDestroy {
active = TabID.General;
imageUrls: Array<string> = [];
selectedCover: string = '';
private readonly onDestroy = new Subject<void>();
private readonly destroyRef = inject(DestroyRef);
get hasSomeSelected() {
return this.selections != null && this.selections.hasSomeSelected();
@ -57,7 +66,7 @@ export class EditCollectionTagsComponent implements OnInit, OnDestroy {
return TabID;
}
constructor(public modal: NgbActiveModal, private seriesService: SeriesService,
constructor(public modal: NgbActiveModal, private seriesService: SeriesService,
private collectionService: CollectionTagService, private toastr: ToastrService,
private confirmSerivce: ConfirmService, private libraryService: LibraryService,
private imageService: ImageService, private uploadService: UploadService,
@ -76,7 +85,7 @@ export class EditCollectionTagsComponent implements OnInit, OnDestroy {
});
this.collectionTagForm.get('title')?.valueChanges.pipe(
debounceTime(100),
debounceTime(100),
distinctUntilChanged(),
switchMap(name => this.collectionService.tagNameExists(name)),
tap(exists => {
@ -84,22 +93,17 @@ export class EditCollectionTagsComponent implements OnInit, OnDestroy {
if (!exists || isExistingName) {
this.collectionTagForm.get('title')?.setErrors(null);
} else {
this.collectionTagForm.get('title')?.setErrors({duplicateName: true})
this.collectionTagForm.get('title')?.setErrors({duplicateName: true})
}
this.cdRef.markForCheck();
}),
takeUntil(this.onDestroy)
takeUntilDestroyed(this.destroyRef)
).subscribe();
this.imageUrls.push(this.imageService.randomize(this.imageService.getCollectionCoverImage(this.tag.id)));
this.loadSeries();
}
ngOnDestroy() {
this.onDestroy.next();
this.onDestroy.complete();
}
onPageChange(pageNum: number) {
this.pagination.currentPage = pageNum;
this.loadSeries();
@ -153,7 +157,7 @@ export class EditCollectionTagsComponent implements OnInit, OnDestroy {
const unselectedIds = this.selections.unselected().map(s => s.id);
const tag = this.collectionTagForm.value;
tag.id = this.tag.id;
if (unselectedIds.length == this.series.length && !await this.confirmSerivce.confirm('Warning! No series are selected, saving will delete the tag. Are you sure you want to continue?')) {
return;
}
@ -162,11 +166,11 @@ export class EditCollectionTagsComponent implements OnInit, OnDestroy {
this.collectionService.updateTag(tag),
this.collectionService.updateSeriesForTag(tag, this.selections.unselected().map(s => s.id))
];
if (selectedIndex > 0) {
apis.push(this.uploadService.updateCollectionCoverImage(this.tag.id, this.selectedCover));
}
forkJoin(apis).subscribe(() => {
this.modal.close({success: true, coverImageUpdated: selectedIndex > 0});
this.toastr.success('Tag updated');

View file

@ -1,4 +1,13 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, DestroyRef,
EventEmitter,
inject,
Input,
OnDestroy,
OnInit
} from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { forkJoin, Observable, of, Subject } from 'rxjs';
@ -21,6 +30,7 @@ import { LibraryService } from 'src/app/_services/library.service';
import { MetadataService } from 'src/app/_services/metadata.service';
import { SeriesService } from 'src/app/_services/series.service';
import { UploadService } from 'src/app/_services/upload.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
enum TabID {
General = 0,
@ -38,9 +48,9 @@ enum TabID {
styleUrls: ['./edit-series-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EditSeriesModalComponent implements OnInit, OnDestroy {
export class EditSeriesModalComponent implements OnInit {
@Input() series!: Series;
@Input({required: true}) series!: Series;
seriesVolumes: any[] = [];
isLoadingVolumes = false;
/**
@ -56,9 +66,7 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
editSeriesForm!: FormGroup;
libraryName: string | undefined = undefined;
size: number = 0;
private readonly onDestroy = new Subject<void>();
private readonly destroyRef = inject(DestroyRef);
// Typeaheads
ageRatingSettings: TypeaheadSettings<AgeRatingDto> = new TypeaheadSettings();
@ -117,16 +125,16 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
private libraryService: LibraryService,
private collectionService: CollectionTagService,
private uploadService: UploadService,
private metadataService: MetadataService,
private metadataService: MetadataService,
private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
this.imageUrls.push(this.imageService.getSeriesCoverImage(this.series.id));
this.libraryService.getLibraryNames().pipe(takeUntil(this.onDestroy)).subscribe(names => {
this.libraryService.getLibraryNames().pipe(takeUntilDestroyed(this.destroyRef)).subscribe(names => {
this.libraryName = names[this.series.libraryId];
});
this.initSeries = Object.assign({}, this.series);
this.editSeriesForm = this.fb.group({
@ -180,41 +188,41 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
this.cdRef.markForCheck();
this.editSeriesForm.get('name')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(val => {
this.editSeriesForm.get('name')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
this.series.nameLocked = true;
this.cdRef.markForCheck();
});
this.editSeriesForm.get('sortName')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(val => {
this.editSeriesForm.get('sortName')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
this.series.sortNameLocked = true;
this.cdRef.markForCheck();
});
this.editSeriesForm.get('localizedName')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(val => {
this.editSeriesForm.get('localizedName')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
this.series.localizedNameLocked = true;
this.cdRef.markForCheck();
});
this.editSeriesForm.get('summary')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(val => {
this.editSeriesForm.get('summary')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
this.metadata.summaryLocked = true;
this.metadata.summary = val;
this.cdRef.markForCheck();
});
this.editSeriesForm.get('ageRating')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(val => {
this.editSeriesForm.get('ageRating')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
this.metadata.ageRating = parseInt(val + '', 10);
this.metadata.ageRatingLocked = true;
this.cdRef.markForCheck();
});
this.editSeriesForm.get('publicationStatus')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(val => {
this.editSeriesForm.get('publicationStatus')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
this.metadata.publicationStatus = parseInt(val + '', 10);
this.metadata.publicationStatusLocked = true;
this.cdRef.markForCheck();
});
this.editSeriesForm.get('releaseYear')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(val => {
this.editSeriesForm.get('releaseYear')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
this.metadata.releaseYear = parseInt(val + '', 10);
this.metadata.releaseYearLocked = true;
this.cdRef.markForCheck();
@ -257,10 +265,6 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
});
}
ngOnDestroy() {
this.onDestroy.next();
this.onDestroy.complete();
}
setupTypeaheads() {
forkJoin([
@ -490,7 +494,7 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
.filter(v => v !== null && v !== '')
.join(',');
const apis = [
this.seriesService.updateMetadata(this.metadata, this.collectionTags)

View file

@ -1,7 +1,17 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
inject,
Input,
OnDestroy,
OnInit
} from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { Action, ActionFactoryService, ActionItem } from 'src/app/_services/action-factory.service';
import { BulkSelectionService } from '../bulk-selection.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Component({
selector: 'app-bulk-operations',
@ -9,16 +19,15 @@ import { BulkSelectionService } from '../bulk-selection.service';
styleUrls: ['./bulk-operations.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BulkOperationsComponent implements OnInit, OnDestroy {
export class BulkOperationsComponent implements OnInit {
@Input() actionCallback!: (action: ActionItem<any>, data: any) => void;
@Input({required: true}) actionCallback!: (action: ActionItem<any>, data: any) => void;
topOffset: number = 56;
hasMarkAsRead: boolean = false;
hasMarkAsUnread: boolean = false;
actions: Array<ActionItem<any>> = [];
private onDestory: Subject<void> = new Subject();
private readonly destroyRef = inject(DestroyRef);
get Action() {
return Action;
@ -28,7 +37,7 @@ export class BulkOperationsComponent implements OnInit, OnDestroy {
private actionFactoryService: ActionFactoryService) { }
ngOnInit(): void {
this.bulkSelectionService.actions$.pipe(takeUntil(this.onDestory)).subscribe(actions => {
this.bulkSelectionService.actions$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(actions => {
// We need to do a recursive callback apply
this.actions = this.actionFactoryService.applyCallbackToList(actions, this.actionCallback.bind(this));
this.hasMarkAsRead = this.actionFactoryService.hasAction(this.actions, Action.MarkAsRead);
@ -37,11 +46,6 @@ export class BulkOperationsComponent implements OnInit, OnDestroy {
});
}
ngOnDestroy(): void {
this.onDestory.next();
this.onDestory.complete();
}
handleActionCallback(action: ActionItem<any>, data: any) {
this.actionCallback(action, data);
}

View file

@ -1,4 +1,13 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
inject,
Input,
OnDestroy,
OnInit
} from '@angular/core';
import { Router } from '@angular/router';
import { NgbActiveOffcanvas } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
@ -24,9 +33,10 @@ import { MetadataService } from 'src/app/_services/metadata.service';
import { ReaderService } from 'src/app/_services/reader.service';
import { SeriesService } from 'src/app/_services/series.service';
import { UploadService } from 'src/app/_services/upload.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
enum TabID {
General = 0,
General = 0,
Metadata = 1,
Cover = 2,
Files = 3
@ -38,13 +48,14 @@ enum TabID {
styleUrls: ['./card-detail-drawer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CardDetailDrawerComponent implements OnInit, OnDestroy {
export class CardDetailDrawerComponent implements OnInit {
@Input() parentName = '';
@Input() seriesId: number = 0;
@Input() libraryId: number = 0;
@Input() data!: Volume | Chapter;
@Input({required: true}) data!: Volume | Chapter;
private readonly destroyRef = inject(DestroyRef);
/**
* If this is a volume, this will be first chapter for said volume.
*/
@ -63,7 +74,7 @@ export class CardDetailDrawerComponent implements OnInit, OnDestroy {
actions: ActionItem<any>[] = [];
chapterActions: ActionItem<Chapter>[] = [];
libraryType: LibraryType = LibraryType.Manga;
libraryType: LibraryType = LibraryType.Manga;
tabs = [{title: 'General', disabled: false}, {title: 'Metadata', disabled: false}, {title: 'Cover', disabled: false}, {title: 'Info', disabled: false}];
@ -74,8 +85,6 @@ export class CardDetailDrawerComponent implements OnInit, OnDestroy {
download$: Observable<Download> | null = null;
downloadInProgress: boolean = false;
private readonly onDestroy = new Subject<void>();
get MangaFormat() {
return MangaFormat;
@ -97,16 +106,15 @@ export class CardDetailDrawerComponent implements OnInit, OnDestroy {
return TabID;
}
constructor(public utilityService: UtilityService,
public imageService: ImageService, private uploadService: UploadService, private toastr: ToastrService,
private accountService: AccountService, private actionFactoryService: ActionFactoryService,
constructor(public utilityService: UtilityService,
public imageService: ImageService, private uploadService: UploadService, private toastr: ToastrService,
private accountService: AccountService, private actionFactoryService: ActionFactoryService,
private actionService: ActionService, private router: Router, private libraryService: LibraryService,
private seriesService: SeriesService, private readerService: ReaderService, public metadataService: MetadataService,
public activeOffcanvas: NgbActiveOffcanvas, private downloadService: DownloadService, private readonly cdRef: ChangeDetectorRef,
private deviceSerivce: DeviceService) {
private seriesService: SeriesService, private readerService: ReaderService,
public activeOffcanvas: NgbActiveOffcanvas, private downloadService: DownloadService, private readonly cdRef: ChangeDetectorRef) {
this.isAdmin$ = this.accountService.currentUser$.pipe(
takeUntil(this.onDestroy),
map(user => (user && this.accountService.hasAdminRole(user)) || false),
takeUntilDestroyed(this.destroyRef),
map(user => (user && this.accountService.hasAdminRole(user)) || false),
shareReplay()
);
}
@ -133,7 +141,7 @@ export class CardDetailDrawerComponent implements OnInit, OnDestroy {
this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this))
.filter(item => item.action !== Action.Edit);
this.chapterActions.push({title: 'Read', action: Action.Read, callback: this.handleChapterActionCallback.bind(this), requiresAdmin: false, children: []});
this.chapterActions.push({title: 'Read', action: Action.Read, callback: this.handleChapterActionCallback.bind(this), requiresAdmin: false, children: []});
if (this.isChapter) {
const chapter = this.utilityService.asChapter(this.data);
this.chapterActions = this.actionFactoryService.filterSendToAction(this.chapterActions, chapter);
@ -146,7 +154,7 @@ export class CardDetailDrawerComponent implements OnInit, OnDestroy {
this.cdRef.markForCheck();
});
var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
this.chapters.forEach((c: Chapter) => {
c.files.sort((a: MangaFile, b: MangaFile) => collator.compare(a.filePath, b.filePath));
@ -157,10 +165,6 @@ export class CardDetailDrawerComponent implements OnInit, OnDestroy {
this.cdRef.markForCheck();
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();
}
close() {
this.activeOffcanvas.close();
@ -193,7 +197,7 @@ export class CardDetailDrawerComponent implements OnInit, OnDestroy {
if (this.seriesId === 0) {
return;
}
this.actionService.markChapterAsRead(this.libraryId, this.seriesId, chapter, () => { this.cdRef.markForCheck(); });
}

View file

@ -116,7 +116,7 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy, OnChanges {
ngOnChanges(): void {
this.jumpBarKeysToRender = [...this.jumpBarKeys];
this.resizeJumpBar();
// Don't resume jump key when there is a custom sort order, as it won't work
if (!this.hasCustomSort()) {
if (!this.hasResumedJumpKey && this.jumpBarKeysToRender.length > 0) {
@ -124,7 +124,7 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy, OnChanges {
if (resumeKey === '') return;
const keys = this.jumpBarKeysToRender.filter(k => k.key === resumeKey);
if (keys.length < 1) return;
this.hasResumedJumpKey = true;
setTimeout(() => this.scrollTo(keys[0]), 100);
}

View file

@ -1,4 +1,15 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, DestroyRef,
EventEmitter,
HostListener,
inject,
Input,
OnDestroy,
OnInit,
Output
} from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { filter, map, takeUntil } from 'rxjs/operators';
import { DownloadEvent, DownloadService } from 'src/app/shared/_services/download.service';
@ -19,6 +30,7 @@ import { LibraryService } from 'src/app/_services/library.service';
import { EVENTS, MessageHubService } from 'src/app/_services/message-hub.service';
import { ScrollService } from 'src/app/_services/scroll.service';
import { BulkSelectionService } from '../bulk-selection.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Component({
selector: 'app-card-item',
@ -26,7 +38,7 @@ import { BulkSelectionService } from '../bulk-selection.service';
styleUrls: ['./card-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CardItemComponent implements OnInit, OnDestroy {
export class CardItemComponent implements OnInit {
/**
* Card item url. Will internally handle error and missing covers
@ -59,7 +71,7 @@ export class CardItemComponent implements OnInit, OnDestroy {
/**
* This is the entity we are representing. It will be returned if an action is executed.
*/
@Input() entity!: Series | Volume | Chapter | CollectionTag | PageBookmark | RecentlyAddedItem;
@Input({required: true}) entity!: Series | Volume | Chapter | CollectionTag | PageBookmark | RecentlyAddedItem;
/**
* If the entity is selected or not.
*/
@ -115,17 +127,17 @@ export class CardItemComponent implements OnInit, OnDestroy {
selectionInProgress: boolean = false;
private user: User | undefined;
private readonly destroyRef = inject(DestroyRef);
get MangaFormat(): typeof MangaFormat {
return MangaFormat;
}
private readonly onDestroy = new Subject<void>();
constructor(public imageService: ImageService, private libraryService: LibraryService,
public utilityService: UtilityService, private downloadService: DownloadService,
public bulkSelectionService: BulkSelectionService,
private messageHub: MessageHubService, private accountService: AccountService,
private messageHub: MessageHubService, private accountService: AccountService,
private scrollService: ScrollService, private readonly cdRef: ChangeDetectorRef,
private actionFactoryService: ActionFactoryService) {}
@ -136,14 +148,14 @@ export class CardItemComponent implements OnInit, OnDestroy {
this.cdRef.markForCheck();
}
if (this.suppressLibraryLink === false) {
if (!this.suppressLibraryLink) {
if (this.entity !== undefined && this.entity.hasOwnProperty('libraryId')) {
this.libraryId = (this.entity as Series).libraryId;
this.cdRef.markForCheck();
}
if (this.libraryId !== undefined && this.libraryId > 0) {
this.libraryService.getLibraryName(this.libraryId).pipe(takeUntil(this.onDestroy)).subscribe(name => {
this.libraryService.getLibraryName(this.libraryId).pipe(takeUntilDestroyed(this.destroyRef)).subscribe(name => {
this.libraryName = name;
this.cdRef.markForCheck();
});
@ -176,18 +188,18 @@ export class CardItemComponent implements OnInit, OnDestroy {
}
this.filterSendTo();
this.accountService.currentUser$.pipe(takeUntil(this.onDestroy)).subscribe(user => {
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
this.user = user;
});
this.messageHub.messages$.pipe(filter(event => event.event === EVENTS.UserProgressUpdate),
map(evt => evt.payload as UserProgressUpdateEvent), takeUntil(this.onDestroy)).subscribe(updateEvent => {
map(evt => evt.payload as UserProgressUpdateEvent), takeUntilDestroyed(this.destroyRef)).subscribe(updateEvent => {
if (this.user === undefined || this.user.username !== updateEvent.username) return;
if (this.utilityService.isChapter(this.entity) && updateEvent.chapterId !== this.entity.id) return;
if (this.utilityService.isVolume(this.entity) && updateEvent.volumeId !== this.entity.id) return;
if (this.utilityService.isSeries(this.entity) && updateEvent.seriesId !== this.entity.id) return;
// For volume or Series, we can't just take the event
// For volume or Series, we can't just take the event
if (this.utilityService.isChapter(this.entity)) {
const c = this.utilityService.asChapter(this.entity);
c.pagesRead = updateEvent.pagesRead;
@ -212,7 +224,7 @@ export class CardItemComponent implements OnInit, OnDestroy {
this.cdRef.detectChanges();
});
this.download$ = this.downloadService.activeDownloads$.pipe(takeUntil(this.onDestroy), map((events) => {
this.download$ = this.downloadService.activeDownloads$.pipe(takeUntilDestroyed(this.destroyRef), map((events) => {
if(this.utilityService.isSeries(this.entity)) return events.find(e => e.entityType === 'series' && e.subTitle === this.downloadService.downloadSubtitle('series', (this.entity as Series))) || null;
if(this.utilityService.isVolume(this.entity)) return events.find(e => e.entityType === 'volume' && e.subTitle === this.downloadService.downloadSubtitle('volume', (this.entity as Volume))) || null;
if(this.utilityService.isChapter(this.entity)) return events.find(e => e.entityType === 'chapter' && e.subTitle === this.downloadService.downloadSubtitle('chapter', (this.entity as Chapter))) || null;
@ -223,10 +235,6 @@ export class CardItemComponent implements OnInit, OnDestroy {
}
ngOnDestroy() {
this.onDestroy.next();
this.onDestroy.complete();
}
@HostListener('touchmove', ['$event'])
onTouchMove(event: TouchEvent) {

View file

@ -14,7 +14,7 @@ export class DownloadIndicatorComponent {
/**
* Observable that represents when the download completes
*/
@Input() download$!: Observable<Download | DownloadEvent | null> | null;
@Input({required: true}) download$!: Observable<Download | DownloadEvent | null> | null;
constructor() { }
}

View file

@ -1,4 +1,14 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, DestroyRef,
EventEmitter,
inject,
Input,
OnDestroy,
OnInit,
Output
} from '@angular/core';
import { FormControl } from '@angular/forms';
import { map, Subject, Observable, of, firstValueFrom, takeUntil, ReplaySubject } from 'rxjs';
import { UtilityService } from 'src/app/shared/_services/utility.service';
@ -10,6 +20,7 @@ import { ImageService } from 'src/app/_services/image.service';
import { LibraryService } from 'src/app/_services/library.service';
import { SearchService } from 'src/app/_services/search.service';
import { SeriesService } from 'src/app/_services/series.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
interface RelationControl {
series: {id: number, name: string} | undefined; // Will add type as well
@ -23,11 +34,11 @@ interface RelationControl {
styleUrls: ['./edit-series-relation.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EditSeriesRelationComponent implements OnInit, OnDestroy {
export class EditSeriesRelationComponent implements OnInit {
@Input() series!: Series;
@Input({required: true}) series!: Series;
/**
* This will tell the component to save based on it's internal state
* This will tell the component to save based on its internal state
*/
@Input() save: EventEmitter<void> = new EventEmitter();
@ -39,15 +50,14 @@ export class EditSeriesRelationComponent implements OnInit, OnDestroy {
libraryNames: {[key:number]: string} = {};
focusTypeahead = new EventEmitter();
private readonly destroyRef = inject(DestroyRef);
get RelationKind() {
return RelationKind;
}
private onDestroy: Subject<void> = new Subject<void>();
constructor(private seriesService: SeriesService, private utilityService: UtilityService,
constructor(private seriesService: SeriesService, private utilityService: UtilityService,
public imageService: ImageService, private libraryService: LibraryService, private searchService: SearchService,
private readonly cdRef: ChangeDetectorRef) {}
@ -74,12 +84,7 @@ export class EditSeriesRelationComponent implements OnInit, OnDestroy {
this.cdRef.markForCheck();
});
this.save.pipe(takeUntil(this.onDestroy)).subscribe(() => this.saveState());
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();
this.save.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.saveState());
}
setupRelationRows(relations: Array<Series>, kind: RelationKind) {

View file

@ -19,9 +19,9 @@ import { ImageService } from 'src/app/_services/image.service';
})
export class EntityInfoCardsComponent implements OnInit, OnDestroy {
@Input() entity!: Volume | Chapter;
@Input({required: true}) entity!: Volume | Chapter;
/**
* This will pull extra information
* This will pull extra information
*/
@Input() includeMetadata: boolean = false;
@ -84,19 +84,19 @@ export class EntityInfoCardsComponent implements OnInit, OnDestroy {
this.cdRef.markForCheck();
});
}
this.totalPages = this.chapter.pages;
if (!this.isChapter) {
this.totalPages = this.utilityService.asVolume(this.entity).pages;
}
this.totalWordCount = this.chapter.wordCount;
if (!this.isChapter) {
this.totalWordCount = this.utilityService.asVolume(this.entity).chapters.map(c => c.wordCount).reduce((sum, d) => sum + d);
}
if (this.isChapter) {
this.readingTime.minHours = this.chapter.minHoursToRead;
this.readingTime.maxHours = this.chapter.maxHoursToRead;

View file

@ -17,13 +17,13 @@ export class EntityTitleComponent implements OnInit {
*/
@Input() libraryType: LibraryType = LibraryType.Manga;
@Input() seriesName: string = '';
@Input() entity!: Volume | Chapter;
@Input({required: true}) entity!: Volume | Chapter;
/**
* When generating the title, should this prepend 'Volume number' before the Chapter wording
*/
@Input() includeVolume: boolean = false;
/**
* When a titleName (aka a title) is avaliable on the entity, show it over Volume X Chapter Y
* When a titleName (aka a title) is available on the entity, show it over Volume X Chapter Y
*/
@Input() prioritizeTitleName: boolean = true;

View file

@ -1,4 +1,14 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, DestroyRef,
EventEmitter,
inject,
Input,
OnDestroy,
OnInit,
Output
} from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { map, Observable, Subject, takeUntil } from 'rxjs';
import { Download } from 'src/app/shared/_models/download';
@ -9,6 +19,7 @@ import { LibraryType } from 'src/app/_models/library';
import { RelationKind } from 'src/app/_models/series-detail/relation-kind';
import { Volume } from 'src/app/_models/volume';
import { Action, ActionItem } from 'src/app/_services/action-factory.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Component({
selector: 'app-list-item',
@ -16,12 +27,12 @@ import { Action, ActionItem } from 'src/app/_services/action-factory.service';
styleUrls: ['./list-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ListItemComponent implements OnInit, OnDestroy {
export class ListItemComponent implements OnInit {
/**
* Volume or Chapter to render
*/
@Input() entity!: Volume | Chapter;
@Input({required: true}) entity!: Volume | Chapter;
/**
* Image to show
*/
@ -59,7 +70,7 @@ export class ListItemComponent implements OnInit, OnDestroy {
*/
@Input() includeVolume: boolean = false;
/**
* Show's the title if avaible on entity
* Show's the title if available on entity
*/
@Input() showTitle: boolean = true;
/**
@ -68,24 +79,23 @@ export class ListItemComponent implements OnInit, OnDestroy {
@Input() blur: boolean = false;
@Output() read: EventEmitter<void> = new EventEmitter<void>();
private readonly destroyRef = inject(DestroyRef);
actionInProgress: boolean = false;
summary: string = '';
isChapter: boolean = false;
download$: Observable<DownloadEvent | null> | null = null;
downloadInProgress: boolean = false;
private readonly onDestroy = new Subject<void>();
get Title() {
if (this.isChapter) return (this.entity as Chapter).titleName;
return '';
}
constructor(private utilityService: UtilityService, private downloadService: DownloadService,
constructor(private utilityService: UtilityService, private downloadService: DownloadService,
private toastr: ToastrService, private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
@ -99,21 +109,16 @@ export class ListItemComponent implements OnInit, OnDestroy {
this.cdRef.markForCheck();
this.download$ = this.downloadService.activeDownloads$.pipe(takeUntil(this.onDestroy), map((events) => {
this.download$ = this.downloadService.activeDownloads$.pipe(takeUntilDestroyed(this.destroyRef), map((events) => {
if(this.utilityService.isVolume(this.entity)) return events.find(e => e.entityType === 'volume' && e.subTitle === this.downloadService.downloadSubtitle('volume', (this.entity as Volume))) || null;
if(this.utilityService.isChapter(this.entity)) return events.find(e => e.entityType === 'chapter' && e.subTitle === this.downloadService.downloadSubtitle('chapter', (this.entity as Chapter))) || null;
return null;
}));
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();
}
performAction(action: ActionItem<any>) {
if (action.action == Action.Download) {
if (this.downloadInProgress === true) {
if (this.downloadInProgress) {
this.toastr.info('Download is already in progress. Please wait.');
return;
}

View file

@ -18,11 +18,11 @@ import { RelationKind } from 'src/app/_models/series-detail/relation-kind';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SeriesCardComponent implements OnInit, OnChanges, OnDestroy {
@Input() data!: Series;
@Input({required: true}) data!: Series;
@Input() libraryId = 0;
@Input() suppressLibraryLink = false;
/**
* If the entity is selected or not.
* If the entity is selected or not.
*/
@Input() selected: boolean = false;
/**
@ -51,7 +51,7 @@ export class SeriesCardComponent implements OnInit, OnChanges, OnDestroy {
constructor(private router: Router, private cdRef: ChangeDetectorRef,
private seriesService: SeriesService, private toastr: ToastrService,
private modalService: NgbModal, private imageService: ImageService,
private modalService: NgbModal, private imageService: ImageService,
private actionFactoryService: ActionFactoryService,
private actionService: ActionService) {}
@ -157,7 +157,7 @@ export class SeriesCardComponent implements OnInit, OnChanges, OnDestroy {
this.data.pagesRead = 0;
this.cdRef.markForCheck();
}
this.dataChanged.emit(series);
});
}

View file

@ -1,4 +1,15 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, DestroyRef,
EventEmitter,
inject,
Input,
OnChanges,
OnDestroy,
OnInit,
Output
} from '@angular/core';
import { debounceTime, filter, map, Subject, takeUntil } from 'rxjs';
import { FilterQueryParam } from 'src/app/shared/_services/filter-utilities.service';
import { UtilityService } from 'src/app/shared/_services/utility.service';
@ -11,6 +22,7 @@ import { AccountService } from 'src/app/_services/account.service';
import { EVENTS, MessageHubService } from 'src/app/_services/message-hub.service';
import { MetadataService } from 'src/app/_services/metadata.service';
import { ReaderService } from 'src/app/_services/reader.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Component({
selector: 'app-series-info-cards',
@ -18,10 +30,10 @@ import { ReaderService } from 'src/app/_services/reader.service';
styleUrls: ['./series-info-cards.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SeriesInfoCardsComponent implements OnInit, OnChanges, OnDestroy {
export class SeriesInfoCardsComponent implements OnInit, OnChanges {
@Input() series!: Series;
@Input() seriesMetadata!: SeriesMetadata;
@Input({required: true}) series!: Series;
@Input({required: true}) seriesMetadata!: SeriesMetadata;
@Input() hasReadingProgress: boolean = false;
@Input() readingTimeLeft: HourEstimateRange | undefined;
/**
@ -31,8 +43,7 @@ export class SeriesInfoCardsComponent implements OnInit, OnChanges, OnDestroy {
@Output() goTo: EventEmitter<{queryParamName: FilterQueryParam, filter: any}> = new EventEmitter();
readingTime: HourEstimateRange = {avgHours: 0, maxHours: 0, minHours: 0};
private readonly onDestroy = new Subject<void>();
private readonly destroyRef = inject(DestroyRef);
get MangaFormat() {
return MangaFormat;
@ -42,16 +53,16 @@ export class SeriesInfoCardsComponent implements OnInit, OnChanges, OnDestroy {
return FilterQueryParam;
}
constructor(public utilityService: UtilityService, public metadataService: MetadataService,
private readerService: ReaderService, private readonly cdRef: ChangeDetectorRef,
constructor(public utilityService: UtilityService, public metadataService: MetadataService,
private readerService: ReaderService, private readonly cdRef: ChangeDetectorRef,
private messageHub: MessageHubService, private accountService: AccountService) {
// Listen for progress events and re-calculate getTimeLeft
this.messageHub.messages$.pipe(filter(event => event.event === EVENTS.UserProgressUpdate),
map(evt => evt.payload as UserProgressUpdateEvent),
debounceTime(500),
takeUntil(this.onDestroy))
takeUntilDestroyed(this.destroyRef))
.subscribe(updateEvent => {
this.accountService.currentUser$.pipe(takeUntil(this.onDestroy)).subscribe(user => {
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
if (user === undefined || user.username !== updateEvent.username) return;
if (updateEvent.seriesId !== this.series.id) return;
this.getReadingTimeLeft();
@ -73,10 +84,6 @@ export class SeriesInfoCardsComponent implements OnInit, OnChanges, OnDestroy {
this.cdRef.markForCheck();
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();
}
handleGoTo(queryParamName: FilterQueryParam, filter: any) {
this.goTo.emit({queryParamName, filter});