2019-08-23 15:06:42 +03:00
|
|
|
///
|
2021-01-11 13:42:16 +02:00
|
|
|
/// Copyright © 2016-2021 The Thingsboard Authors
|
2019-08-23 15:06:42 +03:00
|
|
|
///
|
|
|
|
|
/// 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.
|
|
|
|
|
///
|
|
|
|
|
|
2021-12-01 11:32:32 +02:00
|
|
|
import {
|
|
|
|
|
AfterViewInit,
|
|
|
|
|
ChangeDetectionStrategy,
|
|
|
|
|
ChangeDetectorRef,
|
|
|
|
|
Component,
|
|
|
|
|
ElementRef,
|
|
|
|
|
Input,
|
|
|
|
|
OnInit,
|
|
|
|
|
ViewChild
|
|
|
|
|
} from '@angular/core';
|
2019-08-23 15:06:42 +03:00
|
|
|
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 { EntityRelationService } from '@core/http/entity-relation.service';
|
|
|
|
|
import { Direction, SortOrder } from '@shared/models/page/sort-order';
|
2021-12-02 13:53:00 +02:00
|
|
|
import { forkJoin, fromEvent, merge, Observable } from 'rxjs';
|
2019-08-23 15:06:42 +03:00
|
|
|
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
|
2019-08-27 20:07:09 +03:00
|
|
|
import {
|
|
|
|
|
EntityRelation,
|
|
|
|
|
EntityRelationInfo,
|
|
|
|
|
EntitySearchDirection,
|
|
|
|
|
entitySearchDirectionTranslations,
|
|
|
|
|
RelationTypeGroup
|
|
|
|
|
} from '@shared/models/relation.models';
|
2019-08-23 15:06:42 +03:00
|
|
|
import { EntityId } from '@shared/models/id/entity-id';
|
|
|
|
|
import { RelationsDatasource } from '../../models/datasource/relation-datasource';
|
2019-08-27 20:07:09 +03:00
|
|
|
import { RelationDialogComponent, RelationDialogData } from '@home/components/relation/relation-dialog.component';
|
2021-12-02 13:53:00 +02:00
|
|
|
import { hidePageSizePixelValue } from '@shared/models/constants';
|
|
|
|
|
import { ResizeObserver } from '@juggle/resize-observer';
|
2019-08-23 15:06:42 +03:00
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
|
selector: 'tb-relation-table',
|
|
|
|
|
templateUrl: './relation-table.component.html',
|
|
|
|
|
styleUrls: ['./relation-table.component.scss'],
|
|
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush
|
|
|
|
|
})
|
|
|
|
|
export class RelationTableComponent extends PageComponent implements AfterViewInit, OnInit {
|
|
|
|
|
|
|
|
|
|
directions = EntitySearchDirection;
|
|
|
|
|
|
|
|
|
|
directionTypes = Object.keys(EntitySearchDirection);
|
|
|
|
|
|
|
|
|
|
directionTypeTranslations = entitySearchDirectionTranslations;
|
|
|
|
|
|
|
|
|
|
displayedColumns: string[];
|
|
|
|
|
direction: EntitySearchDirection;
|
|
|
|
|
pageLink: PageLink;
|
2021-12-14 12:40:18 +02:00
|
|
|
hidePageSize = false;
|
2019-08-23 15:06:42 +03:00
|
|
|
textSearchMode = false;
|
|
|
|
|
dataSource: RelationsDatasource;
|
|
|
|
|
|
|
|
|
|
activeValue = false;
|
|
|
|
|
dirtyValue = false;
|
|
|
|
|
entityIdValue: EntityId;
|
|
|
|
|
|
|
|
|
|
viewsInited = false;
|
|
|
|
|
|
2021-12-02 13:53:00 +02:00
|
|
|
private widgetResize$: ResizeObserver;
|
2021-12-01 11:32:32 +02:00
|
|
|
|
2019-08-23 15:06:42 +03:00
|
|
|
@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;
|
|
|
|
|
if (this.viewsInited) {
|
|
|
|
|
this.resetSortAndFilter(this.activeValue);
|
|
|
|
|
if (!this.activeValue) {
|
|
|
|
|
this.dirtyValue = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-10 13:10:14 +02:00
|
|
|
@ViewChild('searchInput') searchInputField: ElementRef;
|
2019-08-23 15:06:42 +03:00
|
|
|
|
2020-02-10 13:10:14 +02:00
|
|
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
|
|
|
|
@ViewChild(MatSort) sort: MatSort;
|
2019-08-23 15:06:42 +03:00
|
|
|
|
|
|
|
|
constructor(protected store: Store<AppState>,
|
|
|
|
|
private entityRelationService: EntityRelationService,
|
|
|
|
|
public translate: TranslateService,
|
|
|
|
|
public dialog: MatDialog,
|
2021-12-01 11:32:32 +02:00
|
|
|
private dialogService: DialogService,
|
2021-12-14 12:40:18 +02:00
|
|
|
private cd: ChangeDetectorRef,
|
|
|
|
|
private elementRef: ElementRef) {
|
2019-08-23 15:06:42 +03:00
|
|
|
super(store);
|
|
|
|
|
this.dirtyValue = !this.activeValue;
|
|
|
|
|
const sortOrder: SortOrder = { property: 'type', direction: Direction.ASC };
|
|
|
|
|
this.direction = EntitySearchDirection.FROM;
|
|
|
|
|
this.pageLink = new PageLink(10, 0, null, sortOrder);
|
|
|
|
|
this.dataSource = new RelationsDatasource(this.entityRelationService, this.translate);
|
|
|
|
|
this.updateColumns();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngOnInit() {
|
2021-12-02 13:53:00 +02:00
|
|
|
this.widgetResize$ = new ResizeObserver(() => {
|
2021-12-14 12:40:18 +02:00
|
|
|
const showHidePageSize = this.elementRef.nativeElement.offsetWidth < hidePageSizePixelValue;
|
2021-12-02 13:53:00 +02:00
|
|
|
if (showHidePageSize !== this.hidePageSize) {
|
|
|
|
|
this.hidePageSize = showHidePageSize;
|
2021-12-14 12:40:18 +02:00
|
|
|
this.cd.markForCheck();
|
2021-12-02 13:53:00 +02:00
|
|
|
}
|
|
|
|
|
});
|
2021-12-14 12:40:18 +02:00
|
|
|
this.widgetResize$.observe(this.elementRef.nativeElement);
|
2021-12-01 11:32:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngOnDestroy() {
|
2021-12-02 13:53:00 +02:00
|
|
|
if (this.widgetResize$) {
|
|
|
|
|
this.widgetResize$.disconnect();
|
2021-12-01 11:32:32 +02:00
|
|
|
}
|
2019-08-23 15:06:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateColumns() {
|
|
|
|
|
if (this.direction === EntitySearchDirection.FROM) {
|
|
|
|
|
this.displayedColumns = ['select', 'type', 'toEntityTypeName', 'toName', 'actions'];
|
|
|
|
|
} else {
|
|
|
|
|
this.displayedColumns = ['select', 'type', 'fromEntityTypeName', 'fromName', 'actions'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
directionChanged(direction: EntitySearchDirection) {
|
|
|
|
|
this.direction = direction;
|
|
|
|
|
this.updateColumns();
|
2021-09-30 17:40:21 +03:00
|
|
|
this.paginator.pageIndex = 0;
|
2019-08-23 15:06:42 +03:00
|
|
|
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.loadRelations(this.direction, this.entityIdValue, 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) {
|
|
|
|
|
this.direction = EntitySearchDirection.FROM;
|
|
|
|
|
this.updateColumns();
|
|
|
|
|
this.pageLink.textSearch = null;
|
|
|
|
|
this.paginator.pageIndex = 0;
|
|
|
|
|
const sortable = this.sort.sortables.get('type');
|
|
|
|
|
this.sort.active = sortable.id;
|
|
|
|
|
this.sort.direction = 'asc';
|
|
|
|
|
if (update) {
|
|
|
|
|
this.updateData(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reloadRelations() {
|
|
|
|
|
this.updateData(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addRelation($event: Event) {
|
|
|
|
|
this.openRelationDialog($event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
editRelation($event: Event, relation: EntityRelationInfo) {
|
|
|
|
|
this.openRelationDialog($event, relation);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deleteRelation($event: Event, relation: EntityRelationInfo) {
|
|
|
|
|
if ($event) {
|
|
|
|
|
$event.stopPropagation();
|
|
|
|
|
}
|
2019-08-27 20:07:09 +03:00
|
|
|
let title;
|
|
|
|
|
let content;
|
|
|
|
|
if (this.direction === EntitySearchDirection.FROM) {
|
|
|
|
|
title = this.translate.instant('relation.delete-to-relation-title', {entityName: relation.toName});
|
|
|
|
|
content = this.translate.instant('relation.delete-to-relation-text', {entityName: relation.toName});
|
|
|
|
|
} else {
|
|
|
|
|
title = this.translate.instant('relation.delete-from-relation-title', {entityName: relation.fromName});
|
|
|
|
|
content = this.translate.instant('relation.delete-from-relation-text', {entityName: relation.fromName});
|
|
|
|
|
}
|
2019-08-23 15:06:42 +03:00
|
|
|
|
2019-08-27 20:07:09 +03:00
|
|
|
this.dialogService.confirm(
|
|
|
|
|
title,
|
|
|
|
|
content,
|
|
|
|
|
this.translate.instant('action.no'),
|
|
|
|
|
this.translate.instant('action.yes'),
|
|
|
|
|
true
|
|
|
|
|
).subscribe((result) => {
|
|
|
|
|
if (result) {
|
|
|
|
|
this.entityRelationService.deleteRelation(
|
|
|
|
|
relation.from,
|
|
|
|
|
relation.type,
|
|
|
|
|
relation.to
|
|
|
|
|
).subscribe(
|
|
|
|
|
() => {
|
|
|
|
|
this.reloadRelations();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
2019-08-23 15:06:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deleteRelations($event: Event) {
|
|
|
|
|
if ($event) {
|
|
|
|
|
$event.stopPropagation();
|
|
|
|
|
}
|
|
|
|
|
if (this.dataSource.selection.selected.length > 0) {
|
2019-08-27 20:07:09 +03:00
|
|
|
let title;
|
|
|
|
|
let content;
|
|
|
|
|
|
|
|
|
|
if (this.direction === EntitySearchDirection.FROM) {
|
|
|
|
|
title = this.translate.instant('relation.delete-to-relations-title', {count: this.dataSource.selection.selected.length});
|
|
|
|
|
content = this.translate.instant('relation.delete-to-relations-text');
|
|
|
|
|
} else {
|
|
|
|
|
title = this.translate.instant('relation.delete-from-relations-title', {count: this.dataSource.selection.selected.length});
|
|
|
|
|
content = this.translate.instant('relation.delete-from-relations-text');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.dialogService.confirm(
|
|
|
|
|
title,
|
|
|
|
|
content,
|
|
|
|
|
this.translate.instant('action.no'),
|
|
|
|
|
this.translate.instant('action.yes'),
|
|
|
|
|
true
|
|
|
|
|
).subscribe((result) => {
|
|
|
|
|
if (result) {
|
|
|
|
|
const tasks: Observable<any>[] = [];
|
|
|
|
|
this.dataSource.selection.selected.forEach((relation) => {
|
|
|
|
|
tasks.push(this.entityRelationService.deleteRelation(
|
|
|
|
|
relation.from,
|
|
|
|
|
relation.type,
|
|
|
|
|
relation.to
|
|
|
|
|
));
|
|
|
|
|
});
|
|
|
|
|
forkJoin(tasks).subscribe(
|
|
|
|
|
() => {
|
|
|
|
|
this.reloadRelations();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
2019-08-23 15:06:42 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-27 20:07:09 +03:00
|
|
|
openRelationDialog($event: Event, relation: EntityRelation = null) {
|
2019-08-23 15:06:42 +03:00
|
|
|
if ($event) {
|
|
|
|
|
$event.stopPropagation();
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-27 20:07:09 +03:00
|
|
|
let isAdd = false;
|
|
|
|
|
if (!relation) {
|
|
|
|
|
isAdd = true;
|
|
|
|
|
relation = {
|
|
|
|
|
from: null,
|
|
|
|
|
to: null,
|
|
|
|
|
type: null,
|
|
|
|
|
typeGroup: RelationTypeGroup.COMMON
|
|
|
|
|
};
|
|
|
|
|
if (this.direction === EntitySearchDirection.FROM) {
|
|
|
|
|
relation.from = this.entityIdValue;
|
|
|
|
|
} else {
|
|
|
|
|
relation.to = this.entityIdValue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.dialog.open<RelationDialogComponent, RelationDialogData, boolean>(RelationDialogComponent, {
|
|
|
|
|
disableClose: true,
|
|
|
|
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
|
|
|
|
data: {
|
|
|
|
|
isAdd,
|
|
|
|
|
direction: this.direction,
|
|
|
|
|
relation: {...relation}
|
|
|
|
|
}
|
|
|
|
|
}).afterClosed().subscribe(
|
|
|
|
|
(res) => {
|
|
|
|
|
if (res) {
|
|
|
|
|
this.reloadRelations();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-08-23 15:06:42 +03:00
|
|
|
|
|
|
|
|
}
|