Edit relation dialog
This commit is contained in:
parent
851a3657db
commit
b438cdd255
@ -27,12 +27,14 @@ import { AuditLogTableComponent } from './audit-log/audit-log-table.component';
|
||||
import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component';
|
||||
import { EventTableComponent } from '@home/components/event/event-table.component';
|
||||
import { RelationTableComponent } from '@home/components/relation/relation-table.component';
|
||||
import { RelationDialogComponent } from './relation/relation-dialog.component';
|
||||
|
||||
@NgModule({
|
||||
entryComponents: [
|
||||
AddEntityDialogComponent,
|
||||
AuditLogDetailsDialogComponent,
|
||||
EventTableHeaderComponent
|
||||
EventTableHeaderComponent,
|
||||
RelationDialogComponent
|
||||
],
|
||||
declarations:
|
||||
[
|
||||
@ -45,7 +47,8 @@ import { RelationTableComponent } from '@home/components/relation/relation-table
|
||||
AuditLogDetailsDialogComponent,
|
||||
EventTableHeaderComponent,
|
||||
EventTableComponent,
|
||||
RelationTableComponent
|
||||
RelationTableComponent,
|
||||
RelationDialogComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<form #relationForm="ngForm" [formGroup]="relationFormGroup" (ngSubmit)="save()" style="min-width: 600px;">
|
||||
<mat-toolbar fxLayout="row" color="primary">
|
||||
<h2>{{ (isAdd ? 'relation.add' : 'relation.edit' ) | translate }}</h2>
|
||||
<span fxFlex></span>
|
||||
<button mat-button mat-icon-button
|
||||
(click)="cancel()"
|
||||
type="button">
|
||||
<mat-icon class="material-icons">close</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
|
||||
</mat-progress-bar>
|
||||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
|
||||
<div mat-dialog-content>
|
||||
<fieldset [disabled]="isLoading$ | async">
|
||||
<tb-relation-type-autocomplete
|
||||
formControlName="type"
|
||||
required="true">
|
||||
</tb-relation-type-autocomplete>
|
||||
<small>{{(direction === entitySearchDirection.FROM ?
|
||||
'relation.to-entity' : 'relation.from-entity') | translate}}</small>
|
||||
<tb-entity-list-select
|
||||
formControlName="targetEntityIds"
|
||||
required="true">
|
||||
</tb-entity-list-select>
|
||||
<tb-json-object-edit
|
||||
formControlName="additionalInfo"
|
||||
label="{{ 'relation.additional-info' | translate }}">
|
||||
</tb-json-object-edit>
|
||||
<div class="tb-error-messages" *ngIf="submitted &&
|
||||
relationFormGroup.get('additionalInfo').invalid" role="alert">
|
||||
<div translate class="tb-error-message">relation.invalid-additional-info</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div mat-dialog-actions fxLayout="row">
|
||||
<span fxFlex></span>
|
||||
<button mat-button mat-raised-button color="primary"
|
||||
type="submit"
|
||||
[disabled]="(isLoading$ | async)">
|
||||
{{ (isAdd ? 'action.add' : 'action.save') | translate }}
|
||||
</button>
|
||||
<button mat-button color="primary"
|
||||
style="margin-right: 20px;"
|
||||
type="button"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="cancel()" cdkFocusInitial>
|
||||
{{ 'action.cancel' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
:host {
|
||||
|
||||
}
|
||||
@ -0,0 +1,134 @@
|
||||
///
|
||||
/// 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 { Component, Inject, OnInit, SkipSelf } from '@angular/core';
|
||||
import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
|
||||
import { PageComponent } from '@shared/components/page.component';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
|
||||
import {
|
||||
CONTAINS_TYPE,
|
||||
EntityRelation,
|
||||
EntitySearchDirection,
|
||||
RelationTypeGroup
|
||||
} from '@shared/models/relation.models';
|
||||
import { EntityRelationService } from '@core/http/entity-relation.service';
|
||||
import { EntityId } from '@shared/models/id/entity-id';
|
||||
import { Observable, forkJoin } from 'rxjs';
|
||||
|
||||
export interface RelationDialogData {
|
||||
isAdd: boolean;
|
||||
direction: EntitySearchDirection;
|
||||
relation: EntityRelation;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'tb-relation-dialog',
|
||||
templateUrl: './relation-dialog.component.html',
|
||||
providers: [{provide: ErrorStateMatcher, useExisting: RelationDialogComponent}],
|
||||
styleUrls: ['./relation-dialog.component.scss']
|
||||
})
|
||||
export class RelationDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher {
|
||||
|
||||
relationFormGroup: FormGroup;
|
||||
|
||||
isAdd: boolean;
|
||||
direction: EntitySearchDirection;
|
||||
entitySearchDirection = EntitySearchDirection;
|
||||
|
||||
submitted = false;
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: RelationDialogData,
|
||||
private entityRelationService: EntityRelationService,
|
||||
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
|
||||
public dialogRef: MatDialogRef<RelationDialogComponent, boolean>,
|
||||
public fb: FormBuilder) {
|
||||
super(store);
|
||||
this.isAdd = data.isAdd;
|
||||
this.direction = data.direction;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.relationFormGroup = this.fb.group({
|
||||
type: [this.isAdd ? CONTAINS_TYPE : this.data.relation.type, [Validators.required]],
|
||||
targetEntityIds: [this.isAdd ? null :
|
||||
[this.direction === EntitySearchDirection.FROM ? this.data.relation.to : this.data.relation.from],
|
||||
[Validators.required]],
|
||||
additionalInfo: [this.data.relation.additionalInfo]
|
||||
});
|
||||
if (!this.isAdd) {
|
||||
this.relationFormGroup.get('type').disable();
|
||||
this.relationFormGroup.get('targetEntityIds').disable();
|
||||
}
|
||||
this.relationFormGroup.valueChanges.subscribe(
|
||||
() => {
|
||||
this.submitted = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
|
||||
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
|
||||
const customErrorState = !!(control && control.invalid && this.submitted);
|
||||
return originalErrorState || customErrorState;
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.submitted = true;
|
||||
if (this.relationFormGroup.valid) {
|
||||
const additionalInfo = this.relationFormGroup.get('additionalInfo').value;
|
||||
if (this.isAdd) {
|
||||
const tasks: Observable<EntityRelation>[] = [];
|
||||
const type: string = this.relationFormGroup.get('type').value;
|
||||
const entityIds: Array<EntityId> = this.relationFormGroup.get('targetEntityIds').value;
|
||||
entityIds.forEach(entityId => {
|
||||
const relation = {
|
||||
type,
|
||||
additionalInfo,
|
||||
typeGroup: RelationTypeGroup.COMMON
|
||||
} as EntityRelation;
|
||||
if (this.direction === EntitySearchDirection.FROM) {
|
||||
relation.from = this.data.relation.from;
|
||||
relation.to = entityId;
|
||||
} else {
|
||||
relation.from = entityId;
|
||||
relation.to = this.data.relation.to;
|
||||
}
|
||||
tasks.push(this.entityRelationService.saveRelation(relation));
|
||||
});
|
||||
forkJoin(tasks).subscribe(
|
||||
() => {
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const relation: EntityRelation = {...this.data.relation};
|
||||
relation.additionalInfo = additionalInfo;
|
||||
this.entityRelationService.saveRelation(relation).subscribe(
|
||||
() => {
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,12 +26,18 @@ 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';
|
||||
import { fromEvent, merge } from 'rxjs';
|
||||
import { forkJoin, fromEvent, merge, Observable } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
|
||||
import { EntityRelationInfo, EntitySearchDirection, entitySearchDirectionTranslations } from '@shared/models/relation.models';
|
||||
import {
|
||||
EntityRelation,
|
||||
EntityRelationInfo,
|
||||
EntitySearchDirection,
|
||||
entitySearchDirectionTranslations,
|
||||
RelationTypeGroup
|
||||
} from '@shared/models/relation.models';
|
||||
import { EntityId } from '@shared/models/id/entity-id';
|
||||
import { RelationsDatasource } from '../../models/datasource/relation-datasource';
|
||||
import { DebugEventType, EventType } from '@shared/models/event.models';
|
||||
import { RelationDialogComponent, RelationDialogData } from '@home/components/relation/relation-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-relation-table',
|
||||
@ -201,8 +207,35 @@ export class RelationTableComponent extends PageComponent implements AfterViewIn
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
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});
|
||||
}
|
||||
|
||||
// TODO:
|
||||
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();
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteRelations($event: Event) {
|
||||
@ -210,16 +243,79 @@ export class RelationTableComponent extends PageComponent implements AfterViewIn
|
||||
$event.stopPropagation();
|
||||
}
|
||||
if (this.dataSource.selection.selected.length > 0) {
|
||||
// TODO:
|
||||
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();
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
openRelationDialog($event: Event, relation: EntityRelationInfo = null) {
|
||||
openRelationDialog($event: Event, relation: EntityRelation = null) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
// TODO:
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<div fxLayout="row" class="tb-entity-list-select" [formGroup]="entityListSelectFormGroup">
|
||||
<tb-entity-type-select
|
||||
style="min-width: 100px; padding-right: 8px;"
|
||||
*ngIf="displayEntityTypeSelect"
|
||||
[showLabel]="true"
|
||||
[required]="required"
|
||||
[useAliasEntityTypes]="useAliasEntityTypes"
|
||||
[allowedEntityTypes]="allowedEntityTypes"
|
||||
formControlName="entityType">
|
||||
</tb-entity-type-select>
|
||||
<tb-entity-list
|
||||
[ngClass]="{'tb-not-empty': this.modelValue.ids?.length > 0}"
|
||||
fxFlex
|
||||
*ngIf="modelValue.entityType"
|
||||
[required]="required"
|
||||
[entityType]="modelValue.entityType"
|
||||
formControlName="entityIds">
|
||||
</tb-entity-list>
|
||||
</div>
|
||||
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
:host {
|
||||
}
|
||||
|
||||
:host ::ng-deep {
|
||||
tb-entity-list {
|
||||
&.tb-not-empty {
|
||||
.mat-form-field-flex {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
.mat-form-field-flex {
|
||||
.mat-form-field-infix {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,170 @@
|
||||
///
|
||||
/// 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, Component, forwardRef, Input, OnInit} from '@angular/core';
|
||||
import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {AppState} from '@core/core.state';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {AliasEntityType, EntityType, entityTypeTranslations} from '@shared/models/entity-type.models';
|
||||
import {EntityService} from '@core/http/entity.service';
|
||||
import {EntityId} from '@shared/models/id/entity-id';
|
||||
import {coerceBooleanProperty} from '@angular/cdk/coercion';
|
||||
|
||||
interface EntityListSelectModel {
|
||||
entityType: EntityType | AliasEntityType;
|
||||
ids: Array<string>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'tb-entity-list-select',
|
||||
templateUrl: './entity-list-select.component.html',
|
||||
styleUrls: ['./entity-list-select.component.scss'],
|
||||
providers: [{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => EntityListSelectComponent),
|
||||
multi: true
|
||||
}]
|
||||
})
|
||||
|
||||
export class EntityListSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit {
|
||||
|
||||
entityListSelectFormGroup: FormGroup;
|
||||
|
||||
modelValue: EntityListSelectModel = {entityType: null, ids: []};
|
||||
|
||||
@Input()
|
||||
allowedEntityTypes: Array<EntityType | AliasEntityType>;
|
||||
|
||||
@Input()
|
||||
useAliasEntityTypes: boolean;
|
||||
|
||||
private requiredValue: boolean;
|
||||
get required(): boolean {
|
||||
return this.requiredValue;
|
||||
}
|
||||
@Input()
|
||||
set required(value: boolean) {
|
||||
this.requiredValue = coerceBooleanProperty(value);
|
||||
}
|
||||
|
||||
@Input()
|
||||
disabled: boolean;
|
||||
|
||||
displayEntityTypeSelect: boolean;
|
||||
|
||||
private defaultEntityType: EntityType | AliasEntityType = null;
|
||||
|
||||
private propagateChange = (v: any) => { };
|
||||
|
||||
constructor(private store: Store<AppState>,
|
||||
private entityService: EntityService,
|
||||
public translate: TranslateService,
|
||||
private fb: FormBuilder) {
|
||||
|
||||
const entityTypes = this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes,
|
||||
this.useAliasEntityTypes);
|
||||
if (entityTypes.length === 1) {
|
||||
this.displayEntityTypeSelect = false;
|
||||
this.defaultEntityType = entityTypes[0];
|
||||
} else {
|
||||
this.displayEntityTypeSelect = true;
|
||||
}
|
||||
|
||||
this.entityListSelectFormGroup = this.fb.group({
|
||||
entityType: [this.defaultEntityType],
|
||||
entityIds: [[]]
|
||||
});
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.propagateChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.entityListSelectFormGroup.get('entityType').valueChanges.subscribe(
|
||||
(value) => {
|
||||
this.updateView(value, this.modelValue.ids);
|
||||
}
|
||||
);
|
||||
this.entityListSelectFormGroup.get('entityIds').valueChanges.subscribe(
|
||||
(values) => {
|
||||
this.updateView(this.modelValue.entityType, values);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
}
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
if (this.disabled) {
|
||||
this.entityListSelectFormGroup.disable();
|
||||
} else {
|
||||
this.entityListSelectFormGroup.enable();
|
||||
}
|
||||
}
|
||||
|
||||
writeValue(value: Array<EntityId> | null): void {
|
||||
if (value != null && value.length > 0) {
|
||||
const id = value[0];
|
||||
this.modelValue = {
|
||||
entityType: id.entityType,
|
||||
ids: value.map(val => val.id)
|
||||
};
|
||||
} else {
|
||||
this.modelValue = {
|
||||
entityType: this.defaultEntityType,
|
||||
ids: []
|
||||
};
|
||||
}
|
||||
this.entityListSelectFormGroup.get('entityType').patchValue(this.modelValue.entityType, {emitEvent: true});
|
||||
this.entityListSelectFormGroup.get('entityIds').patchValue([...this.modelValue.ids], {emitEvent: true});
|
||||
}
|
||||
|
||||
updateView(entityType: EntityType | AliasEntityType | null, entityIds: Array<string> | null) {
|
||||
if (this.modelValue.entityType !== entityType ||
|
||||
!this.compareIds(this.modelValue.ids, entityIds)) {
|
||||
this.modelValue = {
|
||||
entityType,
|
||||
ids: this.modelValue.entityType !== entityType || !entityIds ? [] : [...entityIds]
|
||||
};
|
||||
this.propagateChange(this.toEntityIds(this.modelValue));
|
||||
}
|
||||
}
|
||||
|
||||
compareIds(ids1: Array<string> | null, ids2: Array<string> | null): boolean {
|
||||
if (ids1 !== null && ids2 !== null) {
|
||||
return JSON.stringify(ids1) === JSON.stringify(ids2);
|
||||
} else {
|
||||
return ids1 === ids2;
|
||||
}
|
||||
}
|
||||
|
||||
toEntityIds(modelValue: EntityListSelectModel): Array<EntityId> {
|
||||
if (modelValue !== null && modelValue.entityType && modelValue.ids && modelValue.ids.length > 0) {
|
||||
const entityType = modelValue.entityType;
|
||||
return modelValue.ids.map(id => ({entityType, id}));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
-->
|
||||
<mat-form-field appearance="standard" [formGroup]="entityListFormGroup" class="mat-block">
|
||||
<mat-chip-list #chipList>
|
||||
<mat-chip-list #chipList formControlName="entities">
|
||||
<mat-chip
|
||||
*ngFor="let entity of entities"
|
||||
[selectable]="!disabled"
|
||||
@ -47,7 +47,7 @@
|
||||
</span>
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
<mat-error>
|
||||
<mat-error *ngIf="entityListFormGroup.get('entities').hasError('required')">
|
||||
{{ 'entity.entity-list-empty' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@ -14,16 +14,26 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, SkipSelf, ViewChild} from '@angular/core';
|
||||
import {
|
||||
AfterContentInit,
|
||||
AfterViewInit,
|
||||
Component,
|
||||
ElementRef,
|
||||
forwardRef,
|
||||
Input,
|
||||
OnInit,
|
||||
SkipSelf,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ControlValueAccessor,
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
FormGroupDirective,
|
||||
NG_VALUE_ACCESSOR, NgForm
|
||||
NG_VALUE_ACCESSOR, NgForm, Validators
|
||||
} from '@angular/forms';
|
||||
import {Observable} from 'rxjs';
|
||||
import {Observable, of} from 'rxjs';
|
||||
import {map, mergeMap, startWith, tap, share, pairwise, filter} from 'rxjs/operators';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {AppState} from '@app/core/core.state';
|
||||
@ -34,6 +44,7 @@ import {EntityId} from '@shared/models/id/entity-id';
|
||||
import {EntityService} from '@core/http/entity.service';
|
||||
import {ErrorStateMatcher, MatAutocomplete, MatAutocompleteSelectedEvent, MatChipList} from '@angular/material';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { emptyPageData } from '@shared/models/page/page-data';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-entity-list',
|
||||
@ -69,7 +80,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
|
||||
}
|
||||
@Input()
|
||||
set required(value: boolean) {
|
||||
this.requiredValue = coerceBooleanProperty(value);
|
||||
const newVal = coerceBooleanProperty(value);
|
||||
if (this.requiredValue !== newVal) {
|
||||
this.requiredValue = newVal;
|
||||
this.updateValidators();
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
@ -77,7 +92,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
|
||||
|
||||
@ViewChild('entityInput', {static: false}) entityInput: ElementRef<HTMLInputElement>;
|
||||
@ViewChild('entityAutocomplete', {static: false}) matAutocomplete: MatAutocomplete;
|
||||
@ViewChild('chipList', {static: false}) chipList: MatChipList;
|
||||
@ViewChild('chipList', {static: true}) chipList: MatChipList;
|
||||
|
||||
entities: Array<BaseData<EntityId>> = [];
|
||||
filteredEntities: Observable<Array<BaseData<EntityId>>>;
|
||||
@ -91,10 +106,16 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
|
||||
private entityService: EntityService,
|
||||
private fb: FormBuilder) {
|
||||
this.entityListFormGroup = this.fb.group({
|
||||
entities: [this.entities, this.required ? [Validators.required] : []],
|
||||
entity: [null]
|
||||
});
|
||||
}
|
||||
|
||||
updateValidators() {
|
||||
this.entityListFormGroup.get('entities').setValidators(this.required ? [Validators.required] : []);
|
||||
this.entityListFormGroup.get('entities').updateValueAndValidity();
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.propagateChange = fn;
|
||||
}
|
||||
@ -120,34 +141,39 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
|
||||
);
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {}
|
||||
ngAfterViewInit(): void {
|
||||
}
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
const emitEvent = this.disabled !== isDisabled;
|
||||
this.disabled = isDisabled;
|
||||
if (this.disabled) {
|
||||
this.entityListFormGroup.disable();
|
||||
if (isDisabled) {
|
||||
this.entityListFormGroup.disable({emitEvent});
|
||||
} else {
|
||||
this.entityListFormGroup.enable();
|
||||
this.entityListFormGroup.enable({emitEvent});
|
||||
}
|
||||
}
|
||||
|
||||
writeValue(value: Array<string> | null): void {
|
||||
this.searchText = '';
|
||||
if (value != null) {
|
||||
if (value != null && value.length > 0) {
|
||||
this.modelValue = [...value];
|
||||
this.entityService.getEntities(this.entityTypeValue, value).subscribe(
|
||||
(entities) => {
|
||||
this.entities = entities;
|
||||
this.entityListFormGroup.get('entities').setValue(this.entities);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.entities = [];
|
||||
this.entityListFormGroup.get('entities').setValue(this.entities);
|
||||
this.modelValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.entities = [];
|
||||
this.entityListFormGroup.get('entities').setValue(this.entities);
|
||||
this.modelValue = null;
|
||||
this.entityListFormGroup.get('entity').patchValue('', {emitEvent: true});
|
||||
this.propagateChange(this.modelValue);
|
||||
@ -160,9 +186,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
|
||||
}
|
||||
this.modelValue.push(entity.id.id);
|
||||
this.entities.push(entity);
|
||||
if (this.required) {
|
||||
this.chipList.errorState = false;
|
||||
}
|
||||
this.entityListFormGroup.get('entities').setValue(this.entities);
|
||||
}
|
||||
this.propagateChange(this.modelValue);
|
||||
this.clear();
|
||||
@ -172,12 +196,10 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
|
||||
const index = this.entities.indexOf(entity);
|
||||
if (index >= 0) {
|
||||
this.entities.splice(index, 1);
|
||||
this.entityListFormGroup.get('entities').setValue(this.entities);
|
||||
this.modelValue.splice(index, 1);
|
||||
if (!this.modelValue.length) {
|
||||
this.modelValue = null;
|
||||
if (this.required) {
|
||||
this.chipList.errorState = true;
|
||||
}
|
||||
}
|
||||
this.propagateChange(this.modelValue);
|
||||
this.clear();
|
||||
@ -190,7 +212,8 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
|
||||
|
||||
fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {
|
||||
this.searchText = searchText;
|
||||
return this.entityService.getEntitiesByNameFilter(this.entityTypeValue, searchText,
|
||||
return this.disabled ? of([]) :
|
||||
this.entityService.getEntitiesByNameFilter(this.entityTypeValue, searchText,
|
||||
50, '', false, true).pipe(
|
||||
map((data) => data ? data : []));
|
||||
}
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<div style="background: #fff;" [ngClass]="{'fill-height': fillHeight}"
|
||||
tb-fullscreen [fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()" fxLayout="column">
|
||||
<div fxLayout="row" fxLayoutAlign="start center">
|
||||
<label class="tb-title no-padding"
|
||||
ng-class="{'tb-required': required,
|
||||
'tb-readonly': readonly,
|
||||
'tb-error': !objectValid}">{{ label }}</label>
|
||||
<span fxFlex></span>
|
||||
<button mat-button mat-icon-button (click)="fullscreen = !fullscreen"
|
||||
matTooltip="{{(fullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
|
||||
matTooltipPosition="above">
|
||||
<mat-icon class="material-icons">{{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div fxFlex="0%" id="tb-json-panel" class="tb-json-object-panel" fxLayout="column">
|
||||
<div fxFlex #jsonEditor id="tb-json-input" [ngClass]="{'fill-height': fillHeight}"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
:host {
|
||||
position: relative;
|
||||
|
||||
.fill-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tb-json-object-panel {
|
||||
height: 100%;
|
||||
margin-left: 15px;
|
||||
border: 1px solid #c0c0c0;
|
||||
|
||||
#tb-json-input {
|
||||
width: 100%;
|
||||
min-width: 200px;
|
||||
height: 100%;
|
||||
|
||||
&:not(.fill-height) {
|
||||
min-height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
180
ui-ngx/src/app/shared/components/json-object-edit.component.ts
Normal file
180
ui-ngx/src/app/shared/components/json-object-edit.component.ts
Normal file
@ -0,0 +1,180 @@
|
||||
///
|
||||
/// 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 {
|
||||
Attribute,
|
||||
Component,
|
||||
ElementRef,
|
||||
forwardRef,
|
||||
Input,
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, Validator, NG_VALIDATORS } from '@angular/forms';
|
||||
import * as ace from 'ace-builds';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-json-object-edit',
|
||||
templateUrl: './json-object-edit.component.html',
|
||||
styleUrls: ['./json-object-edit.component.scss'],
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => JsonObjectEditComponent),
|
||||
multi: true
|
||||
},
|
||||
{
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => JsonObjectEditComponent),
|
||||
multi: true,
|
||||
}
|
||||
]
|
||||
})
|
||||
export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Validator {
|
||||
|
||||
@ViewChild('jsonEditor', {static: true})
|
||||
jsonEditorElmRef: ElementRef;
|
||||
|
||||
private jsonEditor: ace.Ace.Editor;
|
||||
|
||||
@Input() label: string;
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@Input() fillHeight: boolean;
|
||||
|
||||
private requiredValue: boolean;
|
||||
get required(): boolean {
|
||||
return this.requiredValue;
|
||||
}
|
||||
@Input()
|
||||
set required(value: boolean) {
|
||||
this.requiredValue = coerceBooleanProperty(value);
|
||||
}
|
||||
|
||||
private readonlyValue: boolean;
|
||||
get readonly(): boolean {
|
||||
return this.readonlyValue;
|
||||
}
|
||||
@Input()
|
||||
set readonly(value: boolean) {
|
||||
this.readonlyValue = coerceBooleanProperty(value);
|
||||
}
|
||||
|
||||
fullscreen = false;
|
||||
|
||||
modelValue: any;
|
||||
|
||||
contentValue: string;
|
||||
|
||||
objectValid: boolean;
|
||||
|
||||
private propagateChange = null;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
const editorElement = this.jsonEditorElmRef.nativeElement;
|
||||
let editorOptions: Partial<ace.Ace.EditorOptions> = {
|
||||
mode: 'ace/mode/json',
|
||||
theme: 'ace/theme/github',
|
||||
showGutter: true,
|
||||
showPrintMargin: false,
|
||||
readOnly: this.readonly
|
||||
};
|
||||
|
||||
const advancedOptions = {
|
||||
enableSnippets: true,
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true
|
||||
};
|
||||
|
||||
editorOptions = {...editorOptions, ...advancedOptions};
|
||||
this.jsonEditor = ace.edit(editorElement, editorOptions);
|
||||
this.jsonEditor.session.setUseWrapMode(false);
|
||||
this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1);
|
||||
this.jsonEditor.on('change', () => {
|
||||
this.updateView();
|
||||
});
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.propagateChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
}
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
public validate(c: FormControl) {
|
||||
return (this.objectValid) ? null : {
|
||||
jsonParseError: {
|
||||
valid: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
writeValue(value: any): void {
|
||||
this.modelValue = value;
|
||||
this.contentValue = '';
|
||||
this.objectValid = false;
|
||||
try {
|
||||
if (this.modelValue) {
|
||||
this.contentValue = JSON.stringify(this.modelValue, undefined, 2);
|
||||
this.objectValid = true;
|
||||
} else {
|
||||
this.objectValid = !this.required;
|
||||
}
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
if (this.jsonEditor) {
|
||||
this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1);
|
||||
}
|
||||
}
|
||||
|
||||
updateView() {
|
||||
const editorValue = this.jsonEditor.getValue();
|
||||
if (this.contentValue !== editorValue) {
|
||||
this.contentValue = editorValue;
|
||||
let data = null;
|
||||
this.objectValid = false;
|
||||
if (this.contentValue && this.contentValue.length > 0) {
|
||||
try {
|
||||
data = JSON.parse(this.contentValue);
|
||||
this.objectValid = true;
|
||||
} catch (ex) {}
|
||||
} else {
|
||||
this.objectValid = !this.required;
|
||||
}
|
||||
this.propagateChange(data);
|
||||
}
|
||||
}
|
||||
|
||||
onFullscreen() {
|
||||
if (this.jsonEditor) {
|
||||
setTimeout(() => {
|
||||
this.jsonEditor.resize();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<mat-form-field [formGroup]="relationTypeFormGroup" class="mat-block">
|
||||
<input matInput type="text" placeholder="{{ required ? ('relation.relation-type' | translate) : ( !modelValue ? ('relation.any-relation-type' | translate) : ' ') }}"
|
||||
#relationTypeInput
|
||||
formControlName="relationType"
|
||||
(focusin)="onFocus()"
|
||||
[required]="required"
|
||||
[matAutocomplete]="relationTypeAutocomplete">
|
||||
<button *ngIf="relationTypeFormGroup.get('relationType').value && !disabled"
|
||||
type="button"
|
||||
matSuffix mat-button mat-icon-button aria-label="Clear"
|
||||
(click)="clear()">
|
||||
<mat-icon class="material-icons">close</mat-icon>
|
||||
</button>
|
||||
<mat-autocomplete
|
||||
class="tb-autocomplete"
|
||||
#relationTypeAutocomplete="matAutocomplete"
|
||||
[displayWith]="displayRelationTypeFn">
|
||||
<mat-option *ngFor="let relationType of filteredRelationTypes | async" [value]="relationType">
|
||||
<span [innerHTML]="relationType | highlight:searchText"></span>
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
<mat-error *ngIf="relationTypeFormGroup.get('relationType').hasError('required')">
|
||||
{{ 'relation.relation-type-required' | translate }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
@ -0,0 +1,168 @@
|
||||
///
|
||||
/// 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, Component, ElementRef, forwardRef, Input, OnInit, ViewChild, OnDestroy} from '@angular/core';
|
||||
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
|
||||
import {Observable, of, throwError, Subscription} from 'rxjs';
|
||||
import {PageLink} from '@shared/models/page/page-link';
|
||||
import {Direction} from '@shared/models/page/sort-order';
|
||||
import {filter, map, mergeMap, publishReplay, refCount, startWith, tap, publish} from 'rxjs/operators';
|
||||
import {PageData, emptyPageData} from '@shared/models/page/page-data';
|
||||
import {DashboardInfo} from '@app/shared/models/dashboard.models';
|
||||
import {DashboardId} from '@app/shared/models/id/dashboard-id';
|
||||
import {DashboardService} from '@core/http/dashboard.service';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {AppState} from '@app/core/core.state';
|
||||
import {getCurrentAuthUser} from '@app/core/auth/auth.selectors';
|
||||
import {Authority} from '@shared/models/authority.enum';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {DeviceService} from '@core/http/device.service';
|
||||
import {EntitySubtype, EntityType} from '@app/shared/models/entity-type.models';
|
||||
import {BroadcastService} from '@app/core/services/broadcast.service';
|
||||
import {coerceBooleanProperty} from '@angular/cdk/coercion';
|
||||
import {AssetService} from '@core/http/asset.service';
|
||||
import {EntityViewService} from '@core/http/entity-view.service';
|
||||
import { RelationTypes } from '@app/shared/models/relation.models';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-relation-type-autocomplete',
|
||||
templateUrl: './relation-type-autocomplete.component.html',
|
||||
styleUrls: [],
|
||||
providers: [{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => RelationTypeAutocompleteComponent),
|
||||
multi: true
|
||||
}]
|
||||
})
|
||||
export class RelationTypeAutocompleteComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
relationTypeFormGroup: FormGroup;
|
||||
|
||||
modelValue: string | null;
|
||||
|
||||
private requiredValue: boolean;
|
||||
get required(): boolean {
|
||||
return this.requiredValue;
|
||||
}
|
||||
@Input()
|
||||
set required(value: boolean) {
|
||||
this.requiredValue = coerceBooleanProperty(value);
|
||||
}
|
||||
|
||||
@Input()
|
||||
disabled: boolean;
|
||||
|
||||
@ViewChild('relationTypeInput', {static: true}) relationTypeInput: ElementRef;
|
||||
|
||||
filteredRelationTypes: Observable<Array<string>>;
|
||||
|
||||
private searchText = '';
|
||||
|
||||
private dirty = false;
|
||||
|
||||
private propagateChange = (v: any) => { };
|
||||
|
||||
constructor(private store: Store<AppState>,
|
||||
private broadcast: BroadcastService,
|
||||
public translate: TranslateService,
|
||||
private fb: FormBuilder) {
|
||||
this.relationTypeFormGroup = this.fb.group({
|
||||
relationType: [null, this.required ? [Validators.required] : []]
|
||||
});
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.propagateChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
this.filteredRelationTypes = this.relationTypeFormGroup.get('relationType').valueChanges
|
||||
.pipe(
|
||||
tap(value => {
|
||||
this.updateView(value);
|
||||
}),
|
||||
// startWith<string | EntitySubtype>(''),
|
||||
map(value => value ? value : ''),
|
||||
mergeMap(type => this.fetchRelationTypes(type) )
|
||||
);
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
}
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
if (this.disabled) {
|
||||
this.relationTypeFormGroup.disable({emitEvent: false});
|
||||
} else {
|
||||
this.relationTypeFormGroup.enable({emitEvent: false});
|
||||
}
|
||||
}
|
||||
|
||||
writeValue(value: string | null): void {
|
||||
this.searchText = '';
|
||||
this.modelValue = value;
|
||||
this.relationTypeFormGroup.get('relationType').patchValue(value, {emitEvent: false});
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
onFocus() {
|
||||
if (this.dirty) {
|
||||
this.relationTypeFormGroup.get('relationType').updateValueAndValidity({onlySelf: true, emitEvent: true});
|
||||
this.dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
updateView(value: string | null) {
|
||||
if (this.modelValue !== value) {
|
||||
this.modelValue = value;
|
||||
this.propagateChange(this.modelValue);
|
||||
}
|
||||
}
|
||||
|
||||
displayRelationTypeFn(relationType?: string): string | undefined {
|
||||
return relationType ? relationType : undefined;
|
||||
}
|
||||
|
||||
fetchRelationTypes(searchText?: string, strictMatch: boolean = false): Observable<Array<string>> {
|
||||
this.searchText = searchText;
|
||||
return of(RelationTypes).pipe(
|
||||
map(relationTypes => relationTypes.filter( relationType => {
|
||||
if (strictMatch) {
|
||||
return searchText ? relationType === searchText : false;
|
||||
} else {
|
||||
return searchText ? relationType.toUpperCase().startsWith(searchText.toUpperCase()) : true;
|
||||
}
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.relationTypeFormGroup.get('relationType').patchValue(null, {emitEvent: true});
|
||||
setTimeout(() => {
|
||||
this.relationTypeInput.nativeElement.blur();
|
||||
this.relationTypeInput.nativeElement.focus();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
}
|
||||
@ -96,7 +96,7 @@ export class PageLink {
|
||||
pageData.totalElements = pageData.data.length;
|
||||
pageData.totalPages = Math.ceil(pageData.totalElements / this.pageSize);
|
||||
if (this.sortOrder) {
|
||||
pageData.data = pageData.data.sort(this.sort);
|
||||
pageData.data = pageData.data.sort((a, b) => this.sort(a, b));
|
||||
}
|
||||
const startIndex = this.pageSize * this.page;
|
||||
const endIndex = startIndex + this.pageSize;
|
||||
|
||||
@ -83,6 +83,9 @@ import {EntitySelectComponent} from './components/entity/entity-select.component
|
||||
import {DatetimeComponent} from '@shared/components/time/datetime.component';
|
||||
import {EntityKeysListComponent} from './components/entity/entity-keys-list.component';
|
||||
import {SocialSharePanelComponent} from './components/socialshare-panel.component';
|
||||
import { RelationTypeAutocompleteComponent } from '@shared/components/relation/relation-type-autocomplete.component';
|
||||
import { EntityListSelectComponent } from './components/entity/entity-list-select.component';
|
||||
import { JsonObjectEditComponent } from './components/json-object-edit.component';
|
||||
|
||||
@NgModule({
|
||||
providers: [
|
||||
@ -122,7 +125,10 @@ import {SocialSharePanelComponent} from './components/socialshare-panel.componen
|
||||
EntityTypeSelectComponent,
|
||||
EntitySelectComponent,
|
||||
EntityKeysListComponent,
|
||||
EntityListSelectComponent,
|
||||
RelationTypeAutocompleteComponent,
|
||||
SocialSharePanelComponent,
|
||||
JsonObjectEditComponent,
|
||||
NospacePipe,
|
||||
MillisecondsToTimeStringPipe,
|
||||
EnumToArrayPipe,
|
||||
@ -192,7 +198,10 @@ import {SocialSharePanelComponent} from './components/socialshare-panel.componen
|
||||
EntityTypeSelectComponent,
|
||||
EntitySelectComponent,
|
||||
EntityKeysListComponent,
|
||||
EntityListSelectComponent,
|
||||
RelationTypeAutocompleteComponent,
|
||||
SocialSharePanelComponent,
|
||||
JsonObjectEditComponent,
|
||||
// ValueInputComponent,
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user