Commit 52ab3d6dea044edaf81b8193e2eeee7685b435fe
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 |