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 | 82 | } else { |
83 | 83 | const authority = Authority[authState.authUser.authority]; |
84 | 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 | 86 | return false; |
98 | 87 | } else { |
99 | 88 | return true; | ... | ... |
... | ... | @@ -50,6 +50,8 @@ export class GlobalHttpInterceptor implements HttpInterceptor { |
50 | 50 | '/api/auth/token' |
51 | 51 | ]; |
52 | 52 | |
53 | + private activeRequests = 0; | |
54 | + | |
53 | 55 | constructor(private store: Store<AppState>, |
54 | 56 | private dialogService: DialogService, |
55 | 57 | private translate: TranslateService, |
... | ... | @@ -135,7 +137,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { |
135 | 137 | } |
136 | 138 | } else if (errorResponse.status === 403) { |
137 | 139 | if (!ignoreErrors) { |
138 | - this.permissionDenied(); | |
140 | + this.dialogService.forbidden(); | |
139 | 141 | } |
140 | 142 | } else if (errorResponse.status === 0 || errorResponse.status === -1) { |
141 | 143 | this.showError('Unable to connect'); |
... | ... | @@ -241,7 +243,16 @@ export class GlobalHttpInterceptor implements HttpInterceptor { |
241 | 243 | |
242 | 244 | private updateLoadingState(config: InterceptorConfig, isLoading: boolean) { |
243 | 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 | 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 | 267 | private showError(error: string, timeout: number = 0) { |
265 | 268 | setTimeout(() => { |
266 | 269 | this.store.dispatch(new ActionNotificationShow({message: error, type: 'error'})); | ... | ... |
... | ... | @@ -15,10 +15,11 @@ |
15 | 15 | /// |
16 | 16 | |
17 | 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 | 20 | export enum NotificationActionTypes { |
21 | - SHOW_NOTIFICATION = '[Notification] Show' | |
21 | + SHOW_NOTIFICATION = '[Notification] Show', | |
22 | + HIDE_NOTIFICATION = '[Notification] Hide' | |
22 | 23 | } |
23 | 24 | |
24 | 25 | export class ActionNotificationShow implements Action { |
... | ... | @@ -27,5 +28,11 @@ export class ActionNotificationShow implements Action { |
27 | 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 | 37 | export type NotificationActions = |
31 | - | ActionNotificationShow; | |
38 | + | ActionNotificationShow | ActionNotificationHide; | ... | ... |
... | ... | @@ -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 | 17 | |
18 | 18 | export interface NotificationState { |
19 | 19 | notification: NotificationMessage; |
20 | + hideNotification: HideNotification; | |
20 | 21 | } |
21 | 22 | |
22 | 23 | export declare type NotificationType = 'info' | 'success' | 'error'; |
... | ... | @@ -31,3 +32,7 @@ export class NotificationMessage { |
31 | 32 | horizontalPosition?: NotificationHorizontalPosition; |
32 | 33 | verticalPosition?: NotificationVerticalPosition; |
33 | 34 | } |
35 | + | |
36 | +export class HideNotification { | |
37 | + target?: string; | |
38 | +} | ... | ... |
... | ... | @@ -18,7 +18,8 @@ import { NotificationState } from './notification.models'; |
18 | 18 | import { NotificationActions, NotificationActionTypes } from './notification.actions'; |
19 | 19 | |
20 | 20 | export const initialState: NotificationState = { |
21 | - notification: null | |
21 | + notification: null, | |
22 | + hideNotification: null | |
22 | 23 | }; |
23 | 24 | |
24 | 25 | export function notificationReducer( |
... | ... | @@ -28,6 +29,8 @@ export function notificationReducer( |
28 | 29 | switch (action.type) { |
29 | 30 | case NotificationActionTypes.SHOW_NOTIFICATION: |
30 | 31 | return { ...state, notification: action.notification }; |
32 | + case NotificationActionTypes.HIDE_NOTIFICATION: | |
33 | + return { ...state, hideNotification: action.hideNotification }; | |
31 | 34 | default: |
32 | 35 | return state; |
33 | 36 | } | ... | ... |
... | ... | @@ -20,7 +20,8 @@ import { MatDialog, MatDialogConfig } from '@angular/material'; |
20 | 20 | import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; |
21 | 21 | import { TranslateService } from '@ngx-translate/core'; |
22 | 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 | 26 | @Injectable( |
26 | 27 | { |
... | ... | @@ -31,6 +32,7 @@ export class DialogService { |
31 | 32 | |
32 | 33 | constructor( |
33 | 34 | private translate: TranslateService, |
35 | + private authService: AuthService, | |
34 | 36 | public dialog: MatDialog |
35 | 37 | ) { |
36 | 38 | } |
... | ... | @@ -68,6 +70,30 @@ export class DialogService { |
68 | 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 | 97 | todo(): Observable<any> { |
72 | 98 | const dialogConfig: MatDialogConfig = { |
73 | 99 | disableClose: true, | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | /// |
16 | 16 | |
17 | 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 | 19 | import { BehaviorSubject, Observable, Subject } from 'rxjs'; |
20 | 20 | |
21 | 21 | |
... | ... | @@ -28,6 +28,8 @@ export class NotificationService { |
28 | 28 | |
29 | 29 | private notificationSubject: Subject<NotificationMessage> = new Subject(); |
30 | 30 | |
31 | + private hideNotificationSubject: Subject<HideNotification> = new Subject(); | |
32 | + | |
31 | 33 | constructor( |
32 | 34 | ) { |
33 | 35 | } |
... | ... | @@ -36,8 +38,15 @@ export class NotificationService { |
36 | 38 | this.notificationSubject.next(notification); |
37 | 39 | } |
38 | 40 | |
41 | + hideNotification(hideNotification: HideNotification) { | |
42 | + this.hideNotificationSubject.next(hideNotification); | |
43 | + } | |
44 | + | |
39 | 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 | 30 | import { ActionStatus, AuditLog } from '@shared/models/audit-log.models'; |
31 | 31 | |
32 | 32 | import * as ace from 'ace-builds'; |
33 | +import { DialogComponent } from '@shared/components/dialog.component'; | |
34 | +import { Router } from '@angular/router'; | |
33 | 35 | |
34 | 36 | export interface AuditLogDetailsDialogData { |
35 | 37 | auditLog: AuditLog; |
... | ... | @@ -40,7 +42,7 @@ export interface AuditLogDetailsDialogData { |
40 | 42 | templateUrl: './audit-log-details-dialog.component.html', |
41 | 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 | 47 | @ViewChild('actionDataEditor', {static: true}) |
46 | 48 | actionDataEditorElmRef: ElementRef; |
... | ... | @@ -58,10 +60,11 @@ export class AuditLogDetailsDialogComponent extends PageComponent implements OnI |
58 | 60 | @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; |
59 | 61 | |
60 | 62 | constructor(protected store: Store<AppState>, |
63 | + protected router: Router, | |
61 | 64 | @Inject(MAT_DIALOG_DATA) public data: AuditLogDetailsDialogData, |
62 | 65 | public dialogRef: MatDialogRef<AuditLogDetailsDialogComponent>, |
63 | 66 | private renderer: Renderer2) { |
64 | - super(store); | |
67 | + super(store, router, dialogRef); | |
65 | 68 | } |
66 | 69 | |
67 | 70 | ngOnInit(): void { |
... | ... | @@ -114,7 +117,7 @@ export class AuditLogDetailsDialogComponent extends PageComponent implements OnI |
114 | 117 | }); |
115 | 118 | newWidth = 8 * maxLineLength + 16; |
116 | 119 | } |
117 | - newHeight = Math.min(400, newHeight); | |
120 | + // newHeight = Math.min(400, newHeight); | |
118 | 121 | this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px'); |
119 | 122 | this.renderer.setStyle(editorElement, 'height', newHeight.toString() + 'px'); |
120 | 123 | this.renderer.setStyle(editorElement, 'width', newWidth.toString() + 'px'); | ... | ... |
... | ... | @@ -27,6 +27,8 @@ import {TbAnchorComponent} from '@shared/components/tb-anchor.component'; |
27 | 27 | import {EntityComponent} from './entity.component'; |
28 | 28 | import {EntityTableConfig} from '@home/models/entity/entities-table-config.models'; |
29 | 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 | 33 | @Component({ |
32 | 34 | selector: 'tb-add-entity-dialog', |
... | ... | @@ -34,7 +36,7 @@ import {AddEntityDialogData} from '@home/models/entity/entity-component.models'; |
34 | 36 | providers: [{provide: ErrorStateMatcher, useExisting: AddEntityDialogComponent}], |
35 | 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 | 41 | entityComponent: EntityComponent<BaseData<HasId>>; |
40 | 42 | detailsForm: NgForm; |
... | ... | @@ -49,11 +51,12 @@ export class AddEntityDialogComponent extends PageComponent implements OnInit, E |
49 | 51 | @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; |
50 | 52 | |
51 | 53 | constructor(protected store: Store<AppState>, |
54 | + protected router: Router, | |
52 | 55 | @Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData<BaseData<HasId>>, |
53 | 56 | public dialogRef: MatDialogRef<AddEntityDialogComponent, BaseData<HasId>>, |
54 | 57 | private componentFactoryResolver: ComponentFactoryResolver, |
55 | 58 | @SkipSelf() private errorStateMatcher: ErrorStateMatcher) { |
56 | - super(store); | |
59 | + super(store, router, dialogRef); | |
57 | 60 | } |
58 | 61 | |
59 | 62 | ngOnInit(): void { | ... | ... |
... | ... | @@ -41,11 +41,12 @@ |
41 | 41 | required="true"> |
42 | 42 | </tb-entity-list-select> |
43 | 43 | <tb-json-object-edit |
44 | - formControlName="additionalInfo" | |
44 | + #additionalInfoEdit | |
45 | + [formControl]="additionalInfo" | |
45 | 46 | label="{{ 'relation.additional-info' | translate }}"> |
46 | 47 | </tb-json-object-edit> |
47 | 48 | <div class="tb-error-messages" *ngIf="submitted && |
48 | - relationFormGroup.get('additionalInfo').invalid" role="alert"> | |
49 | + additionalInfo.invalid" role="alert"> | |
49 | 50 | <div translate class="tb-error-message">relation.invalid-additional-info</div> |
50 | 51 | </div> |
51 | 52 | </fieldset> |
... | ... | @@ -54,7 +55,7 @@ |
54 | 55 | <span fxFlex></span> |
55 | 56 | <button mat-button mat-raised-button color="primary" |
56 | 57 | type="submit" |
57 | - [disabled]="(isLoading$ | async)"> | |
58 | + [disabled]="(isLoading$ | async) || relationFormGroup.invalid || !(relationFormGroup.dirty || additionalInfo.dirty)"> | |
58 | 59 | {{ (isAdd ? 'action.add' : 'action.save') | translate }} |
59 | 60 | </button> |
60 | 61 | <button mat-button color="primary" | ... | ... |
... | ... | @@ -14,9 +14,8 @@ |
14 | 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 | 18 | import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; |
19 | -import { PageComponent } from '@shared/components/page.component'; | |
20 | 19 | import { Store } from '@ngrx/store'; |
21 | 20 | import { AppState } from '@core/core.state'; |
22 | 21 | import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; |
... | ... | @@ -28,7 +27,10 @@ import { |
28 | 27 | } from '@shared/models/relation.models'; |
29 | 28 | import { EntityRelationService } from '@core/http/entity-relation.service'; |
30 | 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 | 35 | export interface RelationDialogData { |
34 | 36 | isAdd: boolean; |
... | ... | @@ -42,7 +44,7 @@ export interface RelationDialogData { |
42 | 44 | providers: [{provide: ErrorStateMatcher, useExisting: RelationDialogComponent}], |
43 | 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 | 49 | relationFormGroup: FormGroup; |
48 | 50 | |
... | ... | @@ -50,15 +52,20 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er |
50 | 52 | direction: EntitySearchDirection; |
51 | 53 | entitySearchDirection = EntitySearchDirection; |
52 | 54 | |
55 | + additionalInfo: FormControl; | |
56 | + | |
57 | + @ViewChild('additionalInfoEdit', {static: true}) additionalInfoEdit: JsonObjectEditComponent; | |
58 | + | |
53 | 59 | submitted = false; |
54 | 60 | |
55 | 61 | constructor(protected store: Store<AppState>, |
62 | + protected router: Router, | |
56 | 63 | @Inject(MAT_DIALOG_DATA) public data: RelationDialogData, |
57 | 64 | private entityRelationService: EntityRelationService, |
58 | 65 | @SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
59 | 66 | public dialogRef: MatDialogRef<RelationDialogComponent, boolean>, |
60 | 67 | public fb: FormBuilder) { |
61 | - super(store); | |
68 | + super(store, router, dialogRef); | |
62 | 69 | this.isAdd = data.isAdd; |
63 | 70 | this.direction = data.direction; |
64 | 71 | } |
... | ... | @@ -68,14 +75,14 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er |
68 | 75 | type: [this.isAdd ? CONTAINS_TYPE : this.data.relation.type, [Validators.required]], |
69 | 76 | targetEntityIds: [this.isAdd ? null : |
70 | 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 | 81 | if (!this.isAdd) { |
75 | 82 | this.relationFormGroup.get('type').disable(); |
76 | 83 | this.relationFormGroup.get('targetEntityIds').disable(); |
77 | 84 | } |
78 | - this.relationFormGroup.valueChanges.subscribe( | |
85 | + this.additionalInfo.valueChanges.subscribe( | |
79 | 86 | () => { |
80 | 87 | this.submitted = false; |
81 | 88 | } |
... | ... | @@ -94,8 +101,8 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er |
94 | 101 | |
95 | 102 | save(): void { |
96 | 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 | 106 | if (this.isAdd) { |
100 | 107 | const tasks: Observable<EntityRelation>[] = []; |
101 | 108 | const type: string = this.relationFormGroup.get('type').value; |
... | ... | @@ -103,7 +110,7 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er |
103 | 110 | entityIds.forEach(entityId => { |
104 | 111 | const relation = { |
105 | 112 | type, |
106 | - additionalInfo, | |
113 | + additionalInfo: this.additionalInfo.value, | |
107 | 114 | typeGroup: RelationTypeGroup.COMMON |
108 | 115 | } as EntityRelation; |
109 | 116 | if (this.direction === EntitySearchDirection.FROM) { |
... | ... | @@ -122,7 +129,7 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er |
122 | 129 | ); |
123 | 130 | } else { |
124 | 131 | const relation: EntityRelation = {...this.data.relation}; |
125 | - relation.additionalInfo = additionalInfo; | |
132 | + relation.additionalInfo = this.additionalInfo.value; | |
126 | 133 | this.entityRelationService.saveRelation(relation).subscribe( |
127 | 134 | () => { |
128 | 135 | this.dialogRef.close(true); | ... | ... |
... | ... | @@ -26,6 +26,8 @@ import {forkJoin, Observable} from 'rxjs'; |
26 | 26 | import {AssetService} from '@core/http/asset.service'; |
27 | 27 | import {EntityViewService} from '@core/http/entity-view.service'; |
28 | 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 | 32 | export interface AddEntitiesToCustomerDialogData { |
31 | 33 | customerId: string; |
... | ... | @@ -38,7 +40,8 @@ export interface AddEntitiesToCustomerDialogData { |
38 | 40 | providers: [{provide: ErrorStateMatcher, useExisting: AddEntitiesToCustomerDialogComponent}], |
39 | 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 | 46 | addEntitiesToCustomerFormGroup: FormGroup; |
44 | 47 | |
... | ... | @@ -50,6 +53,7 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen |
50 | 53 | assignToCustomerText: string; |
51 | 54 | |
52 | 55 | constructor(protected store: Store<AppState>, |
56 | + protected router: Router, | |
53 | 57 | @Inject(MAT_DIALOG_DATA) public data: AddEntitiesToCustomerDialogData, |
54 | 58 | private deviceService: DeviceService, |
55 | 59 | private assetService: AssetService, |
... | ... | @@ -58,7 +62,7 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen |
58 | 62 | @SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
59 | 63 | public dialogRef: MatDialogRef<AddEntitiesToCustomerDialogComponent, boolean>, |
60 | 64 | public fb: FormBuilder) { |
61 | - super(store); | |
65 | + super(store, router, dialogRef); | |
62 | 66 | this.entityType = data.entityType; |
63 | 67 | } |
64 | 68 | ... | ... |
... | ... | @@ -26,6 +26,8 @@ import {EntityType} from '@shared/models/entity-type.models'; |
26 | 26 | import {forkJoin, Observable} from 'rxjs'; |
27 | 27 | import {AssetService} from '@core/http/asset.service'; |
28 | 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 | 32 | export interface AssignToCustomerDialogData { |
31 | 33 | entityIds: Array<EntityId>; |
... | ... | @@ -38,7 +40,8 @@ export interface AssignToCustomerDialogData { |
38 | 40 | providers: [{provide: ErrorStateMatcher, useExisting: AssignToCustomerDialogComponent}], |
39 | 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 | 46 | assignToCustomerFormGroup: FormGroup; |
44 | 47 | |
... | ... | @@ -50,6 +53,7 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On |
50 | 53 | assignToCustomerText: string; |
51 | 54 | |
52 | 55 | constructor(protected store: Store<AppState>, |
56 | + protected router: Router, | |
53 | 57 | @Inject(MAT_DIALOG_DATA) public data: AssignToCustomerDialogData, |
54 | 58 | private deviceService: DeviceService, |
55 | 59 | private assetService: AssetService, |
... | ... | @@ -57,7 +61,7 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On |
57 | 61 | @SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
58 | 62 | public dialogRef: MatDialogRef<AssignToCustomerDialogComponent, boolean>, |
59 | 63 | public fb: FormBuilder) { |
60 | - super(store); | |
64 | + super(store, router, dialogRef); | |
61 | 65 | } |
62 | 66 | |
63 | 67 | ngOnInit(): void { | ... | ... |
... | ... | @@ -20,6 +20,10 @@ |
20 | 20 | <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" |
21 | 21 | [entityId]="entity.id"></tb-event-table> |
22 | 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 | 27 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
24 | 28 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
25 | 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 | 20 | <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" |
21 | 21 | [entityId]="entity.id"></tb-event-table> |
22 | 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 | 27 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
24 | 28 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
25 | 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 | 26 | import {DashboardInfo} from '@app/shared/models/dashboard.models'; |
27 | 27 | import {ActionNotificationShow} from '@core/notification/notification.actions'; |
28 | 28 | import {TranslateService} from '@ngx-translate/core'; |
29 | +import { DialogComponent } from '@shared/components/dialog.component'; | |
30 | +import { Router } from '@angular/router'; | |
29 | 31 | |
30 | 32 | export interface MakeDashboardPublicDialogData { |
31 | 33 | dashboard: DashboardInfo; |
... | ... | @@ -36,19 +38,20 @@ export interface MakeDashboardPublicDialogData { |
36 | 38 | templateUrl: './make-dashboard-public-dialog.component.html', |
37 | 39 | styleUrls: [] |
38 | 40 | }) |
39 | -export class MakeDashboardPublicDialogComponent extends PageComponent implements OnInit { | |
41 | +export class MakeDashboardPublicDialogComponent extends DialogComponent<MakeDashboardPublicDialogComponent> implements OnInit { | |
40 | 42 | |
41 | 43 | dashboard: DashboardInfo; |
42 | 44 | |
43 | 45 | publicLink: string; |
44 | 46 | |
45 | 47 | constructor(protected store: Store<AppState>, |
48 | + protected router: Router, | |
46 | 49 | @Inject(MAT_DIALOG_DATA) public data: MakeDashboardPublicDialogData, |
47 | 50 | public translate: TranslateService, |
48 | 51 | private dashboardService: DashboardService, |
49 | 52 | public dialogRef: MatDialogRef<MakeDashboardPublicDialogComponent>, |
50 | 53 | public fb: FormBuilder) { |
51 | - super(store); | |
54 | + super(store, router, dialogRef); | |
52 | 55 | |
53 | 56 | this.dashboard = data.dashboard; |
54 | 57 | this.publicLink = dashboardService.getPublicDashboardLink(this.dashboard); | ... | ... |
... | ... | @@ -23,6 +23,8 @@ import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm} from '@ |
23 | 23 | import {EntityType} from '@shared/models/entity-type.models'; |
24 | 24 | import {DashboardService} from '@core/http/dashboard.service'; |
25 | 25 | import {forkJoin, Observable} from 'rxjs'; |
26 | +import { DialogComponent } from '@shared/components/dialog.component'; | |
27 | +import { Router } from '@angular/router'; | |
26 | 28 | |
27 | 29 | export type ManageDashboardCustomersActionType = 'assign' | 'manage' | 'unassign'; |
28 | 30 | |
... | ... | @@ -38,7 +40,8 @@ export interface ManageDashboardCustomersDialogData { |
38 | 40 | providers: [{provide: ErrorStateMatcher, useExisting: ManageDashboardCustomersDialogComponent}], |
39 | 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 | 46 | dashboardCustomersFormGroup: FormGroup; |
44 | 47 | |
... | ... | @@ -53,12 +56,13 @@ export class ManageDashboardCustomersDialogComponent extends PageComponent imple |
53 | 56 | assignedCustomersIds: string[]; |
54 | 57 | |
55 | 58 | constructor(protected store: Store<AppState>, |
59 | + protected router: Router, | |
56 | 60 | @Inject(MAT_DIALOG_DATA) public data: ManageDashboardCustomersDialogData, |
57 | 61 | private dashboardService: DashboardService, |
58 | 62 | @SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
59 | 63 | public dialogRef: MatDialogRef<ManageDashboardCustomersDialogComponent, boolean>, |
60 | 64 | public fb: FormBuilder) { |
61 | - super(store); | |
65 | + super(store, router, dialogRef); | |
62 | 66 | |
63 | 67 | this.assignedCustomersIds = data.assignedCustomersIds || []; |
64 | 68 | switch (data.actionType) { | ... | ... |
... | ... | @@ -25,6 +25,8 @@ import { TranslateService } from '@ngx-translate/core'; |
25 | 25 | import { AuthService } from '@core/auth/auth.service'; |
26 | 26 | import {DeviceService} from '@core/http/device.service'; |
27 | 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 | 31 | export interface DeviceCredentialsDialogData { |
30 | 32 | isReadOnly: boolean; |
... | ... | @@ -37,7 +39,8 @@ export interface DeviceCredentialsDialogData { |
37 | 39 | providers: [{provide: ErrorStateMatcher, useExisting: DeviceCredentialsDialogComponent}], |
38 | 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 | 45 | deviceCredentialsFormGroup: FormGroup; |
43 | 46 | |
... | ... | @@ -54,12 +57,13 @@ export class DeviceCredentialsDialogComponent extends PageComponent implements O |
54 | 57 | credentialTypeNamesMap = credentialTypeNames; |
55 | 58 | |
56 | 59 | constructor(protected store: Store<AppState>, |
60 | + protected router: Router, | |
57 | 61 | @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogData, |
58 | 62 | private deviceService: DeviceService, |
59 | 63 | @SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
60 | 64 | public dialogRef: MatDialogRef<DeviceCredentialsDialogComponent, DeviceCredentials>, |
61 | 65 | public fb: FormBuilder) { |
62 | - super(store); | |
66 | + super(store, router, dialogRef); | |
63 | 67 | |
64 | 68 | this.isReadOnly = data.isReadOnly; |
65 | 69 | } | ... | ... |
... | ... | @@ -20,6 +20,10 @@ |
20 | 20 | <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" |
21 | 21 | [entityId]="entity.id"></tb-event-table> |
22 | 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 | 27 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
24 | 28 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
25 | 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 | 23 | import { ActionNotificationShow } from '@core/notification/notification.actions'; |
24 | 24 | import { TranslateService } from '@ngx-translate/core'; |
25 | 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 | 29 | @Component({ |
28 | 30 | selector: 'tb-change-password-dialog', |
29 | 31 | templateUrl: './change-password-dialog.component.html', |
30 | 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 | 36 | changePassword: FormGroup; |
35 | 37 | |
36 | 38 | constructor(protected store: Store<AppState>, |
39 | + protected router: Router, | |
37 | 40 | private translate: TranslateService, |
38 | 41 | private authService: AuthService, |
39 | 42 | public dialogRef: MatDialogRef<ChangePasswordDialogComponent>, |
40 | 43 | public fb: FormBuilder) { |
41 | - super(store); | |
44 | + super(store, router, dialogRef); | |
42 | 45 | } |
43 | 46 | |
44 | 47 | ngOnInit(): void { | ... | ... |
... | ... | @@ -23,6 +23,10 @@ |
23 | 23 | [tenantId]="entity.tenantId.id" |
24 | 24 | [entityId]="entity.id"></tb-event-table> |
25 | 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 | 30 | <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
27 | 31 | label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
28 | 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 | 20 | <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="nullUid" |
21 | 21 | [entityId]="entity.id"></tb-event-table> |
22 | 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 | 21 | import { AppState } from '@core/core.state'; |
22 | 22 | import { TranslateService } from '@ngx-translate/core'; |
23 | 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 | 27 | export interface ActivationLinkDialogData { |
26 | 28 | activationLink: string; |
... | ... | @@ -30,15 +32,16 @@ export interface ActivationLinkDialogData { |
30 | 32 | selector: 'tb-activation-link-dialog', |
31 | 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 | 37 | activationLink: string; |
36 | 38 | |
37 | 39 | constructor(protected store: Store<AppState>, |
40 | + protected router: Router, | |
38 | 41 | @Inject(MAT_DIALOG_DATA) public data: ActivationLinkDialogData, |
39 | 42 | public dialogRef: MatDialogRef<ActivationLinkDialogComponent, void>, |
40 | 43 | private translate: TranslateService) { |
41 | - super(store); | |
44 | + super(store, router, dialogRef); | |
42 | 45 | this.activationLink = this.data.activationLink; |
43 | 46 | } |
44 | 47 | ... | ... |
... | ... | @@ -31,6 +31,8 @@ import { |
31 | 31 | ActivationLinkDialogData |
32 | 32 | } from '@modules/home/pages/user/activation-link-dialog.component'; |
33 | 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 | 37 | export interface AddUserDialogData { |
36 | 38 | tenantId: string; |
... | ... | @@ -42,7 +44,7 @@ export interface AddUserDialogData { |
42 | 44 | selector: 'tb-add-user-dialog', |
43 | 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 | 49 | detailsForm: NgForm; |
48 | 50 | user: User; |
... | ... | @@ -56,11 +58,12 @@ export class AddUserDialogComponent extends PageComponent implements OnInit { |
56 | 58 | @ViewChild(UserComponent, {static: true}) userComponent: UserComponent; |
57 | 59 | |
58 | 60 | constructor(protected store: Store<AppState>, |
61 | + protected router: Router, | |
59 | 62 | @Inject(MAT_DIALOG_DATA) public data: AddUserDialogData, |
60 | 63 | public dialogRef: MatDialogRef<AddUserDialogComponent, User>, |
61 | 64 | private userService: UserService, |
62 | 65 | private dialog: MatDialog) { |
63 | - super(store); | |
66 | + super(store, router, dialogRef); | |
64 | 67 | } |
65 | 68 | |
66 | 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 | 28 | <input matInput type="text" placeholder="{{ !disabled ? ('entity.entity-list' | translate) : '' }}" |
29 | 29 | style="max-width: 200px;" |
30 | 30 | #entityInput |
31 | + (focusin)="onFocus()" | |
31 | 32 | formControlName="entity" |
32 | 33 | matAutocompleteOrigin |
33 | 34 | #origin="matAutocompleteOrigin" | ... | ... |
... | ... | @@ -99,6 +99,8 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV |
99 | 99 | |
100 | 100 | private searchText = ''; |
101 | 101 | |
102 | + private dirty = false; | |
103 | + | |
102 | 104 | private propagateChange = (v: any) => { }; |
103 | 105 | |
104 | 106 | constructor(private store: Store<AppState>, |
... | ... | @@ -126,7 +128,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV |
126 | 128 | ngOnInit() { |
127 | 129 | this.filteredEntities = this.entityListFormGroup.get('entity').valueChanges |
128 | 130 | .pipe( |
129 | - startWith<string | BaseData<EntityId>>(''), | |
131 | + // startWith<string | BaseData<EntityId>>(''), | |
130 | 132 | tap((value) => { |
131 | 133 | if (value && typeof value !== 'string') { |
132 | 134 | this.add(value); |
... | ... | @@ -145,12 +147,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV |
145 | 147 | } |
146 | 148 | |
147 | 149 | setDisabledState(isDisabled: boolean): void { |
148 | - const emitEvent = this.disabled !== isDisabled; | |
149 | 150 | this.disabled = isDisabled; |
150 | 151 | if (isDisabled) { |
151 | - this.entityListFormGroup.disable({emitEvent}); | |
152 | + this.entityListFormGroup.disable({emitEvent: false}); | |
152 | 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 | 170 | this.entityListFormGroup.get('entities').setValue(this.entities); |
170 | 171 | this.modelValue = null; |
171 | 172 | } |
173 | + this.dirty = true; | |
172 | 174 | } |
173 | 175 | |
174 | 176 | reset() { |
175 | 177 | this.entities = []; |
176 | 178 | this.entityListFormGroup.get('entities').setValue(this.entities); |
177 | 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 | 184 | this.propagateChange(this.modelValue); |
185 | + this.dirty = true; | |
180 | 186 | } |
181 | 187 | |
182 | 188 | add(entity: BaseData<EntityId>): void { |
... | ... | @@ -212,12 +218,18 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV |
212 | 218 | |
213 | 219 | fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> { |
214 | 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 | 222 | 50, '', false, true).pipe( |
218 | 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 | 233 | clear(value: string = '') { |
222 | 234 | this.entityInput.nativeElement.value = value; |
223 | 235 | this.entityListFormGroup.get('entity').patchValue(value, {emitEvent: true}); | ... | ... |
... | ... | @@ -54,13 +54,13 @@ export class FullscreenDirective { |
54 | 54 | constructor(public elementRef: ElementRef, |
55 | 55 | private viewContainerRef: ViewContainerRef, |
56 | 56 | private overlay: Overlay) { |
57 | - | |
58 | 57 | } |
59 | 58 | |
60 | 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 | 64 | const position = this.overlay.position(); |
65 | 65 | const config = new OverlayConfig({ |
66 | 66 | hasBackdrop: false, |
... | ... | @@ -73,21 +73,24 @@ export class FullscreenDirective { |
73 | 73 | |
74 | 74 | this.overlayRef = this.overlay.create(config); |
75 | 75 | this.overlayRef.attach(new EmptyPortal()); |
76 | - this.overlayRef.overlayElement.append( this.elementRef.nativeElement ); | |
76 | + this.overlayRef.overlayElement.append( targetElement.nativeElement ); | |
77 | 77 | this.fullscreenChanged.emit(true); |
78 | 78 | } |
79 | 79 | |
80 | 80 | exitFullscreen() { |
81 | + const targetElement = this.elementRef; | |
81 | 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 | 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 | 91 | this.overlayRef.dispose(); |
88 | 92 | this.fullscreenChanged.emit(false); |
89 | 93 | } |
90 | - | |
91 | 94 | } |
92 | 95 | |
93 | 96 | class EmptyPortal extends ComponentPortal<TbAnchorComponent> { | ... | ... |
... | ... | @@ -16,7 +16,8 @@ |
16 | 16 | |
17 | 17 | --> |
18 | 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 | 21 | <div fxLayout="row" fxLayoutAlign="start center"> |
21 | 22 | <label class="tb-title no-padding" |
22 | 23 | ng-class="{'tb-required': required, |
... | ... | @@ -29,7 +30,8 @@ |
29 | 30 | <mat-icon class="material-icons">{{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> |
30 | 31 | </button> |
31 | 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 | 35 | <div fxFlex #jsonEditor id="tb-json-input" [ngClass]="{'fill-height': fillHeight}"></div> |
34 | 36 | </div> |
35 | 37 | </div> | ... | ... |
... | ... | @@ -35,21 +35,20 @@ |
35 | 35 | .fill-height { |
36 | 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 | 26 | import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, Validator, NG_VALIDATORS } from '@angular/forms'; |
27 | 27 | import * as ace from 'ace-builds'; |
28 | 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 | 33 | @Component({ |
31 | 34 | selector: 'tb-json-object-edit', |
... | ... | @@ -83,9 +86,14 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va |
83 | 86 | |
84 | 87 | objectValid: boolean; |
85 | 88 | |
89 | + validationError: string; | |
90 | + | |
91 | + errorShowed = false; | |
92 | + | |
86 | 93 | private propagateChange = null; |
87 | 94 | |
88 | - constructor() { | |
95 | + constructor(public elementRef: ElementRef, | |
96 | + protected store: Store<AppState>) { | |
89 | 97 | } |
90 | 98 | |
91 | 99 | ngOnInit(): void { |
... | ... | @@ -109,6 +117,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va |
109 | 117 | this.jsonEditor.session.setUseWrapMode(false); |
110 | 118 | this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1); |
111 | 119 | this.jsonEditor.on('change', () => { |
120 | + this.cleanupJsonErrors(); | |
112 | 121 | this.updateView(); |
113 | 122 | }); |
114 | 123 | } |
... | ... | @@ -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 | 171 | writeValue(value: any): void { |
136 | 172 | this.modelValue = value; |
137 | 173 | this.contentValue = ''; |
... | ... | @@ -142,6 +178,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va |
142 | 178 | this.objectValid = true; |
143 | 179 | } else { |
144 | 180 | this.objectValid = !this.required; |
181 | + this.validationError = 'Json object is required.'; | |
145 | 182 | } |
146 | 183 | } catch (e) { |
147 | 184 | // |
... | ... | @@ -161,9 +198,20 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va |
161 | 198 | try { |
162 | 199 | data = JSON.parse(this.contentValue); |
163 | 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 | 212 | } else { |
166 | 213 | this.objectValid = !this.required; |
214 | + this.validationError = this.required ? 'Json object is required.' : ''; | |
167 | 215 | } |
168 | 216 | this.propagateChange(data); |
169 | 217 | } | ... | ... |
... | ... | @@ -19,7 +19,7 @@ import { select, Store } from '@ngrx/store'; |
19 | 19 | import { AppState } from '../../core/core.state'; |
20 | 20 | import { Observable, Subscription } from 'rxjs'; |
21 | 21 | import { selectIsLoading } from '../../core/interceptors/load.selectors'; |
22 | -import { delay } from 'rxjs/operators'; | |
22 | +import { delay, share } from 'rxjs/operators'; | |
23 | 23 | import { AbstractControl } from '@angular/forms'; |
24 | 24 | |
25 | 25 | export abstract class PageComponent implements OnDestroy { |
... | ... | @@ -29,7 +29,7 @@ export abstract class PageComponent implements OnDestroy { |
29 | 29 | disabledOnLoadFormControls: Array<AbstractControl> = []; |
30 | 30 | |
31 | 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 | 35 | protected registerDisableOnLoadFormControl(control: AbstractControl) { | ... | ... |
... | ... | @@ -45,6 +45,7 @@ export class ToastDirective implements AfterViewInit, OnDestroy { |
45 | 45 | toastTarget = 'root'; |
46 | 46 | |
47 | 47 | private notificationSubscription: Subscription = null; |
48 | + private hideNotificationSubscription: Subscription = null; | |
48 | 49 | |
49 | 50 | constructor(public elementRef: ElementRef, |
50 | 51 | public viewContainerRef: ViewContainerRef, |
... | ... | @@ -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 | 95 | ngOnDestroy(): void { |
84 | 96 | if (this.notificationSubscription) { |
85 | 97 | this.notificationSubscription.unsubscribe(); |
86 | 98 | } |
99 | + if (this.hideNotificationSubscription) { | |
100 | + this.hideNotificationSubscription.unsubscribe(); | |
101 | + } | |
87 | 102 | } |
88 | 103 | } |
89 | 104 | ... | ... |