diff options
author | crupest <crupest@outlook.com> | 2019-03-13 22:04:09 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2019-03-13 22:04:09 +0800 |
commit | 5bfbb5020904eadba298fdc094172d1c12879278 (patch) | |
tree | 366d295d64bc5447caca2f3a87a188151a7239bd /Timeline/ClientApp | |
parent | eb6cef70b6f9d1060556592dbf474cf54a174902 (diff) | |
download | timeline-5bfbb5020904eadba298fdc094172d1c12879278.tar.gz timeline-5bfbb5020904eadba298fdc094172d1c12879278.tar.bz2 timeline-5bfbb5020904eadba298fdc094172d1c12879278.zip |
Use route to control user dialog.
Diffstat (limited to 'Timeline/ClientApp')
9 files changed, 76 insertions, 113 deletions
diff --git a/Timeline/ClientApp/src/app/app.component.html b/Timeline/ClientApp/src/app/app.component.html index a5df80ac..92c88625 100644 --- a/Timeline/ClientApp/src/app/app.component.html +++ b/Timeline/ClientApp/src/app/app.component.html @@ -1,12 +1,12 @@ <body> <mat-toolbar color="primary" class="mat-elevation-z4"> <a mat-button routerLink="/"> - <img width="30" height="30" src="assets/icon.svg"> Timeline</a> + <img width="30" height="30" src="assets/icon.svg">Timeline</a> <a mat-button routerLink="/todo">TodoList</a> <span class="fill-remaining-space"></span> - <button mat-icon-button (click)="openUserDialog()"> + <a mat-icon-button [routerLink]="[{outlets: {user: ['login']}}]"> <mat-icon>account_circle</mat-icon> - </button> + </a> </mat-toolbar> <div> diff --git a/Timeline/ClientApp/src/app/app.component.ts b/Timeline/ClientApp/src/app/app.component.ts index ee02f833..33f33048 100644 --- a/Timeline/ClientApp/src/app/app.component.ts +++ b/Timeline/ClientApp/src/app/app.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; + import { UserService } from './user/user.service'; @Component({ @@ -7,10 +8,6 @@ import { UserService } from './user/user.service'; styleUrls: ['./app.component.css'] }) export class AppComponent { - - constructor(private userService: UserService) { } - - openUserDialog() { - this.userService.openUserDialog(); - } + // never remove userService because we need it explicit constructing. + constructor(userService: UserService) { } } diff --git a/Timeline/ClientApp/src/app/app.module.ts b/Timeline/ClientApp/src/app/app.module.ts index 85c4c43d..b75e10e2 100644 --- a/Timeline/ClientApp/src/app/app.module.ts +++ b/Timeline/ClientApp/src/app/app.module.ts @@ -9,6 +9,7 @@ import { AppComponent } from './app.component'; import { TodoModule } from './todo/todo.module'; import { HomeModule } from './home/home.module'; import { UserModule } from './user/user.module'; +import { UserService } from './user/user.service'; @NgModule({ @@ -22,6 +23,7 @@ import { UserModule } from './user/user.module'; { path: '', redirectTo: '/home', pathMatch: 'full' }, ]) ], + providers: [UserService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/Timeline/ClientApp/src/app/user/auth.guard.ts b/Timeline/ClientApp/src/app/user/auth.guard.ts index 16f66cd8..64ff93c7 100644 --- a/Timeline/ClientApp/src/app/user/auth.guard.ts +++ b/Timeline/ClientApp/src/app/user/auth.guard.ts @@ -2,38 +2,47 @@ import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router'; import { Observable } from 'rxjs'; -import { UserService } from './user.service'; +import { InternalUserService } from './internal-user-service/internal-user.service'; -export type RequiredAuthData = 'all' | 'requirelogin' | 'requirenologin' | string[]; +export type AuthStrategy = 'all' | 'requirelogin' | 'requirenologin' | string[]; export abstract class AuthGuard implements CanActivate { - constructor(private userService: UserService) { } + constructor(protected internalUserService: InternalUserService) { } - abstract get requiredAuth(): RequiredAuthData; + onAuthFailed() { } + + abstract get authStrategy(): AuthStrategy; canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { - const { requiredAuth } = this; + const { authStrategy } = this; - if (requiredAuth === 'all') { + if (authStrategy === 'all') { return true; } - const { currentUserInfo } = this.userService; + + const { currentUserInfo } = this.internalUserService; if (currentUserInfo === null) { - return requiredAuth === 'requirenologin'; + if (authStrategy === 'requirenologin') { + return true; + } } else { - if (requiredAuth === 'requirelogin') { + if (authStrategy === 'requirelogin') { return true; - } else if (requiredAuth === 'requirenologin') { - return false; - } else { + } else if (authStrategy instanceof Array) { const { roles } = currentUserInfo; - return requiredAuth.every(value => roles.includes(value)); + if (authStrategy.every(value => roles.includes(value))) { + return true; + } } } + + // reach here means auth fails + this.onAuthFailed(); + return false; } } @@ -41,22 +50,30 @@ export abstract class AuthGuard implements CanActivate { providedIn: 'root' }) export class RequireLoginGuard extends AuthGuard { - readonly requiredAuth: RequiredAuthData = 'requirelogin'; + readonly authStrategy: AuthStrategy = 'requirelogin'; // never remove this constructor or you will get an injection error. - constructor(userService: UserService) { - super(userService); - } + constructor(internalUserService: InternalUserService) { + super(internalUserService); + } + + onAuthFailed() { + this.internalUserService.userRouteNavigate(['login', { reason: 'nologin' }]); + } } @Injectable({ providedIn: 'root' }) export class RequireNoLoginGuard extends AuthGuard { - readonly requiredAuth: RequiredAuthData = 'requirenologin'; + readonly authStrategy: AuthStrategy = 'requirenologin'; // never remove this constructor or you will get an injection error. - constructor(userService: UserService) { - super(userService); - } + constructor(internalUserService: InternalUserService) { + super(internalUserService); + } + + onAuthFailed() { + this.internalUserService.userRouteNavigate(['success']); + } } diff --git a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.css b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.css index a443e3c0..e69de29b 100644 --- a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.css +++ b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.css @@ -1,5 +0,0 @@ -.container { - display: flex; - justify-content: center; - align-content: center; -} diff --git a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.html b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.html index 58dff0e4..e8dbb003 100644 --- a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.html +++ b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.html @@ -1,4 +1 @@ -<div class="container"> - <mat-progress-spinner *ngIf="isLoading" mode="indeterminate" diameter="50"></mat-progress-spinner> - <router-outlet name="user"></router-outlet> -</div> +<router-outlet name="user"></router-outlet> diff --git a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts index c56e1ed1..fbabdb1a 100644 --- a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts +++ b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts @@ -6,15 +6,6 @@ import { of, Observable } from 'rxjs'; import { delay } from 'rxjs/operators'; import { UserDialogComponent } from './user-dialog.component'; -import { createMockInternalUserService } from '../internal-user-service/internal-user.service.mock'; -import { InternalUserService, UserLoginState } from '../internal-user-service/internal-user.service'; - -@Component({ - /* tslint:disable-next-line:component-selector*/ - selector: 'mat-progress-spinner', - template: '' -}) -class MatProgressSpinnerStubComponent { } @Component({ /* tslint:disable-next-line:component-selector*/ @@ -27,16 +18,12 @@ class RouterOutletStubComponent { } describe('UserDialogComponent', () => { let component: UserDialogComponent; let fixture: ComponentFixture<UserDialogComponent>; - let mockInternalUserService: jasmine.SpyObj<InternalUserService>; beforeEach(async(() => { - mockInternalUserService = createMockInternalUserService(); - TestBed.configureTestingModule({ - declarations: [UserDialogComponent, MatProgressSpinnerStubComponent, RouterOutletStubComponent], - providers: [{ provide: InternalUserService, useValue: mockInternalUserService }, - { // for the workaround + declarations: [UserDialogComponent, RouterOutletStubComponent], + providers: [{ // for the workaround provide: Router, useValue: { events: new Observable<Event>() } @@ -50,39 +37,4 @@ describe('UserDialogComponent', () => { component = fixture.componentInstance; }); - it('progress spinner should work well', fakeAsync(() => { - mockInternalUserService.refreshAndGetUserState.and.returnValue(of(<UserLoginState>'nologin').pipe(delay(10))); - fixture.detectChanges(); - expect(fixture.debugElement.query(By.css('mat-progress-spinner'))).toBeTruthy(); - tick(10); - fixture.detectChanges(); - expect(fixture.debugElement.query(By.css('mat-progress-spinner'))).toBeFalsy(); - })); - - it('nologin should work well', () => { - mockInternalUserService.refreshAndGetUserState.and.returnValue(of(<UserLoginState>'nologin')); - - fixture.detectChanges(); - - expect(mockInternalUserService.refreshAndGetUserState).toHaveBeenCalled(); - expect(mockInternalUserService.userRouteNavigate).toHaveBeenCalledWith(['login', { reason: 'nologin' }]); - }); - - it('invalid login should work well', () => { - mockInternalUserService.refreshAndGetUserState.and.returnValue(of(<UserLoginState>'invalidlogin')); - - fixture.detectChanges(); - - expect(mockInternalUserService.refreshAndGetUserState).toHaveBeenCalled(); - expect(mockInternalUserService.userRouteNavigate).toHaveBeenCalledWith(['login', { reason: 'invalidlogin' }]); - }); - - it('success should work well', () => { - mockInternalUserService.refreshAndGetUserState.and.returnValue(of(<UserLoginState>'success')); - - fixture.detectChanges(); - - expect(mockInternalUserService.refreshAndGetUserState).toHaveBeenCalled(); - expect(mockInternalUserService.userRouteNavigate).toHaveBeenCalledWith(['success', { reason: 'already' }]); - }); }); diff --git a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.ts b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.ts index cf5f3643..2887f0a6 100644 --- a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.ts +++ b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.ts @@ -1,5 +1,4 @@ -import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { InternalUserService } from '../internal-user-service/internal-user.service'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { RouterOutlet, Router, ActivationStart } from '@angular/router'; @Component({ @@ -7,14 +6,12 @@ import { RouterOutlet, Router, ActivationStart } from '@angular/router'; templateUrl: './user-dialog.component.html', styleUrls: ['./user-dialog.component.css'] }) -export class UserDialogComponent implements OnInit, OnDestroy { +export class UserDialogComponent implements OnInit { - constructor(private userService: InternalUserService, private router: Router) { } + constructor(private router: Router) { } @ViewChild(RouterOutlet) outlet!: RouterOutlet; - isLoading = true; - ngOnInit() { // this is a workaround for a bug. see https://github.com/angular/angular/issues/20694 const subscription = this.router.events.subscribe(e => { @@ -23,19 +20,5 @@ export class UserDialogComponent implements OnInit, OnDestroy { subscription.unsubscribe(); } }); - - - this.userService.refreshAndGetUserState().subscribe(result => { - this.isLoading = false; - if (result === 'success') { - this.userService.userRouteNavigate(['success', { reason: 'already' }]); - } else { - this.userService.userRouteNavigate(['login', { reason: result }]); - } - }); - } - - ngOnDestroy() { - this.userService.userRouteNavigate(null); } } diff --git a/Timeline/ClientApp/src/app/user/user.service.ts b/Timeline/ClientApp/src/app/user/user.service.ts index e876706c..076d0c21 100644 --- a/Timeline/ClientApp/src/app/user/user.service.ts +++ b/Timeline/ClientApp/src/app/user/user.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; -import { MatDialog } from '@angular/material'; +import { MatDialog, MatDialogRef } from '@angular/material'; +import { Router, ActivationStart } from '@angular/router'; import { Observable } from 'rxjs'; @@ -15,7 +16,16 @@ import { UserDialogComponent } from './user-dialog/user-dialog.component'; providedIn: 'root' }) export class UserService { - constructor(private dialog: MatDialog, private internalService: InternalUserService) { } + + private dialogRef: MatDialogRef<UserDialogComponent> | null = null; + + constructor(router: Router, private dialog: MatDialog, private internalService: InternalUserService) { + router.events.subscribe(event => { + if (event instanceof ActivationStart && event.snapshot.outlet === 'user') { + setTimeout(() => this.openUserDialog(), 0); + } + }); + } get currentUserInfo(): UserInfo | null { return this.internalService.currentUserInfo; @@ -25,9 +35,19 @@ export class UserService { return this.internalService.userInfo$; } - openUserDialog() { - this.dialog.open(UserDialogComponent, { + private openUserDialog() { + if (this.dialogRef) { + return; + } + + this.dialogRef = this.dialog.open(UserDialogComponent, { width: '300px' }); + + const subscription = this.dialogRef.afterClosed().subscribe(_ => { + this.internalService.userRouteNavigate(null); + this.dialogRef = null; + subscription.unsubscribe(); + }); } } |