thingsboard/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts

342 lines
11 KiB
TypeScript
Raw Normal View History

2019-08-29 20:04:59 +03:00
///
/// Copyright © 2016-2019 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 {
AfterViewInit,
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
OnInit,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { PageLink } from '@shared/models/page/page-link';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { MatDialog } from '@angular/material/dialog';
import { DialogService } from '@core/services/dialog.service';
import { Direction, SortOrder } from '@shared/models/page/sort-order';
import { fromEvent, merge } from 'rxjs';
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
import { EntityId } from '@shared/models/id/entity-id';
import {
AttributeData,
AttributeScope,
isClientSideTelemetryType, LatestTelemetry,
TelemetryType,
telemetryTypeTranslations
} from '@shared/models/telemetry/telemetry.models';
import { AttributeDatasource } from '@home/models/datasource/attribute-datasource';
import { AttributeService } from '@app/core/http/attribute.service';
import { EntityType } from '@shared/models/entity-type.models';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { RelationDialogComponent, RelationDialogData } from '@home/components/relation/relation-dialog.component';
import {
AddAttributeDialogComponent,
AddAttributeDialogData
} from '@home/components/attribute/add-attribute-dialog.component';
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { TIMEWINDOW_PANEL_DATA, TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component';
import {
EDIT_ATTRIBUTE_VALUE_PANEL_DATA,
EditAttributeValuePanelComponent,
EditAttributeValuePanelData
} from './edit-attribute-value-panel.component';
import { ComponentPortal, PortalInjector } from '@angular/cdk/portal';
@Component({
selector: 'tb-attribute-table',
templateUrl: './attribute-table.component.html',
styleUrls: ['./attribute-table.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AttributeTableComponent extends PageComponent implements AfterViewInit, OnInit {
telemetryTypeTranslationsMap = telemetryTypeTranslations;
isClientSideTelemetryTypeMap = isClientSideTelemetryType;
latestTelemetryTypes = LatestTelemetry;
mode: 'default' | 'widget' = 'default';
attributeScopes: Array<string> = [];
attributeScope: TelemetryType;
displayedColumns = ['select', 'lastUpdateTs', 'key', 'value'];
pageLink: PageLink;
textSearchMode = false;
dataSource: AttributeDatasource;
activeValue = false;
dirtyValue = false;
entityIdValue: EntityId;
attributeScopeSelectionReadonly = false;
viewsInited = false;
private disableAttributeScopeSelectionValue: boolean;
get disableAttributeScopeSelection(): boolean {
return this.disableAttributeScopeSelectionValue;
}
@Input()
set disableAttributeScopeSelection(value: boolean) {
this.disableAttributeScopeSelectionValue = coerceBooleanProperty(value);
}
@Input()
defaultAttributeScope: TelemetryType;
@Input()
set active(active: boolean) {
if (this.activeValue !== active) {
this.activeValue = active;
if (this.activeValue && this.dirtyValue) {
this.dirtyValue = false;
if (this.viewsInited) {
this.updateData(true);
}
}
}
}
@Input()
set entityId(entityId: EntityId) {
if (this.entityIdValue !== entityId) {
this.entityIdValue = entityId;
this.resetSortAndFilter(this.activeValue);
if (!this.activeValue) {
this.dirtyValue = true;
}
}
}
@ViewChild('searchInput', {static: false}) searchInputField: ElementRef;
@ViewChild(MatPaginator, {static: false}) paginator: MatPaginator;
@ViewChild(MatSort, {static: false}) sort: MatSort;
constructor(protected store: Store<AppState>,
private attributeService: AttributeService,
public translate: TranslateService,
public dialog: MatDialog,
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private dialogService: DialogService) {
super(store);
this.dirtyValue = !this.activeValue;
const sortOrder: SortOrder = { property: 'key', direction: Direction.ASC };
this.pageLink = new PageLink(10, 0, null, sortOrder);
this.dataSource = new AttributeDatasource(this.attributeService, this.translate);
}
ngOnInit() {
}
attributeScopeChanged(attributeScope: TelemetryType) {
this.attributeScope = attributeScope;
this.mode = 'default';
this.updateData(true);
}
ngAfterViewInit() {
fromEvent(this.searchInputField.nativeElement, 'keyup')
.pipe(
debounceTime(150),
distinctUntilChanged(),
tap(() => {
this.paginator.pageIndex = 0;
this.updateData();
})
)
.subscribe();
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
merge(this.sort.sortChange, this.paginator.page)
.pipe(
tap(() => this.updateData())
)
.subscribe();
this.viewsInited = true;
if (this.activeValue && this.entityIdValue) {
this.updateData(true);
}
}
updateData(reload: boolean = false) {
this.pageLink.page = this.paginator.pageIndex;
this.pageLink.pageSize = this.paginator.pageSize;
this.pageLink.sortOrder.property = this.sort.active;
this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()];
this.dataSource.loadAttributes(this.entityIdValue, this.attributeScope, this.pageLink, reload);
}
enterFilterMode() {
this.textSearchMode = true;
this.pageLink.textSearch = '';
setTimeout(() => {
this.searchInputField.nativeElement.focus();
this.searchInputField.nativeElement.setSelectionRange(0, 0);
}, 10);
}
exitFilterMode() {
this.textSearchMode = false;
this.pageLink.textSearch = null;
this.paginator.pageIndex = 0;
this.updateData();
}
resetSortAndFilter(update: boolean = true) {
const entityType = this.entityIdValue.entityType;
if (entityType === EntityType.DEVICE || entityType === EntityType.ENTITY_VIEW) {
this.attributeScopes = Object.keys(AttributeScope);
this.attributeScopeSelectionReadonly = false;
} else {
this.attributeScopes = [AttributeScope.SERVER_SCOPE];
this.attributeScopeSelectionReadonly = true;
}
this.mode = 'default';
this.attributeScope = this.defaultAttributeScope;
this.pageLink.textSearch = null;
if (this.viewsInited) {
this.paginator.pageIndex = 0;
const sortable = this.sort.sortables.get('key');
this.sort.active = sortable.id;
this.sort.direction = 'asc';
if (update) {
this.updateData(true);
}
}
}
reloadAttributes() {
this.updateData(true);
}
addAttribute($event: Event) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<AddAttributeDialogComponent, AddAttributeDialogData, boolean>(AddAttributeDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
entityId: this.entityIdValue,
attributeScope: this.attributeScope as AttributeScope
}
}).afterClosed().subscribe(
(res) => {
if (res) {
this.reloadAttributes();
}
}
);
}
editAttribute($event: Event, attribute: AttributeData) {
if ($event) {
$event.stopPropagation();
}
const target = $event.target || $event.srcElement || $event.currentTarget;
const config = new OverlayConfig();
config.backdropClass = 'cdk-overlay-transparent-backdrop';
config.hasBackdrop = true;
const connectedPosition: ConnectedPosition = {
originX: 'end',
originY: 'center',
overlayX: 'end',
overlayY: 'center'
};
config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement)
.withPositions([connectedPosition]);
const overlayRef = this.overlay.create(config);
overlayRef.backdropClick().subscribe(() => {
overlayRef.dispose();
});
const injectionTokens = new WeakMap<any, any>([
[EDIT_ATTRIBUTE_VALUE_PANEL_DATA, {
attributeValue: attribute.value
} as EditAttributeValuePanelData],
[OverlayRef, overlayRef]
]);
const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens);
const componentRef = overlayRef.attach(new ComponentPortal(EditAttributeValuePanelComponent,
this.viewContainerRef, injector));
componentRef.onDestroy(() => {
if (componentRef.instance.result !== null) {
const attributeValue = componentRef.instance.result;
const updatedAttribute = {...attribute};
updatedAttribute.value = attributeValue;
this.attributeService.saveEntityAttributes(this.entityIdValue,
this.attributeScope as AttributeScope, [updatedAttribute]).subscribe(
() => {
this.reloadAttributes();
}
);
}
});
}
deleteAttributes($event: Event) {
if ($event) {
$event.stopPropagation();
}
if (this.dataSource.selection.selected.length > 0) {
this.dialogService.confirm(
this.translate.instant('attribute.delete-attributes-title', {count: this.dataSource.selection.selected.length}),
this.translate.instant('attribute.delete-attributes-text'),
this.translate.instant('action.no'),
this.translate.instant('action.yes'),
true
).subscribe((result) => {
if (result) {
this.attributeService.deleteEntityAttributes(this.entityIdValue,
this.attributeScope as AttributeScope, this.dataSource.selection.selected).subscribe(
() => {
this.reloadAttributes();
}
);
}
});
}
}
enterWidgetMode() {
this.mode = 'widget';
// TODO:
}
exitWidgetMode() {
this.mode = 'default';
this.reloadAttributes();
// TODO:
}
}