Change Detection: On Push aka UI Smoothness (#1369)

* Updated Series Info Cards to use OnPush and hooked in progress events when we do a mark as read/unread on entities. These events update progress bars but also will now trigger a re-calculation on Read Time Left.

* Removed Library Card Component

* Refactored manga reader title and subtitle calculation to the backend.

* Coverted card actionables to onPush

* Series Card on push cleanup

* Updated edit collection tags for on push

* Update cover image chooser for on push

* Cleaned up carsouel reel

* Updated cover image to allow for uploading gif and webp files

* Bulk add to collection on push

* Updated bulk operation to use on push. Updated bulk operation to have mark as unread and read buttons explicitly. Updated so add to collection is visible and delete.

Fixed a bug where manage library component wasn't invoking the trackBy function

* Updating entity title for on push

* Removed file info component

* Updated Mange Library for on push

* Entity info cards on push

* List item on push

* Updated icon and title for on push and fixed some missing change detection on series detail

* Restricted the typeahead interface to simplify the design

* Edit Series Relation now shows a value in the dropdown for Parent relationships and disables the field.

* Updated edit series relation to focus on new typeahead when adding a new relationship

* Added some documentation and when Scanning a library, don't allow the user to enqueue the same job multiple times.

* Applied the No-enqueue if already enqueued logic to other tasks

* Library detail on push

* Updated events widget to onpush

* Card detail drawer on push. Card detail cover chooser now will show all chapter's covers for selection in cover chooser.

* Chapter metadata detail on push

* Removed Card Detail modal

* All collections on push

* Removed some comments

* Updated bulk selection to use an observable rather than function calls so new on push strategy works

* collection detail now uses on push and scroller is placed on correct element

* Updated library recommended to on push. Ensure that when mark as read occurs, the appropriate streams are refreshed.

* Updated library detail to on push

* Update metadata fiter to onpush. Bugs found and reported to Project

* person badge on push

* Read more on push

* Updated tag badge to on push

* User login on push

* When initing side nav, don't call an authenticated api until we are sure a user is logged in

* Updated splash container to on push

* Dashboard on push

* Side nav slight refactor around some api calls

* Cleaned up series card on push to use same cdRef naming convention

* Updated Static Files to use caching

* Added width and height to logo image

* shortcuts modal on push

* reading lists on push

* Reading list detail on push

* draggable ordered list on push

* Refactored reading-list-detail to use a new item which drastically reduces renders on operations

* series format on push

* circular loader on push

* Badge Expander on push

* update notification modal on push

* drawer on push

* Edit Series Modal on push

* reset password on push

* review series modal on push

* series metadata detail on push

* theme manager on push

* confirm reset password on push

* register on push

* confirm migration email on push

* confirm email on push

* add email to account migration on push

* user preferences on push. Made global settings default open

* edit series relation on push

* Fixed an edge case bug for next chapter where if the current volume had a single chapter of 1 and the next volume had a chapter number of 0, it would say there are no more chapters.

* Updated infinite scroller with on push support

* Moved some animations over to typeahead, not integrated yet.

* Manga reader is now on push

* Reader settings on push

* refactored how we close the book

* Updated table of contents for on push

* Updated book reader for on push. Fixed a bug where table of contents wasn't showing current page anchor due to a scroll calulation bug

* Small code tweak

* Icon and title on push

* nav header on push

* grouped typeahead on push

* typeahead on push and added a new trackby identity function to allow even faster rendering of big lists

* pdf reader on push

* code cleanup
This commit is contained in:
Joseph Milazzo 2022-07-11 11:57:07 -04:00 committed by GitHub
parent f5be0fac58
commit 4e49aa47ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
126 changed files with 1658 additions and 1674 deletions

View file

@ -1,18 +1,15 @@
import { Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { SafeUrl } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
import { ConfirmService } from 'src/app/shared/confirm.service';
import { AccountService } from 'src/app/_services/account.service';
import { MemberService } from 'src/app/_services/member.service';
import { ServerService } from 'src/app/_services/server.service';
@Component({
selector: 'app-add-email-to-account-migration-modal',
templateUrl: './add-email-to-account-migration-modal.component.html',
styleUrls: ['./add-email-to-account-migration-modal.component.scss']
styleUrls: ['./add-email-to-account-migration-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AddEmailToAccountMigrationModalComponent implements OnInit {
@ -26,13 +23,14 @@ export class AddEmailToAccountMigrationModalComponent implements OnInit {
error: string = '';
constructor(private accountService: AccountService, private modal: NgbActiveModal,
private serverService: ServerService, private confirmService: ConfirmService, private toastr: ToastrService) {
private toastr: ToastrService, private readonly cdRef: ChangeDetectorRef) {
}
ngOnInit(): void {
this.registerForm.addControl('username', new FormControl(this.username, [Validators.required]));
this.registerForm.addControl('email', new FormControl('', [Validators.required, Validators.email]));
this.registerForm.addControl('password', new FormControl(this.password, [Validators.required]));
this.cdRef.markForCheck();
}
close() {
@ -41,24 +39,12 @@ export class AddEmailToAccountMigrationModalComponent implements OnInit {
save() {
const model = this.registerForm.getRawValue();
model.sendEmail = false;
this.accountService.migrateUser(model).subscribe(async () => {
// if (!canAccess) {
// // Display the email to the user
// this.emailLink = email;
// await this.confirmService.alert('Please click this link to confirm your email. You must confirm to be able to login. The link is in your logs. You may need to log out of the current account before clicking. <br/> <a href="' + this.emailLink + '" target="_blank">' + this.emailLink + '</a>');
// this.modal.close(true);
// } else {
// await this.confirmService.alert('Please check your email (or logs under "Email Link") for the confirmation link. You must confirm to be able to login.');
// this.modal.close(true);
// }
this.toastr.success('Email has been validated');
this.modal.close(true);
}, err => {
this.error = err;
});
model.sendEmail = false;
this.accountService.migrateUser(model).subscribe(async () => {
this.toastr.success('Email has been validated');
this.modal.close(true);
}, err => {
this.error = err;
});
}
}

View file

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
@ -9,9 +9,10 @@ import { NavService } from 'src/app/_services/nav.service';
@Component({
selector: 'app-confirm-email',
templateUrl: './confirm-email.component.html',
styleUrls: ['./confirm-email.component.scss']
styleUrls: ['./confirm-email.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ConfirmEmailComponent implements OnInit {
export class ConfirmEmailComponent {
/**
* Email token used for validating
*/
@ -30,11 +31,13 @@ export class ConfirmEmailComponent implements OnInit {
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService,
private toastr: ToastrService, private themeService: ThemeService, private navService: NavService) {
private toastr: ToastrService, private themeService: ThemeService, private navService: NavService,
private readonly cdRef: ChangeDetectorRef) {
this.navService.hideSideNav();
this.themeService.setTheme(this.themeService.defaultTheme);
const token = this.route.snapshot.queryParamMap.get('token');
const email = this.route.snapshot.queryParamMap.get('email');
this.cdRef.markForCheck();
if (token == undefined || token === '' || token === null) {
// This is not a valid url, redirect to login
this.toastr.error('Invalid confirmation email');
@ -43,9 +46,7 @@ export class ConfirmEmailComponent implements OnInit {
}
this.token = token;
this.registerForm.get('email')?.setValue(email || '');
}
ngOnInit(): void {
this.cdRef.markForCheck();
}
submit() {
@ -57,6 +58,7 @@ export class ConfirmEmailComponent implements OnInit {
}, err => {
console.log('error: ', err);
this.errors = err;
this.cdRef.markForCheck();
});
}

View file

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { ThemeService } from 'src/app/_services/theme.service';
@ -7,15 +7,19 @@ import { AccountService } from 'src/app/_services/account.service';
@Component({
selector: 'app-confirm-migration-email',
templateUrl: './confirm-migration-email.component.html',
styleUrls: ['./confirm-migration-email.component.scss']
styleUrls: ['./confirm-migration-email.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ConfirmMigrationEmailComponent implements OnInit {
export class ConfirmMigrationEmailComponent {
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, private toastr: ToastrService, private themeService: ThemeService) {
constructor(private route: ActivatedRoute, private router: Router,
private accountService: AccountService, private toastr: ToastrService,
private themeService: ThemeService) {
this.themeService.setTheme(this.themeService.defaultTheme);
const token = this.route.snapshot.queryParamMap.get('token');
const email = this.route.snapshot.queryParamMap.get('email');
if (token === undefined || token === '' || token === null || email === undefined || email === '' || email === null) {
// This is not a valid url, redirect to login
this.toastr.error('Invalid confirmation email');
@ -28,9 +32,4 @@ export class ConfirmMigrationEmailComponent implements OnInit {
});
}
ngOnInit(): void {
}
}

View file

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
@ -7,9 +7,10 @@ import { AccountService } from 'src/app/_services/account.service';
@Component({
selector: 'app-confirm-reset-password',
templateUrl: './confirm-reset-password.component.html',
styleUrls: ['./confirm-reset-password.component.scss']
styleUrls: ['./confirm-reset-password.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ConfirmResetPasswordComponent implements OnInit {
export class ConfirmResetPasswordComponent {
token: string = '';
registerForm: FormGroup = new FormGroup({
@ -17,7 +18,10 @@ export class ConfirmResetPasswordComponent implements OnInit {
password: new FormControl('', [Validators.required, Validators.maxLength(32), Validators.minLength(6)]),
});
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, private toastr: ToastrService) {
constructor(private route: ActivatedRoute, private router: Router,
private accountService: AccountService, private toastr: ToastrService,
private readonly cdRef: ChangeDetectorRef) {
const token = this.route.snapshot.queryParamMap.get('token');
const email = this.route.snapshot.queryParamMap.get('email');
if (token == undefined || token === '' || token === null) {
@ -29,11 +33,9 @@ export class ConfirmResetPasswordComponent implements OnInit {
this.token = token;
this.registerForm.get('email')?.setValue(email);
this.cdRef.markForCheck();
}
ngOnInit(): void {
}
submit() {
const model = this.registerForm.getRawValue();
@ -45,6 +47,4 @@ export class ConfirmResetPasswordComponent implements OnInit {
console.error(err, 'There was an error trying to confirm reset password');
});
}
}

View file

@ -1,11 +1,10 @@
import { Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { take } from 'rxjs/operators';
import { AccountService } from 'src/app/_services/account.service';
import { MemberService } from 'src/app/_services/member.service';
import { NavService } from 'src/app/_services/nav.service';
/**
* This is exclusivly used to register the first user on the server and nothing else
@ -13,7 +12,8 @@ import { NavService } from 'src/app/_services/nav.service';
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.scss']
styleUrls: ['./register.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RegisterComponent implements OnInit {
@ -23,9 +23,10 @@ export class RegisterComponent implements OnInit {
password: new FormControl('', [Validators.required, Validators.maxLength(32), Validators.minLength(6)]),
});
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService,
constructor(private router: Router, private accountService: AccountService,
private toastr: ToastrService, private memberService: MemberService) {
this.memberService.adminExists().pipe(take(1)).subscribe(adminExists => {
this.memberService.adminExists().pipe(take(1)).subscribe(adminExists => {
if (adminExists) {
this.router.navigateByUrl('login');
return;
@ -43,5 +44,4 @@ export class RegisterComponent implements OnInit {
this.router.navigateByUrl('login');
});
}
}

View file

@ -1,24 +1,23 @@
import { Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { AccountService } from 'src/app/_services/account.service';
@Component({
selector: 'app-reset-password',
templateUrl: './reset-password.component.html',
styleUrls: ['./reset-password.component.scss']
styleUrls: ['./reset-password.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ResetPasswordComponent implements OnInit {
export class ResetPasswordComponent {
registerForm: FormGroup = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
});
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, private toastr: ToastrService) {}
ngOnInit(): void {
}
constructor(private router: Router, private accountService: AccountService,
private toastr: ToastrService) {}
submit() {
const model = this.registerForm.get('email')?.value;

View file

@ -1,15 +1,9 @@
import { Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'app-splash-container',
templateUrl: './splash-container.component.html',
styleUrls: ['./splash-container.component.scss']
styleUrls: ['./splash-container.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SplashContainerComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
export class SplashContainerComponent {}

View file

@ -1,10 +1,9 @@
import { Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
import { take } from 'rxjs/operators';
import { SettingsService } from '../../admin/settings.service';
import { AddEmailToAccountMigrationModalComponent } from '../add-email-to-account-migration-modal/add-email-to-account-migration-modal.component';
import { User } from '../../_models/user';
import { AccountService } from '../../_services/account.service';
@ -15,18 +14,17 @@ import { NavService } from '../../_services/nav.service';
@Component({
selector: 'app-user-login',
templateUrl: './user-login.component.html',
styleUrls: ['./user-login.component.scss']
styleUrls: ['./user-login.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserLoginComponent implements OnInit {
model: any = {username: '', password: ''};
//model: any = {username: '', password: ''};
loginForm: FormGroup = new FormGroup({
username: new FormControl('', [Validators.required]),
password: new FormControl('', [Validators.required])
});
memberNames: Array<string> = [];
isCollapsed: {[key: string]: boolean} = {};
/**
* If there are no admins on the server, this will enable the registration to kick in.
*/
@ -37,7 +35,8 @@ export class UserLoginComponent implements OnInit {
isLoaded: boolean = false;
constructor(private accountService: AccountService, private router: Router, private memberService: MemberService,
private toastr: ToastrService, private navService: NavService, private settingsService: SettingsService, private modalService: NgbModal) {
private toastr: ToastrService, private navService: NavService, private modalService: NgbModal,
private readonly cdRef: ChangeDetectorRef) {
this.navService.showNavBar();
this.navService.hideSideNav();
}
@ -45,47 +44,42 @@ export class UserLoginComponent implements OnInit {
ngOnInit(): void {
this.navService.showNavBar();
this.navService.hideSideNav();
this.cdRef.markForCheck();
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
if (user) {
this.navService.showSideNav();
this.cdRef.markForCheck();
this.router.navigateByUrl('/libraries');
}
});
this.memberService.adminExists().pipe(take(1)).subscribe(adminExists => {
this.firstTimeFlow = !adminExists;
this.isLoaded = true;
if (this.firstTimeFlow) {
this.router.navigateByUrl('registration/register');
return;
}
this.setupAuthenticatedLoginFlow();
this.isLoaded = true;
this.cdRef.markForCheck();
});
}
setupAuthenticatedLoginFlow() {
if (this.memberNames.indexOf(' Login ') >= 0) { return; }
this.memberNames.push(' Login ');
this.memberNames.forEach(name => this.isCollapsed[name] = false);
const lastLogin = localStorage.getItem(this.accountService.lastLoginKey);
if (lastLogin != undefined && lastLogin != null && lastLogin != '') {
this.loginForm.get('username')?.setValue(lastLogin);
}
}
onAdminCreated(user: User | null) {
if (user != null) {
this.firstTimeFlow = false;
this.cdRef.markForCheck();
} else {
this.toastr.error('There was an issue creating the new user. Please refresh and try again.');
}
}
login() {
this.model = this.loginForm.getRawValue();
this.accountService.login(this.model).subscribe(() => {
const model = this.loginForm.getRawValue();
this.accountService.login(model).subscribe(() => {
this.loginForm.reset();
this.navService.showNavBar();
this.navService.showSideNav();
@ -101,9 +95,8 @@ export class UserLoginComponent implements OnInit {
}, err => {
if (err.error === 'You are missing an email on your account. Please wait while we migrate your account.') {
const modalRef = this.modalService.open(AddEmailToAccountMigrationModalComponent, { scrollable: true, size: 'md' });
modalRef.componentInstance.username = this.model.username;
modalRef.componentInstance.username = model.username;
modalRef.closed.pipe(take(1)).subscribe(() => {
});
} else {
this.toastr.error(err.error);