diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts index d8984d7d5ba444a77f21dfca974665212f70d7fc..217099a3f286a96874427c9cee553953060fc0b2 100644 --- a/smp-angular/src/app/app.module.ts +++ b/smp-angular/src/app/app.module.ts @@ -354,6 +354,7 @@ import {DateTimeService} from "./common/services/date-time.service"; import { SmpTableComponent } from "./common/components/smp-table/smp-table.component"; +import {LocalStorageService} from "./common/services/local-storage.service"; @NgModule({ declarations: [ AccessTokenPanelComponent, @@ -515,6 +516,7 @@ import { SmlIntegrationService, SmpInfoService, ThemeService, + LocalStorageService, UserDetailsService, UserService, WindowSpinnerService, diff --git a/smp-angular/src/app/common/services/local-storage.service.ts b/smp-angular/src/app/common/services/local-storage.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..87a0ab442d6eab8847cefb9408dd590bfbc55d0b --- /dev/null +++ b/smp-angular/src/app/common/services/local-storage.service.ts @@ -0,0 +1,159 @@ +import {Injectable} from '@angular/core'; +import {User} from "../../security/user.model"; +import {ResourceRo} from "../model/resource-ro.model"; +import {SubresourceRo} from "../model/subresource-ro.model"; +import { + ReviewDocumentVersionRo +} from "../model/review-document-version-ro.model"; +import {NavigationNode} from "../../window/sidenav/navigation-model.service"; + +/** + * Service to handle local storage operations + * + * + * @since 5.1 + * @author Joze RIHTARSIC + */ +@Injectable() +export class LocalStorageService { + + private static readonly LOCAL_STORAGE_THEME_KEY: string = "smp-theme"; + private static readonly LOCAL_STORAGE_THEME_DEFAULT: string = "default_theme"; + + private static readonly LOCAL_STORAGE_CURRENT_USER_KEY = 'currentUser'; + private static readonly LOCAL_STORAGE_EDIT_RESOURCE_SELECTED = 'selected-edit-resource'; + private static readonly LOCAL_STORAGE_EDIT_SUBRESOURCE_SELECTED = 'selected-edit-subresource'; + private static readonly LOCAL_STORAGE_EDIT_REVIEW_VERSION_SELECTED = 'selected-edit-review-version'; + private static readonly LOCAL_STORAGE_EDIT_DOCUMENT_VERSION_SELECTED = 'selected-edit-document-version'; + private static readonly LOCAL_STORAGE_NAVIGATION_PATH = 'navigation-path'; + + + constructor() { + } + + private storeJSONEntity(entity: any, storageKey: string): void { + if (!entity) { + localStorage.removeItem(storageKey); + return; + } + let entityAsString: string = JSON.stringify(entity); + localStorage.setItem(storageKey, entityAsString); + } + + private getJSONEntity(storageKey: string): any { + let entityAsString: string = localStorage.getItem(storageKey); + if (!entityAsString) { + return null; + } + return JSON.parse(entityAsString); + } + + private storeJSONArray(arr: any[], storageKey: string): void { + if (!arr) { + localStorage.removeItem(storageKey); + return; + } + let entityAsString: string = JSON.stringify(arr); + localStorage.setItem(storageKey, entityAsString); + } + + private getJSONArray(storageKey: string): any[] { + let arrayAsString: string = localStorage.getItem(storageKey); + if (!arrayAsString) { + return []; + } + return JSON.parse(arrayAsString); + } + + + /** + * Method stores the theme to localStorage + * @param theme + */ + saveUserTheme(theme: string) { + if (!!theme && theme != LocalStorageService.LOCAL_STORAGE_THEME_DEFAULT) { + localStorage.setItem(LocalStorageService.LOCAL_STORAGE_THEME_KEY, theme); + } else { + localStorage.removeItem(LocalStorageService.LOCAL_STORAGE_THEME_KEY) + } + }; + + /** + * Get user theme from local storage + */ + public getUserTheme(): string { + return localStorage.getItem(LocalStorageService.LOCAL_STORAGE_THEME_KEY); + } + + public storeUserDetails(user: User): void { + this.storeJSONEntity(user, LocalStorageService.LOCAL_STORAGE_CURRENT_USER_KEY); + } + + public getUserDetails(): User { + return this.getJSONEntity(LocalStorageService.LOCAL_STORAGE_CURRENT_USER_KEY); + } + + public storeSelectedResource(resource: ResourceRo): void { + this.storeJSONEntity(resource, LocalStorageService.LOCAL_STORAGE_EDIT_RESOURCE_SELECTED); + } + + public getSelectedResource(): ResourceRo { + return this.getJSONEntity(LocalStorageService.LOCAL_STORAGE_EDIT_RESOURCE_SELECTED); + } + + public storeSelectedSubresource(resource: SubresourceRo): void { + this.storeJSONEntity(resource, LocalStorageService.LOCAL_STORAGE_EDIT_SUBRESOURCE_SELECTED); + } + + public getSelectedSubresource(): SubresourceRo { + return this.getJSONEntity(LocalStorageService.LOCAL_STORAGE_EDIT_SUBRESOURCE_SELECTED); + } + + public storeSelectedReviewDocumentVersion(reviewVersion: ReviewDocumentVersionRo): void { + this.storeJSONEntity(reviewVersion, LocalStorageService.LOCAL_STORAGE_EDIT_REVIEW_VERSION_SELECTED); + } + + public getSelectedReviewDocumentVersion(): ReviewDocumentVersionRo { + return this.getJSONEntity(LocalStorageService.LOCAL_STORAGE_EDIT_REVIEW_VERSION_SELECTED); + } + + public storeSelectedDocumentVersionNumber(documentVersion: number): void { + if (documentVersion == null) { + localStorage.removeItem(LocalStorageService.LOCAL_STORAGE_EDIT_DOCUMENT_VERSION_SELECTED); + return; + } + localStorage.setItem(LocalStorageService.LOCAL_STORAGE_EDIT_DOCUMENT_VERSION_SELECTED, documentVersion.toString()); + } + + public storeNavigationPath(navigationPath: NavigationNode[]): void { + this.storeJSONArray(navigationPath, LocalStorageService.LOCAL_STORAGE_NAVIGATION_PATH); + } + + public getNavigationPath(): NavigationNode[] { + let path = this.getJSONArray(LocalStorageService.LOCAL_STORAGE_NAVIGATION_PATH); + return path ? path : []; + } + + public getSelectedDocumentVersionNumber(): number { + let documentVersion: string = localStorage.getItem(LocalStorageService.LOCAL_STORAGE_EDIT_DOCUMENT_VERSION_SELECTED); + if (documentVersion == null) { + return null; + } + return parseInt(documentVersion); + } + + /** + * Method clears all local storage except the theme. Theme is not + * cleared because it is used to set the theme on the next login. + */ + public clearLocalStorage(): void { + localStorage.removeItem(LocalStorageService.LOCAL_STORAGE_CURRENT_USER_KEY); + localStorage.removeItem(LocalStorageService.LOCAL_STORAGE_EDIT_RESOURCE_SELECTED); + localStorage.removeItem(LocalStorageService.LOCAL_STORAGE_EDIT_SUBRESOURCE_SELECTED); + localStorage.removeItem(LocalStorageService.LOCAL_STORAGE_EDIT_REVIEW_VERSION_SELECTED); + localStorage.removeItem(LocalStorageService.LOCAL_STORAGE_EDIT_DOCUMENT_VERSION_SELECTED); + localStorage.removeItem(LocalStorageService.LOCAL_STORAGE_NAVIGATION_PATH); + + } + +} diff --git a/smp-angular/src/app/common/theme-service/theme.service.ts b/smp-angular/src/app/common/theme-service/theme.service.ts index cbd5084e77554b90f6c33f23dcd75895044ed539..f801d1766c4118efc4a42aef04fd20e478530b68 100644 --- a/smp-angular/src/app/common/theme-service/theme.service.ts +++ b/smp-angular/src/app/common/theme-service/theme.service.ts @@ -1,5 +1,6 @@ import {EventEmitter, Injectable} from '@angular/core'; import {SecurityEventService} from "../../security/security-event.service"; +import {LocalStorageService} from "../services/local-storage.service"; /** @@ -45,12 +46,13 @@ export interface ThemeItem { export class ThemeService { selectedTheme: EventEmitter<string> = new EventEmitter<string>(); - private static THEME_STORAGE_NAME = "smp-theme"; - private static DEFAULT_THEME_NAME = "default_theme"; + private static THEME_STORAGE_NAME: string = "smp-theme"; + private static DEFAULT_THEME_NAME: string = "default_theme"; private _themes: ThemeItem[] = SMP_THEME_ITEMS; - constructor(private securityEventService: SecurityEventService) { + constructor(private securityEventService: SecurityEventService, + private localStorageService: LocalStorageService) { securityEventService.onLoginSuccessEvent().subscribe(user => { // set the last logged user as default theme @@ -90,11 +92,7 @@ export class ThemeService { */ persistTheme(theme: string) { this.setTheme(theme); - if (!!theme && theme != ThemeService.DEFAULT_THEME_NAME) { - localStorage.setItem(ThemeService.THEME_STORAGE_NAME, theme); - } else { - localStorage.removeItem(ThemeService.THEME_STORAGE_NAME) - } + this.localStorageService.saveUserTheme(theme); }; /** @@ -115,7 +113,7 @@ export class ThemeService { body.classList.remove(...themeList); } - get currentTheme() { - return localStorage.getItem(ThemeService.THEME_STORAGE_NAME); + get currentTheme(): string { + return this.localStorageService.getUserTheme(); } } diff --git a/smp-angular/src/app/edit/edit-resources/edit-resource.service.ts b/smp-angular/src/app/edit/edit-resources/edit-resource.service.ts index 0076279d3ea0cb334737f24724628094e80fad9b..5a9f1d3dc7cb05c40aa05b75dab6fc9bdc2a76cd 100644 --- a/smp-angular/src/app/edit/edit-resources/edit-resource.service.ts +++ b/smp-angular/src/app/edit/edit-resources/edit-resource.service.ts @@ -1,4 +1,4 @@ -import {Injectable} from '@angular/core'; +import {Injectable, Input} from '@angular/core'; import {Observable} from 'rxjs'; import {HttpClient, HttpParams} from '@angular/common/http'; @@ -14,6 +14,7 @@ import {SubresourceRo} from "../../common/model/subresource-ro.model"; import { ReviewDocumentVersionRo } from "../../common/model/review-document-version-ro.model"; +import {LocalStorageService} from "../../common/services/local-storage.service"; /** * The EditResourceService is used for server interaction on resources, sub-resources and it's documents. @@ -24,15 +25,53 @@ import { @Injectable() export class EditResourceService { - selectedResource: ResourceRo; - selectedSubresource: SubresourceRo; - selectedReviewDocument: ReviewDocumentVersionRo; + _selectedResource: ResourceRo; + _selectedSubresource: SubresourceRo; + _selectedReviewDocument: ReviewDocumentVersionRo; constructor( private http: HttpClient, - private securityService: SecurityService) { + private securityService: SecurityService, + private localStorageService: LocalStorageService) { } + @Input() set selectedResource(value: ResourceRo) { + this._selectedResource = value; + this.localStorageService.storeSelectedResource(value); + } + + get selectedResource(): ResourceRo { + if (this._selectedResource == null) { + this._selectedResource = this.localStorageService.getSelectedResource(); + } + return this._selectedResource; + } + + @Input() set selectedSubresource(value: SubresourceRo) { + this._selectedSubresource = value; + this.localStorageService.storeSelectedSubresource(value); + } + + get selectedSubresource(): SubresourceRo { + if (this._selectedSubresource == null) { + this._selectedSubresource = this.localStorageService.getSelectedSubresource(); + } + return this._selectedSubresource; + } + + @Input() set selectedReviewDocument(value: ReviewDocumentVersionRo) { + this._selectedReviewDocument = value; + this.localStorageService.storeSelectedReviewDocumentVersion(value); + } + + get selectedReviewDocument(): ReviewDocumentVersionRo { + if (this._selectedReviewDocument == null) { + this._selectedReviewDocument = this.localStorageService.getSelectedReviewDocumentVersion(); + } + return this._selectedReviewDocument; + } + + /** * Method return observable of resource list from the server for resource-admin role for selected domain and group filter and paginating data. * diff --git a/smp-angular/src/app/security/security.service.ts b/smp-angular/src/app/security/security.service.ts index 81fc3920531b90836ab3d603a0f13e1aebcaa63f..ed6ee70d24dea981d489bf581a9dfd66cb876ac1 100644 --- a/smp-angular/src/app/security/security.service.ts +++ b/smp-angular/src/app/security/security.service.ts @@ -16,11 +16,13 @@ import {Router} from "@angular/router"; import {TranslateService} from "@ngx-translate/core"; import {WindowSpinnerService} from "../common/services/window-spinner.service"; import {SmpErrorCode} from "../common/enums/smp-error-code.enum"; +import {LocalStorageService} from "../common/services/local-storage.service"; import {SmpInfo} from "../app-info/smp-info.model"; @Injectable() export class SecurityService { + public static readonly TIME_BEFORE_EXPIRATION_IN_SECONDS: number = 60; public static readonly DELAY_BEFORE_UI_SESSION_EXTENSION_IN_MS: number = 3000; public static readonly MAXIMUM_TIMEOUT_VALUE: number = 2147483647; @@ -36,7 +38,8 @@ export class SecurityService { private dialog: MatDialog, private router: Router, private translateService: TranslateService, - private windowSpinnerService: WindowSpinnerService + private windowSpinnerService: WindowSpinnerService, + private localStorageService: LocalStorageService ) { this.securityEventService.onLogoutSuccessEvent().subscribe(() => { this.dialog.closeAll(); @@ -166,7 +169,7 @@ export class SecurityService { } }, (error: any) => { // just clean local storage - this.clearLocalStorage(); + this.localStorageService.clearLocalStorage(); }); } @@ -187,13 +190,13 @@ export class SecurityService { } finalizeLogout(res) { - this.clearLocalStorage(); + this.localStorageService.clearLocalStorage(); this.securityEventService.notifyLogoutSuccessEvent(res); } getCurrentUser(): User { - return JSON.parse(this.readLocalStorage()); + return this.localStorageService.getUserDetails(); } private getCurrentUsernameFromServer(): Observable<User> { @@ -216,7 +219,7 @@ export class SecurityService { this.getCurrentUsernameFromServer().subscribe({ next: (user: User) => { if (!user) { - this.clearLocalStorage(); + this.localStorageService.clearLocalStorage(); } subject.next(user !== null); }, error: (user: any) => { @@ -270,19 +273,15 @@ export class SecurityService { updateUserDetails(userDetails: User) { // store user data to local storage! - this.populateLocalStorage(JSON.stringify(userDetails)); - } - - private populateLocalStorage(userDetails: string) { - localStorage.setItem(this.LOCAL_STORAGE_KEY_CURRENT_USER, userDetails); + this.localStorageService.storeUserDetails(userDetails); } - private readLocalStorage(): string { - return localStorage.getItem(this.LOCAL_STORAGE_KEY_CURRENT_USER); - } - - public clearLocalStorage() { - localStorage.removeItem(this.LOCAL_STORAGE_KEY_CURRENT_USER); + /** + * Method clears all local storage except the theme. Theme is not + * cleared because it is used to set the theme on the next login. + */ + public clearLocalStorage(): void { + this.localStorageService.clearLocalStorage(); } /** diff --git a/smp-angular/src/app/window/sidenav/navigation-model.service.ts b/smp-angular/src/app/window/sidenav/navigation-model.service.ts index 12c2b2822b21c74834e8209e95cb4f606f3e3cee..72c0b96305f761e68f439ef0b259d3da9d31f6f7 100644 --- a/smp-angular/src/app/window/sidenav/navigation-model.service.ts +++ b/smp-angular/src/app/window/sidenav/navigation-model.service.ts @@ -1,5 +1,5 @@ import {MatTreeNestedDataSource} from "@angular/material/tree"; -import {Injectable} from "@angular/core"; +import {Injectable, Input} from "@angular/core"; import {SecurityService} from "../../security/security.service"; import {SecurityEventService} from "../../security/security-event.service"; import {SmpConstants} from "../../smp.constants"; @@ -8,6 +8,7 @@ import {User} from "../../security/user.model"; import {NavigationEnd, Router} from "@angular/router"; import {Observable, Subject} from "rxjs"; import {filter, map} from "rxjs/operators"; +import {LocalStorageService} from "../../common/services/local-storage.service"; /** * The smp navigation tree @@ -75,14 +76,15 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { private selectedPathSubject = new Subject<NavigationNode[]>(); selected: NavigationNode; previousSelected: NavigationNode; - selectedPath: NavigationNode[]; + _selectedPath: NavigationNode[]; private rootNode: NavigationNode = PUBLIC_NAVIGATION_TREE; constructor(protected securityService: SecurityService, protected securityEventService: SecurityEventService, protected http: HttpClient, - protected router: Router) { + protected router: Router, + protected localStorageService: LocalStorageService) { super(); // set tree data. this.refreshNavigationTree(); @@ -101,6 +103,18 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { ); } + @Input() set selectedPath(path: NavigationNode[]) { + this.localStorageService.storeNavigationPath(path); + this._selectedPath = path; + } + + get selectedPath(): NavigationNode[] { + if (!this._selectedPath || this._selectedPath?.length == 0) { + this._selectedPath = this.localStorageService.getNavigationPath(); + } + return this._selectedPath; + } + ngOnDestroy() { console.log('>> STOP listening to route events '); this.sub.unsubscribe(); @@ -108,9 +122,7 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { select(node: NavigationNode) { let targetNode = this.findLeaf(node); - if (targetNode === this.selected) { - console.log("Already selected skip"); return } if (!!targetNode) { @@ -217,17 +229,17 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { * @param parentNode - the root of the tree to start search */ protected findPathForNode(targetNode: NavigationNode, parentNode: NavigationNode): NavigationNode[] { - if (parentNode === targetNode) { + if (parentNode.code === targetNode.code) { return [parentNode]; } if (!parentNode.children) { return null; } - const index = parentNode.children.indexOf(targetNode); - if (index > -1) { + let node: NavigationNode = this.findNodeByCode(targetNode.code, parentNode); + if (node) { // got target return initial array - return [parentNode, targetNode]; + return [parentNode, node]; } for (const child of parentNode.children) { @@ -243,7 +255,6 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { if (!parentNode.children) { return null; } - console.log("find " + nodeCode + " from parent: " + parentNode.code) return parentNode.children.find(node => node.routerLink == nodeCode); } @@ -252,7 +263,6 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { */ public refreshNavigationTree() { this.securityService.isAuthenticated(false).subscribe((isAuthenticated: boolean) => { - console.log("Refresh navigation tree [is authenticated: " + isAuthenticated + "]") if (isAuthenticated) { const currentUser: User = this.securityService.getCurrentUser(); // get navigation for user @@ -392,9 +402,11 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { } public navigateUp(): void { - this.selectedPath?.pop(); - if (this.selectedPath?.length > 0) { - this.select(this.selectedPath[this.selectedPath.length - 1]); + let currentPath = this.selectedPath; + currentPath?.pop(); + this._selectedPath = currentPath; + if (currentPath?.length > 0) { + this.select(currentPath[currentPath?.length - 1]); } }