UI: Improvement load and update time into time series table
This commit is contained in:
parent
eaa2c5785f
commit
d520415d5b
@ -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>
|
||||||
|
|||||||
@ -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))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user