Commit c60e99fad90f572b668e3fae5a153a736ba67d1c
1 parent
e160b6e0
UI: Entity table models improvements
Showing
30 changed files
with
299 additions
and
133 deletions
... | ... | @@ -72,7 +72,7 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink> |
72 | 72 | this.entityType = EntityType.ALARM; |
73 | 73 | this.entityTranslations = entityTypeTranslations.get(EntityType.ALARM); |
74 | 74 | this.entityResources = { |
75 | - } as EntityTypeResource; | |
75 | + } as EntityTypeResource<AlarmInfo>; | |
76 | 76 | this.searchStatus = defaultSearchStatus; |
77 | 77 | |
78 | 78 | this.headerComponent = AlarmTableHeaderComponent; | ... | ... |
... | ... | @@ -68,7 +68,7 @@ export class AuditLogTableConfig extends EntityTableConfig<AuditLog, TimePageLin |
68 | 68 | search: 'audit-log.search' |
69 | 69 | }; |
70 | 70 | this.entityResources = { |
71 | - } as EntityTypeResource; | |
71 | + } as EntityTypeResource<AuditLog>; | |
72 | 72 | |
73 | 73 | this.entitiesFetchFunction = pageLink => this.fetchAuditLogs(pageLink); |
74 | 74 | ... | ... |
... | ... | @@ -19,7 +19,7 @@ |
19 | 19 | <mat-toolbar color="primary"> |
20 | 20 | <h2 translate>{{ translations.add }}</h2> |
21 | 21 | <span fxFlex></span> |
22 | - <div [tb-help]="resources.helpLinkId"></div> | |
22 | + <div [tb-help]="helpLinkId()"></div> | |
23 | 23 | <button mat-icon-button |
24 | 24 | (click)="cancel()" |
25 | 25 | type="button"> | ... | ... |
... | ... | @@ -14,7 +14,7 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component, ComponentFactoryResolver, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core'; | |
17 | +import { Component, ComponentFactoryResolver, Inject, Injector, OnInit, SkipSelf, ViewChild } from '@angular/core'; | |
18 | 18 | import { ErrorStateMatcher } from '@angular/material/core'; |
19 | 19 | import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; |
20 | 20 | import { Store } from '@ngrx/store'; |
... | ... | @@ -29,6 +29,7 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod |
29 | 29 | import { AddEntityDialogData } from '@home/models/entity/entity-component.models'; |
30 | 30 | import { DialogComponent } from '@shared/components/dialog.component'; |
31 | 31 | import { Router } from '@angular/router'; |
32 | +import { Observable } from 'rxjs'; | |
32 | 33 | |
33 | 34 | @Component({ |
34 | 35 | selector: 'tb-add-entity-dialog', |
... | ... | @@ -44,7 +45,7 @@ export class AddEntityDialogComponent extends |
44 | 45 | |
45 | 46 | entitiesTableConfig: EntityTableConfig<BaseData<HasId>>; |
46 | 47 | translations: EntityTypeTranslation; |
47 | - resources: EntityTypeResource; | |
48 | + resources: EntityTypeResource<BaseData<HasId>>; | |
48 | 49 | entity: BaseData<EntityId>; |
49 | 50 | |
50 | 51 | submitted = false; |
... | ... | @@ -56,6 +57,7 @@ export class AddEntityDialogComponent extends |
56 | 57 | @Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData<BaseData<HasId>>, |
57 | 58 | public dialogRef: MatDialogRef<AddEntityDialogComponent, BaseData<HasId>>, |
58 | 59 | private componentFactoryResolver: ComponentFactoryResolver, |
60 | + private injector: Injector, | |
59 | 61 | @SkipSelf() private errorStateMatcher: ErrorStateMatcher) { |
60 | 62 | super(store, router, dialogRef); |
61 | 63 | } |
... | ... | @@ -68,14 +70,35 @@ export class AddEntityDialogComponent extends |
68 | 70 | const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.entityComponent); |
69 | 71 | const viewContainerRef = this.entityDetailsFormAnchor.viewContainerRef; |
70 | 72 | viewContainerRef.clear(); |
71 | - const componentRef = viewContainerRef.createComponent(componentFactory); | |
73 | + const injector: Injector = Injector.create( | |
74 | + { | |
75 | + providers: [ | |
76 | + { | |
77 | + provide: 'entity', | |
78 | + useValue: this.entity | |
79 | + }, | |
80 | + { | |
81 | + provide: 'entitiesTableConfig', | |
82 | + useValue: this.entitiesTableConfig | |
83 | + } | |
84 | + ], | |
85 | + parent: this.injector | |
86 | + } | |
87 | + ); | |
88 | + const componentRef = viewContainerRef.createComponent(componentFactory, 0, injector); | |
72 | 89 | this.entityComponent = componentRef.instance; |
73 | 90 | this.entityComponent.isEdit = true; |
74 | - this.entityComponent.entitiesTableConfig = this.entitiesTableConfig; | |
75 | - this.entityComponent.entity = this.entity; | |
76 | 91 | this.detailsForm = this.entityComponent.entityNgForm; |
77 | 92 | } |
78 | 93 | |
94 | + helpLinkId(): string { | |
95 | + if (this.resources.helpLinkIdForEntity && this.entityComponent.entityForm) { | |
96 | + return this.resources.helpLinkIdForEntity(this.entityComponent.entityForm.getRawValue()); | |
97 | + } else { | |
98 | + return this.resources.helpLinkId; | |
99 | + } | |
100 | + } | |
101 | + | |
79 | 102 | isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { |
80 | 103 | const originalErrorState = this.errorStateMatcher.isErrorState(control, form); |
81 | 104 | const customErrorState = !!(control && control.invalid && this.submitted); | ... | ... |
... | ... | @@ -22,12 +22,15 @@ import { AfterViewInit } from '@angular/core'; |
22 | 22 | import { POSTAL_CODE_PATTERNS } from '@home/models/contact.models'; |
23 | 23 | import { HasId } from '@shared/models/base-data'; |
24 | 24 | import {EntityComponent} from './entity.component'; |
25 | +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; | |
25 | 26 | |
26 | 27 | export abstract class ContactBasedComponent<T extends ContactBased<HasId>> extends EntityComponent<T> implements AfterViewInit { |
27 | 28 | |
28 | 29 | protected constructor(protected store: Store<AppState>, |
29 | - protected fb: FormBuilder) { | |
30 | - super(store); | |
30 | + protected fb: FormBuilder, | |
31 | + protected entityValue: T, | |
32 | + protected entitiesTableConfig: EntityTableConfig<T>) { | |
33 | + super(store, fb, entityValue, entitiesTableConfig); | |
31 | 34 | } |
32 | 35 | |
33 | 36 | buildForm(entity: T): FormGroup { | ... | ... |
... | ... | @@ -135,7 +135,7 @@ |
135 | 135 | </mat-toolbar> |
136 | 136 | <div fxFlex class="table-container"> |
137 | 137 | <table mat-table [dataSource]="dataSource" [trackBy]="trackByEntityId" |
138 | - matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear> | |
138 | + matSort [matSortActive]="pageLink.sortOrder?.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear> | |
139 | 139 | <ng-container matColumnDef="select" sticky> |
140 | 140 | <mat-header-cell *matHeaderCellDef style="width: 30px;"> |
141 | 141 | <mat-checkbox (change)="$event ? dataSource.masterToggle() : null" |
... | ... | @@ -153,8 +153,11 @@ |
153 | 153 | </ng-container> |
154 | 154 | <ng-container [matColumnDef]="column.key" *ngFor="let column of entityColumns; trackBy: trackByColumnKey;"> |
155 | 155 | <mat-header-cell [ngClass]="{'mat-number-cell': column.isNumberColumn}" |
156 | - *matHeaderCellDef [ngStyle]="headerCellStyle(column)" mat-sort-header [disabled]="!column.sortable"> {{ column.title | translate }} </mat-header-cell> | |
156 | + [fxHide.lt-lg]="column.mobileHide" | |
157 | + *matHeaderCellDef [ngStyle]="headerCellStyle(column)" mat-sort-header [disabled]="!column.sortable"> | |
158 | + {{ column.ignoreTranslate ? column.title : (column.title | translate) }} </mat-header-cell> | |
157 | 159 | <mat-cell [ngClass]="{'mat-number-cell': column.isNumberColumn}" |
160 | + [fxHide.lt-lg]="column.mobileHide" | |
158 | 161 | *matCellDef="let entity; let row = index" |
159 | 162 | [matTooltip]="cellTooltip(entity, column, row)" |
160 | 163 | matTooltipPosition="above" |
... | ... | @@ -186,8 +189,7 @@ |
186 | 189 | maxWidth: (cellActionDescriptors.length * 40) + 'px', |
187 | 190 | width: (cellActionDescriptors.length * 40) + 'px' }"> |
188 | 191 | <div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end"> |
189 | - <button mat-icon-button [disabled]="isLoading$ | async" | |
190 | - [style.visibility]="actionDescriptor.isEnabled(entity) ? 'visible' : 'hidden'" | |
192 | + <button mat-icon-button [disabled]="(isLoading$ | async) || !actionDescriptor.isEnabled(entity)" | |
191 | 193 | *ngFor="let actionDescriptor of cellActionDescriptors" |
192 | 194 | matTooltip="{{ actionDescriptor.nameFunction ? actionDescriptor.nameFunction(entity) : actionDescriptor.name }}" |
193 | 195 | matTooltipPosition="above" | ... | ... |
... | ... | @@ -20,8 +20,8 @@ import { |
20 | 20 | Component, |
21 | 21 | ComponentFactoryResolver, |
22 | 22 | ElementRef, |
23 | - Input, | |
24 | - OnInit, | |
23 | + Input, OnChanges, | |
24 | + OnInit, SimpleChanges, | |
25 | 25 | ViewChild |
26 | 26 | } from '@angular/core'; |
27 | 27 | import { PageComponent } from '@shared/components/page.component'; |
... | ... | @@ -51,7 +51,7 @@ import { EntityTypeTranslation } from '@shared/models/entity-type.models'; |
51 | 51 | import { DialogService } from '@core/services/dialog.service'; |
52 | 52 | import { AddEntityDialogComponent } from './add-entity-dialog.component'; |
53 | 53 | import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models'; |
54 | -import { historyInterval, HistoryWindowType, Timewindow } from '@shared/models/time/time.models'; | |
54 | +import { DAY, historyInterval, HistoryWindowType, Timewindow } from '@shared/models/time/time.models'; | |
55 | 55 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; |
56 | 56 | import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; |
57 | 57 | import { isDefined, isUndefined } from '@core/utils'; |
... | ... | @@ -62,7 +62,7 @@ import { isDefined, isUndefined } from '@core/utils'; |
62 | 62 | styleUrls: ['./entities-table.component.scss'], |
63 | 63 | changeDetection: ChangeDetectionStrategy.OnPush |
64 | 64 | }) |
65 | -export class EntitiesTableComponent extends PageComponent implements AfterViewInit, OnInit { | |
65 | +export class EntitiesTableComponent extends PageComponent implements AfterViewInit, OnInit, OnChanges { | |
66 | 66 | |
67 | 67 | @Input() |
68 | 68 | entitiesTableConfig: EntityTableConfig<BaseData<HasId>>; |
... | ... | @@ -112,7 +112,27 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn |
112 | 112 | } |
113 | 113 | |
114 | 114 | ngOnInit() { |
115 | - this.entitiesTableConfig = this.entitiesTableConfig || this.route.snapshot.data.entitiesTableConfig; | |
115 | + if (this.entitiesTableConfig) { | |
116 | + this.init(this.entitiesTableConfig); | |
117 | + } else { | |
118 | + this.init(this.route.snapshot.data.entitiesTableConfig); | |
119 | + } | |
120 | + } | |
121 | + | |
122 | + ngOnChanges(changes: SimpleChanges): void { | |
123 | + for (const propName of Object.keys(changes)) { | |
124 | + const change = changes[propName]; | |
125 | + if (!change.firstChange && change.currentValue !== change.previousValue) { | |
126 | + if (propName === 'entitiesTableConfig' && change.currentValue) { | |
127 | + this.init(change.currentValue); | |
128 | + } | |
129 | + } | |
130 | + } | |
131 | + } | |
132 | + | |
133 | + private init(entitiesTableConfig: EntityTableConfig<BaseData<HasId>>) { | |
134 | + this.isDetailsOpen = false; | |
135 | + this.entitiesTableConfig = entitiesTableConfig; | |
116 | 136 | if (this.entitiesTableConfig.headerComponent) { |
117 | 137 | const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.headerComponent); |
118 | 138 | const viewContainerRef = this.entityTableHeaderAnchor.viewContainerRef; |
... | ... | @@ -138,17 +158,16 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn |
138 | 158 | onAction: ($event, entity) => this.deleteEntity($event, entity) |
139 | 159 | } |
140 | 160 | ); |
161 | + this.groupActionDescriptors.push( | |
162 | + { | |
163 | + name: this.translate.instant('action.delete'), | |
164 | + icon: 'delete', | |
165 | + isEnabled: true, | |
166 | + onAction: ($event, entities) => this.deleteEntities($event, entities) | |
167 | + } | |
168 | + ); | |
141 | 169 | } |
142 | 170 | |
143 | - this.groupActionDescriptors.push( | |
144 | - { | |
145 | - name: this.translate.instant('action.delete'), | |
146 | - icon: 'delete', | |
147 | - isEnabled: this.entitiesTableConfig.entitiesDeleteEnabled, | |
148 | - onAction: ($event, entities) => this.deleteEntities($event, entities) | |
149 | - } | |
150 | - ); | |
151 | - | |
152 | 171 | const enabledGroupActionDescriptors = |
153 | 172 | this.groupActionDescriptors.filter((descriptor) => descriptor.isEnabled); |
154 | 173 | |
... | ... | @@ -156,24 +175,25 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn |
156 | 175 | |
157 | 176 | this.columnsUpdated(); |
158 | 177 | |
159 | - const sortOrder: SortOrder = { property: this.entitiesTableConfig.defaultSortOrder.property, | |
160 | - direction: this.entitiesTableConfig.defaultSortOrder.direction }; | |
178 | + let sortOrder: SortOrder = null; | |
179 | + if (this.entitiesTableConfig.defaultSortOrder) { | |
180 | + sortOrder = { | |
181 | + property: this.entitiesTableConfig.defaultSortOrder.property, | |
182 | + direction: this.entitiesTableConfig.defaultSortOrder.direction | |
183 | + }; | |
184 | + } | |
161 | 185 | |
162 | 186 | if (this.entitiesTableConfig.useTimePageLink) { |
163 | - this.timewindow = historyInterval(24 * 60 * 60 * 1000); | |
187 | + this.timewindow = historyInterval(DAY); | |
164 | 188 | const currentTime = Date.now(); |
165 | 189 | this.pageLink = new TimePageLink(10, 0, null, sortOrder, |
166 | 190 | currentTime - this.timewindow.history.timewindowMs, currentTime); |
167 | 191 | } else { |
168 | 192 | this.pageLink = new PageLink(10, 0, null, sortOrder); |
169 | 193 | } |
170 | - this.dataSource = new EntitiesDataSource<BaseData<HasId>>( | |
171 | - this.entitiesTableConfig.entitiesFetchFunction, | |
172 | - this.entitiesTableConfig.entitySelectionEnabled, | |
173 | - () => { | |
174 | - this.dataLoaded(); | |
175 | - } | |
176 | - ); | |
194 | + this.dataSource = this.entitiesTableConfig.dataSource(() => { | |
195 | + this.dataLoaded(); | |
196 | + }); | |
177 | 197 | if (this.entitiesTableConfig.onLoadAction) { |
178 | 198 | this.entitiesTableConfig.onLoadAction(this.route); |
179 | 199 | } |
... | ... | @@ -214,8 +234,14 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn |
214 | 234 | } |
215 | 235 | this.pageLink.page = this.paginator.pageIndex; |
216 | 236 | this.pageLink.pageSize = this.paginator.pageSize; |
217 | - this.pageLink.sortOrder.property = this.sort.active; | |
218 | - this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; | |
237 | + if (this.sort.active) { | |
238 | + this.pageLink.sortOrder = { | |
239 | + property: this.sort.active, | |
240 | + direction: Direction[this.sort.direction.toUpperCase()] | |
241 | + }; | |
242 | + } else { | |
243 | + this.pageLink.sortOrder = null; | |
244 | + } | |
219 | 245 | if (this.entitiesTableConfig.useTimePageLink) { |
220 | 246 | const timePageLink = this.pageLink as TimePageLink; |
221 | 247 | if (this.timewindow.history.historyType === HistoryWindowType.LAST_INTERVAL) { | ... | ... |
... | ... | @@ -25,9 +25,9 @@ |
25 | 25 | (applyDetails)="saveEntity()" |
26 | 26 | [theForm]="detailsForm"> |
27 | 27 | <div class="details-buttons"> |
28 | - <div [tb-help]="resources.helpLinkId"></div> | |
28 | + <div [tb-help]="helpLinkId()"></div> | |
29 | 29 | </div> |
30 | - <mat-tab-group class="tb-absolute-fill" [ngClass]="{'tb-headless': isEditValue}" fxFlex [(selectedIndex)]="selectedTab"> | |
30 | + <mat-tab-group class="tb-absolute-fill" [ngClass]="{'tb-headless': hideDetailsTabs()}" fxFlex [(selectedIndex)]="selectedTab"> | |
31 | 31 | <mat-tab label="{{ 'details.details' | translate }}"> |
32 | 32 | <tb-anchor #entityDetailsForm></tb-anchor> |
33 | 33 | </mat-tab> | ... | ... |
... | ... | @@ -16,10 +16,10 @@ |
16 | 16 | |
17 | 17 | import { |
18 | 18 | AfterViewInit, |
19 | - ChangeDetectionStrategy, | |
19 | + ChangeDetectionStrategy, ChangeDetectorRef, | |
20 | 20 | Component, |
21 | 21 | ComponentFactoryResolver, |
22 | - EventEmitter, | |
22 | + EventEmitter, Injector, | |
23 | 23 | Input, |
24 | 24 | OnDestroy, |
25 | 25 | OnInit, |
... | ... | @@ -41,6 +41,7 @@ import { EntityAction } from '@home/models/entity/entity-component.models'; |
41 | 41 | import { Subscription } from 'rxjs'; |
42 | 42 | import { MatTab, MatTabGroup } from '@angular/material/tabs'; |
43 | 43 | import { EntityTabsComponent } from '@home/components/entity/entity-tabs.component'; |
44 | +import { deepClone } from '@core/utils'; | |
44 | 45 | |
45 | 46 | @Component({ |
46 | 47 | selector: 'tb-entity-details-panel', |
... | ... | @@ -79,13 +80,16 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit |
79 | 80 | @ViewChildren(MatTab) inclusiveTabs: QueryList<MatTab>; |
80 | 81 | |
81 | 82 | translations: EntityTypeTranslation; |
82 | - resources: EntityTypeResource; | |
83 | + resources: EntityTypeResource<BaseData<HasId>>; | |
83 | 84 | entity: BaseData<HasId>; |
85 | + editingEntity: BaseData<HasId>; | |
84 | 86 | |
85 | 87 | private currentEntityId: HasId; |
86 | 88 | private entityActionSubscription: Subscription; |
87 | 89 | |
88 | 90 | constructor(protected store: Store<AppState>, |
91 | + private injector: Injector, | |
92 | + private cd: ChangeDetectorRef, | |
89 | 93 | private componentFactoryResolver: ComponentFactoryResolver) { |
90 | 94 | super(store); |
91 | 95 | } |
... | ... | @@ -127,15 +131,32 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit |
127 | 131 | const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.entityComponent); |
128 | 132 | const viewContainerRef = this.entityDetailsFormAnchor.viewContainerRef; |
129 | 133 | viewContainerRef.clear(); |
130 | - const componentRef = viewContainerRef.createComponent(componentFactory); | |
134 | + const injector: Injector = Injector.create( | |
135 | + { | |
136 | + providers: [ | |
137 | + { | |
138 | + provide: 'entity', | |
139 | + useValue: this.entity | |
140 | + }, | |
141 | + { | |
142 | + provide: 'entitiesTableConfig', | |
143 | + useValue: this.entitiesTableConfig | |
144 | + } | |
145 | + ], | |
146 | + parent: this.injector | |
147 | + } | |
148 | + ); | |
149 | + const componentRef = viewContainerRef.createComponent(componentFactory, 0, injector); | |
131 | 150 | this.entityComponent = componentRef.instance; |
132 | 151 | this.entityComponent.isEdit = this.isEdit; |
133 | - this.entityComponent.entitiesTableConfig = this.entitiesTableConfig; | |
134 | 152 | this.detailsForm = this.entityComponent.entityNgForm; |
135 | 153 | this.entityActionSubscription = this.entityComponent.entityAction.subscribe((action) => { |
136 | 154 | this.entityAction.emit(action); |
137 | 155 | }); |
138 | 156 | this.buildEntityTabsComponent(); |
157 | + this.entityComponent.entityForm.valueChanges.subscribe(() => { | |
158 | + this.cd.detectChanges(); | |
159 | + }); | |
139 | 160 | } |
140 | 161 | |
141 | 162 | buildEntityTabsComponent() { |
... | ... | @@ -147,9 +168,14 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit |
147 | 168 | this.entityTabsComponent = componentTabsRef.instance; |
148 | 169 | this.entityTabsComponent.isEdit = this.isEdit; |
149 | 170 | this.entityTabsComponent.entitiesTableConfig = this.entitiesTableConfig; |
171 | + this.entityTabsComponent.detailsForm = this.detailsForm; | |
150 | 172 | } |
151 | 173 | } |
152 | 174 | |
175 | + hideDetailsTabs(): boolean { | |
176 | + return this.isEditValue && this.entitiesTableConfig.hideDetailsTabsOnEdit; | |
177 | + } | |
178 | + | |
153 | 179 | reload(): void { |
154 | 180 | this.isEdit = false; |
155 | 181 | this.entitiesTableConfig.loadEntity(this.currentEntityId).subscribe( |
... | ... | @@ -175,13 +201,28 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit |
175 | 201 | this.entityTabsComponent.entity = this.entity; |
176 | 202 | } |
177 | 203 | } else { |
178 | - this.selectedTab = 0; | |
204 | + this.editingEntity = deepClone(this.entity); | |
205 | + this.entityComponent.entity = this.editingEntity; | |
206 | + if (this.entityTabsComponent) { | |
207 | + this.entityTabsComponent.entity = this.editingEntity; | |
208 | + } | |
209 | + if (this.entitiesTableConfig.hideDetailsTabsOnEdit) { | |
210 | + this.selectedTab = 0; | |
211 | + } | |
212 | + } | |
213 | + } | |
214 | + | |
215 | + helpLinkId(): string { | |
216 | + if (this.resources.helpLinkIdForEntity && this.entityComponent.entityForm) { | |
217 | + return this.resources.helpLinkIdForEntity(this.entityComponent.entityForm.getRawValue()); | |
218 | + } else { | |
219 | + return this.resources.helpLinkId; | |
179 | 220 | } |
180 | 221 | } |
181 | 222 | |
182 | 223 | saveEntity() { |
183 | 224 | if (this.detailsForm.valid) { |
184 | - const editingEntity = {...this.entity, ...this.entityComponent.entityFormValue()}; | |
225 | + const editingEntity = {...this.editingEntity, ...this.entityComponent.entityFormValue()}; | |
185 | 226 | this.entitiesTableConfig.saveEntity(editingEntity).subscribe( |
186 | 227 | (entity) => { |
187 | 228 | this.entity = entity; | ... | ... |
... | ... | @@ -31,9 +31,15 @@ import { AuditLogMode } from '@shared/models/audit-log.models'; |
31 | 31 | import { DebugEventType, EventType } from '@shared/models/event.models'; |
32 | 32 | import { AttributeScope, LatestTelemetry } from '@shared/models/telemetry/telemetry.models'; |
33 | 33 | import { NULL_UUID } from '@shared/models/id/has-uuid'; |
34 | +import { NgForm } from '@angular/forms'; | |
35 | +import { PageLink } from '@shared/models/page/page-link'; | |
34 | 36 | |
35 | 37 | @Directive() |
36 | -export abstract class EntityTabsComponent<T extends BaseData<HasId>> extends PageComponent implements OnInit, AfterViewInit { | |
38 | +export abstract class EntityTabsComponent<T extends BaseData<HasId>, | |
39 | + P extends PageLink = PageLink, | |
40 | + L extends BaseData<HasId> = T, | |
41 | + C extends EntityTableConfig<T, P, L> = EntityTableConfig<T, P, L>> | |
42 | + extends PageComponent implements OnInit, AfterViewInit { | |
37 | 43 | |
38 | 44 | attributeScopes = AttributeScope; |
39 | 45 | latestTelemetryTypes = LatestTelemetry; |
... | ... | @@ -54,6 +60,8 @@ export abstract class EntityTabsComponent<T extends BaseData<HasId>> extends Pag |
54 | 60 | |
55 | 61 | entityValue: T; |
56 | 62 | |
63 | + entitiesTableConfigValue: C; | |
64 | + | |
57 | 65 | @ViewChildren(MatTab) entityTabs: QueryList<MatTab>; |
58 | 66 | |
59 | 67 | isEditValue: boolean; |
... | ... | @@ -69,7 +77,7 @@ export abstract class EntityTabsComponent<T extends BaseData<HasId>> extends Pag |
69 | 77 | |
70 | 78 | @Input() |
71 | 79 | set entity(entity: T) { |
72 | - this.entityValue = entity; | |
80 | + this.setEntity(entity); | |
73 | 81 | } |
74 | 82 | |
75 | 83 | get entity(): T { |
... | ... | @@ -77,7 +85,16 @@ export abstract class EntityTabsComponent<T extends BaseData<HasId>> extends Pag |
77 | 85 | } |
78 | 86 | |
79 | 87 | @Input() |
80 | - entitiesTableConfig: EntityTableConfig<T>; | |
88 | + set entitiesTableConfig(entitiesTableConfig: C) { | |
89 | + this.setEntitiesTableConfig(entitiesTableConfig); | |
90 | + } | |
91 | + | |
92 | + get entitiesTableConfig(): C { | |
93 | + return this.entitiesTableConfigValue; | |
94 | + } | |
95 | + | |
96 | + @Input() | |
97 | + detailsForm: NgForm; | |
81 | 98 | |
82 | 99 | private entityTabsSubject = new BehaviorSubject<Array<MatTab>>(null); |
83 | 100 | |
... | ... | @@ -100,4 +117,12 @@ export abstract class EntityTabsComponent<T extends BaseData<HasId>> extends Pag |
100 | 117 | ); |
101 | 118 | } |
102 | 119 | |
120 | + protected setEntity(entity: T) { | |
121 | + this.entityValue = entity; | |
122 | + } | |
123 | + | |
124 | + protected setEntitiesTableConfig(entitiesTableConfig: C) { | |
125 | + this.entitiesTableConfigValue = entitiesTableConfig; | |
126 | + } | |
127 | + | |
103 | 128 | } | ... | ... |
... | ... | @@ -15,18 +15,22 @@ |
15 | 15 | /// |
16 | 16 | |
17 | 17 | import { BaseData, HasId } from '@shared/models/base-data'; |
18 | -import { FormGroup, NgForm } from '@angular/forms'; | |
18 | +import { FormBuilder, FormGroup, NgForm } from '@angular/forms'; | |
19 | 19 | import { PageComponent } from '@shared/components/page.component'; |
20 | 20 | import { EventEmitter, Input, OnInit, Output, ViewChild, Directive } from '@angular/core'; |
21 | 21 | import { Store } from '@ngrx/store'; |
22 | 22 | import { AppState } from '@core/core.state'; |
23 | 23 | import { EntityAction } from '@home/models/entity/entity-component.models'; |
24 | 24 | import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; |
25 | +import { PageLink } from '@shared/models/page/page-link'; | |
25 | 26 | |
26 | 27 | @Directive() |
27 | -export abstract class EntityComponent<T extends BaseData<HasId>> extends PageComponent implements OnInit { | |
28 | +export abstract class EntityComponent<T extends BaseData<HasId>, | |
29 | + P extends PageLink = PageLink, | |
30 | + L extends BaseData<HasId> = T, | |
31 | + C extends EntityTableConfig<T, P, L> = EntityTableConfig<T, P, L>> | |
32 | + extends PageComponent implements OnInit { | |
28 | 33 | |
29 | - entityValue: T; | |
30 | 34 | entityForm: FormGroup; |
31 | 35 | |
32 | 36 | @ViewChild('entityNgForm', {static: true}) entityNgForm: NgForm; |
... | ... | @@ -51,7 +55,8 @@ export abstract class EntityComponent<T extends BaseData<HasId>> extends PageCom |
51 | 55 | set entity(entity: T) { |
52 | 56 | this.entityValue = entity; |
53 | 57 | if (this.entityForm) { |
54 | - this.entityForm.reset(); | |
58 | + this.entityForm.reset(undefined, {emitEvent: false}); | |
59 | + this.entityForm.markAsPristine(); | |
55 | 60 | this.updateForm(entity); |
56 | 61 | } |
57 | 62 | } |
... | ... | @@ -60,18 +65,18 @@ export abstract class EntityComponent<T extends BaseData<HasId>> extends PageCom |
60 | 65 | return this.entityValue; |
61 | 66 | } |
62 | 67 | |
63 | - @Input() | |
64 | - entitiesTableConfig: EntityTableConfig<T>; | |
65 | - | |
66 | 68 | @Output() |
67 | 69 | entityAction = new EventEmitter<EntityAction<T>>(); |
68 | 70 | |
69 | - protected constructor(protected store: Store<AppState>) { | |
71 | + protected constructor(protected store: Store<AppState>, | |
72 | + protected fb: FormBuilder, | |
73 | + protected entityValue: T, | |
74 | + protected entitiesTableConfig: C) { | |
70 | 75 | super(store); |
76 | + this.entityForm = this.buildForm(this.entityValue); | |
71 | 77 | } |
72 | 78 | |
73 | 79 | ngOnInit() { |
74 | - this.entityForm = this.buildForm(this.entityValue); | |
75 | 80 | } |
76 | 81 | |
77 | 82 | onEntityAction($event: Event, action: string) { |
... | ... | @@ -96,7 +101,7 @@ export abstract class EntityComponent<T extends BaseData<HasId>> extends PageCom |
96 | 101 | } |
97 | 102 | |
98 | 103 | entityFormValue() { |
99 | - const formValue = this.entityForm ? {...this.entityForm.value} : {}; | |
104 | + const formValue = this.entityForm ? {...this.entityForm.getRawValue()} : {}; | |
100 | 105 | return this.prepareFormValue(formValue); |
101 | 106 | } |
102 | 107 | ... | ... |
... | ... | @@ -94,7 +94,7 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> { |
94 | 94 | noEntities: 'event.no-events-prompt' |
95 | 95 | }; |
96 | 96 | this.entityResources = { |
97 | - } as EntityTypeResource; | |
97 | + } as EntityTypeResource<Event>; | |
98 | 98 | this.entitiesFetchFunction = pageLink => this.fetchEvents(pageLink); |
99 | 99 | |
100 | 100 | this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; | ... | ... |
... | ... | @@ -36,8 +36,8 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink = |
36 | 36 | public currentEntity: T = null; |
37 | 37 | |
38 | 38 | constructor(private fetchFunction: EntitiesFetchFunction<T, P>, |
39 | - private selectionEnabledFunction: EntityBooleanFunction<T>, | |
40 | - private dataLoadedFunction: () => void) {} | |
39 | + protected selectionEnabledFunction: EntityBooleanFunction<T>, | |
40 | + protected dataLoadedFunction: () => void) {} | |
41 | 41 | |
42 | 42 | connect(collectionViewer: CollectionViewer): Observable<T[] | ReadonlyArray<T>> { |
43 | 43 | return this.entitiesSubject.asObservable(); | ... | ... |
... | ... | @@ -14,21 +14,21 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import {BaseData, HasId} from '@shared/models/base-data'; | |
18 | -import {EntitiesFetchFunction} from '@home/models/datasource/entity-datasource'; | |
19 | -import {Observable, of} from 'rxjs'; | |
20 | -import {emptyPageData} from '@shared/models/page/page-data'; | |
21 | -import {DatePipe} from '@angular/common'; | |
22 | -import {Direction, SortOrder} from '@shared/models/page/sort-order'; | |
23 | -import {EntityType, EntityTypeResource, EntityTypeTranslation} from '@shared/models/entity-type.models'; | |
24 | -import {EntityComponent} from '@home/components/entity/entity.component'; | |
25 | -import {Type} from '@angular/core'; | |
26 | -import {EntityAction} from './entity-component.models'; | |
27 | -import {HasUUID} from '@shared/models/id/has-uuid'; | |
28 | -import {PageLink} from '@shared/models/page/page-link'; | |
29 | -import {EntitiesTableComponent} from '@home/components/entity/entities-table.component'; | |
30 | -import {EntityTableHeaderComponent} from '@home/components/entity/entity-table-header.component'; | |
31 | -import {ActivatedRoute} from '@angular/router'; | |
17 | +import { BaseData, HasId } from '@shared/models/base-data'; | |
18 | +import { EntitiesDataSource, EntitiesFetchFunction } from '@home/models/datasource/entity-datasource'; | |
19 | +import { Observable, of } from 'rxjs'; | |
20 | +import { emptyPageData } from '@shared/models/page/page-data'; | |
21 | +import { DatePipe } from '@angular/common'; | |
22 | +import { Direction, SortOrder } from '@shared/models/page/sort-order'; | |
23 | +import { EntityType, EntityTypeResource, EntityTypeTranslation } from '@shared/models/entity-type.models'; | |
24 | +import { EntityComponent } from '@home/components/entity/entity.component'; | |
25 | +import { Type } from '@angular/core'; | |
26 | +import { EntityAction } from './entity-component.models'; | |
27 | +import { HasUUID } from '@shared/models/id/has-uuid'; | |
28 | +import { PageLink } from '@shared/models/page/page-link'; | |
29 | +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; | |
30 | +import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; | |
31 | +import { ActivatedRoute } from '@angular/router'; | |
32 | 32 | import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; |
33 | 33 | |
34 | 34 | export type EntityBooleanFunction<T extends BaseData<HasId>> = (entity: T) => boolean; |
... | ... | @@ -76,7 +76,9 @@ export class BaseEntityTableColumn<T extends BaseData<HasId>> { |
76 | 76 | public key: string, |
77 | 77 | public title: string, |
78 | 78 | public width: string = '0px', |
79 | - public sortable: boolean = true) { | |
79 | + public sortable: boolean = true, | |
80 | + public ignoreTranslate: boolean = false, | |
81 | + public mobileHide: boolean = false) { | |
80 | 82 | } |
81 | 83 | } |
82 | 84 | |
... | ... | @@ -120,7 +122,7 @@ export class DateEntityTableColumn<T extends BaseData<HasId>> extends EntityTabl |
120 | 122 | |
121 | 123 | export type EntityColumn<T extends BaseData<HasId>> = EntityTableColumn<T> | EntityActionTableColumn<T>; |
122 | 124 | |
123 | -export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = PageLink> { | |
125 | +export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = PageLink, L extends BaseData<HasId> = T> { | |
124 | 126 | |
125 | 127 | constructor() {} |
126 | 128 | |
... | ... | @@ -137,31 +139,35 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P |
137 | 139 | addEnabled = true; |
138 | 140 | entitiesDeleteEnabled = true; |
139 | 141 | detailsPanelEnabled = true; |
142 | + hideDetailsTabsOnEdit = true; | |
140 | 143 | actionsColumnTitle = null; |
141 | 144 | entityTranslations: EntityTypeTranslation; |
142 | - entityResources: EntityTypeResource; | |
143 | - entityComponent: Type<EntityComponent<T>>; | |
144 | - entityTabsComponent: Type<EntityTabsComponent<T>>; | |
145 | + entityResources: EntityTypeResource<T>; | |
146 | + entityComponent: Type<EntityComponent<T, P, L>>; | |
147 | + entityTabsComponent: Type<EntityTabsComponent<T, P, L>>; | |
145 | 148 | addDialogStyle = {}; |
146 | 149 | defaultSortOrder: SortOrder = {property: 'createdTime', direction: Direction.ASC}; |
147 | - columns: Array<EntityColumn<T>> = []; | |
148 | - cellActionDescriptors: Array<CellActionDescriptor<T>> = []; | |
149 | - groupActionDescriptors: Array<GroupActionDescriptor<T>> = []; | |
150 | + columns: Array<EntityColumn<L>> = []; | |
151 | + cellActionDescriptors: Array<CellActionDescriptor<L>> = []; | |
152 | + groupActionDescriptors: Array<GroupActionDescriptor<L>> = []; | |
150 | 153 | headerActionDescriptors: Array<HeaderActionDescriptor> = []; |
151 | 154 | addActionDescriptors: Array<HeaderActionDescriptor> = []; |
152 | - headerComponent: Type<EntityTableHeaderComponent<T>>; | |
155 | + headerComponent: Type<EntityTableHeaderComponent<L>>; | |
153 | 156 | addEntity: CreateEntityOperation<T> = null; |
157 | + dataSource: (dataLoadedFunction: () => void) => EntitiesDataSource<L> = (dataLoadedFunction: () => void) => { | |
158 | + return new EntitiesDataSource(this.entitiesFetchFunction, this.entitySelectionEnabled, dataLoadedFunction); | |
159 | + }; | |
154 | 160 | detailsReadonly: EntityBooleanFunction<T> = () => false; |
155 | - entitySelectionEnabled: EntityBooleanFunction<T> = () => true; | |
156 | - deleteEnabled: EntityBooleanFunction<T> = () => true; | |
157 | - deleteEntityTitle: EntityStringFunction<T> = () => ''; | |
158 | - deleteEntityContent: EntityStringFunction<T> = () => ''; | |
161 | + entitySelectionEnabled: EntityBooleanFunction<L> = () => true; | |
162 | + deleteEnabled: EntityBooleanFunction<T | L> = () => true; | |
163 | + deleteEntityTitle: EntityStringFunction<L> = () => ''; | |
164 | + deleteEntityContent: EntityStringFunction<L> = () => ''; | |
159 | 165 | deleteEntitiesTitle: EntityCountStringFunction = () => ''; |
160 | 166 | deleteEntitiesContent: EntityCountStringFunction = () => ''; |
161 | 167 | loadEntity: EntityByIdOperation<T> = () => of(); |
162 | 168 | saveEntity: EntityTwoWayOperation<T> = (entity) => of(entity); |
163 | 169 | deleteEntity: EntityIdOneWayOperation = () => of(); |
164 | - entitiesFetchFunction: EntitiesFetchFunction<T, P> = () => of(emptyPageData<T>()); | |
170 | + entitiesFetchFunction: EntitiesFetchFunction<L, P> = () => of(emptyPageData<L>()); | |
165 | 171 | onEntityAction: EntityActionFunction<T> = () => false; |
166 | 172 | entityTitle: EntityStringFunction<T> = (entity) => entity?.name; |
167 | 173 | } | ... | ... |
... | ... | @@ -14,7 +14,7 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component } from '@angular/core'; | |
17 | +import { Component, Inject } from '@angular/core'; | |
18 | 18 | import { Store } from '@ngrx/store'; |
19 | 19 | import { AppState } from '@core/core.state'; |
20 | 20 | import { EntityComponent } from '../../components/entity/entity.component'; |
... | ... | @@ -24,6 +24,7 @@ import { NULL_UUID } from '@shared/models/id/has-uuid'; |
24 | 24 | import { ActionNotificationShow } from '@core/notification/notification.actions'; |
25 | 25 | import { TranslateService } from '@ngx-translate/core'; |
26 | 26 | import { AssetInfo } from '@app/shared/models/asset.models'; |
27 | +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; | |
27 | 28 | |
28 | 29 | @Component({ |
29 | 30 | selector: 'tb-asset', |
... | ... | @@ -38,8 +39,10 @@ export class AssetComponent extends EntityComponent<AssetInfo> { |
38 | 39 | |
39 | 40 | constructor(protected store: Store<AppState>, |
40 | 41 | protected translate: TranslateService, |
42 | + @Inject('entity') protected entityValue: AssetInfo, | |
43 | + @Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<AssetInfo>, | |
41 | 44 | public fb: FormBuilder) { |
42 | - super(store); | |
45 | + super(store, fb, entityValue, entitiesTableConfig); | |
43 | 46 | } |
44 | 47 | |
45 | 48 | ngOnInit() { | ... | ... |
... | ... | @@ -124,7 +124,7 @@ const routes: Routes = [ |
124 | 124 | breadcrumb: { |
125 | 125 | labelFunction: dashboardBreadcumbLabelFunction, |
126 | 126 | icon: 'dashboard' |
127 | - } as BreadCrumbConfig, | |
127 | + } as BreadCrumbConfig<DashboardPageComponent>, | |
128 | 128 | auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], |
129 | 129 | title: 'customer.dashboard', |
130 | 130 | widgetEditMode: false | ... | ... |
... | ... | @@ -14,7 +14,7 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component } from '@angular/core'; | |
17 | +import { Component, Inject } from '@angular/core'; | |
18 | 18 | import { Store } from '@ngrx/store'; |
19 | 19 | import { AppState } from '@core/core.state'; |
20 | 20 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
... | ... | @@ -22,6 +22,7 @@ import { Customer } from '@shared/models/customer.model'; |
22 | 22 | import { ActionNotificationShow } from '@app/core/notification/notification.actions'; |
23 | 23 | import { TranslateService } from '@ngx-translate/core'; |
24 | 24 | import { ContactBasedComponent } from '../../components/entity/contact-based.component'; |
25 | +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; | |
25 | 26 | |
26 | 27 | @Component({ |
27 | 28 | selector: 'tb-customer', |
... | ... | @@ -33,8 +34,10 @@ export class CustomerComponent extends ContactBasedComponent<Customer> { |
33 | 34 | |
34 | 35 | constructor(protected store: Store<AppState>, |
35 | 36 | protected translate: TranslateService, |
37 | + @Inject('entity') protected entityValue: Customer, | |
38 | + @Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<Customer>, | |
36 | 39 | protected fb: FormBuilder) { |
37 | - super(store, fb); | |
40 | + super(store, fb, entityValue, entitiesTableConfig); | |
38 | 41 | } |
39 | 42 | |
40 | 43 | hideDelete() { | ... | ... |
... | ... | @@ -14,7 +14,7 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component } from '@angular/core'; | |
17 | +import { Component, Inject } from '@angular/core'; | |
18 | 18 | import { Store } from '@ngrx/store'; |
19 | 19 | import { AppState } from '@core/core.state'; |
20 | 20 | import { EntityComponent } from '../../components/entity/entity.component'; |
... | ... | @@ -28,6 +28,7 @@ import { |
28 | 28 | isPublicDashboard |
29 | 29 | } from '@shared/models/dashboard.models'; |
30 | 30 | import { DashboardService } from '@core/http/dashboard.service'; |
31 | +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; | |
31 | 32 | |
32 | 33 | @Component({ |
33 | 34 | selector: 'tb-dashboard-form', |
... | ... | @@ -45,8 +46,10 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> { |
45 | 46 | constructor(protected store: Store<AppState>, |
46 | 47 | protected translate: TranslateService, |
47 | 48 | private dashboardService: DashboardService, |
49 | + @Inject('entity') protected entityValue: Dashboard, | |
50 | + @Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<Dashboard>, | |
48 | 51 | public fb: FormBuilder) { |
49 | - super(store); | |
52 | + super(store, fb, entityValue, entitiesTableConfig); | |
50 | 53 | } |
51 | 54 | |
52 | 55 | ngOnInit() { |
... | ... | @@ -103,7 +106,9 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> { |
103 | 106 | } |
104 | 107 | |
105 | 108 | private updateFields(entity: Dashboard): void { |
106 | - this.assignedCustomersText = getDashboardAssignedCustomersText(entity); | |
107 | - this.publicLink = this.dashboardService.getPublicDashboardLink(entity); | |
109 | + if (entity) { | |
110 | + this.assignedCustomersText = getDashboardAssignedCustomersText(entity); | |
111 | + this.publicLink = this.dashboardService.getPublicDashboardLink(entity); | |
112 | + } | |
108 | 113 | } |
109 | 114 | } | ... | ... |
... | ... | @@ -43,7 +43,8 @@ export class DashboardResolver implements Resolve<Dashboard> { |
43 | 43 | } |
44 | 44 | } |
45 | 45 | |
46 | -export const dashboardBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => component.dashboard.title); | |
46 | +export const dashboardBreadcumbLabelFunction: BreadCrumbLabelFunction<DashboardPageComponent> | |
47 | + = ((route, translate, component) => component.dashboard.title); | |
47 | 48 | |
48 | 49 | const routes: Routes = [ |
49 | 50 | { |
... | ... | @@ -74,7 +75,7 @@ const routes: Routes = [ |
74 | 75 | breadcrumb: { |
75 | 76 | labelFunction: dashboardBreadcumbLabelFunction, |
76 | 77 | icon: 'dashboard' |
77 | - } as BreadCrumbConfig, | |
78 | + } as BreadCrumbConfig<DashboardPageComponent>, | |
78 | 79 | auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], |
79 | 80 | title: 'dashboard.dashboard', |
80 | 81 | widgetEditMode: false | ... | ... |
... | ... | @@ -14,7 +14,7 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component } from '@angular/core'; | |
17 | +import { Component, Inject } from '@angular/core'; | |
18 | 18 | import { Store } from '@ngrx/store'; |
19 | 19 | import { AppState } from '@core/core.state'; |
20 | 20 | import { EntityComponent } from '../../components/entity/entity.component'; |
... | ... | @@ -26,6 +26,7 @@ import { ActionNotificationShow } from '@core/notification/notification.actions' |
26 | 26 | import { TranslateService } from '@ngx-translate/core'; |
27 | 27 | import { DeviceService } from '@core/http/device.service'; |
28 | 28 | import { ClipboardService } from 'ngx-clipboard'; |
29 | +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; | |
29 | 30 | |
30 | 31 | @Component({ |
31 | 32 | selector: 'tb-device', |
... | ... | @@ -42,8 +43,10 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> { |
42 | 43 | protected translate: TranslateService, |
43 | 44 | private deviceService: DeviceService, |
44 | 45 | private clipboardService: ClipboardService, |
46 | + @Inject('entity') protected entityValue: DeviceInfo, | |
47 | + @Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<DeviceInfo>, | |
45 | 48 | public fb: FormBuilder) { |
46 | - super(store); | |
49 | + super(store, fb, entityValue, entitiesTableConfig); | |
47 | 50 | } |
48 | 51 | |
49 | 52 | ngOnInit() { | ... | ... |
... | ... | @@ -14,7 +14,7 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component } from '@angular/core'; | |
17 | +import { Component, Inject } from '@angular/core'; | |
18 | 18 | import { Store } from '@ngrx/store'; |
19 | 19 | import { AppState } from '@core/core.state'; |
20 | 20 | import { EntityComponent } from '../../components/entity/entity.component'; |
... | ... | @@ -27,6 +27,7 @@ import { EntityViewInfo } from '@app/shared/models/entity-view.models'; |
27 | 27 | import { Observable } from 'rxjs'; |
28 | 28 | import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; |
29 | 29 | import { EntityId } from '@app/shared/models/id/entity-id'; |
30 | +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; | |
30 | 31 | |
31 | 32 | @Component({ |
32 | 33 | selector: 'tb-entity-view', |
... | ... | @@ -50,8 +51,10 @@ export class EntityViewComponent extends EntityComponent<EntityViewInfo> { |
50 | 51 | |
51 | 52 | constructor(protected store: Store<AppState>, |
52 | 53 | protected translate: TranslateService, |
54 | + @Inject('entity') protected entityValue: EntityViewInfo, | |
55 | + @Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<EntityViewInfo>, | |
53 | 56 | public fb: FormBuilder) { |
54 | - super(store); | |
57 | + super(store, fb, entityValue, entitiesTableConfig); | |
55 | 58 | } |
56 | 59 | |
57 | 60 | ngOnInit() { | ... | ... |
... | ... | @@ -126,7 +126,8 @@ export class RuleChainImportGuard implements CanActivate { |
126 | 126 | |
127 | 127 | } |
128 | 128 | |
129 | -export const ruleChainBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => { | |
129 | +export const ruleChainBreadcumbLabelFunction: BreadCrumbLabelFunction<RuleChainPageComponent> | |
130 | + = ((route, translate, component) => { | |
130 | 131 | let label: string = component.ruleChain.name; |
131 | 132 | if (component.ruleChain.root) { |
132 | 133 | label += ` (${translate.instant('rulechain.root')})`; |
... | ... | @@ -134,7 +135,8 @@ export const ruleChainBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, |
134 | 135 | return label; |
135 | 136 | }); |
136 | 137 | |
137 | -export const importRuleChainBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => { | |
138 | +export const importRuleChainBreadcumbLabelFunction: BreadCrumbLabelFunction<RuleChainPageComponent> = | |
139 | + ((route, translate, component) => { | |
138 | 140 | return `${translate.instant('rulechain.import')}: ${component.ruleChain.name}`; |
139 | 141 | }); |
140 | 142 | |
... | ... | @@ -167,7 +169,7 @@ const routes: Routes = [ |
167 | 169 | breadcrumb: { |
168 | 170 | labelFunction: ruleChainBreadcumbLabelFunction, |
169 | 171 | icon: 'settings_ethernet' |
170 | - } as BreadCrumbConfig, | |
172 | + } as BreadCrumbConfig<RuleChainPageComponent>, | |
171 | 173 | auth: [Authority.TENANT_ADMIN], |
172 | 174 | title: 'rulechain.rulechain', |
173 | 175 | import: false |
... | ... | @@ -187,7 +189,7 @@ const routes: Routes = [ |
187 | 189 | breadcrumb: { |
188 | 190 | labelFunction: importRuleChainBreadcumbLabelFunction, |
189 | 191 | icon: 'settings_ethernet' |
190 | - } as BreadCrumbConfig, | |
192 | + } as BreadCrumbConfig<RuleChainPageComponent>, | |
191 | 193 | auth: [Authority.TENANT_ADMIN], |
192 | 194 | title: 'rulechain.rulechain', |
193 | 195 | import: true | ... | ... |
... | ... | @@ -14,7 +14,7 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component } from '@angular/core'; | |
17 | +import { Component, Inject } from '@angular/core'; | |
18 | 18 | import { Store } from '@ngrx/store'; |
19 | 19 | import { AppState } from '@core/core.state'; |
20 | 20 | import { EntityComponent } from '../../components/entity/entity.component'; |
... | ... | @@ -22,6 +22,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
22 | 22 | import { ActionNotificationShow } from '@core/notification/notification.actions'; |
23 | 23 | import { TranslateService } from '@ngx-translate/core'; |
24 | 24 | import { RuleChain } from '@shared/models/rule-chain.models'; |
25 | +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; | |
25 | 26 | |
26 | 27 | @Component({ |
27 | 28 | selector: 'tb-rulechain', |
... | ... | @@ -32,8 +33,10 @@ export class RuleChainComponent extends EntityComponent<RuleChain> { |
32 | 33 | |
33 | 34 | constructor(protected store: Store<AppState>, |
34 | 35 | protected translate: TranslateService, |
36 | + @Inject('entity') protected entityValue: RuleChain, | |
37 | + @Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<RuleChain>, | |
35 | 38 | public fb: FormBuilder) { |
36 | - super(store); | |
39 | + super(store, fb, entityValue, entitiesTableConfig); | |
37 | 40 | } |
38 | 41 | |
39 | 42 | hideDelete() { | ... | ... |
... | ... | @@ -14,7 +14,7 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component } from '@angular/core'; | |
17 | +import { Component, Inject } from '@angular/core'; | |
18 | 18 | import { Store } from '@ngrx/store'; |
19 | 19 | import { AppState } from '@core/core.state'; |
20 | 20 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
... | ... | @@ -23,6 +23,7 @@ import {Tenant} from '@app/shared/models/tenant.model'; |
23 | 23 | import {ActionNotificationShow} from '@app/core/notification/notification.actions'; |
24 | 24 | import {TranslateService} from '@ngx-translate/core'; |
25 | 25 | import {ContactBasedComponent} from '../../components/entity/contact-based.component'; |
26 | +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; | |
26 | 27 | |
27 | 28 | @Component({ |
28 | 29 | selector: 'tb-tenant', |
... | ... | @@ -32,8 +33,10 @@ export class TenantComponent extends ContactBasedComponent<Tenant> { |
32 | 33 | |
33 | 34 | constructor(protected store: Store<AppState>, |
34 | 35 | protected translate: TranslateService, |
36 | + @Inject('entity') protected entityValue: Tenant, | |
37 | + @Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<Tenant>, | |
35 | 38 | protected fb: FormBuilder) { |
36 | - super(store, fb); | |
39 | + super(store, fb, entityValue, entitiesTableConfig); | |
37 | 40 | } |
38 | 41 | |
39 | 42 | hideDelete() { | ... | ... |
... | ... | @@ -14,7 +14,7 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component } from '@angular/core'; | |
17 | +import { Component, Inject } from '@angular/core'; | |
18 | 18 | import { select, Store } from '@ngrx/store'; |
19 | 19 | import { AppState } from '@core/core.state'; |
20 | 20 | import { EntityComponent } from '../../components/entity/entity.component'; |
... | ... | @@ -24,6 +24,7 @@ import { selectAuth } from '@core/auth/auth.selectors'; |
24 | 24 | import { map } from 'rxjs/operators'; |
25 | 25 | import { Authority } from '@shared/models/authority.enum'; |
26 | 26 | import { isUndefined } from '@core/utils'; |
27 | +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; | |
27 | 28 | |
28 | 29 | @Component({ |
29 | 30 | selector: 'tb-user', |
... | ... | @@ -40,8 +41,10 @@ export class UserComponent extends EntityComponent<User> { |
40 | 41 | ); |
41 | 42 | |
42 | 43 | constructor(protected store: Store<AppState>, |
44 | + @Inject('entity') protected entityValue: User, | |
45 | + @Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<User>, | |
43 | 46 | public fb: FormBuilder) { |
44 | - super(store); | |
47 | + super(store, fb, entityValue, entitiesTableConfig); | |
45 | 48 | } |
46 | 49 | |
47 | 50 | hideDelete() { | ... | ... |
... | ... | @@ -114,10 +114,11 @@ export class WidgetEditorAddDataResolver implements Resolve<WidgetEditorData> { |
114 | 114 | } |
115 | 115 | } |
116 | 116 | |
117 | -export const widgetTypesBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate) => route.data.widgetsBundle.title); | |
117 | +export const widgetTypesBreadcumbLabelFunction: BreadCrumbLabelFunction<any> = ((route, translate) => | |
118 | + route.data.widgetsBundle.title); | |
118 | 119 | |
119 | -export const widgetEditorBreadcumbLabelFunction: BreadCrumbLabelFunction = | |
120 | - ((route, translate, component) => component ? (component as WidgetEditorComponent).widget.widgetName : ''); | |
120 | +export const widgetEditorBreadcumbLabelFunction: BreadCrumbLabelFunction<WidgetEditorComponent> = | |
121 | + ((route, translate, component) => component ? component.widget.widgetName : ''); | |
121 | 122 | |
122 | 123 | export const routes: Routes = [ |
123 | 124 | { |
... | ... | @@ -146,7 +147,7 @@ export const routes: Routes = [ |
146 | 147 | breadcrumb: { |
147 | 148 | labelFunction: widgetTypesBreadcumbLabelFunction, |
148 | 149 | icon: 'now_widgets' |
149 | - } as BreadCrumbConfig | |
150 | + } as BreadCrumbConfig<any> | |
150 | 151 | }, |
151 | 152 | resolve: { |
152 | 153 | widgetsBundle: WidgetsBundleResolver |
... | ... | @@ -173,7 +174,7 @@ export const routes: Routes = [ |
173 | 174 | breadcrumb: { |
174 | 175 | labelFunction: widgetEditorBreadcumbLabelFunction, |
175 | 176 | icon: 'insert_chart' |
176 | - } as BreadCrumbConfig | |
177 | + } as BreadCrumbConfig<WidgetEditorComponent> | |
177 | 178 | }, |
178 | 179 | resolve: { |
179 | 180 | widgetEditorData: WidgetEditorDataResolver |
... | ... | @@ -189,7 +190,7 @@ export const routes: Routes = [ |
189 | 190 | breadcrumb: { |
190 | 191 | labelFunction: widgetEditorBreadcumbLabelFunction, |
191 | 192 | icon: 'insert_chart' |
192 | - } as BreadCrumbConfig | |
193 | + } as BreadCrumbConfig<WidgetEditorComponent> | |
193 | 194 | }, |
194 | 195 | resolve: { |
195 | 196 | widgetEditorData: WidgetEditorAddDataResolver | ... | ... |
... | ... | @@ -14,12 +14,13 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import {Component} from '@angular/core'; | |
17 | +import { Component, Inject } from '@angular/core'; | |
18 | 18 | import {Store} from '@ngrx/store'; |
19 | 19 | import {AppState} from '@core/core.state'; |
20 | 20 | import {EntityComponent} from '../../components/entity/entity.component'; |
21 | 21 | import {FormBuilder, FormGroup, Validators} from '@angular/forms'; |
22 | 22 | import {WidgetsBundle} from '@shared/models/widgets-bundle.model'; |
23 | +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; | |
23 | 24 | |
24 | 25 | @Component({ |
25 | 26 | selector: 'tb-widgets-bundle', |
... | ... | @@ -29,8 +30,10 @@ import {WidgetsBundle} from '@shared/models/widgets-bundle.model'; |
29 | 30 | export class WidgetsBundleComponent extends EntityComponent<WidgetsBundle> { |
30 | 31 | |
31 | 32 | constructor(protected store: Store<AppState>, |
33 | + @Inject('entity') protected entityValue: WidgetsBundle, | |
34 | + @Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<WidgetsBundle>, | |
32 | 35 | public fb: FormBuilder) { |
33 | - super(store); | |
36 | + super(store, fb, entityValue, entitiesTableConfig); | |
34 | 37 | } |
35 | 38 | |
36 | 39 | hideDelete() { | ... | ... |
... | ... | @@ -65,7 +65,7 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { |
65 | 65 | buildBreadCrumbs(route: ActivatedRouteSnapshot, breadcrumbs: Array<BreadCrumb> = []): Array<BreadCrumb> { |
66 | 66 | let newBreadcrumbs = breadcrumbs; |
67 | 67 | if (route.routeConfig && route.routeConfig.data) { |
68 | - const breadcrumbConfig = route.routeConfig.data.breadcrumb as BreadCrumbConfig; | |
68 | + const breadcrumbConfig = route.routeConfig.data.breadcrumb as BreadCrumbConfig<any>; | |
69 | 69 | if (breadcrumbConfig && !breadcrumbConfig.skip) { |
70 | 70 | let label; |
71 | 71 | let labelFunction; | ... | ... |
... | ... | @@ -27,10 +27,10 @@ export interface BreadCrumb { |
27 | 27 | queryParams: Params; |
28 | 28 | } |
29 | 29 | |
30 | -export type BreadCrumbLabelFunction = (route: ActivatedRouteSnapshot, translate: TranslateService, component: any) => string; | |
30 | +export type BreadCrumbLabelFunction<C> = (route: ActivatedRouteSnapshot, translate: TranslateService, component: C) => string; | |
31 | 31 | |
32 | -export interface BreadCrumbConfig { | |
33 | - labelFunction: BreadCrumbLabelFunction; | |
32 | +export interface BreadCrumbConfig<C> { | |
33 | + labelFunction: BreadCrumbLabelFunction<C>; | |
34 | 34 | label: string; |
35 | 35 | icon: string; |
36 | 36 | skip: boolean; | ... | ... |
... | ... | @@ -15,6 +15,7 @@ |
15 | 15 | /// |
16 | 16 | |
17 | 17 | import {TenantId} from './id/tenant-id'; |
18 | +import { BaseData, HasId } from '@shared/models/base-data'; | |
18 | 19 | |
19 | 20 | /// |
20 | 21 | /// Copyright © 2016-2019 The Thingsboard Authors |
... | ... | @@ -63,8 +64,9 @@ export interface EntityTypeTranslation { |
63 | 64 | search?: string; |
64 | 65 | } |
65 | 66 | |
66 | -export interface EntityTypeResource { | |
67 | +export interface EntityTypeResource<T> { | |
67 | 68 | helpLinkId: string; |
69 | + helpLinkIdForEntity?(entity: T): string; | |
68 | 70 | } |
69 | 71 | |
70 | 72 | export const entityTypeTranslations = new Map<EntityType | AliasEntityType, EntityTypeTranslation>( |
... | ... | @@ -223,7 +225,7 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti |
223 | 225 | ] |
224 | 226 | ); |
225 | 227 | |
226 | -export const entityTypeResources = new Map<EntityType, EntityTypeResource>( | |
228 | +export const entityTypeResources = new Map<EntityType, EntityTypeResource<BaseData<HasId>>>( | |
227 | 229 | [ |
228 | 230 | [ |
229 | 231 | EntityType.TENANT, | ... | ... |