Commit 52ab3d6dea044edaf81b8193e2eeee7685b435fe

Authored by Igor Kulikov
1 parent b438cdd2

Relation edit dialog improvements. Minor improvements.

Showing 34 changed files with 343 additions and 99 deletions
@@ -82,18 +82,7 @@ export class AuthGuard implements CanActivate, CanActivateChild { @@ -82,18 +82,7 @@ export class AuthGuard implements CanActivate, CanActivateChild {
82 } else { 82 } else {
83 const authority = Authority[authState.authUser.authority]; 83 const authority = Authority[authState.authUser.authority];
84 if (data.auth && data.auth.indexOf(authority) === -1) { 84 if (data.auth && data.auth.indexOf(authority) === -1) {
85 - this.dialogService.confirm(  
86 - this.translate.instant('access.access-forbidden'),  
87 - this.translate.instant('access.access-forbidden-text'),  
88 - this.translate.instant('action.cancel'),  
89 - this.translate.instant('action.sign-in'),  
90 - true  
91 - ).subscribe((res) => {  
92 - if (res) {  
93 - this.authService.logout();  
94 - }  
95 - }  
96 - ); 85 + this.dialogService.forbidden();
97 return false; 86 return false;
98 } else { 87 } else {
99 return true; 88 return true;
@@ -50,6 +50,8 @@ export class GlobalHttpInterceptor implements HttpInterceptor { @@ -50,6 +50,8 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
50 '/api/auth/token' 50 '/api/auth/token'
51 ]; 51 ];
52 52
  53 + private activeRequests = 0;
  54 +
53 constructor(private store: Store<AppState>, 55 constructor(private store: Store<AppState>,
54 private dialogService: DialogService, 56 private dialogService: DialogService,
55 private translate: TranslateService, 57 private translate: TranslateService,
@@ -135,7 +137,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { @@ -135,7 +137,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
135 } 137 }
136 } else if (errorResponse.status === 403) { 138 } else if (errorResponse.status === 403) {
137 if (!ignoreErrors) { 139 if (!ignoreErrors) {
138 - this.permissionDenied(); 140 + this.dialogService.forbidden();
139 } 141 }
140 } else if (errorResponse.status === 0 || errorResponse.status === -1) { 142 } else if (errorResponse.status === 0 || errorResponse.status === -1) {
141 this.showError('Unable to connect'); 143 this.showError('Unable to connect');
@@ -241,7 +243,16 @@ export class GlobalHttpInterceptor implements HttpInterceptor { @@ -241,7 +243,16 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
241 243
242 private updateLoadingState(config: InterceptorConfig, isLoading: boolean) { 244 private updateLoadingState(config: InterceptorConfig, isLoading: boolean) {
243 if (!config.ignoreLoading) { 245 if (!config.ignoreLoading) {
244 - this.store.dispatch(isLoading ? new ActionLoadStart() : new ActionLoadFinish()); 246 + if (isLoading) {
  247 + this.activeRequests++;
  248 + } else {
  249 + this.activeRequests--;
  250 + }
  251 + if (this.activeRequests === 1) {
  252 + this.store.dispatch(new ActionLoadStart());
  253 + } else if (this.activeRequests === 0) {
  254 + this.store.dispatch(new ActionLoadFinish());
  255 + }
245 } 256 }
246 } 257 }
247 258
@@ -253,14 +264,6 @@ export class GlobalHttpInterceptor implements HttpInterceptor { @@ -253,14 +264,6 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
253 } 264 }
254 } 265 }
255 266
256 - private permissionDenied() {  
257 - this.dialogService.alert(  
258 - this.translate.instant('access.permission-denied'),  
259 - this.translate.instant('access.permission-denied-text'),  
260 - this.translate.instant('action.close')  
261 - );  
262 - }  
263 -  
264 private showError(error: string, timeout: number = 0) { 267 private showError(error: string, timeout: number = 0) {
265 setTimeout(() => { 268 setTimeout(() => {
266 this.store.dispatch(new ActionNotificationShow({message: error, type: 'error'})); 269 this.store.dispatch(new ActionNotificationShow({message: error, type: 'error'}));
@@ -15,10 +15,11 @@ @@ -15,10 +15,11 @@
15 /// 15 ///
16 16
17 import { Action } from '@ngrx/store'; 17 import { Action } from '@ngrx/store';
18 -import { NotificationMessage } from '@app/core/notification/notification.models'; 18 +import { NotificationMessage, HideNotification } from '@app/core/notification/notification.models';
19 19
20 export enum NotificationActionTypes { 20 export enum NotificationActionTypes {
21 - SHOW_NOTIFICATION = '[Notification] Show' 21 + SHOW_NOTIFICATION = '[Notification] Show',
  22 + HIDE_NOTIFICATION = '[Notification] Hide'
22 } 23 }
23 24
24 export class ActionNotificationShow implements Action { 25 export class ActionNotificationShow implements Action {
@@ -27,5 +28,11 @@ export class ActionNotificationShow implements Action { @@ -27,5 +28,11 @@ export class ActionNotificationShow implements Action {
27 constructor(readonly notification: NotificationMessage ) {} 28 constructor(readonly notification: NotificationMessage ) {}
28 } 29 }
29 30
  31 +export class ActionNotificationHide implements Action {
  32 + readonly type = NotificationActionTypes.HIDE_NOTIFICATION;
  33 +
  34 + constructor(readonly hideNotification: HideNotification ) {}
  35 +}
  36 +
30 export type NotificationActions = 37 export type NotificationActions =
31 - | ActionNotificationShow; 38 + | ActionNotificationShow | ActionNotificationHide;
@@ -44,4 +44,13 @@ export class NotificationEffects { @@ -44,4 +44,13 @@ export class NotificationEffects {
44 }) 44 })
45 ); 45 );
46 46
  47 + @Effect({dispatch: false})
  48 + hideNotification = this.actions$.pipe(
  49 + ofType(
  50 + NotificationActionTypes.HIDE_NOTIFICATION,
  51 + ),
  52 + map(({ hideNotification }) => {
  53 + this.notificationService.hideNotification(hideNotification);
  54 + })
  55 + );
47 } 56 }
@@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
17 17
18 export interface NotificationState { 18 export interface NotificationState {
19 notification: NotificationMessage; 19 notification: NotificationMessage;
  20 + hideNotification: HideNotification;
20 } 21 }
21 22
22 export declare type NotificationType = 'info' | 'success' | 'error'; 23 export declare type NotificationType = 'info' | 'success' | 'error';
@@ -31,3 +32,7 @@ export class NotificationMessage { @@ -31,3 +32,7 @@ export class NotificationMessage {
31 horizontalPosition?: NotificationHorizontalPosition; 32 horizontalPosition?: NotificationHorizontalPosition;
32 verticalPosition?: NotificationVerticalPosition; 33 verticalPosition?: NotificationVerticalPosition;
33 } 34 }
  35 +
  36 +export class HideNotification {
  37 + target?: string;
  38 +}
@@ -18,7 +18,8 @@ import { NotificationState } from './notification.models'; @@ -18,7 +18,8 @@ import { NotificationState } from './notification.models';
18 import { NotificationActions, NotificationActionTypes } from './notification.actions'; 18 import { NotificationActions, NotificationActionTypes } from './notification.actions';
19 19
20 export const initialState: NotificationState = { 20 export const initialState: NotificationState = {
21 - notification: null 21 + notification: null,
  22 + hideNotification: null
22 }; 23 };
23 24
24 export function notificationReducer( 25 export function notificationReducer(
@@ -28,6 +29,8 @@ export function notificationReducer( @@ -28,6 +29,8 @@ export function notificationReducer(
28 switch (action.type) { 29 switch (action.type) {
29 case NotificationActionTypes.SHOW_NOTIFICATION: 30 case NotificationActionTypes.SHOW_NOTIFICATION:
30 return { ...state, notification: action.notification }; 31 return { ...state, notification: action.notification };
  32 + case NotificationActionTypes.HIDE_NOTIFICATION:
  33 + return { ...state, hideNotification: action.hideNotification };
31 default: 34 default:
32 return state; 35 return state;
33 } 36 }
@@ -20,7 +20,8 @@ import { MatDialog, MatDialogConfig } from '@angular/material'; @@ -20,7 +20,8 @@ import { MatDialog, MatDialogConfig } from '@angular/material';
20 import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; 20 import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component';
21 import { TranslateService } from '@ngx-translate/core'; 21 import { TranslateService } from '@ngx-translate/core';
22 import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; 22 import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component';
23 -import {TodoDialogComponent} from "@core/services/dialog/todo-dialog.component"; 23 +import { TodoDialogComponent } from '@core/services/dialog/todo-dialog.component';
  24 +import { AuthService } from '@core/auth/auth.service';
24 25
25 @Injectable( 26 @Injectable(
26 { 27 {
@@ -31,6 +32,7 @@ export class DialogService { @@ -31,6 +32,7 @@ export class DialogService {
31 32
32 constructor( 33 constructor(
33 private translate: TranslateService, 34 private translate: TranslateService,
  35 + private authService: AuthService,
34 public dialog: MatDialog 36 public dialog: MatDialog
35 ) { 37 ) {
36 } 38 }
@@ -68,6 +70,30 @@ export class DialogService { @@ -68,6 +70,30 @@ export class DialogService {
68 return dialogRef.afterClosed(); 70 return dialogRef.afterClosed();
69 } 71 }
70 72
  73 + private permissionDenied() {
  74 + this.alert(
  75 + this.translate.instant('access.permission-denied'),
  76 + this.translate.instant('access.permission-denied-text'),
  77 + this.translate.instant('action.close')
  78 + );
  79 + }
  80 +
  81 + forbidden(): Observable<boolean> {
  82 + const observable = this.confirm(
  83 + this.translate.instant('access.access-forbidden'),
  84 + this.translate.instant('access.access-forbidden-text'),
  85 + this.translate.instant('action.cancel'),
  86 + this.translate.instant('action.sign-in'),
  87 + true
  88 + );
  89 + observable.subscribe((res) => {
  90 + if (res) {
  91 + this.authService.logout();
  92 + }
  93 + });
  94 + return observable;
  95 + }
  96 +
71 todo(): Observable<any> { 97 todo(): Observable<any> {
72 const dialogConfig: MatDialogConfig = { 98 const dialogConfig: MatDialogConfig = {
73 disableClose: true, 99 disableClose: true,
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 /// 15 ///
16 16
17 import { Injectable } from '@angular/core'; 17 import { Injectable } from '@angular/core';
18 -import { NotificationMessage } from '@app/core/notification/notification.models'; 18 +import { HideNotification, NotificationMessage } from '@app/core/notification/notification.models';
19 import { BehaviorSubject, Observable, Subject } from 'rxjs'; 19 import { BehaviorSubject, Observable, Subject } from 'rxjs';
20 20
21 21
@@ -28,6 +28,8 @@ export class NotificationService { @@ -28,6 +28,8 @@ export class NotificationService {
28 28
29 private notificationSubject: Subject<NotificationMessage> = new Subject(); 29 private notificationSubject: Subject<NotificationMessage> = new Subject();
30 30
  31 + private hideNotificationSubject: Subject<HideNotification> = new Subject();
  32 +
31 constructor( 33 constructor(
32 ) { 34 ) {
33 } 35 }
@@ -36,8 +38,15 @@ export class NotificationService { @@ -36,8 +38,15 @@ export class NotificationService {
36 this.notificationSubject.next(notification); 38 this.notificationSubject.next(notification);
37 } 39 }
38 40
  41 + hideNotification(hideNotification: HideNotification) {
  42 + this.hideNotificationSubject.next(hideNotification);
  43 + }
  44 +
39 getNotification(): Observable<NotificationMessage> { 45 getNotification(): Observable<NotificationMessage> {
40 - return this.notificationSubject; 46 + return this.notificationSubject.asObservable();
41 } 47 }
42 48
  49 + getHideNotification(): Observable<HideNotification> {
  50 + return this.hideNotificationSubject.asObservable();
  51 + }
43 } 52 }
@@ -30,6 +30,8 @@ import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; @@ -30,6 +30,8 @@ import { TbAnchorComponent } from '@shared/components/tb-anchor.component';
30 import { ActionStatus, AuditLog } from '@shared/models/audit-log.models'; 30 import { ActionStatus, AuditLog } from '@shared/models/audit-log.models';
31 31
32 import * as ace from 'ace-builds'; 32 import * as ace from 'ace-builds';
  33 +import { DialogComponent } from '@shared/components/dialog.component';
  34 +import { Router } from '@angular/router';
33 35
34 export interface AuditLogDetailsDialogData { 36 export interface AuditLogDetailsDialogData {
35 auditLog: AuditLog; 37 auditLog: AuditLog;
@@ -40,7 +42,7 @@ export interface AuditLogDetailsDialogData { @@ -40,7 +42,7 @@ export interface AuditLogDetailsDialogData {
40 templateUrl: './audit-log-details-dialog.component.html', 42 templateUrl: './audit-log-details-dialog.component.html',
41 styleUrls: ['./audit-log-details-dialog.component.scss'] 43 styleUrls: ['./audit-log-details-dialog.component.scss']
42 }) 44 })
43 -export class AuditLogDetailsDialogComponent extends PageComponent implements OnInit { 45 +export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDetailsDialogComponent> implements OnInit {
44 46
45 @ViewChild('actionDataEditor', {static: true}) 47 @ViewChild('actionDataEditor', {static: true})
46 actionDataEditorElmRef: ElementRef; 48 actionDataEditorElmRef: ElementRef;
@@ -58,10 +60,11 @@ export class AuditLogDetailsDialogComponent extends PageComponent implements OnI @@ -58,10 +60,11 @@ export class AuditLogDetailsDialogComponent extends PageComponent implements OnI
58 @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; 60 @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent;
59 61
60 constructor(protected store: Store<AppState>, 62 constructor(protected store: Store<AppState>,
  63 + protected router: Router,
61 @Inject(MAT_DIALOG_DATA) public data: AuditLogDetailsDialogData, 64 @Inject(MAT_DIALOG_DATA) public data: AuditLogDetailsDialogData,
62 public dialogRef: MatDialogRef<AuditLogDetailsDialogComponent>, 65 public dialogRef: MatDialogRef<AuditLogDetailsDialogComponent>,
63 private renderer: Renderer2) { 66 private renderer: Renderer2) {
64 - super(store); 67 + super(store, router, dialogRef);
65 } 68 }
66 69
67 ngOnInit(): void { 70 ngOnInit(): void {
@@ -114,7 +117,7 @@ export class AuditLogDetailsDialogComponent extends PageComponent implements OnI @@ -114,7 +117,7 @@ export class AuditLogDetailsDialogComponent extends PageComponent implements OnI
114 }); 117 });
115 newWidth = 8 * maxLineLength + 16; 118 newWidth = 8 * maxLineLength + 16;
116 } 119 }
117 - newHeight = Math.min(400, newHeight); 120 + // newHeight = Math.min(400, newHeight);
118 this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px'); 121 this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px');
119 this.renderer.setStyle(editorElement, 'height', newHeight.toString() + 'px'); 122 this.renderer.setStyle(editorElement, 'height', newHeight.toString() + 'px');
120 this.renderer.setStyle(editorElement, 'width', newWidth.toString() + 'px'); 123 this.renderer.setStyle(editorElement, 'width', newWidth.toString() + 'px');
@@ -27,6 +27,8 @@ import {TbAnchorComponent} from '@shared/components/tb-anchor.component'; @@ -27,6 +27,8 @@ import {TbAnchorComponent} from '@shared/components/tb-anchor.component';
27 import {EntityComponent} from './entity.component'; 27 import {EntityComponent} from './entity.component';
28 import {EntityTableConfig} from '@home/models/entity/entities-table-config.models'; 28 import {EntityTableConfig} from '@home/models/entity/entities-table-config.models';
29 import {AddEntityDialogData} from '@home/models/entity/entity-component.models'; 29 import {AddEntityDialogData} from '@home/models/entity/entity-component.models';
  30 +import { DialogComponent } from '@shared/components/dialog.component';
  31 +import { Router } from '@angular/router';
30 32
31 @Component({ 33 @Component({
32 selector: 'tb-add-entity-dialog', 34 selector: 'tb-add-entity-dialog',
@@ -34,7 +36,7 @@ import {AddEntityDialogData} from '@home/models/entity/entity-component.models'; @@ -34,7 +36,7 @@ import {AddEntityDialogData} from '@home/models/entity/entity-component.models';
34 providers: [{provide: ErrorStateMatcher, useExisting: AddEntityDialogComponent}], 36 providers: [{provide: ErrorStateMatcher, useExisting: AddEntityDialogComponent}],
35 styleUrls: ['./add-entity-dialog.component.scss'] 37 styleUrls: ['./add-entity-dialog.component.scss']
36 }) 38 })
37 -export class AddEntityDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { 39 +export class AddEntityDialogComponent extends DialogComponent<AddEntityDialogComponent, BaseData<HasId>> implements OnInit, ErrorStateMatcher {
38 40
39 entityComponent: EntityComponent<BaseData<HasId>>; 41 entityComponent: EntityComponent<BaseData<HasId>>;
40 detailsForm: NgForm; 42 detailsForm: NgForm;
@@ -49,11 +51,12 @@ export class AddEntityDialogComponent extends PageComponent implements OnInit, E @@ -49,11 +51,12 @@ export class AddEntityDialogComponent extends PageComponent implements OnInit, E
49 @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; 51 @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent;
50 52
51 constructor(protected store: Store<AppState>, 53 constructor(protected store: Store<AppState>,
  54 + protected router: Router,
52 @Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData<BaseData<HasId>>, 55 @Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData<BaseData<HasId>>,
53 public dialogRef: MatDialogRef<AddEntityDialogComponent, BaseData<HasId>>, 56 public dialogRef: MatDialogRef<AddEntityDialogComponent, BaseData<HasId>>,
54 private componentFactoryResolver: ComponentFactoryResolver, 57 private componentFactoryResolver: ComponentFactoryResolver,
55 @SkipSelf() private errorStateMatcher: ErrorStateMatcher) { 58 @SkipSelf() private errorStateMatcher: ErrorStateMatcher) {
56 - super(store); 59 + super(store, router, dialogRef);
57 } 60 }
58 61
59 ngOnInit(): void { 62 ngOnInit(): void {
@@ -41,11 +41,12 @@ @@ -41,11 +41,12 @@
41 required="true"> 41 required="true">
42 </tb-entity-list-select> 42 </tb-entity-list-select>
43 <tb-json-object-edit 43 <tb-json-object-edit
44 - formControlName="additionalInfo" 44 + #additionalInfoEdit
  45 + [formControl]="additionalInfo"
45 label="{{ 'relation.additional-info' | translate }}"> 46 label="{{ 'relation.additional-info' | translate }}">
46 </tb-json-object-edit> 47 </tb-json-object-edit>
47 <div class="tb-error-messages" *ngIf="submitted && 48 <div class="tb-error-messages" *ngIf="submitted &&
48 - relationFormGroup.get('additionalInfo').invalid" role="alert"> 49 + additionalInfo.invalid" role="alert">
49 <div translate class="tb-error-message">relation.invalid-additional-info</div> 50 <div translate class="tb-error-message">relation.invalid-additional-info</div>
50 </div> 51 </div>
51 </fieldset> 52 </fieldset>
@@ -54,7 +55,7 @@ @@ -54,7 +55,7 @@
54 <span fxFlex></span> 55 <span fxFlex></span>
55 <button mat-button mat-raised-button color="primary" 56 <button mat-button mat-raised-button color="primary"
56 type="submit" 57 type="submit"
57 - [disabled]="(isLoading$ | async)"> 58 + [disabled]="(isLoading$ | async) || relationFormGroup.invalid || !(relationFormGroup.dirty || additionalInfo.dirty)">
58 {{ (isAdd ? 'action.add' : 'action.save') | translate }} 59 {{ (isAdd ? 'action.add' : 'action.save') | translate }}
59 </button> 60 </button>
60 <button mat-button color="primary" 61 <button mat-button color="primary"
@@ -14,9 +14,8 @@ @@ -14,9 +14,8 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; 17 +import { Component, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core';
18 import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; 18 import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
19 -import { PageComponent } from '@shared/components/page.component';  
20 import { Store } from '@ngrx/store'; 19 import { Store } from '@ngrx/store';
21 import { AppState } from '@core/core.state'; 20 import { AppState } from '@core/core.state';
22 import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; 21 import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
@@ -28,7 +27,10 @@ import { @@ -28,7 +27,10 @@ import {
28 } from '@shared/models/relation.models'; 27 } from '@shared/models/relation.models';
29 import { EntityRelationService } from '@core/http/entity-relation.service'; 28 import { EntityRelationService } from '@core/http/entity-relation.service';
30 import { EntityId } from '@shared/models/id/entity-id'; 29 import { EntityId } from '@shared/models/id/entity-id';
31 -import { Observable, forkJoin } from 'rxjs'; 30 +import { forkJoin, Observable } from 'rxjs';
  31 +import { JsonObjectEditComponent } from '@app/shared/components/json-object-edit.component';
  32 +import { Router } from '@angular/router';
  33 +import { DialogComponent } from '@app/shared/components/dialog.component';
32 34
33 export interface RelationDialogData { 35 export interface RelationDialogData {
34 isAdd: boolean; 36 isAdd: boolean;
@@ -42,7 +44,7 @@ export interface RelationDialogData { @@ -42,7 +44,7 @@ export interface RelationDialogData {
42 providers: [{provide: ErrorStateMatcher, useExisting: RelationDialogComponent}], 44 providers: [{provide: ErrorStateMatcher, useExisting: RelationDialogComponent}],
43 styleUrls: ['./relation-dialog.component.scss'] 45 styleUrls: ['./relation-dialog.component.scss']
44 }) 46 })
45 -export class RelationDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { 47 +export class RelationDialogComponent extends DialogComponent<RelationDialogComponent, boolean> implements OnInit, ErrorStateMatcher {
46 48
47 relationFormGroup: FormGroup; 49 relationFormGroup: FormGroup;
48 50
@@ -50,15 +52,20 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er @@ -50,15 +52,20 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er
50 direction: EntitySearchDirection; 52 direction: EntitySearchDirection;
51 entitySearchDirection = EntitySearchDirection; 53 entitySearchDirection = EntitySearchDirection;
52 54
  55 + additionalInfo: FormControl;
  56 +
  57 + @ViewChild('additionalInfoEdit', {static: true}) additionalInfoEdit: JsonObjectEditComponent;
  58 +
53 submitted = false; 59 submitted = false;
54 60
55 constructor(protected store: Store<AppState>, 61 constructor(protected store: Store<AppState>,
  62 + protected router: Router,
56 @Inject(MAT_DIALOG_DATA) public data: RelationDialogData, 63 @Inject(MAT_DIALOG_DATA) public data: RelationDialogData,
57 private entityRelationService: EntityRelationService, 64 private entityRelationService: EntityRelationService,
58 @SkipSelf() private errorStateMatcher: ErrorStateMatcher, 65 @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
59 public dialogRef: MatDialogRef<RelationDialogComponent, boolean>, 66 public dialogRef: MatDialogRef<RelationDialogComponent, boolean>,
60 public fb: FormBuilder) { 67 public fb: FormBuilder) {
61 - super(store); 68 + super(store, router, dialogRef);
62 this.isAdd = data.isAdd; 69 this.isAdd = data.isAdd;
63 this.direction = data.direction; 70 this.direction = data.direction;
64 } 71 }
@@ -68,14 +75,14 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er @@ -68,14 +75,14 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er
68 type: [this.isAdd ? CONTAINS_TYPE : this.data.relation.type, [Validators.required]], 75 type: [this.isAdd ? CONTAINS_TYPE : this.data.relation.type, [Validators.required]],
69 targetEntityIds: [this.isAdd ? null : 76 targetEntityIds: [this.isAdd ? null :
70 [this.direction === EntitySearchDirection.FROM ? this.data.relation.to : this.data.relation.from], 77 [this.direction === EntitySearchDirection.FROM ? this.data.relation.to : this.data.relation.from],
71 - [Validators.required]],  
72 - additionalInfo: [this.data.relation.additionalInfo] 78 + [Validators.required]]
73 }); 79 });
  80 + this.additionalInfo = new FormControl(this.data.relation.additionalInfo ? {...this.data.relation.additionalInfo} : null);
74 if (!this.isAdd) { 81 if (!this.isAdd) {
75 this.relationFormGroup.get('type').disable(); 82 this.relationFormGroup.get('type').disable();
76 this.relationFormGroup.get('targetEntityIds').disable(); 83 this.relationFormGroup.get('targetEntityIds').disable();
77 } 84 }
78 - this.relationFormGroup.valueChanges.subscribe( 85 + this.additionalInfo.valueChanges.subscribe(
79 () => { 86 () => {
80 this.submitted = false; 87 this.submitted = false;
81 } 88 }
@@ -94,8 +101,8 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er @@ -94,8 +101,8 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er
94 101
95 save(): void { 102 save(): void {
96 this.submitted = true; 103 this.submitted = true;
97 - if (this.relationFormGroup.valid) {  
98 - const additionalInfo = this.relationFormGroup.get('additionalInfo').value; 104 + this.additionalInfoEdit.validateOnSubmit();
  105 + if (!this.relationFormGroup.invalid && !this.additionalInfo.invalid) {
99 if (this.isAdd) { 106 if (this.isAdd) {
100 const tasks: Observable<EntityRelation>[] = []; 107 const tasks: Observable<EntityRelation>[] = [];
101 const type: string = this.relationFormGroup.get('type').value; 108 const type: string = this.relationFormGroup.get('type').value;
@@ -103,7 +110,7 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er @@ -103,7 +110,7 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er
103 entityIds.forEach(entityId => { 110 entityIds.forEach(entityId => {
104 const relation = { 111 const relation = {
105 type, 112 type,
106 - additionalInfo, 113 + additionalInfo: this.additionalInfo.value,
107 typeGroup: RelationTypeGroup.COMMON 114 typeGroup: RelationTypeGroup.COMMON
108 } as EntityRelation; 115 } as EntityRelation;
109 if (this.direction === EntitySearchDirection.FROM) { 116 if (this.direction === EntitySearchDirection.FROM) {
@@ -122,7 +129,7 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er @@ -122,7 +129,7 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er
122 ); 129 );
123 } else { 130 } else {
124 const relation: EntityRelation = {...this.data.relation}; 131 const relation: EntityRelation = {...this.data.relation};
125 - relation.additionalInfo = additionalInfo; 132 + relation.additionalInfo = this.additionalInfo.value;
126 this.entityRelationService.saveRelation(relation).subscribe( 133 this.entityRelationService.saveRelation(relation).subscribe(
127 () => { 134 () => {
128 this.dialogRef.close(true); 135 this.dialogRef.close(true);
@@ -26,6 +26,8 @@ import {forkJoin, Observable} from 'rxjs'; @@ -26,6 +26,8 @@ import {forkJoin, Observable} from 'rxjs';
26 import {AssetService} from '@core/http/asset.service'; 26 import {AssetService} from '@core/http/asset.service';
27 import {EntityViewService} from '@core/http/entity-view.service'; 27 import {EntityViewService} from '@core/http/entity-view.service';
28 import {DashboardService} from '@core/http/dashboard.service'; 28 import {DashboardService} from '@core/http/dashboard.service';
  29 +import { DialogComponent } from '@shared/components/dialog.component';
  30 +import { Router } from '@angular/router';
29 31
30 export interface AddEntitiesToCustomerDialogData { 32 export interface AddEntitiesToCustomerDialogData {
31 customerId: string; 33 customerId: string;
@@ -38,7 +40,8 @@ export interface AddEntitiesToCustomerDialogData { @@ -38,7 +40,8 @@ export interface AddEntitiesToCustomerDialogData {
38 providers: [{provide: ErrorStateMatcher, useExisting: AddEntitiesToCustomerDialogComponent}], 40 providers: [{provide: ErrorStateMatcher, useExisting: AddEntitiesToCustomerDialogComponent}],
39 styleUrls: [] 41 styleUrls: []
40 }) 42 })
41 -export class AddEntitiesToCustomerDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { 43 +export class AddEntitiesToCustomerDialogComponent extends
  44 + DialogComponent<AddEntitiesToCustomerDialogComponent, boolean> implements OnInit, ErrorStateMatcher {
42 45
43 addEntitiesToCustomerFormGroup: FormGroup; 46 addEntitiesToCustomerFormGroup: FormGroup;
44 47
@@ -50,6 +53,7 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen @@ -50,6 +53,7 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen
50 assignToCustomerText: string; 53 assignToCustomerText: string;
51 54
52 constructor(protected store: Store<AppState>, 55 constructor(protected store: Store<AppState>,
  56 + protected router: Router,
53 @Inject(MAT_DIALOG_DATA) public data: AddEntitiesToCustomerDialogData, 57 @Inject(MAT_DIALOG_DATA) public data: AddEntitiesToCustomerDialogData,
54 private deviceService: DeviceService, 58 private deviceService: DeviceService,
55 private assetService: AssetService, 59 private assetService: AssetService,
@@ -58,7 +62,7 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen @@ -58,7 +62,7 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen
58 @SkipSelf() private errorStateMatcher: ErrorStateMatcher, 62 @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
59 public dialogRef: MatDialogRef<AddEntitiesToCustomerDialogComponent, boolean>, 63 public dialogRef: MatDialogRef<AddEntitiesToCustomerDialogComponent, boolean>,
60 public fb: FormBuilder) { 64 public fb: FormBuilder) {
61 - super(store); 65 + super(store, router, dialogRef);
62 this.entityType = data.entityType; 66 this.entityType = data.entityType;
63 } 67 }
64 68
@@ -26,6 +26,8 @@ import {EntityType} from '@shared/models/entity-type.models'; @@ -26,6 +26,8 @@ import {EntityType} from '@shared/models/entity-type.models';
26 import {forkJoin, Observable} from 'rxjs'; 26 import {forkJoin, Observable} from 'rxjs';
27 import {AssetService} from '@core/http/asset.service'; 27 import {AssetService} from '@core/http/asset.service';
28 import {EntityViewService} from '@core/http/entity-view.service'; 28 import {EntityViewService} from '@core/http/entity-view.service';
  29 +import { DialogComponent } from '@shared/components/dialog.component';
  30 +import { Router } from '@angular/router';
29 31
30 export interface AssignToCustomerDialogData { 32 export interface AssignToCustomerDialogData {
31 entityIds: Array<EntityId>; 33 entityIds: Array<EntityId>;
@@ -38,7 +40,8 @@ export interface AssignToCustomerDialogData { @@ -38,7 +40,8 @@ export interface AssignToCustomerDialogData {
38 providers: [{provide: ErrorStateMatcher, useExisting: AssignToCustomerDialogComponent}], 40 providers: [{provide: ErrorStateMatcher, useExisting: AssignToCustomerDialogComponent}],
39 styleUrls: [] 41 styleUrls: []
40 }) 42 })
41 -export class AssignToCustomerDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { 43 +export class AssignToCustomerDialogComponent extends
  44 + DialogComponent<AssignToCustomerDialogComponent, boolean> implements OnInit, ErrorStateMatcher {
42 45
43 assignToCustomerFormGroup: FormGroup; 46 assignToCustomerFormGroup: FormGroup;
44 47
@@ -50,6 +53,7 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On @@ -50,6 +53,7 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On
50 assignToCustomerText: string; 53 assignToCustomerText: string;
51 54
52 constructor(protected store: Store<AppState>, 55 constructor(protected store: Store<AppState>,
  56 + protected router: Router,
53 @Inject(MAT_DIALOG_DATA) public data: AssignToCustomerDialogData, 57 @Inject(MAT_DIALOG_DATA) public data: AssignToCustomerDialogData,
54 private deviceService: DeviceService, 58 private deviceService: DeviceService,
55 private assetService: AssetService, 59 private assetService: AssetService,
@@ -57,7 +61,7 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On @@ -57,7 +61,7 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On
57 @SkipSelf() private errorStateMatcher: ErrorStateMatcher, 61 @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
58 public dialogRef: MatDialogRef<AssignToCustomerDialogComponent, boolean>, 62 public dialogRef: MatDialogRef<AssignToCustomerDialogComponent, boolean>,
59 public fb: FormBuilder) { 63 public fb: FormBuilder) {
60 - super(store); 64 + super(store, router, dialogRef);
61 } 65 }
62 66
63 ngOnInit(): void { 67 ngOnInit(): void {
@@ -20,6 +20,10 @@ @@ -20,6 +20,10 @@
20 <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" 20 <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
21 [entityId]="entity.id"></tb-event-table> 21 [entityId]="entity.id"></tb-event-table>
22 </mat-tab> 22 </mat-tab>
  23 +<mat-tab *ngIf="entity"
  24 + label="{{ 'relation.relations' | translate }}" #relationsTab="matTab">
  25 + <tb-relation-table [active]="relationsTab.isActive" [entityId]="entity.id"></tb-relation-table>
  26 +</mat-tab>
23 <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" 27 <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
24 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> 28 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
25 <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table> 29 <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table>
@@ -20,6 +20,10 @@ @@ -20,6 +20,10 @@
20 <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" 20 <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
21 [entityId]="entity.id"></tb-event-table> 21 [entityId]="entity.id"></tb-event-table>
22 </mat-tab> 22 </mat-tab>
  23 +<mat-tab *ngIf="entity"
  24 + label="{{ 'relation.relations' | translate }}" #relationsTab="matTab">
  25 + <tb-relation-table [active]="relationsTab.isActive" [entityId]="entity.id"></tb-relation-table>
  26 +</mat-tab>
23 <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" 27 <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
24 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> 28 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
25 <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.CUSTOMER" [customerId]="entity.id" detailsMode="true"></tb-audit-log-table> 29 <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.CUSTOMER" [customerId]="entity.id" detailsMode="true"></tb-audit-log-table>
@@ -26,6 +26,8 @@ import {forkJoin, Observable} from 'rxjs'; @@ -26,6 +26,8 @@ import {forkJoin, Observable} from 'rxjs';
26 import {DashboardInfo} from '@app/shared/models/dashboard.models'; 26 import {DashboardInfo} from '@app/shared/models/dashboard.models';
27 import {ActionNotificationShow} from '@core/notification/notification.actions'; 27 import {ActionNotificationShow} from '@core/notification/notification.actions';
28 import {TranslateService} from '@ngx-translate/core'; 28 import {TranslateService} from '@ngx-translate/core';
  29 +import { DialogComponent } from '@shared/components/dialog.component';
  30 +import { Router } from '@angular/router';
29 31
30 export interface MakeDashboardPublicDialogData { 32 export interface MakeDashboardPublicDialogData {
31 dashboard: DashboardInfo; 33 dashboard: DashboardInfo;
@@ -36,19 +38,20 @@ export interface MakeDashboardPublicDialogData { @@ -36,19 +38,20 @@ export interface MakeDashboardPublicDialogData {
36 templateUrl: './make-dashboard-public-dialog.component.html', 38 templateUrl: './make-dashboard-public-dialog.component.html',
37 styleUrls: [] 39 styleUrls: []
38 }) 40 })
39 -export class MakeDashboardPublicDialogComponent extends PageComponent implements OnInit { 41 +export class MakeDashboardPublicDialogComponent extends DialogComponent<MakeDashboardPublicDialogComponent> implements OnInit {
40 42
41 dashboard: DashboardInfo; 43 dashboard: DashboardInfo;
42 44
43 publicLink: string; 45 publicLink: string;
44 46
45 constructor(protected store: Store<AppState>, 47 constructor(protected store: Store<AppState>,
  48 + protected router: Router,
46 @Inject(MAT_DIALOG_DATA) public data: MakeDashboardPublicDialogData, 49 @Inject(MAT_DIALOG_DATA) public data: MakeDashboardPublicDialogData,
47 public translate: TranslateService, 50 public translate: TranslateService,
48 private dashboardService: DashboardService, 51 private dashboardService: DashboardService,
49 public dialogRef: MatDialogRef<MakeDashboardPublicDialogComponent>, 52 public dialogRef: MatDialogRef<MakeDashboardPublicDialogComponent>,
50 public fb: FormBuilder) { 53 public fb: FormBuilder) {
51 - super(store); 54 + super(store, router, dialogRef);
52 55
53 this.dashboard = data.dashboard; 56 this.dashboard = data.dashboard;
54 this.publicLink = dashboardService.getPublicDashboardLink(this.dashboard); 57 this.publicLink = dashboardService.getPublicDashboardLink(this.dashboard);
@@ -23,6 +23,8 @@ import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm} from '@ @@ -23,6 +23,8 @@ import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm} from '@
23 import {EntityType} from '@shared/models/entity-type.models'; 23 import {EntityType} from '@shared/models/entity-type.models';
24 import {DashboardService} from '@core/http/dashboard.service'; 24 import {DashboardService} from '@core/http/dashboard.service';
25 import {forkJoin, Observable} from 'rxjs'; 25 import {forkJoin, Observable} from 'rxjs';
  26 +import { DialogComponent } from '@shared/components/dialog.component';
  27 +import { Router } from '@angular/router';
26 28
27 export type ManageDashboardCustomersActionType = 'assign' | 'manage' | 'unassign'; 29 export type ManageDashboardCustomersActionType = 'assign' | 'manage' | 'unassign';
28 30
@@ -38,7 +40,8 @@ export interface ManageDashboardCustomersDialogData { @@ -38,7 +40,8 @@ export interface ManageDashboardCustomersDialogData {
38 providers: [{provide: ErrorStateMatcher, useExisting: ManageDashboardCustomersDialogComponent}], 40 providers: [{provide: ErrorStateMatcher, useExisting: ManageDashboardCustomersDialogComponent}],
39 styleUrls: [] 41 styleUrls: []
40 }) 42 })
41 -export class ManageDashboardCustomersDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { 43 +export class ManageDashboardCustomersDialogComponent extends
  44 + DialogComponent<ManageDashboardCustomersDialogComponent, boolean> implements OnInit, ErrorStateMatcher {
42 45
43 dashboardCustomersFormGroup: FormGroup; 46 dashboardCustomersFormGroup: FormGroup;
44 47
@@ -53,12 +56,13 @@ export class ManageDashboardCustomersDialogComponent extends PageComponent imple @@ -53,12 +56,13 @@ export class ManageDashboardCustomersDialogComponent extends PageComponent imple
53 assignedCustomersIds: string[]; 56 assignedCustomersIds: string[];
54 57
55 constructor(protected store: Store<AppState>, 58 constructor(protected store: Store<AppState>,
  59 + protected router: Router,
56 @Inject(MAT_DIALOG_DATA) public data: ManageDashboardCustomersDialogData, 60 @Inject(MAT_DIALOG_DATA) public data: ManageDashboardCustomersDialogData,
57 private dashboardService: DashboardService, 61 private dashboardService: DashboardService,
58 @SkipSelf() private errorStateMatcher: ErrorStateMatcher, 62 @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
59 public dialogRef: MatDialogRef<ManageDashboardCustomersDialogComponent, boolean>, 63 public dialogRef: MatDialogRef<ManageDashboardCustomersDialogComponent, boolean>,
60 public fb: FormBuilder) { 64 public fb: FormBuilder) {
61 - super(store); 65 + super(store, router, dialogRef);
62 66
63 this.assignedCustomersIds = data.assignedCustomersIds || []; 67 this.assignedCustomersIds = data.assignedCustomersIds || [];
64 switch (data.actionType) { 68 switch (data.actionType) {
@@ -25,6 +25,8 @@ import { TranslateService } from '@ngx-translate/core'; @@ -25,6 +25,8 @@ import { TranslateService } from '@ngx-translate/core';
25 import { AuthService } from '@core/auth/auth.service'; 25 import { AuthService } from '@core/auth/auth.service';
26 import {DeviceService} from '@core/http/device.service'; 26 import {DeviceService} from '@core/http/device.service';
27 import {DeviceCredentials, DeviceCredentialsType, credentialTypeNames} from '@shared/models/device.models'; 27 import {DeviceCredentials, DeviceCredentialsType, credentialTypeNames} from '@shared/models/device.models';
  28 +import { DialogComponent } from '@shared/components/dialog.component';
  29 +import { Router } from '@angular/router';
28 30
29 export interface DeviceCredentialsDialogData { 31 export interface DeviceCredentialsDialogData {
30 isReadOnly: boolean; 32 isReadOnly: boolean;
@@ -37,7 +39,8 @@ export interface DeviceCredentialsDialogData { @@ -37,7 +39,8 @@ export interface DeviceCredentialsDialogData {
37 providers: [{provide: ErrorStateMatcher, useExisting: DeviceCredentialsDialogComponent}], 39 providers: [{provide: ErrorStateMatcher, useExisting: DeviceCredentialsDialogComponent}],
38 styleUrls: [] 40 styleUrls: []
39 }) 41 })
40 -export class DeviceCredentialsDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { 42 +export class DeviceCredentialsDialogComponent extends
  43 + DialogComponent<DeviceCredentialsDialogComponent, DeviceCredentials> implements OnInit, ErrorStateMatcher {
41 44
42 deviceCredentialsFormGroup: FormGroup; 45 deviceCredentialsFormGroup: FormGroup;
43 46
@@ -54,12 +57,13 @@ export class DeviceCredentialsDialogComponent extends PageComponent implements O @@ -54,12 +57,13 @@ export class DeviceCredentialsDialogComponent extends PageComponent implements O
54 credentialTypeNamesMap = credentialTypeNames; 57 credentialTypeNamesMap = credentialTypeNames;
55 58
56 constructor(protected store: Store<AppState>, 59 constructor(protected store: Store<AppState>,
  60 + protected router: Router,
57 @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogData, 61 @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogData,
58 private deviceService: DeviceService, 62 private deviceService: DeviceService,
59 @SkipSelf() private errorStateMatcher: ErrorStateMatcher, 63 @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
60 public dialogRef: MatDialogRef<DeviceCredentialsDialogComponent, DeviceCredentials>, 64 public dialogRef: MatDialogRef<DeviceCredentialsDialogComponent, DeviceCredentials>,
61 public fb: FormBuilder) { 65 public fb: FormBuilder) {
62 - super(store); 66 + super(store, router, dialogRef);
63 67
64 this.isReadOnly = data.isReadOnly; 68 this.isReadOnly = data.isReadOnly;
65 } 69 }
@@ -20,6 +20,10 @@ @@ -20,6 +20,10 @@
20 <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" 20 <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
21 [entityId]="entity.id"></tb-event-table> 21 [entityId]="entity.id"></tb-event-table>
22 </mat-tab> 22 </mat-tab>
  23 +<mat-tab *ngIf="entity"
  24 + label="{{ 'relation.relations' | translate }}" #relationsTab="matTab">
  25 + <tb-relation-table [active]="relationsTab.isActive" [entityId]="entity.id"></tb-relation-table>
  26 +</mat-tab>
23 <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" 27 <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
24 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> 28 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
25 <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table> 29 <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table>
@@ -23,22 +23,25 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @@ -23,22 +23,25 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
23 import { ActionNotificationShow } from '@core/notification/notification.actions'; 23 import { ActionNotificationShow } from '@core/notification/notification.actions';
24 import { TranslateService } from '@ngx-translate/core'; 24 import { TranslateService } from '@ngx-translate/core';
25 import { AuthService } from '@core/auth/auth.service'; 25 import { AuthService } from '@core/auth/auth.service';
  26 +import { DialogComponent } from '@shared/components/dialog.component';
  27 +import { Router } from '@angular/router';
26 28
27 @Component({ 29 @Component({
28 selector: 'tb-change-password-dialog', 30 selector: 'tb-change-password-dialog',
29 templateUrl: './change-password-dialog.component.html', 31 templateUrl: './change-password-dialog.component.html',
30 styleUrls: ['./change-password-dialog.component.scss'] 32 styleUrls: ['./change-password-dialog.component.scss']
31 }) 33 })
32 -export class ChangePasswordDialogComponent extends PageComponent implements OnInit { 34 +export class ChangePasswordDialogComponent extends DialogComponent<ChangePasswordDialogComponent> implements OnInit {
33 35
34 changePassword: FormGroup; 36 changePassword: FormGroup;
35 37
36 constructor(protected store: Store<AppState>, 38 constructor(protected store: Store<AppState>,
  39 + protected router: Router,
37 private translate: TranslateService, 40 private translate: TranslateService,
38 private authService: AuthService, 41 private authService: AuthService,
39 public dialogRef: MatDialogRef<ChangePasswordDialogComponent>, 42 public dialogRef: MatDialogRef<ChangePasswordDialogComponent>,
40 public fb: FormBuilder) { 43 public fb: FormBuilder) {
41 - super(store); 44 + super(store, router, dialogRef);
42 } 45 }
43 46
44 ngOnInit(): void { 47 ngOnInit(): void {
@@ -23,6 +23,10 @@ @@ -23,6 +23,10 @@
23 [tenantId]="entity.tenantId.id" 23 [tenantId]="entity.tenantId.id"
24 [entityId]="entity.id"></tb-event-table> 24 [entityId]="entity.id"></tb-event-table>
25 </mat-tab> 25 </mat-tab>
  26 +<mat-tab *ngIf="entity"
  27 + label="{{ 'relation.relations' | translate }}" #relationsTab="matTab">
  28 + <tb-relation-table [active]="relationsTab.isActive" [entityId]="entity.id"></tb-relation-table>
  29 +</mat-tab>
26 <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" 30 <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
27 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> 31 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
28 <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table> 32 <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table>
@@ -20,3 +20,7 @@ @@ -20,3 +20,7 @@
20 <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="nullUid" 20 <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="nullUid"
21 [entityId]="entity.id"></tb-event-table> 21 [entityId]="entity.id"></tb-event-table>
22 </mat-tab> 22 </mat-tab>
  23 +<mat-tab *ngIf="entity"
  24 + label="{{ 'relation.relations' | translate }}" #relationsTab="matTab">
  25 + <tb-relation-table [active]="relationsTab.isActive" [entityId]="entity.id"></tb-relation-table>
  26 +</mat-tab>
@@ -21,6 +21,8 @@ import { Store } from '@ngrx/store'; @@ -21,6 +21,8 @@ import { Store } from '@ngrx/store';
21 import { AppState } from '@core/core.state'; 21 import { AppState } from '@core/core.state';
22 import { TranslateService } from '@ngx-translate/core'; 22 import { TranslateService } from '@ngx-translate/core';
23 import { ActionNotificationShow } from '@core/notification/notification.actions'; 23 import { ActionNotificationShow } from '@core/notification/notification.actions';
  24 +import { DialogComponent } from '@shared/components/dialog.component';
  25 +import { Router } from '@angular/router';
24 26
25 export interface ActivationLinkDialogData { 27 export interface ActivationLinkDialogData {
26 activationLink: string; 28 activationLink: string;
@@ -30,15 +32,16 @@ export interface ActivationLinkDialogData { @@ -30,15 +32,16 @@ export interface ActivationLinkDialogData {
30 selector: 'tb-activation-link-dialog', 32 selector: 'tb-activation-link-dialog',
31 templateUrl: './activation-link-dialog.component.html' 33 templateUrl: './activation-link-dialog.component.html'
32 }) 34 })
33 -export class ActivationLinkDialogComponent extends PageComponent implements OnInit { 35 +export class ActivationLinkDialogComponent extends DialogComponent<ActivationLinkDialogComponent, void> implements OnInit {
34 36
35 activationLink: string; 37 activationLink: string;
36 38
37 constructor(protected store: Store<AppState>, 39 constructor(protected store: Store<AppState>,
  40 + protected router: Router,
38 @Inject(MAT_DIALOG_DATA) public data: ActivationLinkDialogData, 41 @Inject(MAT_DIALOG_DATA) public data: ActivationLinkDialogData,
39 public dialogRef: MatDialogRef<ActivationLinkDialogComponent, void>, 42 public dialogRef: MatDialogRef<ActivationLinkDialogComponent, void>,
40 private translate: TranslateService) { 43 private translate: TranslateService) {
41 - super(store); 44 + super(store, router, dialogRef);
42 this.activationLink = this.data.activationLink; 45 this.activationLink = this.data.activationLink;
43 } 46 }
44 47
@@ -31,6 +31,8 @@ import { @@ -31,6 +31,8 @@ import {
31 ActivationLinkDialogData 31 ActivationLinkDialogData
32 } from '@modules/home/pages/user/activation-link-dialog.component'; 32 } from '@modules/home/pages/user/activation-link-dialog.component';
33 import {TenantId} from '@app/shared/models/id/tenant-id'; 33 import {TenantId} from '@app/shared/models/id/tenant-id';
  34 +import { DialogComponent } from '@shared/components/dialog.component';
  35 +import { Router } from '@angular/router';
34 36
35 export interface AddUserDialogData { 37 export interface AddUserDialogData {
36 tenantId: string; 38 tenantId: string;
@@ -42,7 +44,7 @@ export interface AddUserDialogData { @@ -42,7 +44,7 @@ export interface AddUserDialogData {
42 selector: 'tb-add-user-dialog', 44 selector: 'tb-add-user-dialog',
43 templateUrl: './add-user-dialog.component.html' 45 templateUrl: './add-user-dialog.component.html'
44 }) 46 })
45 -export class AddUserDialogComponent extends PageComponent implements OnInit { 47 +export class AddUserDialogComponent extends DialogComponent<AddUserDialogComponent, User> implements OnInit {
46 48
47 detailsForm: NgForm; 49 detailsForm: NgForm;
48 user: User; 50 user: User;
@@ -56,11 +58,12 @@ export class AddUserDialogComponent extends PageComponent implements OnInit { @@ -56,11 +58,12 @@ export class AddUserDialogComponent extends PageComponent implements OnInit {
56 @ViewChild(UserComponent, {static: true}) userComponent: UserComponent; 58 @ViewChild(UserComponent, {static: true}) userComponent: UserComponent;
57 59
58 constructor(protected store: Store<AppState>, 60 constructor(protected store: Store<AppState>,
  61 + protected router: Router,
59 @Inject(MAT_DIALOG_DATA) public data: AddUserDialogData, 62 @Inject(MAT_DIALOG_DATA) public data: AddUserDialogData,
60 public dialogRef: MatDialogRef<AddUserDialogComponent, User>, 63 public dialogRef: MatDialogRef<AddUserDialogComponent, User>,
61 private userService: UserService, 64 private userService: UserService,
62 private dialog: MatDialog) { 65 private dialog: MatDialog) {
63 - super(store); 66 + super(store, router, dialogRef);
64 } 67 }
65 68
66 ngOnInit(): void { 69 ngOnInit(): void {
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { OnDestroy } from '@angular/core';
  18 +import { PageComponent } from './page.component';
  19 +import { Store } from '@ngrx/store';
  20 +import { AppState } from '@core/core.state';
  21 +import { MatDialogRef } from '@angular/material/dialog';
  22 +import { NavigationStart, Router, RouterEvent } from '@angular/router';
  23 +import { Subscription } from 'rxjs';
  24 +import { filter } from 'rxjs/operators';
  25 +
  26 +export abstract class DialogComponent<T, R = any> extends PageComponent implements OnDestroy {
  27 +
  28 + routerSubscription: Subscription;
  29 +
  30 + protected constructor(protected store: Store<AppState>,
  31 + protected router: Router,
  32 + protected dialogRef: MatDialogRef<T, R>) {
  33 + super(store);
  34 + this.routerSubscription = this.router.events
  35 + .pipe(
  36 + filter((event: RouterEvent) => event instanceof NavigationStart),
  37 + filter(() => !!this.dialogRef)
  38 + )
  39 + .subscribe(() => {
  40 + this.dialogRef.close();
  41 + });
  42 + }
  43 +
  44 + ngOnDestroy(): void {
  45 + console.log('Dialog destroy called');
  46 + super.ngOnDestroy();
  47 + if (this.routerSubscription) {
  48 + this.routerSubscription.unsubscribe();
  49 + }
  50 + }
  51 +}
@@ -28,6 +28,7 @@ @@ -28,6 +28,7 @@
28 <input matInput type="text" placeholder="{{ !disabled ? ('entity.entity-list' | translate) : '' }}" 28 <input matInput type="text" placeholder="{{ !disabled ? ('entity.entity-list' | translate) : '' }}"
29 style="max-width: 200px;" 29 style="max-width: 200px;"
30 #entityInput 30 #entityInput
  31 + (focusin)="onFocus()"
31 formControlName="entity" 32 formControlName="entity"
32 matAutocompleteOrigin 33 matAutocompleteOrigin
33 #origin="matAutocompleteOrigin" 34 #origin="matAutocompleteOrigin"
@@ -99,6 +99,8 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV @@ -99,6 +99,8 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
99 99
100 private searchText = ''; 100 private searchText = '';
101 101
  102 + private dirty = false;
  103 +
102 private propagateChange = (v: any) => { }; 104 private propagateChange = (v: any) => { };
103 105
104 constructor(private store: Store<AppState>, 106 constructor(private store: Store<AppState>,
@@ -126,7 +128,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV @@ -126,7 +128,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
126 ngOnInit() { 128 ngOnInit() {
127 this.filteredEntities = this.entityListFormGroup.get('entity').valueChanges 129 this.filteredEntities = this.entityListFormGroup.get('entity').valueChanges
128 .pipe( 130 .pipe(
129 - startWith<string | BaseData<EntityId>>(''), 131 + // startWith<string | BaseData<EntityId>>(''),
130 tap((value) => { 132 tap((value) => {
131 if (value && typeof value !== 'string') { 133 if (value && typeof value !== 'string') {
132 this.add(value); 134 this.add(value);
@@ -145,12 +147,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV @@ -145,12 +147,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
145 } 147 }
146 148
147 setDisabledState(isDisabled: boolean): void { 149 setDisabledState(isDisabled: boolean): void {
148 - const emitEvent = this.disabled !== isDisabled;  
149 this.disabled = isDisabled; 150 this.disabled = isDisabled;
150 if (isDisabled) { 151 if (isDisabled) {
151 - this.entityListFormGroup.disable({emitEvent}); 152 + this.entityListFormGroup.disable({emitEvent: false});
152 } else { 153 } else {
153 - this.entityListFormGroup.enable({emitEvent}); 154 + this.entityListFormGroup.enable({emitEvent: false});
154 } 155 }
155 } 156 }
156 157
@@ -169,14 +170,19 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV @@ -169,14 +170,19 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
169 this.entityListFormGroup.get('entities').setValue(this.entities); 170 this.entityListFormGroup.get('entities').setValue(this.entities);
170 this.modelValue = null; 171 this.modelValue = null;
171 } 172 }
  173 + this.dirty = true;
172 } 174 }
173 175
174 reset() { 176 reset() {
175 this.entities = []; 177 this.entities = [];
176 this.entityListFormGroup.get('entities').setValue(this.entities); 178 this.entityListFormGroup.get('entities').setValue(this.entities);
177 this.modelValue = null; 179 this.modelValue = null;
178 - this.entityListFormGroup.get('entity').patchValue('', {emitEvent: true}); 180 + if (this.entityInput) {
  181 + this.entityInput.nativeElement.value = '';
  182 + }
  183 + this.entityListFormGroup.get('entity').patchValue('', {emitEvent: false});
179 this.propagateChange(this.modelValue); 184 this.propagateChange(this.modelValue);
  185 + this.dirty = true;
180 } 186 }
181 187
182 add(entity: BaseData<EntityId>): void { 188 add(entity: BaseData<EntityId>): void {
@@ -212,12 +218,18 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV @@ -212,12 +218,18 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
212 218
213 fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> { 219 fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {
214 this.searchText = searchText; 220 this.searchText = searchText;
215 - return this.disabled ? of([]) :  
216 - this.entityService.getEntitiesByNameFilter(this.entityTypeValue, searchText, 221 + return this.entityService.getEntitiesByNameFilter(this.entityTypeValue, searchText,
217 50, '', false, true).pipe( 222 50, '', false, true).pipe(
218 map((data) => data ? data : [])); 223 map((data) => data ? data : []));
219 } 224 }
220 225
  226 + onFocus() {
  227 + if (this.dirty) {
  228 + this.entityListFormGroup.get('entity').updateValueAndValidity({onlySelf: true, emitEvent: true});
  229 + this.dirty = false;
  230 + }
  231 + }
  232 +
221 clear(value: string = '') { 233 clear(value: string = '') {
222 this.entityInput.nativeElement.value = value; 234 this.entityInput.nativeElement.value = value;
223 this.entityListFormGroup.get('entity').patchValue(value, {emitEvent: true}); 235 this.entityListFormGroup.get('entity').patchValue(value, {emitEvent: true});
@@ -54,13 +54,13 @@ export class FullscreenDirective { @@ -54,13 +54,13 @@ export class FullscreenDirective {
54 constructor(public elementRef: ElementRef, 54 constructor(public elementRef: ElementRef,
55 private viewContainerRef: ViewContainerRef, 55 private viewContainerRef: ViewContainerRef,
56 private overlay: Overlay) { 56 private overlay: Overlay) {
57 -  
58 } 57 }
59 58
60 enterFullscreen() { 59 enterFullscreen() {
61 - this.parentElement = this.elementRef.nativeElement.parentElement;  
62 - this.parentElement.removeChild(this.elementRef.nativeElement);  
63 - this.elementRef.nativeElement.classList.add('tb-fullscreen'); 60 + const targetElement = this.elementRef;
  61 + this.parentElement = targetElement.nativeElement.parentElement;
  62 + this.parentElement.removeChild(targetElement.nativeElement);
  63 + targetElement.nativeElement.classList.add('tb-fullscreen');
64 const position = this.overlay.position(); 64 const position = this.overlay.position();
65 const config = new OverlayConfig({ 65 const config = new OverlayConfig({
66 hasBackdrop: false, 66 hasBackdrop: false,
@@ -73,21 +73,24 @@ export class FullscreenDirective { @@ -73,21 +73,24 @@ export class FullscreenDirective {
73 73
74 this.overlayRef = this.overlay.create(config); 74 this.overlayRef = this.overlay.create(config);
75 this.overlayRef.attach(new EmptyPortal()); 75 this.overlayRef.attach(new EmptyPortal());
76 - this.overlayRef.overlayElement.append( this.elementRef.nativeElement ); 76 + this.overlayRef.overlayElement.append( targetElement.nativeElement );
77 this.fullscreenChanged.emit(true); 77 this.fullscreenChanged.emit(true);
78 } 78 }
79 79
80 exitFullscreen() { 80 exitFullscreen() {
  81 + const targetElement = this.elementRef;
81 if (this.parentElement) { 82 if (this.parentElement) {
82 - this.overlayRef.overlayElement.removeChild( this.elementRef.nativeElement );  
83 - this.parentElement.append( this.elementRef.nativeElement ); 83 + this.overlayRef.overlayElement.removeChild( targetElement.nativeElement );
  84 + this.parentElement.append(targetElement.nativeElement);
84 this.parentElement = null; 85 this.parentElement = null;
85 } 86 }
86 - this.elementRef.nativeElement.classList.remove('tb-fullscreen'); 87 + targetElement.nativeElement.classList.remove('tb-fullscreen');
  88 + if (this.elementRef !== targetElement) {
  89 + this.elementRef.nativeElement.classList.remove('tb-fullscreen');
  90 + }
87 this.overlayRef.dispose(); 91 this.overlayRef.dispose();
88 this.fullscreenChanged.emit(false); 92 this.fullscreenChanged.emit(false);
89 } 93 }
90 -  
91 } 94 }
92 95
93 class EmptyPortal extends ComponentPortal<TbAnchorComponent> { 96 class EmptyPortal extends ComponentPortal<TbAnchorComponent> {
@@ -16,7 +16,8 @@ @@ -16,7 +16,8 @@
16 16
17 --> 17 -->
18 <div style="background: #fff;" [ngClass]="{'fill-height': fillHeight}" 18 <div style="background: #fff;" [ngClass]="{'fill-height': fillHeight}"
19 - tb-fullscreen [fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()" fxLayout="column"> 19 + tb-fullscreen
  20 + [fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()" fxLayout="column">
20 <div fxLayout="row" fxLayoutAlign="start center"> 21 <div fxLayout="row" fxLayoutAlign="start center">
21 <label class="tb-title no-padding" 22 <label class="tb-title no-padding"
22 ng-class="{'tb-required': required, 23 ng-class="{'tb-required': required,
@@ -29,7 +30,8 @@ @@ -29,7 +30,8 @@
29 <mat-icon class="material-icons">{{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> 30 <mat-icon class="material-icons">{{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
30 </button> 31 </button>
31 </div> 32 </div>
32 - <div fxFlex="0%" id="tb-json-panel" class="tb-json-object-panel" fxLayout="column"> 33 + <div fxFlex="0%" id="tb-json-panel" tb-toast toastTarget="jsonObjectEditor"
  34 + class="tb-json-object-panel" fxLayout="column">
33 <div fxFlex #jsonEditor id="tb-json-input" [ngClass]="{'fill-height': fillHeight}"></div> 35 <div fxFlex #jsonEditor id="tb-json-input" [ngClass]="{'fill-height': fillHeight}"></div>
34 </div> 36 </div>
35 </div> 37 </div>
@@ -35,21 +35,20 @@ @@ -35,21 +35,20 @@
35 .fill-height { 35 .fill-height {
36 height: 100%; 36 height: 100%;
37 } 37 }
  38 +}
38 39
39 - .tb-json-object-panel {  
40 - height: 100%;  
41 - margin-left: 15px;  
42 - border: 1px solid #c0c0c0; 40 +.tb-json-object-panel {
  41 + height: 100%;
  42 + margin-left: 15px;
  43 + border: 1px solid #c0c0c0;
43 44
44 - #tb-json-input {  
45 - width: 100%;  
46 - min-width: 200px;  
47 - height: 100%; 45 + #tb-json-input {
  46 + width: 100%;
  47 + min-width: 200px;
  48 + height: 100%;
48 49
49 - &:not(.fill-height) {  
50 - min-height: 200px;  
51 - } 50 + &:not(.fill-height) {
  51 + min-height: 200px;
52 } 52 }
53 } 53 }
54 -  
55 } 54 }
@@ -26,6 +26,9 @@ import { @@ -26,6 +26,9 @@ import {
26 import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, Validator, NG_VALIDATORS } from '@angular/forms'; 26 import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, Validator, NG_VALIDATORS } from '@angular/forms';
27 import * as ace from 'ace-builds'; 27 import * as ace from 'ace-builds';
28 import { coerceBooleanProperty } from '@angular/cdk/coercion'; 28 import { coerceBooleanProperty } from '@angular/cdk/coercion';
  29 +import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions';
  30 +import { Store } from '@ngrx/store';
  31 +import { AppState } from '@core/core.state';
29 32
30 @Component({ 33 @Component({
31 selector: 'tb-json-object-edit', 34 selector: 'tb-json-object-edit',
@@ -83,9 +86,14 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va @@ -83,9 +86,14 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
83 86
84 objectValid: boolean; 87 objectValid: boolean;
85 88
  89 + validationError: string;
  90 +
  91 + errorShowed = false;
  92 +
86 private propagateChange = null; 93 private propagateChange = null;
87 94
88 - constructor() { 95 + constructor(public elementRef: ElementRef,
  96 + protected store: Store<AppState>) {
89 } 97 }
90 98
91 ngOnInit(): void { 99 ngOnInit(): void {
@@ -109,6 +117,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va @@ -109,6 +117,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
109 this.jsonEditor.session.setUseWrapMode(false); 117 this.jsonEditor.session.setUseWrapMode(false);
110 this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1); 118 this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1);
111 this.jsonEditor.on('change', () => { 119 this.jsonEditor.on('change', () => {
  120 + this.cleanupJsonErrors();
112 this.updateView(); 121 this.updateView();
113 }); 122 });
114 } 123 }
@@ -132,6 +141,33 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va @@ -132,6 +141,33 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
132 }; 141 };
133 } 142 }
134 143
  144 + validateOnSubmit(): void {
  145 + if (!this.readonly) {
  146 + this.cleanupJsonErrors();
  147 + if (!this.objectValid) {
  148 + this.store.dispatch(new ActionNotificationShow(
  149 + {
  150 + message: this.validationError,
  151 + type: 'error',
  152 + target: 'jsonObjectEditor',
  153 + verticalPosition: 'bottom',
  154 + horizontalPosition: 'left'
  155 + }));
  156 + this.errorShowed = true;
  157 + }
  158 + }
  159 + }
  160 +
  161 + cleanupJsonErrors(): void {
  162 + if (this.errorShowed) {
  163 + this.store.dispatch(new ActionNotificationHide(
  164 + {
  165 + target: 'jsonObjectEditor'
  166 + }));
  167 + this.errorShowed = false;
  168 + }
  169 + }
  170 +
135 writeValue(value: any): void { 171 writeValue(value: any): void {
136 this.modelValue = value; 172 this.modelValue = value;
137 this.contentValue = ''; 173 this.contentValue = '';
@@ -142,6 +178,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va @@ -142,6 +178,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
142 this.objectValid = true; 178 this.objectValid = true;
143 } else { 179 } else {
144 this.objectValid = !this.required; 180 this.objectValid = !this.required;
  181 + this.validationError = 'Json object is required.';
145 } 182 }
146 } catch (e) { 183 } catch (e) {
147 // 184 //
@@ -161,9 +198,20 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va @@ -161,9 +198,20 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
161 try { 198 try {
162 data = JSON.parse(this.contentValue); 199 data = JSON.parse(this.contentValue);
163 this.objectValid = true; 200 this.objectValid = true;
164 - } catch (ex) {} 201 + this.validationError = '';
  202 + } catch (ex) {
  203 + let errorInfo = 'Error:';
  204 + if (ex.name) {
  205 + errorInfo += ' ' + ex.name + ':';
  206 + }
  207 + if (ex.message) {
  208 + errorInfo += ' ' + ex.message;
  209 + }
  210 + this.validationError = errorInfo;
  211 + }
165 } else { 212 } else {
166 this.objectValid = !this.required; 213 this.objectValid = !this.required;
  214 + this.validationError = this.required ? 'Json object is required.' : '';
167 } 215 }
168 this.propagateChange(data); 216 this.propagateChange(data);
169 } 217 }
@@ -19,7 +19,7 @@ import { select, Store } from '@ngrx/store'; @@ -19,7 +19,7 @@ import { select, Store } from '@ngrx/store';
19 import { AppState } from '../../core/core.state'; 19 import { AppState } from '../../core/core.state';
20 import { Observable, Subscription } from 'rxjs'; 20 import { Observable, Subscription } from 'rxjs';
21 import { selectIsLoading } from '../../core/interceptors/load.selectors'; 21 import { selectIsLoading } from '../../core/interceptors/load.selectors';
22 -import { delay } from 'rxjs/operators'; 22 +import { delay, share } from 'rxjs/operators';
23 import { AbstractControl } from '@angular/forms'; 23 import { AbstractControl } from '@angular/forms';
24 24
25 export abstract class PageComponent implements OnDestroy { 25 export abstract class PageComponent implements OnDestroy {
@@ -29,7 +29,7 @@ export abstract class PageComponent implements OnDestroy { @@ -29,7 +29,7 @@ export abstract class PageComponent implements OnDestroy {
29 disabledOnLoadFormControls: Array<AbstractControl> = []; 29 disabledOnLoadFormControls: Array<AbstractControl> = [];
30 30
31 protected constructor(protected store: Store<AppState>) { 31 protected constructor(protected store: Store<AppState>) {
32 - this.isLoading$ = this.store.pipe(delay(0), select(selectIsLoading), delay(100)); 32 + this.isLoading$ = this.store.pipe(delay(0), select(selectIsLoading), share());
33 } 33 }
34 34
35 protected registerDisableOnLoadFormControl(control: AbstractControl) { 35 protected registerDisableOnLoadFormControl(control: AbstractControl) {
@@ -45,6 +45,7 @@ export class ToastDirective implements AfterViewInit, OnDestroy { @@ -45,6 +45,7 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
45 toastTarget = 'root'; 45 toastTarget = 'root';
46 46
47 private notificationSubscription: Subscription = null; 47 private notificationSubscription: Subscription = null;
  48 + private hideNotificationSubscription: Subscription = null;
48 49
49 constructor(public elementRef: ElementRef, 50 constructor(public elementRef: ElementRef,
50 public viewContainerRef: ViewContainerRef, 51 public viewContainerRef: ViewContainerRef,
@@ -78,12 +79,26 @@ export class ToastDirective implements AfterViewInit, OnDestroy { @@ -78,12 +79,26 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
78 } 79 }
79 } 80 }
80 ); 81 );
  82 +
  83 + this.hideNotificationSubscription = this.notificationService.getHideNotification().subscribe(
  84 + (hideNotification) => {
  85 + if (hideNotification) {
  86 + const target = hideNotification.target || 'root';
  87 + if (this.toastTarget === target) {
  88 + this.snackBar.dismiss();
  89 + }
  90 + }
  91 + }
  92 + );
81 } 93 }
82 94
83 ngOnDestroy(): void { 95 ngOnDestroy(): void {
84 if (this.notificationSubscription) { 96 if (this.notificationSubscription) {
85 this.notificationSubscription.unsubscribe(); 97 this.notificationSubscription.unsubscribe();
86 } 98 }
  99 + if (this.hideNotificationSubscription) {
  100 + this.hideNotificationSubscription.unsubscribe();
  101 + }
87 } 102 }
88 } 103 }
89 104