Commit d520415d5bb4e34e879226f36bc05d73d41bebbf
1 parent
eaa2c578
UI: Improvement load and update time into time series table
Showing
2 changed files
with
123 additions
and
107 deletions
... | ... | @@ -39,73 +39,75 @@ |
39 | 39 | </mat-toolbar> |
40 | 40 | <mat-tab-group [ngClass]="{'tb-headless': sources.length === 1}" fxFlex |
41 | 41 | [(selectedIndex)]="sourceIndex" (selectedIndexChange)="onSourceIndexChanged()"> |
42 | - <mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex" label="{{ source.datasource.name }}"> | |
43 | - <div fxFlex class="table-container"> | |
44 | - <table mat-table [dataSource]="source.timeseriesDatasource" [trackBy]="trackByRowTimestamp" | |
45 | - matSort [matSortActive]="source.pageLink.sortOrder.property" [matSortDirection]="source.pageLink.sortDirection()" matSortDisableClear> | |
46 | - <ng-container *ngIf="showTimestamp" [matColumnDef]="'0'"> | |
47 | - <mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</mat-header-cell> | |
48 | - <mat-cell *matCellDef="let row;" | |
49 | - [innerHTML]="cellContent(source, 0, row, row[0])" | |
50 | - [ngStyle]="cellStyle(source, 0, row[0])"> | |
51 | - </mat-cell> | |
52 | - </ng-container> | |
53 | - <ng-container [matColumnDef]="h.index + ''" *ngFor="let h of source.header; trackBy: trackByColumnIndex;"> | |
54 | - <mat-header-cell *matHeaderCellDef mat-sort-header> {{ h.dataKey.label }} </mat-header-cell> | |
55 | - <mat-cell *matCellDef="let row;" | |
56 | - [innerHTML]="cellContent(source, h.index, row, row[h.index])" | |
57 | - [ngStyle]="cellStyle(source, h.index, row[h.index])"> | |
58 | - </mat-cell> | |
59 | - </ng-container> | |
60 | - <ng-container matColumnDef="actions" stickyEnd> | |
61 | - <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px', | |
62 | - maxWidth: (actionCellDescriptors.length * 40) + 'px', | |
63 | - width: (actionCellDescriptors.length * 40) + 'px' }"> | |
64 | - </mat-header-cell> | |
65 | - <mat-cell *matCellDef="let row" [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px', | |
66 | - maxWidth: (actionCellDescriptors.length * 40) + 'px', | |
67 | - width: (actionCellDescriptors.length * 40) + 'px' }"> | |
68 | - <div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end"> | |
69 | - <button mat-button mat-icon-button [disabled]="isLoading$ | async" | |
70 | - *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId" | |
71 | - matTooltip="{{ actionDescriptor.displayName }}" | |
72 | - matTooltipPosition="above" | |
73 | - (click)="onActionButtonClick($event, row, actionDescriptor)"> | |
74 | - <mat-icon>{{actionDescriptor.icon}}</mat-icon> | |
75 | - </button> | |
76 | - </div> | |
77 | - <div fxHide fxShow.lt-lg *ngIf="actionCellDescriptors.length"> | |
78 | - <button mat-button mat-icon-button | |
79 | - (click)="$event.stopPropagation(); ctx.detectChanges();" | |
80 | - [matMenuTriggerFor]="cellActionsMenu"> | |
81 | - <mat-icon class="material-icons">more_vert</mat-icon> | |
82 | - </button> | |
83 | - <mat-menu #cellActionsMenu="matMenu" xPosition="before"> | |
84 | - <button mat-menu-item *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId" | |
85 | - [disabled]="isLoading$ | async" | |
42 | + <mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex; let index = index;" label="{{ source.datasource.name }}"> | |
43 | + <ng-template [ngIf]="isActiveTab(index)"> | |
44 | + <div fxFlex class="table-container"> | |
45 | + <table mat-table [dataSource]="source.timeseriesDatasource" [trackBy]="trackByRowTimestamp" | |
46 | + matSort [matSortActive]="source.pageLink.sortOrder.property" [matSortDirection]="source.pageLink.sortDirection()" matSortDisableClear> | |
47 | + <ng-container *ngIf="showTimestamp" [matColumnDef]="'0'"> | |
48 | + <mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</mat-header-cell> | |
49 | + <mat-cell *matCellDef="let row;" | |
50 | + [innerHTML]="cellContent(source, 0, row, row[0])" | |
51 | + [ngStyle]="cellStyle(source, 0, row[0])"> | |
52 | + </mat-cell> | |
53 | + </ng-container> | |
54 | + <ng-container [matColumnDef]="h.index + ''" *ngFor="let h of source.header; trackBy: trackByColumnIndex;"> | |
55 | + <mat-header-cell *matHeaderCellDef mat-sort-header> {{ h.dataKey.label }} </mat-header-cell> | |
56 | + <mat-cell *matCellDef="let row;" | |
57 | + [innerHTML]="cellContent(source, h.index, row, row[h.index])" | |
58 | + [ngStyle]="cellStyle(source, h.index, row[h.index])"> | |
59 | + </mat-cell> | |
60 | + </ng-container> | |
61 | + <ng-container matColumnDef="actions" stickyEnd> | |
62 | + <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px', | |
63 | + maxWidth: (actionCellDescriptors.length * 40) + 'px', | |
64 | + width: (actionCellDescriptors.length * 40) + 'px' }"> | |
65 | + </mat-header-cell> | |
66 | + <mat-cell *matCellDef="let row" [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px', | |
67 | + maxWidth: (actionCellDescriptors.length * 40) + 'px', | |
68 | + width: (actionCellDescriptors.length * 40) + 'px' }"> | |
69 | + <div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end"> | |
70 | + <button mat-button mat-icon-button [disabled]="isLoading$ | async" | |
71 | + *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId" | |
72 | + matTooltip="{{ actionDescriptor.displayName }}" | |
73 | + matTooltipPosition="above" | |
86 | 74 | (click)="onActionButtonClick($event, row, actionDescriptor)"> |
87 | 75 | <mat-icon>{{actionDescriptor.icon}}</mat-icon> |
88 | - <span>{{ actionDescriptor.displayName }}</span> | |
89 | 76 | </button> |
90 | - </mat-menu> | |
91 | - </div> | |
92 | - </mat-cell> | |
93 | - </ng-container> | |
94 | - <mat-header-row *matHeaderRowDef="source.displayedColumns; sticky: true"></mat-header-row> | |
95 | - <mat-row *matRowDef="let row; columns: source.displayedColumns;" | |
96 | - (click)="onRowClick($event, row)"></mat-row> | |
97 | - </table> | |
98 | - <span [fxShow]="source.timeseriesDatasource.isEmpty() | async" | |
99 | - fxLayoutAlign="center center" | |
100 | - class="no-data-found" translate>widget.no-data-found</span> | |
101 | - </div> | |
102 | - <mat-divider *ngIf="displayPagination"></mat-divider> | |
103 | - <mat-paginator *ngIf="displayPagination" | |
104 | - [length]="source.timeseriesDatasource.total() | async" | |
105 | - [pageIndex]="source.pageLink.page" | |
106 | - [pageSize]="source.pageLink.pageSize" | |
107 | - [pageSizeOptions]="pageSizeOptions" | |
108 | - showFirstLastButtons></mat-paginator> | |
77 | + </div> | |
78 | + <div fxHide fxShow.lt-lg *ngIf="actionCellDescriptors.length"> | |
79 | + <button mat-button mat-icon-button | |
80 | + (click)="$event.stopPropagation(); ctx.detectChanges();" | |
81 | + [matMenuTriggerFor]="cellActionsMenu"> | |
82 | + <mat-icon class="material-icons">more_vert</mat-icon> | |
83 | + </button> | |
84 | + <mat-menu #cellActionsMenu="matMenu" xPosition="before"> | |
85 | + <button mat-menu-item *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId" | |
86 | + [disabled]="isLoading$ | async" | |
87 | + (click)="onActionButtonClick($event, row, actionDescriptor)"> | |
88 | + <mat-icon>{{actionDescriptor.icon}}</mat-icon> | |
89 | + <span>{{ actionDescriptor.displayName }}</span> | |
90 | + </button> | |
91 | + </mat-menu> | |
92 | + </div> | |
93 | + </mat-cell> | |
94 | + </ng-container> | |
95 | + <mat-header-row *matHeaderRowDef="source.displayedColumns; sticky: true"></mat-header-row> | |
96 | + <mat-row *matRowDef="let row; columns: source.displayedColumns;" | |
97 | + (click)="onRowClick($event, row)"></mat-row> | |
98 | + </table> | |
99 | + <span [fxShow]="source.timeseriesDatasource.isEmpty() | async" | |
100 | + fxLayoutAlign="center center" | |
101 | + class="no-data-found" translate>widget.no-data-found</span> | |
102 | + </div> | |
103 | + <mat-divider *ngIf="displayPagination"></mat-divider> | |
104 | + <mat-paginator *ngIf="displayPagination" | |
105 | + [length]="source.timeseriesDatasource.total() | async" | |
106 | + [pageIndex]="source.pageLink.page" | |
107 | + [pageSize]="source.pageLink.pageSize" | |
108 | + [pageSizeOptions]="pageSizeOptions" | |
109 | + showFirstLastButtons></mat-paginator> | |
110 | + </ng-template> | |
109 | 111 | </mat-tab> |
110 | 112 | </mat-tab-group> |
111 | 113 | </div> | ... | ... |
... | ... | @@ -40,12 +40,12 @@ import { |
40 | 40 | } from '@shared/models/widget.models'; |
41 | 41 | import { UtilsService } from '@core/services/utils.service'; |
42 | 42 | import { TranslateService } from '@ngx-translate/core'; |
43 | -import {hashCode, isDefined, isDefinedAndNotNull, isNumber} from '@core/utils'; | |
43 | +import { hashCode, isDefined, isEqual, isNumber } from '@core/utils'; | |
44 | 44 | import cssjs from '@core/css/css'; |
45 | 45 | import { PageLink } from '@shared/models/page/page-link'; |
46 | 46 | import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; |
47 | 47 | import { CollectionViewer, DataSource } from '@angular/cdk/collections'; |
48 | -import { BehaviorSubject, fromEvent, merge, Observable, of } from 'rxjs'; | |
48 | +import { BehaviorSubject, fromEvent, merge, Observable, of, Subscription } from 'rxjs'; | |
49 | 49 | import { emptyPageData, PageData } from '@shared/models/page/page-data'; |
50 | 50 | import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators'; |
51 | 51 | import { MatPaginator } from '@angular/material/paginator'; |
... | ... | @@ -129,6 +129,8 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI |
129 | 129 | public showTimestamp = true; |
130 | 130 | private dateFormatFilter: string; |
131 | 131 | |
132 | + private subscriptions: Subscription[] = []; | |
133 | + | |
132 | 134 | private searchAction: WidgetAction = { |
133 | 135 | name: 'action.search', |
134 | 136 | show: true, |
... | ... | @@ -166,40 +168,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI |
166 | 168 | debounceTime(150), |
167 | 169 | distinctUntilChanged(), |
168 | 170 | tap(() => { |
169 | - if (this.displayPagination) { | |
170 | - this.paginators.forEach((paginator) => { | |
171 | - paginator.pageIndex = 0; | |
172 | - }); | |
173 | - } | |
174 | 171 | this.sources.forEach((source) => { |
175 | 172 | source.pageLink.textSearch = this.textSearch; |
173 | + if (this.displayPagination) { | |
174 | + source.pageLink.page = 0; | |
175 | + } | |
176 | 176 | }); |
177 | - this.updateAllData(); | |
177 | + this.loadCurrentSourceRow(); | |
178 | + this.ctx.detectChanges(); | |
178 | 179 | }) |
179 | 180 | ) |
180 | 181 | .subscribe(); |
181 | 182 | |
182 | - if (this.displayPagination) { | |
183 | - this.sorts.forEach((sort, index) => { | |
184 | - sort.sortChange.subscribe(() => this.paginators.toArray()[index].pageIndex = 0); | |
185 | - }); | |
186 | - } | |
187 | - this.sorts.forEach((sort, index) => { | |
188 | - const paginator = this.displayPagination ? this.paginators.toArray()[index] : null; | |
189 | - sort.sortChange.subscribe(() => this.paginators.toArray()[index].pageIndex = 0); | |
190 | - ((this.displayPagination ? merge(sort.sortChange, paginator.page) : sort.sortChange) as Observable<any>) | |
191 | - .pipe( | |
192 | - tap(() => this.updateData(sort, paginator, index)) | |
193 | - ) | |
194 | - .subscribe(); | |
183 | + this.sorts.changes.subscribe(() => { | |
184 | + this.initSubscriptionsToSortAndPaginator(); | |
195 | 185 | }); |
196 | - this.updateAllData(); | |
186 | + | |
187 | + this.initSubscriptionsToSortAndPaginator(); | |
197 | 188 | } |
198 | 189 | |
199 | 190 | public onDataUpdated() { |
200 | - this.sources.forEach((source) => { | |
201 | - source.timeseriesDatasource.dataUpdated(this.data); | |
202 | - }); | |
191 | + this.updateCurrentSourceData(); | |
203 | 192 | } |
204 | 193 | |
205 | 194 | private initialize() { |
... | ... | @@ -305,7 +294,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI |
305 | 294 | this.ctx.activeEntityInfo = activeEntityInfo; |
306 | 295 | } |
307 | 296 | |
297 | + private initSubscriptionsToSortAndPaginator() { | |
298 | + this.subscriptions.forEach(subscription => subscription.unsubscribe()); | |
299 | + this.sorts.forEach((sort, index) => { | |
300 | + let paginator = null; | |
301 | + const observables = [sort.sortChange]; | |
302 | + if (this.displayPagination) { | |
303 | + paginator = this.paginators.toArray()[index]; | |
304 | + this.subscriptions.push( | |
305 | + sort.sortChange.subscribe(() => paginator.pageIndex = 0) | |
306 | + ); | |
307 | + observables.push(paginator.page); | |
308 | + } | |
309 | + this.updateData(sort, paginator); | |
310 | + this.subscriptions.push(merge(...observables).pipe( | |
311 | + tap(() => this.updateData(sort, paginator)) | |
312 | + ).subscribe()); | |
313 | + }); | |
314 | + } | |
315 | + | |
308 | 316 | onSourceIndexChanged() { |
317 | + this.updateCurrentSourceData(); | |
309 | 318 | this.updateActiveEntityInfo(); |
310 | 319 | } |
311 | 320 | |
... | ... | @@ -326,30 +335,19 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI |
326 | 335 | exitFilterMode() { |
327 | 336 | this.textSearchMode = false; |
328 | 337 | this.textSearch = null; |
329 | - this.sources.forEach((source, index) => { | |
338 | + this.sources.forEach((source) => { | |
330 | 339 | source.pageLink.textSearch = this.textSearch; |
331 | - const sort = this.sorts.toArray()[index]; | |
332 | - let paginator = null; | |
333 | 340 | if (this.displayPagination) { |
334 | - paginator = this.paginators.toArray()[index]; | |
335 | - paginator.pageIndex = 0; | |
341 | + source.pageLink.page = 0; | |
336 | 342 | } |
337 | - this.updateData(sort, paginator, index); | |
338 | 343 | }); |
344 | + this.loadCurrentSourceRow(); | |
339 | 345 | this.ctx.hideTitlePanel = false; |
340 | 346 | this.ctx.detectChanges(true); |
341 | 347 | } |
342 | 348 | |
343 | - private updateAllData() { | |
344 | - this.sources.forEach((source, index) => { | |
345 | - const sort = this.sorts.toArray()[index]; | |
346 | - const paginator = this.displayPagination ? this.paginators.toArray()[index] : null; | |
347 | - this.updateData(sort, paginator, index); | |
348 | - }); | |
349 | - } | |
350 | - | |
351 | - private updateData(sort: MatSort, paginator: MatPaginator, index: number) { | |
352 | - const source = this.sources[index]; | |
349 | + private updateData(sort: MatSort, paginator: MatPaginator) { | |
350 | + const source = this.sources[this.sourceIndex]; | |
353 | 351 | if (this.displayPagination) { |
354 | 352 | source.pageLink.page = paginator.pageIndex; |
355 | 353 | source.pageLink.pageSize = paginator.pageSize; |
... | ... | @@ -418,7 +416,6 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI |
418 | 416 | |
419 | 417 | if (!isDefined(content)) { |
420 | 418 | return ''; |
421 | - | |
422 | 419 | } else { |
423 | 420 | switch (typeof content) { |
424 | 421 | case 'string': |
... | ... | @@ -462,6 +459,18 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI |
462 | 459 | } |
463 | 460 | this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, row, entityLabel); |
464 | 461 | } |
462 | + | |
463 | + public isActiveTab(index: number): boolean { | |
464 | + return index === this.sourceIndex; | |
465 | + } | |
466 | + | |
467 | + private updateCurrentSourceData() { | |
468 | + this.sources[this.sourceIndex].timeseriesDatasource.dataUpdated(this.data); | |
469 | + } | |
470 | + | |
471 | + private loadCurrentSourceRow() { | |
472 | + this.sources[this.sourceIndex].timeseriesDatasource.loadRows(); | |
473 | + } | |
465 | 474 | } |
466 | 475 | |
467 | 476 | class TimeseriesDatasource implements DataSource<TimeseriesRow> { |
... | ... | @@ -482,6 +491,10 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> { |
482 | 491 | } |
483 | 492 | |
484 | 493 | connect(collectionViewer: CollectionViewer): Observable<TimeseriesRow[] | ReadonlyArray<TimeseriesRow>> { |
494 | + if (this.rowsSubject.isStopped) { | |
495 | + this.rowsSubject.isStopped = false; | |
496 | + this.pageDataSubject.isStopped = false; | |
497 | + } | |
485 | 498 | return this.rowsSubject.asObservable(); |
486 | 499 | } |
487 | 500 | |
... | ... | @@ -565,7 +578,8 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> { |
565 | 578 | |
566 | 579 | private fetchRows(pageLink: PageLink): Observable<PageData<TimeseriesRow>> { |
567 | 580 | return this.allRows$.pipe( |
568 | - map((data) => pageLink.filterData(data)) | |
581 | + map((data) => pageLink.filterData(data)), | |
582 | + distinctUntilChanged((prev, curr) => isEqual(prev, curr)) | |
569 | 583 | ); |
570 | 584 | } |
571 | 585 | } | ... | ... |