mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
79700: Feedback 2021-06-15 applied
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { delay, distinctUntilChanged, filter, take } from 'rxjs/operators';
|
import { delay, distinctUntilChanged, filter, take, withLatestFrom } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
|
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
|
||||||
|
|
||||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, of } from 'rxjs';
|
||||||
import { select, Store } from '@ngrx/store';
|
import { select, Store } from '@ngrx/store';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||||
@@ -38,6 +38,8 @@ import { ThemeService } from './shared/theme-support/theme.service';
|
|||||||
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
||||||
import { DEFAULT_THEME_CONFIG } from './shared/theme-support/theme.effects';
|
import { DEFAULT_THEME_CONFIG } from './shared/theme-support/theme.effects';
|
||||||
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||||
|
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-app',
|
selector: 'ds-app',
|
||||||
@@ -70,6 +72,11 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
isThemeLoading$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
isThemeLoading$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the idle modal is is currently open
|
||||||
|
*/
|
||||||
|
idleModalOpen: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
||||||
@Inject(DOCUMENT) private document: any,
|
@Inject(DOCUMENT) private document: any,
|
||||||
@@ -87,6 +94,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
private windowService: HostWindowService,
|
private windowService: HostWindowService,
|
||||||
private localeService: LocaleService,
|
private localeService: LocaleService,
|
||||||
private breadcrumbsService: BreadcrumbsService,
|
private breadcrumbsService: BreadcrumbsService,
|
||||||
|
private modalService: NgbModal,
|
||||||
@Optional() private cookiesService: KlaroService,
|
@Optional() private cookiesService: KlaroService,
|
||||||
@Optional() private googleAnalyticsService: GoogleAnalyticsService,
|
@Optional() private googleAnalyticsService: GoogleAnalyticsService,
|
||||||
) {
|
) {
|
||||||
@@ -108,6 +116,11 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
|
this.authService.trackTokenExpiration();
|
||||||
|
this.trackIdleModal();
|
||||||
|
}
|
||||||
|
|
||||||
// Load all the languages that are defined as active from the config file
|
// Load all the languages that are defined as active from the config file
|
||||||
translate.addLangs(environment.languages.filter((LangConfig) => LangConfig.active === true).map((a) => a.code));
|
translate.addLangs(environment.languages.filter((LangConfig) => LangConfig.active === true).map((a) => a.code));
|
||||||
|
|
||||||
@@ -130,7 +143,6 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
console.info(environment);
|
console.info(environment);
|
||||||
}
|
}
|
||||||
this.storeCSSVariables();
|
this.storeCSSVariables();
|
||||||
this.authService.trackTokenExpiration();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -229,4 +241,23 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
};
|
};
|
||||||
head.appendChild(link);
|
head.appendChild(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private trackIdleModal() {
|
||||||
|
const isIdle$ = this.authService.isUserIdle();
|
||||||
|
const isAuthenticated$ = this.authService.isAuthenticated();
|
||||||
|
isIdle$.pipe(withLatestFrom(isAuthenticated$))
|
||||||
|
.subscribe(([userIdle, authenticated]) => {
|
||||||
|
if (userIdle && authenticated) {
|
||||||
|
if (!this.idleModalOpen) {
|
||||||
|
const modalRef = this.modalService.open(IdleModalComponent);
|
||||||
|
this.idleModalOpen = true;
|
||||||
|
modalRef.componentInstance.response.pipe(take(1)).subscribe((closed: boolean) => {
|
||||||
|
if (closed) {
|
||||||
|
this.idleModalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -240,15 +240,9 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
|
|||||||
});
|
});
|
||||||
|
|
||||||
case AuthActionTypes.SET_USER_AS_IDLE:
|
case AuthActionTypes.SET_USER_AS_IDLE:
|
||||||
if (state.authenticated) {
|
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
idle: true,
|
idle: true,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
idle: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
case AuthActionTypes.UNSET_USER_AS_IDLE:
|
case AuthActionTypes.UNSET_USER_AS_IDLE:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
|
@@ -447,12 +447,15 @@ export class AuthService {
|
|||||||
* @param redirectUrl
|
* @param redirectUrl
|
||||||
*/
|
*/
|
||||||
public navigateToRedirectUrl(redirectUrl: string) {
|
public navigateToRedirectUrl(redirectUrl: string) {
|
||||||
|
// Don't do redirect if already on reload url
|
||||||
|
if (!hasValue(redirectUrl) || !redirectUrl.includes('/reload/')) {
|
||||||
let url = `/reload/${new Date().getTime()}`;
|
let url = `/reload/${new Date().getTime()}`;
|
||||||
if (isNotEmpty(redirectUrl) && !redirectUrl.startsWith(LOGIN_ROUTE)) {
|
if (isNotEmpty(redirectUrl) && !redirectUrl.startsWith(LOGIN_ROUTE)) {
|
||||||
url += `?redirect=${encodeURIComponent(redirectUrl)}`;
|
url += `?redirect=${encodeURIComponent(redirectUrl)}`;
|
||||||
}
|
}
|
||||||
this.hardRedirectService.redirect(url);
|
this.hardRedirectService.redirect(url);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh route navigated
|
* Refresh route navigated
|
||||||
|
@@ -1,13 +1,8 @@
|
|||||||
import { map, take } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { Component, Inject, OnInit, Optional, Input } from '@angular/core';
|
import { Component, Inject, OnInit, Input } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import {
|
import { combineLatest as combineLatestObservable, Observable, of } from 'rxjs';
|
||||||
combineLatest as observableCombineLatest,
|
|
||||||
combineLatest as combineLatestObservable,
|
|
||||||
Observable,
|
|
||||||
of
|
|
||||||
} from 'rxjs';
|
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||||
@@ -23,11 +18,7 @@ import { HostWindowService } from '../shared/host-window.service';
|
|||||||
import { ThemeConfig } from '../../config/theme.model';
|
import { ThemeConfig } from '../../config/theme.model';
|
||||||
import { Angulartics2DSpace } from '../statistics/angulartics/dspace-provider';
|
import { Angulartics2DSpace } from '../statistics/angulartics/dspace-provider';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
import { LocaleService } from '../core/locale/locale.service';
|
|
||||||
import { KlaroService } from '../shared/cookies/klaro.service';
|
|
||||||
import { slideSidebarPadding } from '../shared/animations/slide';
|
import { slideSidebarPadding } from '../shared/animations/slide';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
|
||||||
import { IdleModalComponent } from '../shared/idle-modal/idle-modal.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-root',
|
selector: 'ds-root',
|
||||||
@@ -54,11 +45,6 @@ export class RootComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() shouldShowRouteLoader: boolean;
|
@Input() shouldShowRouteLoader: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the idle modal is is currently open
|
|
||||||
*/
|
|
||||||
idleModalOpen: boolean;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
@@ -70,10 +56,7 @@ export class RootComponent implements OnInit {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private cssService: CSSVariableService,
|
private cssService: CSSVariableService,
|
||||||
private menuService: MenuService,
|
private menuService: MenuService,
|
||||||
private windowService: HostWindowService,
|
private windowService: HostWindowService
|
||||||
private localeService: LocaleService,
|
|
||||||
@Optional() private cookiesService: KlaroService,
|
|
||||||
private modalService: NgbModal
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,20 +71,5 @@ export class RootComponent implements OnInit {
|
|||||||
.pipe(
|
.pipe(
|
||||||
map(([collapsed, mobile]) => collapsed || mobile)
|
map(([collapsed, mobile]) => collapsed || mobile)
|
||||||
);
|
);
|
||||||
|
|
||||||
observableCombineLatest([this.authService.isUserIdle(), this.authService.isAuthenticated()])
|
|
||||||
.subscribe(([userIdle, authenticated]) => {
|
|
||||||
if (userIdle && authenticated) {
|
|
||||||
if (!this.idleModalOpen) {
|
|
||||||
const modalRef = this.modalService.open(IdleModalComponent);
|
|
||||||
this.idleModalOpen = true;
|
|
||||||
modalRef.componentInstance.response.pipe(take(1)).subscribe((closed: boolean) => {
|
|
||||||
if (closed) {
|
|
||||||
this.idleModalOpen = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { IdleModalComponent } from './idle-modal.component';
|
import { IdleModalComponent } from './idle-modal.component';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { HardRedirectService } from '../../core/services/hard-redirect.service';
|
||||||
|
|
||||||
describe('IdleModalComponent', () => {
|
describe('IdleModalComponent', () => {
|
||||||
let component: IdleModalComponent;
|
let component: IdleModalComponent;
|
||||||
@@ -12,15 +13,18 @@ describe('IdleModalComponent', () => {
|
|||||||
let debugElement: DebugElement;
|
let debugElement: DebugElement;
|
||||||
|
|
||||||
const modalStub = jasmine.createSpyObj('modalStub', ['close']);
|
const modalStub = jasmine.createSpyObj('modalStub', ['close']);
|
||||||
const authServiceStub = jasmine.createSpyObj('authService', ['setIdle', 'logout']);
|
const authServiceStub = jasmine.createSpyObj('authService', ['setIdle', 'logout', 'navigateToRedirectUrl']);
|
||||||
|
let hardRedirectService;
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
|
hardRedirectService = jasmine.createSpyObj('hardRedirectService', ['getCurrentRoute']);
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot()],
|
imports: [TranslateModule.forRoot()],
|
||||||
declarations: [IdleModalComponent],
|
declarations: [IdleModalComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: NgbActiveModal, useValue: modalStub },
|
{ provide: NgbActiveModal, useValue: modalStub },
|
||||||
{ provide: AuthService, useValue: authServiceStub }
|
{ provide: AuthService, useValue: authServiceStub },
|
||||||
|
{ provide: HardRedirectService, useValue: hardRedirectService }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
@@ -63,6 +67,10 @@ describe('IdleModalComponent', () => {
|
|||||||
it('should close the modal', () => {
|
it('should close the modal', () => {
|
||||||
expect(modalStub.close).toHaveBeenCalled();
|
expect(modalStub.close).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
it('should reload', () => {
|
||||||
|
expect(hardRedirectService.getCurrentRoute).toHaveBeenCalled();
|
||||||
|
expect(authServiceStub.navigateToRedirectUrl).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('closePressed', () => {
|
describe('closePressed', () => {
|
||||||
|
@@ -4,6 +4,7 @@ import { environment } from '../../../environments/environment';
|
|||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { hasValue } from '../empty.util';
|
import { hasValue } from '../empty.util';
|
||||||
|
import { HardRedirectService } from '../../core/services/hard-redirect.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-idle-modal',
|
selector: 'ds-idle-modal',
|
||||||
@@ -29,7 +30,8 @@ export class IdleModalComponent implements OnInit {
|
|||||||
response: Subject<boolean> = new Subject();
|
response: Subject<boolean> = new Subject();
|
||||||
|
|
||||||
constructor(private activeModal: NgbActiveModal,
|
constructor(private activeModal: NgbActiveModal,
|
||||||
private authService: AuthService) {
|
private authService: AuthService,
|
||||||
|
protected hardRedirectService: HardRedirectService) {
|
||||||
this.timeToExpire = (environment.auth.ui.timeUntilIdle + environment.auth.ui.idleGracePeriod) / 1000 / 60; // ms => min
|
this.timeToExpire = (environment.auth.ui.timeUntilIdle + environment.auth.ui.idleGracePeriod) / 1000 / 60; // ms => min
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,8 +55,9 @@ export class IdleModalComponent implements OnInit {
|
|||||||
* Close modal and logout
|
* Close modal and logout
|
||||||
*/
|
*/
|
||||||
logOutPressed() {
|
logOutPressed() {
|
||||||
this.authService.logout();
|
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
|
this.authService.logout();
|
||||||
|
this.authService.navigateToRedirectUrl(this.hardRedirectService.getCurrentRoute());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user