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

@ -1,26 +0,0 @@
import { ThemeProvider } from '../../_models/preferences/site-theme';
import { SiteThemeProviderPipe } from './site-theme-provider.pipe';
describe('SiteThemeProviderPipe', () => {
let siteThemeProviderPipe: SiteThemeProviderPipe;
beforeEach(() => {
siteThemeProviderPipe = new SiteThemeProviderPipe();
})
it('translates system to System', () => {
expect(siteThemeProviderPipe.transform(ThemeProvider.System)).toBe('System');
});
it('translates user to User', () => {
expect(siteThemeProviderPipe.transform(ThemeProvider.User)).toBe('User');
});
it('translates null to empty string', () => {
expect(siteThemeProviderPipe.transform(null)).toBe('');
});
it('translates undefined to empty string', () => {
expect(siteThemeProviderPipe.transform(undefined)).toBe('');
});
});

View file

@ -1,17 +1,25 @@
import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { Subject } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators';
import { ConfirmService } from 'src/app/shared/confirm.service';
import { AccountService } from 'src/app/_services/account.service';
import { Clipboard } from '@angular/cdk/clipboard';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, DestroyRef,
ElementRef, inject,
Input,
OnInit,
ViewChild
} from '@angular/core';
import {ToastrService} from 'ngx-toastr';
import {ConfirmService} from 'src/app/shared/confirm.service';
import {AccountService} from 'src/app/_services/account.service';
import {Clipboard} from '@angular/cdk/clipboard';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Component({
selector: 'app-api-key',
templateUrl: './api-key.component.html',
styleUrls: ['./api-key.component.scss']
styleUrls: ['./api-key.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ApiKeyComponent implements OnInit, OnDestroy {
export class ApiKeyComponent implements OnInit {
@Input() title: string = 'API Key';
@Input() showRefresh: boolean = true;
@ -19,13 +27,14 @@ export class ApiKeyComponent implements OnInit, OnDestroy {
@Input() tooltipText: string = '';
@ViewChild('apiKey') inputElem!: ElementRef;
key: string = '';
private readonly onDestroy = new Subject<void>();
private readonly destroyRef = inject(DestroyRef);
constructor(private confirmService: ConfirmService, private accountService: AccountService, private toastr: ToastrService, private clipboard: Clipboard) { }
constructor(private confirmService: ConfirmService, private accountService: AccountService, private toastr: ToastrService, private clipboard: Clipboard,
private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
this.accountService.currentUser$.pipe(takeUntil(this.onDestroy)).subscribe(user => {
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
let key = '';
if (user) {
key = user.apiKey;
@ -35,19 +44,16 @@ export class ApiKeyComponent implements OnInit, OnDestroy {
if (this.transform != undefined) {
this.key = this.transform(key);
this.cdRef.markForCheck();
}
});
}
ngOnDestroy() {
this.onDestroy.next();
this.onDestroy.complete();
}
async copy() {
this.inputElem.nativeElement.select();
this.clipboard.copy(this.inputElem.nativeElement.value);
this.inputElem.nativeElement.setSelectionRange(0, 0);
this.cdRef.markForCheck();
}
async refresh() {
@ -56,6 +62,7 @@ export class ApiKeyComponent implements OnInit, OnDestroy {
}
this.accountService.resetApiKey().subscribe(newKey => {
this.key = newKey;
this.cdRef.markForCheck();
this.toastr.success('API Key reset');
});
}
@ -63,6 +70,7 @@ export class ApiKeyComponent implements OnInit, OnDestroy {
selectAll() {
if (this.inputElem) {
this.inputElem.nativeElement.setSelectionRange(0, this.key.length);
this.cdRef.markForCheck();
}
}

View file

@ -1,10 +1,19 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, DestroyRef,
EventEmitter,
inject,
OnDestroy,
OnInit
} from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { Observable, of, Subject, takeUntil, shareReplay, map, take } from 'rxjs';
import { AgeRestriction } from 'src/app/_models/metadata/age-restriction';
import { AgeRating } from 'src/app/_models/metadata/age-rating';
import { User } from 'src/app/_models/user';
import { AccountService } from 'src/app/_services/account.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Component({
selector: 'app-change-age-restriction',
@ -12,7 +21,7 @@ import { AccountService } from 'src/app/_services/account.service';
styleUrls: ['./change-age-restriction.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChangeAgeRestrictionComponent implements OnInit, OnDestroy {
export class ChangeAgeRestrictionComponent implements OnInit {
user: User | undefined = undefined;
hasChangeAgeRestrictionAbility: Observable<boolean> = of(false);
@ -20,22 +29,21 @@ export class ChangeAgeRestrictionComponent implements OnInit, OnDestroy {
selectedRestriction!: AgeRestriction;
originalRestriction!: AgeRestriction;
reset: EventEmitter<AgeRestriction> = new EventEmitter();
private readonly destroyRef = inject(DestroyRef);
get AgeRating() { return AgeRating; }
private onDestroy = new Subject<void>();
get AgeRating() { return AgeRating; }
constructor(private accountService: AccountService, private toastr: ToastrService, private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
this.accountService.currentUser$.pipe(takeUntil(this.onDestroy), shareReplay(), take(1)).subscribe(user => {
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), shareReplay(), take(1)).subscribe(user => {
if (!user) return;
this.user = user;
this.originalRestriction = this.user.ageRestriction;
this.cdRef.markForCheck();
});
this.hasChangeAgeRestrictionAbility = this.accountService.currentUser$.pipe(takeUntil(this.onDestroy), shareReplay(), map(user => {
this.hasChangeAgeRestrictionAbility = this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), shareReplay(), map(user => {
return user !== undefined && (!this.accountService.hasAdminRole(user) && this.accountService.hasChangeAgeRestrictionRole(user));
}));
this.cdRef.markForCheck();
@ -45,11 +53,6 @@ export class ChangeAgeRestrictionComponent implements OnInit, OnDestroy {
this.selectedRestriction = restriction;
}
ngOnDestroy() {
this.onDestroy.next();
this.onDestroy.complete();
}
resetForm() {
if (!this.user) return;
this.reset.emit(this.originalRestriction);

View file

@ -1,17 +1,19 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { Observable, of, Subject, takeUntil, shareReplay, map, tap, take } from 'rxjs';
import { UpdateEmailResponse } from 'src/app/_models/auth/update-email-response';
import { User } from 'src/app/_models/user';
import { AccountService } from 'src/app/_services/account.service';
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';
import {ToastrService} from 'ngx-toastr';
import {Observable, of, shareReplay, take} from 'rxjs';
import {UpdateEmailResponse} from 'src/app/_models/auth/update-email-response';
import {User} from 'src/app/_models/user';
import {AccountService} from 'src/app/_services/account.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Component({
selector: 'app-change-email',
templateUrl: './change-email.component.html',
styleUrls: ['./change-email.component.scss']
styleUrls: ['./change-email.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChangeEmailComponent implements OnInit, OnDestroy {
export class ChangeEmailComponent implements OnInit {
form: FormGroup = new FormGroup({});
user: User | undefined = undefined;
@ -21,17 +23,16 @@ export class ChangeEmailComponent implements OnInit, OnDestroy {
isViewMode: boolean = true;
emailLink: string = '';
emailConfirmed: boolean = true;
private readonly destroyRef = inject(DestroyRef);
public get email() { return this.form.get('email'); }
private onDestroy = new Subject<void>();
makeLink: (val: string) => string = (val: string) => {return this.emailLink};
constructor(public accountService: AccountService, private toastr: ToastrService, private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
this.accountService.currentUser$.pipe(takeUntil(this.onDestroy), shareReplay(), take(1)).subscribe(user => {
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), shareReplay(), take(1)).subscribe(user => {
this.user = user;
this.form.addControl('email', new FormControl(user?.email, [Validators.required, Validators.email]));
this.form.addControl('password', new FormControl('', [Validators.required]));
@ -41,13 +42,6 @@ export class ChangeEmailComponent implements OnInit, OnDestroy {
this.cdRef.markForCheck();
});
});
}
ngOnDestroy() {
this.onDestroy.next();
this.onDestroy.complete();
}
resetForm() {
@ -71,9 +65,9 @@ export class ChangeEmailComponent implements OnInit, OnDestroy {
} else {
this.toastr.success('The server is not publicly accessible. Ask the admin to fetch your confirmation link from the logs');
}
this.resetForm();
this.isViewMode = true;
this.resetForm();
}, err => {
this.errors = err;
})

View file

@ -1,9 +1,18 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
inject,
OnDestroy,
OnInit
} from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { map, Observable, of, shareReplay, Subject, take, takeUntil } from 'rxjs';
import { User } from 'src/app/_models/user';
import { AccountService } from 'src/app/_services/account.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Component({
selector: 'app-change-password',
@ -19,23 +28,23 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
observableHandles: Array<any> = [];
passwordsMatch = false;
resetPasswordErrors: string[] = [];
isViewMode: boolean = true;
isViewMode: boolean = true;
private readonly destroyRef = inject(DestroyRef);
public get password() { return this.passwordChangeForm.get('password'); }
public get confirmPassword() { return this.passwordChangeForm.get('confirmPassword'); }
private onDestroy = new Subject<void>();
constructor(private accountService: AccountService, private toastr: ToastrService, private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
this.accountService.currentUser$.pipe(takeUntil(this.onDestroy), shareReplay(), take(1)).subscribe(user => {
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), shareReplay(), take(1)).subscribe(user => {
this.user = user;
this.cdRef.markForCheck();
});
this.hasChangePasswordAbility = this.accountService.currentUser$.pipe(takeUntil(this.onDestroy), shareReplay(), map(user => {
this.hasChangePasswordAbility = this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), shareReplay(), map(user => {
return user !== undefined && (this.accountService.hasAdminRole(user) || this.accountService.hasChangePasswordRole(user));
}));
this.cdRef.markForCheck();
@ -53,8 +62,6 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
ngOnDestroy() {
this.observableHandles.forEach(o => o.unsubscribe());
this.onDestroy.next();
this.onDestroy.complete();
}
resetPasswordForm() {

View file

@ -1,10 +1,23 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, DestroyRef,
EventEmitter,
inject,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
SimpleChanges
} from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { Subject, takeUntil } from 'rxjs';
import { Device } from 'src/app/_models/device/device';
import { DevicePlatform, devicePlatforms } from 'src/app/_models/device/device-platform';
import { DeviceService } from 'src/app/_services/device.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Component({
selector: 'app-edit-device',
@ -12,29 +25,29 @@ import { DeviceService } from 'src/app/_services/device.service';
styleUrls: ['./edit-device.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EditDeviceComponent implements OnInit, OnChanges, OnDestroy {
export class EditDeviceComponent implements OnInit, OnChanges {
@Input() device: Device | undefined;
@Output() deviceAdded: EventEmitter<void> = new EventEmitter();
@Output() deviceUpdated: EventEmitter<Device> = new EventEmitter();
private readonly destroyRef = inject(DestroyRef);
settingsForm: FormGroup = new FormGroup({});
devicePlatforms = devicePlatforms;
private readonly onDestroy = new Subject<void>();
constructor(public deviceService: DeviceService, private toastr: ToastrService,
constructor(public deviceService: DeviceService, private toastr: ToastrService,
private readonly cdRef: ChangeDetectorRef) { }
ngOnInit(): void {
this.settingsForm.addControl('name', new FormControl(this.device?.name || '', [Validators.required]));
this.settingsForm.addControl('email', new FormControl(this.device?.emailAddress || '', [Validators.required, Validators.email]));
this.settingsForm.addControl('platform', new FormControl(this.device?.platform || DevicePlatform.Custom, [Validators.required]));
// If user has filled in email and the platform hasn't been explicitly updated, try to update it for them
this.settingsForm.get('email')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(email => {
this.settingsForm.get('email')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(email => {
if (this.settingsForm.get('platform')?.dirty) return;
if (email === null || email === undefined || email === '') return;
if (email.endsWith('@kindle.com')) this.settingsForm.get('platform')?.setValue(DevicePlatform.Kindle);
@ -54,11 +67,6 @@ export class EditDeviceComponent implements OnInit, OnChanges, OnDestroy {
}
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();
}
addDevice() {
if (this.device !== undefined) {
this.deviceService.updateDevice(this.device.id, this.settingsForm.value.name, parseInt(this.settingsForm.value.platform, 10), this.settingsForm.value.email).subscribe(() => {

View file

@ -1,10 +1,19 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
inject,
OnDestroy,
OnInit
} from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { distinctUntilChanged, Subject, take, takeUntil } from 'rxjs';
import { ThemeService } from 'src/app/_services/theme.service';
import { SiteTheme, ThemeProvider } from 'src/app/_models/preferences/site-theme';
import { User } from 'src/app/_models/user';
import { AccountService } from 'src/app/_services/account.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Component({
selector: 'app-theme-manager',
@ -12,23 +21,23 @@ import { AccountService } from 'src/app/_services/account.service';
styleUrls: ['./theme-manager.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ThemeManagerComponent implements OnDestroy {
export class ThemeManagerComponent {
currentTheme: SiteTheme | undefined;
isAdmin: boolean = false;
user: User | undefined;
private readonly onDestroy = new Subject<void>();
private readonly destroyRef = inject(DestroyRef);
get ThemeProvider() {
return ThemeProvider;
}
constructor(public themeService: ThemeService, private accountService: AccountService,
constructor(public themeService: ThemeService, private accountService: AccountService,
private toastr: ToastrService, private readonly cdRef: ChangeDetectorRef) {
themeService.currentTheme$.pipe(takeUntil(this.onDestroy), distinctUntilChanged()).subscribe(theme => {
themeService.currentTheme$.pipe(takeUntilDestroyed(this.destroyRef), distinctUntilChanged()).subscribe(theme => {
this.currentTheme = theme;
this.cdRef.markForCheck();
});
accountService.currentUser$.pipe(take(1)).subscribe(user => {
@ -40,11 +49,6 @@ export class ThemeManagerComponent implements OnDestroy {
});
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();
}
applyTheme(theme: SiteTheme) {
if (this.user) {

View file

@ -1,4 +1,12 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
inject,
OnDestroy,
OnInit
} from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { take, takeUntil } from 'rxjs/operators';
@ -23,6 +31,7 @@ import { forkJoin, Subject } from 'rxjs';
import { bookColorThemes } from 'src/app/book-reader/_components/reader-settings/reader-settings.component';
import { BookService } from 'src/app/book-reader/_services/book.service';
import { environment } from 'src/environments/environment';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
enum AccordionPanelID {
ImageReader = 'image-reader',
@ -79,8 +88,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
opdsEnabled: boolean = false;
baseUrl: string = '';
makeUrl: (val: string) => string = (val: string) => {return this.transformKeyToOpdsUrl(val)};
private onDestroy = new Subject<void>();
private readonly destroyRef = inject(DestroyRef);
get AccordionPanelID() {
return AccordionPanelID;
@ -165,7 +173,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
this.cdRef.markForCheck();
});
this.settingsForm.get('bookReaderImmersiveMode')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(mode => {
this.settingsForm.get('bookReaderImmersiveMode')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(mode => {
if (mode) {
this.settingsForm.get('bookReaderTapToPaginate')?.setValue(true);
this.cdRef.markForCheck();
@ -176,8 +184,6 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
ngOnDestroy() {
this.observableHandles.forEach(o => o.unsubscribe());
this.onDestroy.next();
this.onDestroy.complete();
}