From 52ab3d6dea044edaf81b8193e2eeee7685b435fe Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 28 Aug 2019 16:02:27 +0300 Subject: [PATCH] Relation edit dialog improvements. Minor improvements. --- ui-ngx/src/app/core/guards/auth.guard.ts | 13 +---- .../interceptors/global-http-interceptor.ts | 23 ++++---- .../core/notification/notification.actions.ts | 13 +++-- .../core/notification/notification.effects.ts | 9 ++++ .../core/notification/notification.models.ts | 5 ++ .../core/notification/notification.reducer.ts | 5 +- .../src/app/core/services/dialog.service.ts | 28 +++++++++- .../app/core/services/notification.service.ts | 13 ++++- .../audit-log-details-dialog.component.ts | 9 ++-- .../entity/add-entity-dialog.component.ts | 7 ++- .../relation/relation-dialog.component.html | 7 +-- .../relation/relation-dialog.component.ts | 31 ++++++----- ...d-entities-to-customer-dialog.component.ts | 8 ++- .../assign-to-customer-dialog.component.ts | 8 ++- .../pages/asset/asset-tabs.component.html | 4 ++ .../customer/customer-tabs.component.html | 4 ++ .../make-dashboard-public-dialog.component.ts | 7 ++- ...ge-dashboard-customers-dialog.component.ts | 8 ++- .../device-credentials-dialog.component.ts | 8 ++- .../entity-view-tabs.component.html | 4 ++ .../change-password-dialog.component.ts | 7 ++- .../rulechain/rulechain-tabs.component.html | 4 ++ .../pages/tenant/tenant-tabs.component.html | 4 ++ .../user/activation-link-dialog.component.ts | 7 ++- .../pages/user/add-user-dialog.component.ts | 7 ++- .../app/shared/components/dialog.component.ts | 51 ++++++++++++++++++ .../entity/entity-list.component.html | 1 + .../entity/entity-list.component.ts | 26 +++++++--- .../shared/components/fullscreen.directive.ts | 21 ++++---- .../json-object-edit.component.html | 6 ++- .../json-object-edit.component.scss | 23 ++++---- .../components/json-object-edit.component.ts | 52 ++++++++++++++++++- .../app/shared/components/page.component.ts | 4 +- .../app/shared/components/toast.directive.ts | 15 ++++++ 34 files changed, 343 insertions(+), 99 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/dialog.component.ts diff --git a/ui-ngx/src/app/core/guards/auth.guard.ts b/ui-ngx/src/app/core/guards/auth.guard.ts index 4ba3ca7612..f022d6d44b 100644 --- a/ui-ngx/src/app/core/guards/auth.guard.ts +++ b/ui-ngx/src/app/core/guards/auth.guard.ts @@ -82,18 +82,7 @@ export class AuthGuard implements CanActivate, CanActivateChild { } else { const authority = Authority[authState.authUser.authority]; if (data.auth && data.auth.indexOf(authority) === -1) { - this.dialogService.confirm( - this.translate.instant('access.access-forbidden'), - this.translate.instant('access.access-forbidden-text'), - this.translate.instant('action.cancel'), - this.translate.instant('action.sign-in'), - true - ).subscribe((res) => { - if (res) { - this.authService.logout(); - } - } - ); + this.dialogService.forbidden(); return false; } else { return true; diff --git a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts index 3b87d3f6ed..7955604908 100644 --- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -50,6 +50,8 @@ export class GlobalHttpInterceptor implements HttpInterceptor { '/api/auth/token' ]; + private activeRequests = 0; + constructor(private store: Store, private dialogService: DialogService, private translate: TranslateService, @@ -135,7 +137,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } } else if (errorResponse.status === 403) { if (!ignoreErrors) { - this.permissionDenied(); + this.dialogService.forbidden(); } } else if (errorResponse.status === 0 || errorResponse.status === -1) { this.showError('Unable to connect'); @@ -241,7 +243,16 @@ export class GlobalHttpInterceptor implements HttpInterceptor { private updateLoadingState(config: InterceptorConfig, isLoading: boolean) { if (!config.ignoreLoading) { - this.store.dispatch(isLoading ? new ActionLoadStart() : new ActionLoadFinish()); + if (isLoading) { + this.activeRequests++; + } else { + this.activeRequests--; + } + if (this.activeRequests === 1) { + this.store.dispatch(new ActionLoadStart()); + } else if (this.activeRequests === 0) { + this.store.dispatch(new ActionLoadFinish()); + } } } @@ -253,14 +264,6 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } } - private permissionDenied() { - this.dialogService.alert( - this.translate.instant('access.permission-denied'), - this.translate.instant('access.permission-denied-text'), - this.translate.instant('action.close') - ); - } - private showError(error: string, timeout: number = 0) { setTimeout(() => { this.store.dispatch(new ActionNotificationShow({message: error, type: 'error'})); diff --git a/ui-ngx/src/app/core/notification/notification.actions.ts b/ui-ngx/src/app/core/notification/notification.actions.ts index aa1fb865af..5fc3402d21 100644 --- a/ui-ngx/src/app/core/notification/notification.actions.ts +++ b/ui-ngx/src/app/core/notification/notification.actions.ts @@ -15,10 +15,11 @@ /// import { Action } from '@ngrx/store'; -import { NotificationMessage } from '@app/core/notification/notification.models'; +import { NotificationMessage, HideNotification } from '@app/core/notification/notification.models'; export enum NotificationActionTypes { - SHOW_NOTIFICATION = '[Notification] Show' + SHOW_NOTIFICATION = '[Notification] Show', + HIDE_NOTIFICATION = '[Notification] Hide' } export class ActionNotificationShow implements Action { @@ -27,5 +28,11 @@ export class ActionNotificationShow implements Action { constructor(readonly notification: NotificationMessage ) {} } +export class ActionNotificationHide implements Action { + readonly type = NotificationActionTypes.HIDE_NOTIFICATION; + + constructor(readonly hideNotification: HideNotification ) {} +} + export type NotificationActions = - | ActionNotificationShow; + | ActionNotificationShow | ActionNotificationHide; diff --git a/ui-ngx/src/app/core/notification/notification.effects.ts b/ui-ngx/src/app/core/notification/notification.effects.ts index dd687cc8be..8e95c40a38 100644 --- a/ui-ngx/src/app/core/notification/notification.effects.ts +++ b/ui-ngx/src/app/core/notification/notification.effects.ts @@ -44,4 +44,13 @@ export class NotificationEffects { }) ); + @Effect({dispatch: false}) + hideNotification = this.actions$.pipe( + ofType( + NotificationActionTypes.HIDE_NOTIFICATION, + ), + map(({ hideNotification }) => { + this.notificationService.hideNotification(hideNotification); + }) + ); } diff --git a/ui-ngx/src/app/core/notification/notification.models.ts b/ui-ngx/src/app/core/notification/notification.models.ts index f0fc4135c3..26df85b101 100644 --- a/ui-ngx/src/app/core/notification/notification.models.ts +++ b/ui-ngx/src/app/core/notification/notification.models.ts @@ -17,6 +17,7 @@ export interface NotificationState { notification: NotificationMessage; + hideNotification: HideNotification; } export declare type NotificationType = 'info' | 'success' | 'error'; @@ -31,3 +32,7 @@ export class NotificationMessage { horizontalPosition?: NotificationHorizontalPosition; verticalPosition?: NotificationVerticalPosition; } + +export class HideNotification { + target?: string; +} diff --git a/ui-ngx/src/app/core/notification/notification.reducer.ts b/ui-ngx/src/app/core/notification/notification.reducer.ts index 81c0387fa2..b4a97511af 100644 --- a/ui-ngx/src/app/core/notification/notification.reducer.ts +++ b/ui-ngx/src/app/core/notification/notification.reducer.ts @@ -18,7 +18,8 @@ import { NotificationState } from './notification.models'; import { NotificationActions, NotificationActionTypes } from './notification.actions'; export const initialState: NotificationState = { - notification: null + notification: null, + hideNotification: null }; export function notificationReducer( @@ -28,6 +29,8 @@ export function notificationReducer( switch (action.type) { case NotificationActionTypes.SHOW_NOTIFICATION: return { ...state, notification: action.notification }; + case NotificationActionTypes.HIDE_NOTIFICATION: + return { ...state, hideNotification: action.hideNotification }; default: return state; } diff --git a/ui-ngx/src/app/core/services/dialog.service.ts b/ui-ngx/src/app/core/services/dialog.service.ts index ada1baf26c..36fe76d693 100644 --- a/ui-ngx/src/app/core/services/dialog.service.ts +++ b/ui-ngx/src/app/core/services/dialog.service.ts @@ -20,7 +20,8 @@ import { MatDialog, MatDialogConfig } from '@angular/material'; import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; import { TranslateService } from '@ngx-translate/core'; import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; -import {TodoDialogComponent} from "@core/services/dialog/todo-dialog.component"; +import { TodoDialogComponent } from '@core/services/dialog/todo-dialog.component'; +import { AuthService } from '@core/auth/auth.service'; @Injectable( { @@ -31,6 +32,7 @@ export class DialogService { constructor( private translate: TranslateService, + private authService: AuthService, public dialog: MatDialog ) { } @@ -68,6 +70,30 @@ export class DialogService { return dialogRef.afterClosed(); } + private permissionDenied() { + this.alert( + this.translate.instant('access.permission-denied'), + this.translate.instant('access.permission-denied-text'), + this.translate.instant('action.close') + ); + } + + forbidden(): Observable { + const observable = this.confirm( + this.translate.instant('access.access-forbidden'), + this.translate.instant('access.access-forbidden-text'), + this.translate.instant('action.cancel'), + this.translate.instant('action.sign-in'), + true + ); + observable.subscribe((res) => { + if (res) { + this.authService.logout(); + } + }); + return observable; + } + todo(): Observable { const dialogConfig: MatDialogConfig = { disableClose: true, diff --git a/ui-ngx/src/app/core/services/notification.service.ts b/ui-ngx/src/app/core/services/notification.service.ts index fed93cf93a..f63cd8adec 100644 --- a/ui-ngx/src/app/core/services/notification.service.ts +++ b/ui-ngx/src/app/core/services/notification.service.ts @@ -15,7 +15,7 @@ /// import { Injectable } from '@angular/core'; -import { NotificationMessage } from '@app/core/notification/notification.models'; +import { HideNotification, NotificationMessage } from '@app/core/notification/notification.models'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; @@ -28,6 +28,8 @@ export class NotificationService { private notificationSubject: Subject = new Subject(); + private hideNotificationSubject: Subject = new Subject(); + constructor( ) { } @@ -36,8 +38,15 @@ export class NotificationService { this.notificationSubject.next(notification); } + hideNotification(hideNotification: HideNotification) { + this.hideNotificationSubject.next(hideNotification); + } + getNotification(): Observable { - return this.notificationSubject; + return this.notificationSubject.asObservable(); } + getHideNotification(): Observable { + return this.hideNotificationSubject.asObservable(); + } } diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts index 4980c61b49..bdd869ca94 100644 --- a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts @@ -30,6 +30,8 @@ import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; import { ActionStatus, AuditLog } from '@shared/models/audit-log.models'; import * as ace from 'ace-builds'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; export interface AuditLogDetailsDialogData { auditLog: AuditLog; @@ -40,7 +42,7 @@ export interface AuditLogDetailsDialogData { templateUrl: './audit-log-details-dialog.component.html', styleUrls: ['./audit-log-details-dialog.component.scss'] }) -export class AuditLogDetailsDialogComponent extends PageComponent implements OnInit { +export class AuditLogDetailsDialogComponent extends DialogComponent implements OnInit { @ViewChild('actionDataEditor', {static: true}) actionDataEditorElmRef: ElementRef; @@ -58,10 +60,11 @@ export class AuditLogDetailsDialogComponent extends PageComponent implements OnI @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; constructor(protected store: Store, + protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AuditLogDetailsDialogData, public dialogRef: MatDialogRef, private renderer: Renderer2) { - super(store); + super(store, router, dialogRef); } ngOnInit(): void { @@ -114,7 +117,7 @@ export class AuditLogDetailsDialogComponent extends PageComponent implements OnI }); newWidth = 8 * maxLineLength + 16; } - newHeight = Math.min(400, newHeight); + // newHeight = Math.min(400, newHeight); this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px'); this.renderer.setStyle(editorElement, 'height', newHeight.toString() + 'px'); this.renderer.setStyle(editorElement, 'width', newWidth.toString() + 'px'); diff --git a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts index 4364e5ff3a..902d62da03 100644 --- a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts @@ -27,6 +27,8 @@ import {TbAnchorComponent} from '@shared/components/tb-anchor.component'; import {EntityComponent} from './entity.component'; import {EntityTableConfig} from '@home/models/entity/entities-table-config.models'; import {AddEntityDialogData} from '@home/models/entity/entity-component.models'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; @Component({ selector: 'tb-add-entity-dialog', @@ -34,7 +36,7 @@ import {AddEntityDialogData} from '@home/models/entity/entity-component.models'; providers: [{provide: ErrorStateMatcher, useExisting: AddEntityDialogComponent}], styleUrls: ['./add-entity-dialog.component.scss'] }) -export class AddEntityDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { +export class AddEntityDialogComponent extends DialogComponent> implements OnInit, ErrorStateMatcher { entityComponent: EntityComponent>; detailsForm: NgForm; @@ -49,11 +51,12 @@ export class AddEntityDialogComponent extends PageComponent implements OnInit, E @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; constructor(protected store: Store, + protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData>, public dialogRef: MatDialogRef>, private componentFactoryResolver: ComponentFactoryResolver, @SkipSelf() private errorStateMatcher: ErrorStateMatcher) { - super(store); + super(store, router, dialogRef); } ngOnInit(): void { diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html index f8b5ff439f..2a9ba7fec4 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html @@ -41,11 +41,12 @@ required="true"> @@ -54,7 +55,7 @@ -
+
diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.scss b/ui-ngx/src/app/shared/components/json-object-edit.component.scss index 913da86348..c4590b4375 100644 --- a/ui-ngx/src/app/shared/components/json-object-edit.component.scss +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.scss @@ -35,21 +35,20 @@ .fill-height { height: 100%; } +} - .tb-json-object-panel { - height: 100%; - margin-left: 15px; - border: 1px solid #c0c0c0; +.tb-json-object-panel { + height: 100%; + margin-left: 15px; + border: 1px solid #c0c0c0; - #tb-json-input { - width: 100%; - min-width: 200px; - height: 100%; + #tb-json-input { + width: 100%; + min-width: 200px; + height: 100%; - &:not(.fill-height) { - min-height: 200px; - } + &:not(.fill-height) { + min-height: 200px; } } - } diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.ts b/ui-ngx/src/app/shared/components/json-object-edit.component.ts index 72f64e1bb7..f0789711ab 100644 --- a/ui-ngx/src/app/shared/components/json-object-edit.component.ts +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.ts @@ -26,6 +26,9 @@ import { import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, Validator, NG_VALIDATORS } from '@angular/forms'; import * as ace from 'ace-builds'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; @Component({ selector: 'tb-json-object-edit', @@ -83,9 +86,14 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va objectValid: boolean; + validationError: string; + + errorShowed = false; + private propagateChange = null; - constructor() { + constructor(public elementRef: ElementRef, + protected store: Store) { } ngOnInit(): void { @@ -109,6 +117,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va this.jsonEditor.session.setUseWrapMode(false); this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1); this.jsonEditor.on('change', () => { + this.cleanupJsonErrors(); this.updateView(); }); } @@ -132,6 +141,33 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va }; } + validateOnSubmit(): void { + if (!this.readonly) { + this.cleanupJsonErrors(); + if (!this.objectValid) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.validationError, + type: 'error', + target: 'jsonObjectEditor', + verticalPosition: 'bottom', + horizontalPosition: 'left' + })); + this.errorShowed = true; + } + } + } + + cleanupJsonErrors(): void { + if (this.errorShowed) { + this.store.dispatch(new ActionNotificationHide( + { + target: 'jsonObjectEditor' + })); + this.errorShowed = false; + } + } + writeValue(value: any): void { this.modelValue = value; this.contentValue = ''; @@ -142,6 +178,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va this.objectValid = true; } else { this.objectValid = !this.required; + this.validationError = 'Json object is required.'; } } catch (e) { // @@ -161,9 +198,20 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va try { data = JSON.parse(this.contentValue); this.objectValid = true; - } catch (ex) {} + this.validationError = ''; + } catch (ex) { + let errorInfo = 'Error:'; + if (ex.name) { + errorInfo += ' ' + ex.name + ':'; + } + if (ex.message) { + errorInfo += ' ' + ex.message; + } + this.validationError = errorInfo; + } } else { this.objectValid = !this.required; + this.validationError = this.required ? 'Json object is required.' : ''; } this.propagateChange(data); } diff --git a/ui-ngx/src/app/shared/components/page.component.ts b/ui-ngx/src/app/shared/components/page.component.ts index 3ed33e3e5f..64922008c4 100644 --- a/ui-ngx/src/app/shared/components/page.component.ts +++ b/ui-ngx/src/app/shared/components/page.component.ts @@ -19,7 +19,7 @@ import { select, Store } from '@ngrx/store'; import { AppState } from '../../core/core.state'; import { Observable, Subscription } from 'rxjs'; import { selectIsLoading } from '../../core/interceptors/load.selectors'; -import { delay } from 'rxjs/operators'; +import { delay, share } from 'rxjs/operators'; import { AbstractControl } from '@angular/forms'; export abstract class PageComponent implements OnDestroy { @@ -29,7 +29,7 @@ export abstract class PageComponent implements OnDestroy { disabledOnLoadFormControls: Array = []; protected constructor(protected store: Store) { - this.isLoading$ = this.store.pipe(delay(0), select(selectIsLoading), delay(100)); + this.isLoading$ = this.store.pipe(delay(0), select(selectIsLoading), share()); } protected registerDisableOnLoadFormControl(control: AbstractControl) { diff --git a/ui-ngx/src/app/shared/components/toast.directive.ts b/ui-ngx/src/app/shared/components/toast.directive.ts index 1d6c9ed24d..de92352874 100644 --- a/ui-ngx/src/app/shared/components/toast.directive.ts +++ b/ui-ngx/src/app/shared/components/toast.directive.ts @@ -45,6 +45,7 @@ export class ToastDirective implements AfterViewInit, OnDestroy { toastTarget = 'root'; private notificationSubscription: Subscription = null; + private hideNotificationSubscription: Subscription = null; constructor(public elementRef: ElementRef, public viewContainerRef: ViewContainerRef, @@ -78,12 +79,26 @@ export class ToastDirective implements AfterViewInit, OnDestroy { } } ); + + this.hideNotificationSubscription = this.notificationService.getHideNotification().subscribe( + (hideNotification) => { + if (hideNotification) { + const target = hideNotification.target || 'root'; + if (this.toastTarget === target) { + this.snackBar.dismiss(); + } + } + } + ); } ngOnDestroy(): void { if (this.notificationSubscription) { this.notificationSubscription.unsubscribe(); } + if (this.hideNotificationSubscription) { + this.hideNotificationSubscription.unsubscribe(); + } } }