UI: Entities table model improvements.

This commit is contained in:
Igor Kulikov 2020-04-08 19:31:08 +03:00
parent f9375a0fca
commit b0c5479c64
19 changed files with 187 additions and 65 deletions

View File

@ -29,8 +29,8 @@ export abstract class ContactBasedComponent<T extends ContactBased<HasId>> exten
protected constructor(protected store: Store<AppState>,
protected fb: FormBuilder,
protected entityValue: T,
protected entitiesTableConfig: EntityTableConfig<T>) {
super(store, fb, entityValue, entitiesTableConfig);
protected entitiesTableConfigValue: EntityTableConfig<T>) {
super(store, fb, entityValue, entitiesTableConfigValue);
}
buildForm(entity: T): FormGroup {

View File

@ -25,7 +25,7 @@
<tb-entity-details-panel
[entitiesTableConfig]="entitiesTableConfig"
[entityId]="dataSource.currentEntity?.id"
(closeEntityDetails)="isDetailsOpen = false"
(closeEntityDetails)="isDetailsOpen = false; detailsPanelOpened.emit(isDetailsOpen);"
(entityUpdated)="onEntityUpdated($event)"
(entityAction)="onEntityAction($event)"
>
@ -227,11 +227,12 @@
fxLayoutAlign="center center"
class="no-data-found" translate>{{ translations.noEntities }}</span>
</div>
<mat-divider></mat-divider>
<mat-paginator [length]="dataSource.total() | async"
<mat-divider *ngIf="displayPagination"></mat-divider>
<mat-paginator *ngIf="displayPagination"
[length]="dataSource.total() | async"
[pageIndex]="pageLink.page"
[pageSize]="pageLink.pageSize"
[pageSizeOptions]="[10, 20, 30]"></mat-paginator>
[pageSizeOptions]="pageSizeOptions"></mat-paginator>
</div>
</div>
</mat-drawer-content>

View File

@ -19,7 +19,7 @@ import {
ChangeDetectionStrategy,
Component,
ComponentFactoryResolver,
ElementRef,
ElementRef, EventEmitter,
Input, OnChanges,
OnInit, SimpleChanges,
ViewChild
@ -27,14 +27,14 @@ import {
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { PageLink, TimePageLink } from '@shared/models/page/page-link';
import { MAX_SAFE_PAGE_SIZE, PageLink, TimePageLink } from '@shared/models/page/page-link';
import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { EntitiesDataSource } from '@home/models/datasource/entity-datasource';
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
import { Direction, SortOrder } from '@shared/models/page/sort-order';
import { forkJoin, fromEvent, merge, Observable } from 'rxjs';
import { forkJoin, fromEvent, merge, Observable, Subscription } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { BaseData, HasId } from '@shared/models/base-data';
import { ActivatedRoute } from '@angular/router';
@ -87,12 +87,16 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
selectionEnabled;
defaultPageSize = 10;
displayPagination = true;
pageSizeOptions;
pageLink: PageLink;
textSearchMode = false;
timewindow: Timewindow;
dataSource: EntitiesDataSource<BaseData<HasId>>;
isDetailsOpen = false;
detailsPanelOpened = new EventEmitter<boolean>();
@ViewChild('entityTableHeader', {static: true}) entityTableHeaderAnchor: TbAnchorComponent;
@ -101,6 +105,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
private sortSubscription: Subscription;
private updateDataSubscription: Subscription;
private viewInited = false;
constructor(protected store: Store<AppState>,
private route: ActivatedRoute,
public translate: TranslateService,
@ -183,6 +191,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
};
}
this.displayPagination = this.entitiesTableConfig.displayPagination;
this.defaultPageSize = this.entitiesTableConfig.defaultPageSize;
this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];
if (this.entitiesTableConfig.useTimePageLink) {
this.timewindow = historyInterval(DAY);
const currentTime = Date.now();
@ -191,6 +203,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
} else {
this.pageLink = new PageLink(10, 0, null, sortOrder);
}
this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : MAX_SAFE_PAGE_SIZE;
this.dataSource = this.entitiesTableConfig.dataSource(() => {
this.dataLoaded();
});
@ -200,6 +213,11 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
if (this.entitiesTableConfig.loadDataOnInit) {
this.dataSource.loadEntities(this.pageLink);
}
if (this.viewInited) {
setTimeout(() => {
this.updatePaginationSubscriptions();
}, 0);
}
}
ngAfterViewInit() {
@ -209,15 +227,31 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
debounceTime(150),
distinctUntilChanged(),
tap(() => {
if (this.displayPagination) {
this.paginator.pageIndex = 0;
}
this.updateData();
})
)
.subscribe();
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
this.updatePaginationSubscriptions();
this.viewInited = true;
}
merge(this.sort.sortChange, this.paginator.page)
private updatePaginationSubscriptions() {
if (this.sortSubscription) {
this.sortSubscription.unsubscribe();
this.sortSubscription = null;
}
if (this.updateDataSubscription) {
this.updateDataSubscription.unsubscribe();
this.updateDataSubscription = null;
}
if (this.displayPagination) {
this.sortSubscription = this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
}
this.updateDataSubscription = (this.displayPagination ? merge(this.sort.sortChange, this.paginator.page) : this.sort.sortChange)
.pipe(
tap(() => this.updateData())
)
@ -232,8 +266,12 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
if (closeDetails) {
this.isDetailsOpen = false;
}
if (this.displayPagination) {
this.pageLink.page = this.paginator.pageIndex;
this.pageLink.pageSize = this.paginator.pageSize;
} else {
this.pageLink.page = 0;
}
if (this.sort.active) {
this.pageLink.sortOrder = {
property: this.sort.active,
@ -264,6 +302,12 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
}
onRowClick($event: Event, entity) {
if (!this.entitiesTableConfig.handleRowClick($event, entity)) {
this.toggleEntityDetails($event, entity);
}
}
toggleEntityDetails($event: Event, entity) {
if ($event) {
$event.stopPropagation();
}
@ -272,6 +316,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
} else {
this.isDetailsOpen = !this.isDetailsOpen;
}
this.detailsPanelOpened.emit(this.isDetailsOpen);
}
addEntity($event: Event) {
@ -371,16 +416,20 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
exitFilterMode() {
this.textSearchMode = false;
this.pageLink.textSearch = null;
if (this.displayPagination) {
this.paginator.pageIndex = 0;
}
this.updateData();
}
resetSortAndFilter(update: boolean = true) {
this.pageLink.textSearch = null;
if (this.entitiesTableConfig.useTimePageLink) {
this.timewindow = historyInterval(24 * 60 * 60 * 1000);
this.timewindow = historyInterval(DAY);
}
if (this.displayPagination) {
this.paginator.pageIndex = 0;
}
const sortable = this.sort.sortables.get(this.entitiesTableConfig.defaultSortOrder.property);
this.sort.active = sortable.id;
this.sort.direction = this.entitiesTableConfig.defaultSortOrder.direction === Direction.ASC ? 'asc' : 'desc';

View File

@ -51,8 +51,6 @@ import { deepClone } from '@core/utils';
})
export class EntityDetailsPanelComponent extends PageComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() entitiesTableConfig: EntityTableConfig<BaseData<HasId>>;
@Output()
closeEntityDetails = new EventEmitter<void>();
@ -66,6 +64,7 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit
entityTabsComponent: EntityTabsComponent<BaseData<HasId>>;
detailsForm: NgForm;
entitiesTableConfigValue: EntityTableConfig<BaseData<HasId>>;
isEditValue = false;
selectedTab = 0;
@ -102,9 +101,26 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit
}
}
@Input()
set entitiesTableConfig(entitiesTableConfig: EntityTableConfig<BaseData<HasId>>) {
this.entitiesTableConfigValue = entitiesTableConfig;
if (this.entityComponent) {
this.entityComponent.entitiesTableConfig = entitiesTableConfig;
}
if (this.entityTabsComponent) {
this.entityTabsComponent.entitiesTableConfig = entitiesTableConfig;
}
}
get entitiesTableConfig(): EntityTableConfig<BaseData<HasId>> {
return this.entitiesTableConfigValue;
}
set isEdit(val: boolean) {
this.isEditValue = val;
if (this.entityComponent) {
this.entityComponent.isEdit = val;
}
if (this.entityTabsComponent) {
this.entityTabsComponent.isEdit = val;
}

View File

@ -20,12 +20,25 @@ import { Input, OnInit, Directive } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
import { PageLink } from '@shared/models/page/page-link';
@Directive()
export abstract class EntityTableHeaderComponent<T extends BaseData<HasId>> extends PageComponent implements OnInit {
export abstract class EntityTableHeaderComponent<T extends BaseData<HasId>,
P extends PageLink = PageLink,
L extends BaseData<HasId> = T,
C extends EntityTableConfig<T, P, L> = EntityTableConfig<T, P, L>>
extends PageComponent implements OnInit {
entitiesTableConfigValue: C;
@Input()
entitiesTableConfig: EntityTableConfig<T>;
set entitiesTableConfig(entitiesTableConfig: C) {
this.setEntitiesTableConfig(entitiesTableConfig);
}
get entitiesTableConfig(): C {
return this.entitiesTableConfigValue;
}
protected constructor(protected store: Store<AppState>) {
super(store);
@ -34,4 +47,8 @@ export abstract class EntityTableHeaderComponent<T extends BaseData<HasId>> exte
ngOnInit() {
}
protected setEntitiesTableConfig(entitiesTableConfig: C) {
this.entitiesTableConfigValue = entitiesTableConfig;
}
}

View File

@ -38,6 +38,15 @@ export abstract class EntityComponent<T extends BaseData<HasId>,
isEditValue: boolean;
@Input()
set entitiesTableConfig(entitiesTableConfig: C) {
this.entitiesTableConfigValue = entitiesTableConfig;
}
get entitiesTableConfig(): C {
return this.entitiesTableConfigValue;
}
@Input()
set isEdit(isEdit: boolean) {
this.isEditValue = isEdit;
@ -72,7 +81,7 @@ export abstract class EntityComponent<T extends BaseData<HasId>,
protected constructor(protected store: Store<AppState>,
protected fb: FormBuilder,
protected entityValue: T,
protected entitiesTableConfig: C) {
protected entitiesTableConfigValue: C) {
super(store);
this.entityForm = this.buildForm(this.entityValue);
}

View File

@ -29,7 +29,6 @@ import {
OnDestroy,
OnInit,
SimpleChanges,
Type,
ViewChild,
ViewContainerRef,
ViewEncapsulation
@ -44,7 +43,8 @@ import {
Widget,
WidgetActionDescriptor,
widgetActionSources,
WidgetActionType, WidgetComparisonSettings,
WidgetActionType,
WidgetComparisonSettings,
WidgetResource,
widgetType,
WidgetTypeParameters
@ -90,27 +90,7 @@ import { DashboardService } from '@core/http/dashboard.service';
import { DatasourceService } from '@core/api/datasource.service';
import { WidgetSubscription } from '@core/api/widget-subscription';
import { EntityService } from '@core/http/entity.service';
import { AssetService } from '@core/http/asset.service';
import { DialogService } from '@core/services/dialog.service';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
import { DatePipe } from '@angular/common';
import { AttributeService } from '@core/http/attribute.service';
import { TranslateService } from '@ngx-translate/core';
import { HttpClient } from '@angular/common/http';
import { EntityRelationService } from '@app/core/http/entity-relation.service';
const ServicesMap = new Map<string, Type<any>>();
ServicesMap.set('deviceService', DeviceService);
ServicesMap.set('assetService', AssetService);
ServicesMap.set('attributeService', AttributeService);
ServicesMap.set('entityRelationService', EntityRelationService);
ServicesMap.set('entityService', EntityService);
ServicesMap.set('dialogs', DialogService);
ServicesMap.set('customDialog', CustomDialogService);
ServicesMap.set('date', DatePipe);
ServicesMap.set('utils', UtilsService);
ServicesMap.set('translate', TranslateService);
ServicesMap.set('http', HttpClient);
import { ServicesMap } from '@home/models/services.map';
@Component({
selector: 'tb-widget',

View File

@ -39,6 +39,7 @@ export type EntityByIdOperation<T extends BaseData<HasId>> = (id: HasUUID) => Ob
export type EntityIdOneWayOperation = (id: HasUUID) => Observable<any>;
export type EntityActionFunction<T extends BaseData<HasId>> = (action: EntityAction<T>) => boolean;
export type CreateEntityOperation<T extends BaseData<HasId>> = () => Observable<T>;
export type EntityRowClickFunction<T extends BaseData<HasId>> = (event: Event, entity: T) => boolean;
export type CellContentFunction<T extends BaseData<HasId>> = (entity: T, key: string) => string;
export type CellTooltipFunction<T extends BaseData<HasId>> = (entity: T, key: string) => string | undefined;
@ -147,12 +148,14 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
entityTabsComponent: Type<EntityTabsComponent<T, P, L>>;
addDialogStyle = {};
defaultSortOrder: SortOrder = {property: 'createdTime', direction: Direction.ASC};
displayPagination = true;
defaultPageSize = 10;
columns: Array<EntityColumn<L>> = [];
cellActionDescriptors: Array<CellActionDescriptor<L>> = [];
groupActionDescriptors: Array<GroupActionDescriptor<L>> = [];
headerActionDescriptors: Array<HeaderActionDescriptor> = [];
addActionDescriptors: Array<HeaderActionDescriptor> = [];
headerComponent: Type<EntityTableHeaderComponent<L>>;
headerComponent: Type<EntityTableHeaderComponent<T, P, L>>;
addEntity: CreateEntityOperation<T> = null;
dataSource: (dataLoadedFunction: () => void) => EntitiesDataSource<L> = (dataLoadedFunction: () => void) => {
return new EntitiesDataSource(this.entitiesFetchFunction, this.entitySelectionEnabled, dataLoadedFunction);
@ -169,6 +172,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
deleteEntity: EntityIdOneWayOperation = () => of();
entitiesFetchFunction: EntitiesFetchFunction<L, P> = () => of(emptyPageData<L>());
onEntityAction: EntityActionFunction<T> = () => false;
handleRowClick: EntityRowClickFunction<L> = () => false;
entityTitle: EntityStringFunction<T> = (entity) => entity?.name;
}

View File

@ -0,0 +1,44 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Type } from '@angular/core';
import { DeviceService } from '@core/http/device.service';
import { AssetService } from '@core/http/asset.service';
import { AttributeService } from '@core/http/attribute.service';
import { EntityRelationService } from '@core/http/entity-relation.service';
import { EntityService } from '@core/http/entity.service';
import { DialogService } from '@core/services/dialog.service';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
import { DatePipe } from '@angular/common';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { HttpClient } from '@angular/common/http';
export const ServicesMap = new Map<string, Type<any>>(
[
['deviceService', DeviceService],
['assetService', AssetService],
['attributeService', AttributeService],
['entityRelationService', EntityRelationService],
['entityService', EntityService],
['dialogs', DialogService],
['customDialog', CustomDialogService],
['date', DatePipe],
['utils', UtilsService],
['translate', TranslateService],
['http', HttpClient]
]
);

View File

@ -40,9 +40,9 @@ export class AssetComponent extends EntityComponent<AssetInfo> {
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Inject('entity') protected entityValue: AssetInfo,
@Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<AssetInfo>,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<AssetInfo>,
public fb: FormBuilder) {
super(store, fb, entityValue, entitiesTableConfig);
super(store, fb, entityValue, entitiesTableConfigValue);
}
ngOnInit() {

View File

@ -35,9 +35,9 @@ export class CustomerComponent extends ContactBasedComponent<Customer> {
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Inject('entity') protected entityValue: Customer,
@Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<Customer>,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<Customer>,
protected fb: FormBuilder) {
super(store, fb, entityValue, entitiesTableConfig);
super(store, fb, entityValue, entitiesTableConfigValue);
}
hideDelete() {

View File

@ -47,9 +47,9 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> {
protected translate: TranslateService,
private dashboardService: DashboardService,
@Inject('entity') protected entityValue: Dashboard,
@Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<Dashboard>,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<Dashboard>,
public fb: FormBuilder) {
super(store, fb, entityValue, entitiesTableConfig);
super(store, fb, entityValue, entitiesTableConfigValue);
}
ngOnInit() {

View File

@ -44,9 +44,9 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
private deviceService: DeviceService,
private clipboardService: ClipboardService,
@Inject('entity') protected entityValue: DeviceInfo,
@Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<DeviceInfo>,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<DeviceInfo>,
public fb: FormBuilder) {
super(store, fb, entityValue, entitiesTableConfig);
super(store, fb, entityValue, entitiesTableConfigValue);
}
ngOnInit() {

View File

@ -52,9 +52,9 @@ export class EntityViewComponent extends EntityComponent<EntityViewInfo> {
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Inject('entity') protected entityValue: EntityViewInfo,
@Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<EntityViewInfo>,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<EntityViewInfo>,
public fb: FormBuilder) {
super(store, fb, entityValue, entitiesTableConfig);
super(store, fb, entityValue, entitiesTableConfigValue);
}
ngOnInit() {

View File

@ -34,9 +34,9 @@ export class RuleChainComponent extends EntityComponent<RuleChain> {
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Inject('entity') protected entityValue: RuleChain,
@Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<RuleChain>,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<RuleChain>,
public fb: FormBuilder) {
super(store, fb, entityValue, entitiesTableConfig);
super(store, fb, entityValue, entitiesTableConfigValue);
}
hideDelete() {

View File

@ -34,9 +34,9 @@ export class TenantComponent extends ContactBasedComponent<Tenant> {
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Inject('entity') protected entityValue: Tenant,
@Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<Tenant>,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<Tenant>,
protected fb: FormBuilder) {
super(store, fb, entityValue, entitiesTableConfig);
super(store, fb, entityValue, entitiesTableConfigValue);
}
hideDelete() {

View File

@ -42,9 +42,9 @@ export class UserComponent extends EntityComponent<User> {
constructor(protected store: Store<AppState>,
@Inject('entity') protected entityValue: User,
@Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<User>,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<User>,
public fb: FormBuilder) {
super(store, fb, entityValue, entitiesTableConfig);
super(store, fb, entityValue, entitiesTableConfigValue);
}
hideDelete() {

View File

@ -31,9 +31,9 @@ export class WidgetsBundleComponent extends EntityComponent<WidgetsBundle> {
constructor(protected store: Store<AppState>,
@Inject('entity') protected entityValue: WidgetsBundle,
@Inject('entitiesTableConfig') protected entitiesTableConfig: EntityTableConfig<WidgetsBundle>,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<WidgetsBundle>,
public fb: FormBuilder) {
super(store, fb, entityValue, entitiesTableConfig);
super(store, fb, entityValue, entitiesTableConfigValue);
}
hideDelete() {

View File

@ -19,6 +19,8 @@ import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { getDescendantProp, isObject } from '@core/utils';
import { SortDirection } from '@angular/material/sort';
export const MAX_SAFE_PAGE_SIZE = 2147483647;
export type PageLinkSearchFunction<T> = (entity: T, textSearch: string) => boolean;
const defaultPageLinkSearchFunction: PageLinkSearchFunction<any> =