Angular Upgrade (#1059)

* Upgraded to Angular 12

* Bump ng-bootstrap for upgrade

* Angular 13 upgrade, ng-bootstrap bump

* Angular 13 upgrade (broken)

* Angular 13 upgrade. CSS is broken completely

* Angular 13 upgrade is complete.
This commit is contained in:
Joseph Milazzo 2022-02-11 15:23:26 -08:00 committed by GitHub
parent d7450497a6
commit 97b1249a0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 4993 additions and 8937 deletions

2
.gitignore vendored
View File

@ -526,3 +526,5 @@ API/config/post-metadata/
API.Tests/TestResults/ API.Tests/TestResults/
UI/Web/.vscode/settings.json UI/Web/.vscode/settings.json
/API.Tests/Services/Test Data/ArchiveService/CoverImages/output/* /API.Tests/Services/Test Data/ArchiveService/CoverImages/output/*
/UI/Web/.angular/

View File

@ -28,7 +28,6 @@
"main": "src/main.ts", "main": "src/main.ts",
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"aot": true,
"assets": [ "assets": [
"src/assets", "src/assets",
"src/site.webmanifest" "src/site.webmanifest"
@ -46,7 +45,12 @@
"node_modules/lazysizes/lazysizes.min.js", "node_modules/lazysizes/lazysizes.min.js",
"node_modules/lazysizes/plugins/rias/ls.rias.min.js", "node_modules/lazysizes/plugins/rias/ls.rias.min.js",
"node_modules/lazysizes/plugins/attrchange/ls.attrchange.min.js" "node_modules/lazysizes/plugins/attrchange/ls.attrchange.min.js"
] ],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"optimization": true,
"namedChunks": true
}, },
"configurations": { "configurations": {
"production": { "production": {
@ -60,7 +64,6 @@
"outputHashing": "all", "outputHashing": "all",
"namedChunks": false, "namedChunks": false,
"extractLicenses": true, "extractLicenses": true,
"vendorChunk": true,
"buildOptimizer": true, "buildOptimizer": true,
"budgets": [ "budgets": [
{ {
@ -75,7 +78,8 @@
} }
] ]
} }
} },
"defaultConfiguration": ""
}, },
"serve": { "serve": {
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",

13706
UI/Web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
"build": "ng build", "build": "ng build",
"prod": "ng build --prod", "prod": "ng build --configuration production",
"explore": "ng build --stats-json && webpack-bundle-analyzer dist/stats.json", "explore": "ng build --stats-json && webpack-bundle-analyzer dist/stats.json",
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
@ -16,50 +16,50 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular-slider/ngx-slider": "^2.0.3", "@angular-slider/ngx-slider": "^2.0.3",
"@angular/animations": "~11.0.0", "@angular/animations": "~13.2.2",
"@angular/cdk": "^12.2.3", "@angular/cdk": "^13.2.2",
"@angular/common": "~11.0.0", "@angular/common": "~13.2.2",
"@angular/compiler": "~11.0.0", "@angular/compiler": "~13.2.2",
"@angular/core": "~11.0.0", "@angular/core": "~13.2.2",
"@angular/forms": "~11.0.0", "@angular/forms": "~13.2.2",
"@angular/localize": "~11.0.0", "@angular/localize": "~13.2.2",
"@angular/platform-browser": "~11.0.0", "@angular/platform-browser": "~13.2.2",
"@angular/platform-browser-dynamic": "~11.0.0", "@angular/platform-browser-dynamic": "~13.2.2",
"@angular/router": "~11.0.0", "@angular/router": "~13.2.2",
"@fortawesome/fontawesome-free": "^5.15.1", "@fortawesome/fontawesome-free": "^6.0.0",
"@microsoft/signalr": "^5.0.8", "@microsoft/signalr": "^6.0.2",
"@ng-bootstrap/ng-bootstrap": "^9.1.0", "@ng-bootstrap/ng-bootstrap": "^11.0.0",
"@ngx-lite/nav-drawer": "^0.4.6", "@ngx-lite/nav-drawer": "^0.4.7",
"@ngx-lite/util": "0.0.0", "@ngx-lite/util": "0.0.1",
"@types/file-saver": "^2.0.1", "@types/file-saver": "^2.0.5",
"bootstrap": "^4.5.0", "bootstrap": "^4.6.1",
"bowser": "^2.11.0", "bowser": "^2.11.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"lazysizes": "^5.3.2", "lazysizes": "^5.3.2",
"ng-circle-progress": "^1.6.0", "ng-circle-progress": "^1.6.0",
"ng-lazyload-image": "^9.1.0", "ng-lazyload-image": "^9.1.2",
"ngx-file-drop": "^11.1.0", "ngx-file-drop": "^13.0.0",
"ngx-toastr": "^13.2.1", "ngx-toastr": "^14.2.1",
"rxjs": "~6.6.0", "rxjs": "~7.5.4",
"swiper": "^6.5.8", "swiper": "^8.0.3",
"tslib": "^2.0.0", "tslib": "^2.3.1",
"webpack-bundle-analyzer": "^4.4.2", "webpack-bundle-analyzer": "^4.5.0",
"zone.js": "~0.10.2" "zone.js": "~0.11.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~0.1100.0", "@angular-devkit/build-angular": "~13.2.3",
"@angular/cli": "^11.2.11", "@angular/cli": "^13.2.3",
"@angular/compiler-cli": "~11.0.0", "@angular/compiler-cli": "~13.2.2",
"@types/jest": "^26.0.20", "@types/jest": "^27.4.0",
"@types/node": "^12.11.1", "@types/node": "^17.0.17",
"codelyzer": "^6.0.0", "codelyzer": "^6.0.2",
"jest": "^26.6.3", "jest": "^27.5.1",
"jest-preset-angular": "^8.3.2", "jest-preset-angular": "^11.1.0",
"karma-coverage": "~2.0.3", "karma-coverage": "~2.2.0",
"protractor": "~7.0.0", "protractor": "~7.0.0",
"ts-node": "~8.3.0", "ts-node": "~10.5.0",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"typescript": "~4.0.2" "typescript": "~4.5.5"
}, },
"jest": { "jest": {
"preset": "jest-preset-angular", "preset": "jest-preset-angular",

View File

@ -3,7 +3,6 @@ import { CanActivate } from '@angular/router';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators'; import { map, take } from 'rxjs/operators';
import { User } from '../_models/user';
import { AccountService } from '../_services/account.service'; import { AccountService } from '../_services/account.service';
@Injectable({ @Injectable({
@ -15,8 +14,8 @@ export class AdminGuard implements CanActivate {
canActivate(): Observable<boolean> { canActivate(): Observable<boolean> {
// this automaticallys subs due to being router guard // this automaticallys subs due to being router guard
return this.accountService.currentUser$.pipe(take(1), return this.accountService.currentUser$.pipe(take(1),
map((user: User) => { map((user) => {
if (this.accountService.hasAdminRole(user)) { if (user && this.accountService.hasAdminRole(user)) {
return true; return true;
} }

View File

@ -3,7 +3,6 @@ import { CanActivate, Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators'; import { map, take } from 'rxjs/operators';
import { User } from '../_models/user';
import { AccountService } from '../_services/account.service'; import { AccountService } from '../_services/account.service';
@Injectable({ @Injectable({
@ -15,7 +14,7 @@ export class AuthGuard implements CanActivate {
canActivate(): Observable<boolean> { canActivate(): Observable<boolean> {
return this.accountService.currentUser$.pipe(take(1), return this.accountService.currentUser$.pipe(take(1),
map((user: User) => { map((user) => {
if (user) { if (user) {
return true; return true;
} }

View File

@ -7,7 +7,6 @@ import {
} from '@angular/common/http'; } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { AccountService } from '../_services/account.service'; import { AccountService } from '../_services/account.service';
import { User } from '../_models/user';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
@Injectable() @Injectable()
@ -16,16 +15,13 @@ export class JwtInterceptor implements HttpInterceptor {
constructor(private accountService: AccountService) {} constructor(private accountService: AccountService) {}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
let currentUser: User;
// Take 1 means we don't have to unsubscribe because we take 1 then complete // Take 1 means we don't have to unsubscribe because we take 1 then complete
this.accountService.currentUser$.pipe(take(1)).subscribe(user => { this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
currentUser = user; if (user) {
if (currentUser) {
request = request.clone({ request = request.clone({
setHeaders: { setHeaders: {
Authorization: `Bearer ${currentUser.token}` Authorization: `Bearer ${user.token}`
} }
}); });
} }

View File

@ -19,7 +19,10 @@ export class AccountService implements OnDestroy {
currentUser: User | undefined; currentUser: User | undefined;
// Stores values, when someone subscribes gives (1) of last values seen. // Stores values, when someone subscribes gives (1) of last values seen.
private currentUserSource = new ReplaySubject<User>(1); private currentUserSource = new ReplaySubject<User | undefined>(1);
/**
*
*/
currentUser$ = this.currentUserSource.asObservable(); currentUser$ = this.currentUserSource.asObservable();
/** /**

View File

@ -35,8 +35,10 @@ export class ManageUsersComponent implements OnInit, OnDestroy {
private confirmService: ConfirmService, private confirmService: ConfirmService,
public messageHub: MessageHubService, public messageHub: MessageHubService,
private serverService: ServerService) { private serverService: ServerService) {
this.accountService.currentUser$.pipe(take(1)).subscribe((user: User) => { this.accountService.currentUser$.pipe(take(1)).subscribe((user) => {
this.loggedInUsername = user.username; if (user) {
this.loggedInUsername = user.username;
}
}); });
} }

View File

@ -2,14 +2,14 @@
<div> <div>
<h2 style="display: inline-block;"><a href="javascript:void(0)" (click)="sectionClicked($event)" class="section-title">{{title}}</a></h2> <h2 style="display: inline-block;"><a href="javascript:void(0)" (click)="sectionClicked($event)" class="section-title">{{title}}</a></h2>
<div class="float-right"> <div class="float-right">
<button class="btn btn-icon" [disabled]="swiper?.isBeginning" (click)="prevPage()"><i class="fa fa-angle-left" aria-hidden="true"></i><span class="sr-only">Previous Items</span></button> <button class="btn btn-icon" [disabled]="isBeginning" (click)="prevPage()"><i class="fa fa-angle-left" aria-hidden="true"></i><span class="sr-only">Previous Items</span></button>
<button class="btn btn-icon" [disabled]="swiper?.isEnd" (click)="nextPage()"><i class="fa fa-angle-right" aria-hidden="true"></i><span class="sr-only">Next Items</span></button> <button class="btn btn-icon" [disabled]="isEnd" (click)="nextPage()"><i class="fa fa-angle-right" aria-hidden="true"></i><span class="sr-only">Next Items</span></button>
</div> </div>
</div> </div>
<div> <div>
<swiper <swiper
#swiper
[slidesPerView]="'auto'" [slidesPerView]="'auto'"
(swiper)="onSwiper($event)"
[freeMode]="true"> [freeMode]="true">
<ng-template *ngFor="let item of items; index as i;" swiperSlide> <ng-template *ngFor="let item of items; index as i;" swiperSlide>
<ng-container [ngTemplateOutlet]="carouselItemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container> <ng-container [ngTemplateOutlet]="carouselItemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>

View File

@ -1,5 +1,7 @@
import { Component, ContentChild, EventEmitter, Input, OnInit, Output, TemplateRef } from '@angular/core'; import { Component, ContentChild, EventEmitter, Input, OnInit, Output, TemplateRef, ViewChild } from '@angular/core';
import Swiper from 'swiper'; import { SwiperComponent } from 'swiper/angular';
//import Swiper from 'swiper';
//import { SwiperEvents, Swiper } from 'swiper/types';
@Component({ @Component({
selector: 'app-carousel-reel', selector: 'app-carousel-reel',
@ -13,10 +15,20 @@ export class CarouselReelComponent implements OnInit {
@Input() title = ''; @Input() title = '';
@Output() sectionClick = new EventEmitter<string>(); @Output() sectionClick = new EventEmitter<string>();
@ViewChild('swiper', { static: false }) swiper?: SwiperComponent;
swiper!: Swiper;
//swiper!: Swiper;
trackByIdentity: (index: number, item: any) => string; trackByIdentity: (index: number, item: any) => string;
get isEnd() {
return this.swiper?.swiperRef.isEnd;
}
get isBeginning() {
return this.swiper?.swiperRef.isBeginning;
}
constructor() { constructor() {
this.trackByIdentity = (index: number, item: any) => `${this.title}_${item.id}_${item?.name}_${item?.pagesRead}_${index}`; this.trackByIdentity = (index: number, item: any) => `${this.title}_${item.id}_${item?.name}_${item?.pagesRead}_${index}`;
} }
@ -25,13 +37,13 @@ export class CarouselReelComponent implements OnInit {
nextPage() { nextPage() {
if (this.swiper) { if (this.swiper) {
this.swiper.setProgress(this.swiper.progress + 0.25, 600); this.swiper.swiperRef.setProgress(this.swiper.swiperRef.progress + 0.25, 600);
} }
} }
prevPage() { prevPage() {
if (this.swiper) { if (this.swiper) {
this.swiper.setProgress(this.swiper.progress - 0.25, 600); this.swiper.swiperRef.setProgress(this.swiper.swiperRef.progress - 0.25, 600);
} }
} }
@ -39,7 +51,14 @@ export class CarouselReelComponent implements OnInit {
this.sectionClick.emit(this.title); this.sectionClick.emit(this.title);
} }
onSwiper(swiper: any) { // onSwiper(eventParams: Parameters<SwiperEvents['init']>) {
this.swiper = swiper; // console.log('swiper: ', eventParams);
} // [this.swiper] = eventParams;
// }
// onSwiper(params: Swiper) {
// // const [swiper] = params;
// // console.log(swiper);
// // return params;
// }
} }

View File

@ -73,11 +73,13 @@ export class LibraryComponent implements OnInit, OnDestroy {
this.isLoading = true; this.isLoading = true;
this.accountService.currentUser$.pipe(take(1)).subscribe(user => { this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
this.user = user; this.user = user;
this.isAdmin = this.accountService.hasAdminRole(this.user); if (this.user) {
this.libraryService.getLibrariesForMember().pipe(take(1)).subscribe(libraries => { this.isAdmin = this.accountService.hasAdminRole(this.user);
this.libraries = libraries; this.libraryService.getLibrariesForMember().pipe(take(1)).subscribe(libraries => {
this.isLoading = false; this.libraries = libraries;
}); this.isLoading = false;
});
}
}); });
this.reloadSeries(); this.reloadSeries();

View File

@ -1,7 +1,9 @@
@use '../../../../theme/colors';
.clickable { .clickable {
cursor: pointer; cursor: pointer;
} }
.clickable:hover, .clickable:focus { .clickable:hover, .clickable:focus {
background-color: lightgreen; background-color: colors.$primary-color;
} }

View File

@ -83,7 +83,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
ngOnInit(): void { ngOnInit(): void {
this.titleService.setTitle('Kavita - User Preferences'); this.titleService.setTitle('Kavita - User Preferences');
this.accountService.currentUser$.pipe(take(1)).subscribe((user: User) => { this.accountService.currentUser$.pipe(take(1)).subscribe((user) => {
if (user) { if (user) {
this.user = user; this.user = user;
this.isAdmin = this.accountService.hasAdminRole(user); this.isAdmin = this.accountService.hasAdminRole(user);

View File

@ -15,4 +15,4 @@ export const environment = {
* This import should be commented out in production mode because it will have a negative impact * This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown. * on performance if an error is thrown.
*/ */
// import 'zone.js/dist/zone-error'; // Included with Angular CLI. // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.

View File

@ -59,7 +59,7 @@ import '@angular/localize/init';
/*************************************************************************************************** /***************************************************************************************************
* Zone JS is required by default for Angular itself. * Zone JS is required by default for Angular itself.
*/ */
import 'zone.js/dist/zone'; // Included with Angular CLI. import 'zone.js'; // Included with Angular CLI.
/*************************************************************************************************** /***************************************************************************************************

View File

@ -1,14 +1,20 @@
@use '../node_modules/swiper/swiper.scss' as swiper;
@use '~bootstrap/scss/mixins/_breakpoints.scss' as breakpoints;
// Import colors for overrides of bootstrap theme // Import colors for overrides of bootstrap theme
@import './theme/colors'; @import './theme/colors';
@import './theme/toastr'; @import './theme/toastr';
// Bootstrap must be after _colors since we define the colors there // Bootstrap must be after _colors since we define the colors there
@import '~bootstrap/scss/bootstrap'; @import '~bootstrap/scss/bootstrap';
@import '~bootstrap/scss/mixins/_breakpoints.scss';
@import '~swiper/swiper.scss';
@import './assets/themes/dark.scss';
@import './theme/dark.scss';
// Global Styles // Global Styles
button:disabled, .form-control:disabled, .form-control[readonly], .disabled, :disabled { button:disabled, .form-control:disabled, .form-control[readonly], .disabled, :disabled {
@ -72,14 +78,16 @@ app-root {
} }
// Utiliities // Utiliities
@include media-breakpoint-down(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) { @include breakpoints.media-breakpoint-down(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) {
.phone-hidden { .phone-hidden {
display: none; display: none;
} }
} }
@include media-breakpoint-up(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) { @include breakpoints.media-breakpoint-up(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) {
.not-phone-hidden { .not-phone-hidden {
display: none; display: none;
} }

View File

@ -18,6 +18,26 @@ $dark-item-accent-bg: #292d32;
$white-item-accent-bg: rgba(247, 247, 247, 1); $white-item-accent-bg: rgba(247, 247, 247, 1);
// Default variables for light mode
:root {
--primary-color: #4ac694;
--error-color: #ff4136;
--bs-body-bg: #fff;
}
// Dark mode theme
:root .bg-dark {
--primary-color: #4ac694;
--error-color: #ff4136;
--bs-body-bg: red; // This is bootstrap v5 only
}
// E-ink theme
:root .bg-eink {
--primary-color: black;
--bs-body-bg: white;
}
//========================= //=========================
// Ratings // Ratings

View File

@ -1,6 +1,10 @@
// All dark style overrides should live here // All dark style overrides should live here
@use "../../theme/colors"; @use "./colors";
// :root {
// --bs-body-color: #212529;
// }
.bg-dark { .bg-dark {
color: $dark-text-color; color: $dark-text-color;
@ -155,7 +159,7 @@
&::before { &::before {
content: ""; content: "";
background-image: url('../../assets/images/login-bg.jpg'); background-image: url('../assets/images/login-bg.jpg');
background-size: cover; background-size: cover;
position: absolute; position: absolute;
top: 0; top: 0;