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,5 +1,5 @@
import { HttpClient } from '@angular/common/http';
import { Injectable, OnDestroy } from '@angular/core';
import {DestroyRef, inject, Injectable, OnDestroy} from '@angular/core';
import { of, ReplaySubject, Subject } from 'rxjs';
import { filter, map, switchMap, takeUntil } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
@ -14,20 +14,22 @@ import { UpdateEmailResponse } from '../_models/auth/update-email-response';
import { AgeRating } from '../_models/metadata/age-rating';
import { AgeRestriction } from '../_models/metadata/age-restriction';
import { TextResonse } from '../_types/text-response';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
export enum Role {
Admin = 'Admin',
ChangePassword = 'Change Password',
Bookmark = 'Bookmark',
Download = 'Download',
ChangeRestriction = 'Change Restriction'
ChangeRestriction = 'Change Restriction'
}
@Injectable({
providedIn: 'root'
})
export class AccountService implements OnDestroy {
export class AccountService {
private readonly destroyRef = inject(DestroyRef);
baseUrl = environment.apiUrl;
userKey = 'kavita-user';
public lastLoginKey = 'kavita-lastlogin';
@ -42,21 +44,14 @@ export class AccountService implements OnDestroy {
*/
private refreshTokenTimeout: ReturnType<typeof setTimeout> | undefined;
private readonly onDestroy = new Subject<void>();
constructor(private httpClient: HttpClient, private router: Router,
constructor(private httpClient: HttpClient, private router: Router,
private messageHub: MessageHubService, private themeService: ThemeService) {
messageHub.messages$.pipe(filter(evt => evt.event === EVENTS.UserUpdate),
messageHub.messages$.pipe(filter(evt => evt.event === EVENTS.UserUpdate),
map(evt => evt.payload as UserUpdateEvent),
filter(userUpdateEvent => userUpdateEvent.userName === this.currentUser?.username),
filter(userUpdateEvent => userUpdateEvent.userName === this.currentUser?.username),
switchMap(() => this.refreshToken()))
.subscribe(() => {});
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();
}
hasAdminRole(user: User) {
return user && user.roles.includes(Role.Admin);
@ -91,7 +86,7 @@ export class AccountService implements OnDestroy {
this.messageHub.createHubConnection(user, this.hasAdminRole(user));
}
}),
takeUntil(this.onDestroy)
takeUntilDestroyed(this.destroyRef)
);
}
@ -114,7 +109,7 @@ export class AccountService implements OnDestroy {
this.currentUser = user;
this.currentUserSource.next(user);
this.stopRefreshTokenTimer();
if (this.currentUser !== undefined) {
@ -135,15 +130,15 @@ export class AccountService implements OnDestroy {
/**
* Registers the first admin on the account. Only used for that. All other registrations must occur through invite
* @param model
* @returns
* @param model
* @returns
*/
register(model: {username: string, password: string, email: string}) {
return this.httpClient.post<User>(this.baseUrl + 'account/register', model).pipe(
map((user: User) => {
return user;
}),
takeUntil(this.onDestroy)
takeUntilDestroyed(this.destroyRef)
);
}
@ -177,8 +172,8 @@ export class AccountService implements OnDestroy {
/**
* Given a user id, returns a full url for setting up the user account
* @param userId
* @returns
* @param userId
* @returns
*/
getInviteUrl(userId: number, withBaseUrl: boolean = true) {
return this.httpClient.get<string>(this.baseUrl + 'account/invite-url?userId=' + userId + '&withBaseUrl=' + withBaseUrl, TextResonse);
@ -214,7 +209,7 @@ export class AccountService implements OnDestroy {
/**
* This will get latest preferences for a user and cache them into user store
* @returns
* @returns
*/
getPreferences() {
return this.httpClient.get<Preferences>(this.baseUrl + 'users/get-preferences').pipe(map(pref => {
@ -223,7 +218,7 @@ export class AccountService implements OnDestroy {
this.setCurrentUser(this.currentUser);
}
return pref;
}), takeUntil(this.onDestroy));
}), takeUntilDestroyed(this.destroyRef));
}
updatePreferences(userPreferences: Preferences) {
@ -233,13 +228,13 @@ export class AccountService implements OnDestroy {
this.setCurrentUser(this.currentUser);
}
return settings;
}), takeUntil(this.onDestroy));
}), takeUntilDestroyed(this.destroyRef));
}
getUserFromLocalStorage(): User | undefined {
const userString = localStorage.getItem(this.userKey);
if (userString) {
return JSON.parse(userString)
};
@ -254,7 +249,7 @@ export class AccountService implements OnDestroy {
user.apiKey = key;
localStorage.setItem(this.userKey, JSON.stringify(user));
this.currentUserSource.next(user);
this.currentUser = user;
}
@ -270,7 +265,7 @@ export class AccountService implements OnDestroy {
this.currentUser.token = user.token;
this.currentUser.refreshToken = user.refreshToken;
}
this.setCurrentUser(this.currentUser);
return user;
}));

View file

@ -1,16 +1,17 @@
import { Injectable, OnDestroy } from '@angular/core';
import {DestroyRef, inject, Injectable, OnDestroy} from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import { ThemeService } from './theme.service';
import { RecentlyAddedItem } from '../_models/recently-added-item';
import { AccountService } from './account.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Injectable({
providedIn: 'root'
})
export class ImageService implements OnDestroy {
export class ImageService {
private readonly destroyRef = inject(DestroyRef);
baseUrl = environment.apiUrl;
apiKey: string = '';
encodedKey: string = '';
@ -19,10 +20,8 @@ export class ImageService implements OnDestroy {
public resetCoverImage = 'assets/images/image-reset-cover-min.png';
public errorWebLinkImage = 'assets/images/broken-white-32x32.png';
private onDestroy: Subject<void> = new Subject();
constructor(private accountService: AccountService, private themeService: ThemeService) {
this.themeService.currentTheme$.pipe(takeUntil(this.onDestroy)).subscribe(theme => {
this.themeService.currentTheme$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(theme => {
if (this.themeService.isDarkTheme()) {
this.placeholderImage = 'assets/images/image-placeholder.dark-min.png';
this.errorImage = 'assets/images/error-placeholder2.dark-min.png';
@ -34,7 +33,7 @@ export class ImageService implements OnDestroy {
}
});
this.accountService.currentUser$.pipe(takeUntil(this.onDestroy)).subscribe(user => {
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
if (user) {
this.apiKey = user.apiKey;
this.encodedKey = encodeURIComponent(this.apiKey);
@ -42,11 +41,6 @@ export class ImageService implements OnDestroy {
});
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();
}
getRecentlyAddedItem(item: RecentlyAddedItem) {
if (item.chapterId === 0) {
return this.getVolumeCoverImage(item.volumeId);
@ -56,8 +50,8 @@ export class ImageService implements OnDestroy {
/**
* Returns the entity type from a cover image url. Undefied if not applicable
* @param url
* @returns
* @param url
* @returns
*/
getEntityTypeFromUrl(url: string) {
if (url.indexOf('?') < 0) return undefined;

View file

@ -1,5 +1,5 @@
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import {DestroyRef, inject, Injectable} from '@angular/core';
import { Location } from '@angular/common';
import { Router } from '@angular/router';
import { environment } from 'src/environments/environment';
@ -19,6 +19,7 @@ import { TextResonse } from '../_types/text-response';
import { AccountService } from './account.service';
import { Subject, takeUntil } from 'rxjs';
import { OnDestroy } from '@angular/core';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
export const CHAPTER_ID_DOESNT_EXIST = -1;
export const CHAPTER_ID_NOT_FETCHED = -2;
@ -26,29 +27,25 @@ export const CHAPTER_ID_NOT_FETCHED = -2;
@Injectable({
providedIn: 'root'
})
export class ReaderService implements OnDestroy {
export class ReaderService {
private readonly destroyRef = inject(DestroyRef);
baseUrl = environment.apiUrl;
encodedKey: string = '';
private onDestroy: Subject<void> = new Subject();
// Override background color for reader and restore it onDestroy
private originalBodyColor!: string;
constructor(private httpClient: HttpClient, private router: Router,
constructor(private httpClient: HttpClient, private router: Router,
private location: Location, private utilityService: UtilityService,
private filterUtilitySerivce: FilterUtilitiesService, private accountService: AccountService) {
this.accountService.currentUser$.pipe(takeUntil(this.onDestroy)).subscribe(user => {
private filterUtilityService: FilterUtilitiesService, private accountService: AccountService) {
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
if (user) {
this.encodedKey = encodeURIComponent(user.apiKey);
}
});
}
ngOnDestroy() {
this.onDestroy.next();
this.onDestroy.complete();
}
getNavigationArray(libraryId: number, seriesId: number, chapterId: number, format: MangaFormat) {
if (format === undefined) format = MangaFormat.ARCHIVE;
@ -77,7 +74,7 @@ export class ReaderService implements OnDestroy {
getAllBookmarks(filter: SeriesFilter | undefined) {
let params = new HttpParams();
params = this.utilityService.addPaginationIfExists(params, undefined, undefined);
const data = this.filterUtilitySerivce.createSeriesFilter(filter);
const data = this.filterUtilityService.createSeriesFilter(filter);
return this.httpClient.post<PageBookmark[]>(this.baseUrl + 'reader/all-bookmarks', data);
}

View file

@ -1,6 +1,15 @@
import { DOCUMENT } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Inject, Injectable, OnDestroy, Renderer2, RendererFactory2, SecurityContext } from '@angular/core';
import {
DestroyRef,
inject,
Inject,
Injectable,
OnDestroy,
Renderer2,
RendererFactory2,
SecurityContext
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { ToastrService } from 'ngx-toastr';
import { map, ReplaySubject, Subject, takeUntil, take } from 'rxjs';
@ -10,13 +19,15 @@ import { NotificationProgressEvent } from '../_models/events/notification-progre
import { SiteTheme, ThemeProvider } from '../_models/preferences/site-theme';
import { TextResonse } from '../_types/text-response';
import { EVENTS, MessageHubService } from './message-hub.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Injectable({
providedIn: 'root'
})
export class ThemeService implements OnDestroy {
export class ThemeService {
private readonly destroyRef = inject(DestroyRef);
public defaultTheme: string = 'dark';
public defaultBookTheme: string = 'Dark';
@ -25,13 +36,12 @@ export class ThemeService implements OnDestroy {
private themesSource = new ReplaySubject<SiteTheme[]>(1);
public themes$ = this.themesSource.asObservable();
/**
* Maintain a cache of themes. SignalR will inform us if we need to refresh cache
*/
private themeCache: Array<SiteTheme> = [];
private readonly onDestroy = new Subject<void>();
private renderer: Renderer2;
private baseUrl = environment.apiUrl;
@ -42,7 +52,7 @@ export class ThemeService implements OnDestroy {
this.getThemes();
messageHub.messages$.pipe(takeUntil(this.onDestroy)).subscribe(message => {
messageHub.messages$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(message => {
if (message.event !== EVENTS.NotificationProgress) return;
const notificationEvent = (message.payload as NotificationProgressEvent);
@ -56,31 +66,26 @@ export class ThemeService implements OnDestroy {
});
}
ngOnDestroy(): void {
this.onDestroy.next();
this.onDestroy.complete();
}
getColorScheme() {
return getComputedStyle(this.document.body).getPropertyValue('--color-scheme').trim();
}
/**
* --theme-color from theme. Updates the meta tag
* @returns
* @returns
*/
getThemeColor() {
return getComputedStyle(this.document.body).getPropertyValue('--theme-color').trim();
}
/**
* --msapplication-TileColor from theme. Updates the meta tag
* @returns
* @returns
*/
getTileColor() {
return getComputedStyle(this.document.body).getPropertyValue('--title-color').trim();
}
getCssVariable(variable: string) {
return getComputedStyle(this.document.body).getPropertyValue(variable).trim();
}