UI: Improvement load and update time into time series table

This commit is contained in:
Vladyslav_Prykhodko 2021-02-12 17:08:43 +02:00
parent eaa2c5785f
commit d520415d5b
2 changed files with 123 additions and 107 deletions

View File

@ -39,73 +39,75 @@
</mat-toolbar> </mat-toolbar>
<mat-tab-group [ngClass]="{'tb-headless': sources.length === 1}" fxFlex <mat-tab-group [ngClass]="{'tb-headless': sources.length === 1}" fxFlex
[(selectedIndex)]="sourceIndex" (selectedIndexChange)="onSourceIndexChanged()"> [(selectedIndex)]="sourceIndex" (selectedIndexChange)="onSourceIndexChanged()">
<mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex" label="{{ source.datasource.name }}"> <mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex; let index = index;" label="{{ source.datasource.name }}">
<div fxFlex class="table-container"> <ng-template [ngIf]="isActiveTab(index)">
<table mat-table [dataSource]="source.timeseriesDatasource" [trackBy]="trackByRowTimestamp" <div fxFlex class="table-container">
matSort [matSortActive]="source.pageLink.sortOrder.property" [matSortDirection]="source.pageLink.sortDirection()" matSortDisableClear> <table mat-table [dataSource]="source.timeseriesDatasource" [trackBy]="trackByRowTimestamp"
<ng-container *ngIf="showTimestamp" [matColumnDef]="'0'"> matSort [matSortActive]="source.pageLink.sortOrder.property" [matSortDirection]="source.pageLink.sortDirection()" matSortDisableClear>
<mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</mat-header-cell> <ng-container *ngIf="showTimestamp" [matColumnDef]="'0'">
<mat-cell *matCellDef="let row;" <mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</mat-header-cell>
[innerHTML]="cellContent(source, 0, row, row[0])" <mat-cell *matCellDef="let row;"
[ngStyle]="cellStyle(source, 0, row[0])"> [innerHTML]="cellContent(source, 0, row, row[0])"
</mat-cell> [ngStyle]="cellStyle(source, 0, row[0])">
</ng-container> </mat-cell>
<ng-container [matColumnDef]="h.index + ''" *ngFor="let h of source.header; trackBy: trackByColumnIndex;"> </ng-container>
<mat-header-cell *matHeaderCellDef mat-sort-header> {{ h.dataKey.label }} </mat-header-cell> <ng-container [matColumnDef]="h.index + ''" *ngFor="let h of source.header; trackBy: trackByColumnIndex;">
<mat-cell *matCellDef="let row;" <mat-header-cell *matHeaderCellDef mat-sort-header> {{ h.dataKey.label }} </mat-header-cell>
[innerHTML]="cellContent(source, h.index, row, row[h.index])" <mat-cell *matCellDef="let row;"
[ngStyle]="cellStyle(source, h.index, row[h.index])"> [innerHTML]="cellContent(source, h.index, row, row[h.index])"
</mat-cell> [ngStyle]="cellStyle(source, h.index, row[h.index])">
</ng-container> </mat-cell>
<ng-container matColumnDef="actions" stickyEnd> </ng-container>
<mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px', <ng-container matColumnDef="actions" stickyEnd>
maxWidth: (actionCellDescriptors.length * 40) + 'px', <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px',
width: (actionCellDescriptors.length * 40) + 'px' }"> maxWidth: (actionCellDescriptors.length * 40) + 'px',
</mat-header-cell> width: (actionCellDescriptors.length * 40) + 'px' }">
<mat-cell *matCellDef="let row" [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px', </mat-header-cell>
maxWidth: (actionCellDescriptors.length * 40) + 'px', <mat-cell *matCellDef="let row" [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px',
width: (actionCellDescriptors.length * 40) + 'px' }"> maxWidth: (actionCellDescriptors.length * 40) + 'px',
<div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end"> width: (actionCellDescriptors.length * 40) + 'px' }">
<button mat-button mat-icon-button [disabled]="isLoading$ | async" <div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">
*ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId" <button mat-button mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ actionDescriptor.displayName }}" *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId"
matTooltipPosition="above" matTooltip="{{ actionDescriptor.displayName }}"
(click)="onActionButtonClick($event, row, actionDescriptor)"> matTooltipPosition="above"
<mat-icon>{{actionDescriptor.icon}}</mat-icon>
</button>
</div>
<div fxHide fxShow.lt-lg *ngIf="actionCellDescriptors.length">
<button mat-button mat-icon-button
(click)="$event.stopPropagation(); ctx.detectChanges();"
[matMenuTriggerFor]="cellActionsMenu">
<mat-icon class="material-icons">more_vert</mat-icon>
</button>
<mat-menu #cellActionsMenu="matMenu" xPosition="before">
<button mat-menu-item *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId"
[disabled]="isLoading$ | async"
(click)="onActionButtonClick($event, row, actionDescriptor)"> (click)="onActionButtonClick($event, row, actionDescriptor)">
<mat-icon>{{actionDescriptor.icon}}</mat-icon> <mat-icon>{{actionDescriptor.icon}}</mat-icon>
<span>{{ actionDescriptor.displayName }}</span>
</button> </button>
</mat-menu> </div>
</div> <div fxHide fxShow.lt-lg *ngIf="actionCellDescriptors.length">
</mat-cell> <button mat-button mat-icon-button
</ng-container> (click)="$event.stopPropagation(); ctx.detectChanges();"
<mat-header-row *matHeaderRowDef="source.displayedColumns; sticky: true"></mat-header-row> [matMenuTriggerFor]="cellActionsMenu">
<mat-row *matRowDef="let row; columns: source.displayedColumns;" <mat-icon class="material-icons">more_vert</mat-icon>
(click)="onRowClick($event, row)"></mat-row> </button>
</table> <mat-menu #cellActionsMenu="matMenu" xPosition="before">
<span [fxShow]="source.timeseriesDatasource.isEmpty() | async" <button mat-menu-item *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId"
fxLayoutAlign="center center" [disabled]="isLoading$ | async"
class="no-data-found" translate>widget.no-data-found</span> (click)="onActionButtonClick($event, row, actionDescriptor)">
</div> <mat-icon>{{actionDescriptor.icon}}</mat-icon>
<mat-divider *ngIf="displayPagination"></mat-divider> <span>{{ actionDescriptor.displayName }}</span>
<mat-paginator *ngIf="displayPagination" </button>
[length]="source.timeseriesDatasource.total() | async" </mat-menu>
[pageIndex]="source.pageLink.page" </div>
[pageSize]="source.pageLink.pageSize" </mat-cell>
[pageSizeOptions]="pageSizeOptions" </ng-container>
showFirstLastButtons></mat-paginator> <mat-header-row *matHeaderRowDef="source.displayedColumns; sticky: true"></mat-header-row>
<mat-row *matRowDef="let row; columns: source.displayedColumns;"
(click)="onRowClick($event, row)"></mat-row>
</table>
<span [fxShow]="source.timeseriesDatasource.isEmpty() | async"
fxLayoutAlign="center center"
class="no-data-found" translate>widget.no-data-found</span>
</div>
<mat-divider *ngIf="displayPagination"></mat-divider>
<mat-paginator *ngIf="displayPagination"
[length]="source.timeseriesDatasource.total() | async"
[pageIndex]="source.pageLink.page"
[pageSize]="source.pageLink.pageSize"
[pageSizeOptions]="pageSizeOptions"
showFirstLastButtons></mat-paginator>
</ng-template>
</mat-tab> </mat-tab>
</mat-tab-group> </mat-tab-group>
</div> </div>

View File

@ -40,12 +40,12 @@ import {
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import {hashCode, isDefined, isDefinedAndNotNull, isNumber} from '@core/utils'; import { hashCode, isDefined, isEqual, isNumber } from '@core/utils';
import cssjs from '@core/css/css'; import cssjs from '@core/css/css';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
import { CollectionViewer, DataSource } from '@angular/cdk/collections'; import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, fromEvent, merge, Observable, of } from 'rxjs'; import { BehaviorSubject, fromEvent, merge, Observable, of, Subscription } from 'rxjs';
import { emptyPageData, PageData } from '@shared/models/page/page-data'; import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators'; import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators';
import { MatPaginator } from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
@ -129,6 +129,8 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
public showTimestamp = true; public showTimestamp = true;
private dateFormatFilter: string; private dateFormatFilter: string;
private subscriptions: Subscription[] = [];
private searchAction: WidgetAction = { private searchAction: WidgetAction = {
name: 'action.search', name: 'action.search',
show: true, show: true,
@ -166,40 +168,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
debounceTime(150), debounceTime(150),
distinctUntilChanged(), distinctUntilChanged(),
tap(() => { tap(() => {
if (this.displayPagination) {
this.paginators.forEach((paginator) => {
paginator.pageIndex = 0;
});
}
this.sources.forEach((source) => { this.sources.forEach((source) => {
source.pageLink.textSearch = this.textSearch; source.pageLink.textSearch = this.textSearch;
if (this.displayPagination) {
source.pageLink.page = 0;
}
}); });
this.updateAllData(); this.loadCurrentSourceRow();
this.ctx.detectChanges();
}) })
) )
.subscribe(); .subscribe();
if (this.displayPagination) { this.sorts.changes.subscribe(() => {
this.sorts.forEach((sort, index) => { this.initSubscriptionsToSortAndPaginator();
sort.sortChange.subscribe(() => this.paginators.toArray()[index].pageIndex = 0);
});
}
this.sorts.forEach((sort, index) => {
const paginator = this.displayPagination ? this.paginators.toArray()[index] : null;
sort.sortChange.subscribe(() => this.paginators.toArray()[index].pageIndex = 0);
((this.displayPagination ? merge(sort.sortChange, paginator.page) : sort.sortChange) as Observable<any>)
.pipe(
tap(() => this.updateData(sort, paginator, index))
)
.subscribe();
}); });
this.updateAllData();
this.initSubscriptionsToSortAndPaginator();
} }
public onDataUpdated() { public onDataUpdated() {
this.sources.forEach((source) => { this.updateCurrentSourceData();
source.timeseriesDatasource.dataUpdated(this.data);
});
} }
private initialize() { private initialize() {
@ -305,7 +294,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
this.ctx.activeEntityInfo = activeEntityInfo; this.ctx.activeEntityInfo = activeEntityInfo;
} }
private initSubscriptionsToSortAndPaginator() {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
this.sorts.forEach((sort, index) => {
let paginator = null;
const observables = [sort.sortChange];
if (this.displayPagination) {
paginator = this.paginators.toArray()[index];
this.subscriptions.push(
sort.sortChange.subscribe(() => paginator.pageIndex = 0)
);
observables.push(paginator.page);
}
this.updateData(sort, paginator);
this.subscriptions.push(merge(...observables).pipe(
tap(() => this.updateData(sort, paginator))
).subscribe());
});
}
onSourceIndexChanged() { onSourceIndexChanged() {
this.updateCurrentSourceData();
this.updateActiveEntityInfo(); this.updateActiveEntityInfo();
} }
@ -326,30 +335,19 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
exitFilterMode() { exitFilterMode() {
this.textSearchMode = false; this.textSearchMode = false;
this.textSearch = null; this.textSearch = null;
this.sources.forEach((source, index) => { this.sources.forEach((source) => {
source.pageLink.textSearch = this.textSearch; source.pageLink.textSearch = this.textSearch;
const sort = this.sorts.toArray()[index];
let paginator = null;
if (this.displayPagination) { if (this.displayPagination) {
paginator = this.paginators.toArray()[index]; source.pageLink.page = 0;
paginator.pageIndex = 0;
} }
this.updateData(sort, paginator, index);
}); });
this.loadCurrentSourceRow();
this.ctx.hideTitlePanel = false; this.ctx.hideTitlePanel = false;
this.ctx.detectChanges(true); this.ctx.detectChanges(true);
} }
private updateAllData() { private updateData(sort: MatSort, paginator: MatPaginator) {
this.sources.forEach((source, index) => { const source = this.sources[this.sourceIndex];
const sort = this.sorts.toArray()[index];
const paginator = this.displayPagination ? this.paginators.toArray()[index] : null;
this.updateData(sort, paginator, index);
});
}
private updateData(sort: MatSort, paginator: MatPaginator, index: number) {
const source = this.sources[index];
if (this.displayPagination) { if (this.displayPagination) {
source.pageLink.page = paginator.pageIndex; source.pageLink.page = paginator.pageIndex;
source.pageLink.pageSize = paginator.pageSize; source.pageLink.pageSize = paginator.pageSize;
@ -418,7 +416,6 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
if (!isDefined(content)) { if (!isDefined(content)) {
return ''; return '';
} else { } else {
switch (typeof content) { switch (typeof content) {
case 'string': case 'string':
@ -462,6 +459,18 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
} }
this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, row, entityLabel); this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, row, entityLabel);
} }
public isActiveTab(index: number): boolean {
return index === this.sourceIndex;
}
private updateCurrentSourceData() {
this.sources[this.sourceIndex].timeseriesDatasource.dataUpdated(this.data);
}
private loadCurrentSourceRow() {
this.sources[this.sourceIndex].timeseriesDatasource.loadRows();
}
} }
class TimeseriesDatasource implements DataSource<TimeseriesRow> { class TimeseriesDatasource implements DataSource<TimeseriesRow> {
@ -482,6 +491,10 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> {
} }
connect(collectionViewer: CollectionViewer): Observable<TimeseriesRow[] | ReadonlyArray<TimeseriesRow>> { connect(collectionViewer: CollectionViewer): Observable<TimeseriesRow[] | ReadonlyArray<TimeseriesRow>> {
if (this.rowsSubject.isStopped) {
this.rowsSubject.isStopped = false;
this.pageDataSubject.isStopped = false;
}
return this.rowsSubject.asObservable(); return this.rowsSubject.asObservable();
} }
@ -565,7 +578,8 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> {
private fetchRows(pageLink: PageLink): Observable<PageData<TimeseriesRow>> { private fetchRows(pageLink: PageLink): Observable<PageData<TimeseriesRow>> {
return this.allRows$.pipe( return this.allRows$.pipe(
map((data) => pageLink.filterData(data)) map((data) => pageLink.filterData(data)),
distinctUntilChanged((prev, curr) => isEqual(prev, curr))
); );
} }
} }