Commit 09b6b06abb19380e87f83241b5adc63436f88fa4

Authored by Vladyslav_Prykhodko
Committed by Andrew Shvayka
1 parent 023915c2

Refactoring lwm2m device profile transport, configuration observe attributes/telemetry

Showing 19 changed files with 814 additions and 619 deletions
... ... @@ -34,7 +34,7 @@ export interface Lwm2mAttributesDialogData {
34 34 @Component({
35 35 selector: 'tb-lwm2m-attributes-dialog',
36 36 templateUrl: './lwm2m-attributes-dialog.component.html',
37   - styleUrls: ['./lwm2m-attributes.component.scss'],
  37 + styleUrls: [],
38 38 providers: [{provide: ErrorStateMatcher, useExisting: Lwm2mAttributesDialogComponent}],
39 39 })
40 40 export class Lwm2mAttributesDialogComponent
... ...
... ... @@ -15,7 +15,7 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<div fxLayout="row" [fxHide]="disabled && isEmpty()" fxLayoutAlign="end center" matTooltip="{{ tooltipSetAttributesTelemetry | translate }}" matTooltipPosition="above">
  18 +<div [fxHide]="disabled && isEmpty()" matTooltip="{{ tooltipSetAttributesTelemetry | translate }}" matTooltipPosition="above">
19 19 <button type="button"
20 20 [disabled]="isDisableBtn()"
21 21 mat-button mat-icon-button
... ...
... ... @@ -14,29 +14,31 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core';
  17 +import { Component, forwardRef, Input, OnDestroy } from '@angular/core';
18 18 import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
19 19 import { coerceBooleanProperty } from '@angular/cdk/coercion';
20 20 import { isEmpty, isUndefinedOrNull } from '@core/utils';
21 21 import { Lwm2mAttributesDialogComponent, Lwm2mAttributesDialogData } from './lwm2m-attributes-dialog.component';
22 22 import { MatDialog } from '@angular/material/dialog';
23 23 import { AttributesNameValueMap } from './lwm2m-profile-config.models';
24   -
  24 +import { Subject } from 'rxjs';
  25 +import { takeUntil } from 'rxjs/operators';
25 26
26 27 @Component({
27 28 selector: 'tb-profile-lwm2m-attributes',
28 29 templateUrl: './lwm2m-attributes.component.html',
29   - styleUrls: ['./lwm2m-attributes.component.scss'],
  30 + styleUrls: [],
30 31 providers: [{
31 32 provide: NG_VALUE_ACCESSOR,
32 33 useExisting: forwardRef(() => Lwm2mAttributesComponent),
33 34 multi: true
34 35 }]
35 36 })
36   -export class Lwm2mAttributesComponent implements ControlValueAccessor {
37   - attributeLwm2mFormGroup: FormGroup;
  37 +export class Lwm2mAttributesComponent implements ControlValueAccessor, OnDestroy {
  38 + attributesFormGroup: FormGroup;
38 39
39 40 private requiredValue: boolean;
  41 + private destroy$ = new Subject();
40 42
41 43 @Input()
42 44 isAttributeTelemetry: boolean;
... ... @@ -50,9 +52,6 @@ export class Lwm2mAttributesComponent implements ControlValueAccessor {
50 52 @Input()
51 53 isResource = false;
52 54
53   - @Output()
54   - updateAttributeLwm2m = new EventEmitter<any>();
55   -
56 55 @Input()
57 56 set required(value: boolean) {
58 57 this.requiredValue = coerceBooleanProperty(value);
... ... @@ -62,7 +61,21 @@ export class Lwm2mAttributesComponent implements ControlValueAccessor {
62 61 }
63 62
64 63 constructor(private dialog: MatDialog,
65   - private fb: FormBuilder) {}
  64 + private fb: FormBuilder) {
  65 + this.attributesFormGroup = this.fb.group({
  66 + attributes: [{}]
  67 + });
  68 + this.attributesFormGroup.get('attributes').valueChanges.pipe(
  69 + takeUntil(this.destroy$)
  70 + ).subscribe(attributes => {
  71 + this.propagateChange(attributes);
  72 + });
  73 + }
  74 +
  75 + ngOnDestroy() {
  76 + this.destroy$.next();
  77 + this.destroy$.complete();
  78 + }
66 79
67 80 registerOnChange(fn: any): void {
68 81 this.propagateChange = fn;
... ... @@ -74,24 +87,18 @@ export class Lwm2mAttributesComponent implements ControlValueAccessor {
74 87 setDisabledState(isDisabled: boolean): void {
75 88 this.disabled = isDisabled;
76 89 if (isDisabled) {
77   - this.attributeLwm2mFormGroup.disable({emitEvent: false});
  90 + this.attributesFormGroup.disable({emitEvent: false});
78 91 } else {
79   - this.attributeLwm2mFormGroup.enable({emitEvent: false});
  92 + this.attributesFormGroup.enable({emitEvent: false});
80 93 }
81 94 }
82 95
83   - ngOnInit() {
84   - this.attributeLwm2mFormGroup = this.fb.group({
85   - attributes: [{}]
86   - });
87   - }
88   -
89 96 writeValue(value: AttributesNameValueMap | null) {
90   - this.attributeLwm2mFormGroup.patchValue({attributes: value}, {emitEvent: false});
  97 + this.attributesFormGroup.patchValue({attributes: value}, {emitEvent: false});
91 98 }
92 99
93 100 get attributesValueMap(): AttributesNameValueMap {
94   - return this.attributeLwm2mFormGroup.get('attributes').value;
  101 + return this.attributesFormGroup.get('attributes').value;
95 102 }
96 103
97 104 isDisableBtn(): boolean {
... ... @@ -140,8 +147,7 @@ export class Lwm2mAttributesComponent implements ControlValueAccessor {
140 147 }
141 148 }).afterClosed().subscribe((result) => {
142 149 if (result) {
143   - this.attributeLwm2mFormGroup.patchValue({attributeLwm2m: result});
144   - this.updateAttributeLwm2m.next(result);
  150 + this.attributesFormGroup.patchValue({attributes: result});
145 151 }
146 152 });
147 153 }
... ...
... ... @@ -287,7 +287,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
287 287
288 288 private updateDeviceProfileValue(config): void {
289 289 if (this.lwm2mDeviceProfileFormGroup.valid) {
290   - this.updateObserveAttrTelemetryFromGroupToJson(config.observeAttrTelemetry.clientLwM2M);
  290 + this.updateObserveAttrTelemetryFromGroupToJson(config.observeAttrTelemetry);
291 291 }
292 292 this.configurationValue.bootstrap.bootstrapServer = config.bootstrap.bootstrapServer;
293 293 this.configurationValue.bootstrap.lwm2mServer = config.bootstrap.lwm2mServer;
... ... @@ -297,7 +297,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
297 297 this.updateModel();
298 298 }
299 299
300   - private getObserveAttrTelemetryObjects = (objectList: ObjectLwM2M[]): object => {
  300 + private getObserveAttrTelemetryObjects = (objectList: ObjectLwM2M[]): ObjectLwM2M[] => {
301 301 const objectLwM2MS = deepClone(objectList);
302 302 if (this.configurationValue.observeAttr && objectLwM2MS.length > 0) {
303 303 const attributeArray = this.configurationValue.observeAttr.attribute;
... ... @@ -317,7 +317,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
317 317 this.updateObserveAttrTelemetryObjects(telemetryArray, objectLwM2MS, TELEMETRY);
318 318 }
319 319 if (isDefinedAndNotNull(this.configurationValue.observeAttr.attributeLwm2m)) {
320   - this.updateAttributeLwm2m(objectLwM2MS);
  320 + this.updateAttributes(objectLwM2MS);
321 321 }
322 322 if (isDefinedAndNotNull(keyNameJson)) {
323 323 this.configurationValue.observeAttr.keyName = this.validateKeyNameObjects(keyNameJson, attributeArray, telemetryArray);
... ... @@ -325,7 +325,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
325 325 this.updateKeyNameObjects(objectLwM2MS);
326 326 }
327 327 }
328   - return {clientLwM2M: objectLwM2MS};
  328 + return objectLwM2MS;
329 329 }
330 330
331 331 private includesNotZeroInstance = (attribute: string[], telemetry: string[]): boolean => {
... ... @@ -369,7 +369,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
369 369 });
370 370 }
371 371
372   - private updateAttributeLwm2m = (objectLwM2MS: ObjectLwM2M[]): void => {
  372 + private updateAttributes = (objectLwM2MS: ObjectLwM2M[]): void => {
373 373 Object.keys(this.configurationValue.observeAttr.attributeLwm2m).forEach(key => {
374 374 const [objectKeyId, instanceId, resourceId] = Array.from(key.substring(1).split('/'), String);
375 375 const objectLwM2M = objectLwM2MS.find(objectLwm2m => objectLwm2m.keyId === objectKeyId);
... ... @@ -377,12 +377,12 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
377 377 const instance = objectLwM2M.instances.find(obj => obj.id === +instanceId);
378 378 if (instance && resourceId) {
379 379 instance.resources.find(resource => resource.id === +resourceId)
380   - .attributeLwm2m = this.configurationValue.observeAttr.attributeLwm2m[key];
  380 + .attributes = this.configurationValue.observeAttr.attributeLwm2m[key];
381 381 } else if (instance) {
382   - instance.attributeLwm2m = this.configurationValue.observeAttr.attributeLwm2m[key];
  382 + instance.attributes = this.configurationValue.observeAttr.attributeLwm2m[key];
383 383 }
384 384 } else if (objectLwM2M) {
385   - objectLwM2M.attributeLwm2m = this.configurationValue.observeAttr.attributeLwm2m[key];
  385 + objectLwM2M.attributes = this.configurationValue.observeAttr.attributeLwm2m[key];
386 386 }
387 387 });
388 388 }
... ... @@ -415,19 +415,19 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
415 415 const observeArray: Array<string> = [];
416 416 const attributeArray: Array<string> = [];
417 417 const telemetryArray: Array<string> = [];
418   - const attributeLwm2m: any = {};
  418 + const attributes: any = {};
419 419 const keyNameNew = {};
420 420 const observeJson: ObjectLwM2M[] = JSON.parse(JSON.stringify(val));
421 421 observeJson.forEach(obj => {
422   - if (isDefinedAndNotNull(obj.attributeLwm2m) && !isEmpty(obj.attributeLwm2m)) {
  422 + if (isDefinedAndNotNull(obj.attributes) && !isEmpty(obj.attributes)) {
423 423 const pathObject = `/${obj.keyId}`;
424   - attributeLwm2m[pathObject] = obj.attributeLwm2m;
  424 + attributes[pathObject] = obj.attributes;
425 425 }
426 426 if (obj.hasOwnProperty(INSTANCES) && Array.isArray(obj.instances)) {
427 427 obj.instances.forEach(instance => {
428   - if (isDefinedAndNotNull(instance.attributeLwm2m) && !isEmpty(instance.attributeLwm2m)) {
  428 + if (isDefinedAndNotNull(instance.attributes) && !isEmpty(instance.attributes)) {
429 429 const pathInstance = `/${obj.keyId}/${instance.id}`;
430   - attributeLwm2m[pathInstance] = instance.attributeLwm2m;
  430 + attributes[pathInstance] = instance.attributes;
431 431 }
432 432 if (instance.hasOwnProperty(RESOURCES) && Array.isArray(instance.resources)) {
433 433 instance.resources.forEach(resource => {
... ... @@ -443,8 +443,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
443 443 telemetryArray.push(pathRes);
444 444 }
445 445 keyNameNew[pathRes] = resource.keyName;
446   - if (isDefinedAndNotNull(resource.attributeLwm2m) && !isEmpty(resource.attributeLwm2m)) {
447   - attributeLwm2m[pathRes] = resource.attributeLwm2m;
  446 + if (isDefinedAndNotNull(resource.attributes) && !isEmpty(resource.attributes)) {
  447 + attributes[pathRes] = resource.attributes;
448 448 }
449 449 }
450 450 });
... ... @@ -458,14 +458,14 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
458 458 attribute: attributeArray,
459 459 telemetry: telemetryArray,
460 460 keyName: this.sortObjectKeyPathJson(KEY_NAME, keyNameNew),
461   - attributeLwm2m
  461 + attributeLwm2m: attributes
462 462 };
463 463 } else {
464 464 this.configurationValue.observeAttr.observe = observeArray;
465 465 this.configurationValue.observeAttr.attribute = attributeArray;
466 466 this.configurationValue.observeAttr.telemetry = telemetryArray;
467 467 this.configurationValue.observeAttr.keyName = this.sortObjectKeyPathJson(KEY_NAME, keyNameNew);
468   - this.configurationValue.observeAttr.attributeLwm2m = attributeLwm2m;
  468 + this.configurationValue.observeAttr.attributeLwm2m = attributes;
469 469 }
470 470 }
471 471
... ... @@ -535,7 +535,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
535 535 this.removeObserveAttrTelemetryFromJson(TELEMETRY, value.keyId);
536 536 this.removeObserveAttrTelemetryFromJson(ATTRIBUTE, value.keyId);
537 537 this.removeKeyNameFromJson(value.keyId);
538   - this.removeAttributeLwm2mFromJson(value.keyId);
  538 + this.removeAttributesFromJson(value.keyId);
539 539 this.updateObserveAttrTelemetryObjectFormGroup(objectsOld);
540 540 this.upDateJsonAllConfig();
541 541 }
... ... @@ -558,7 +558,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
558 558 });
559 559 }
560 560
561   - private removeAttributeLwm2mFromJson = (keyId: string): void => {
  561 + private removeAttributesFromJson = (keyId: string): void => {
562 562 const keyNameJson = this.configurationValue.observeAttr.attributeLwm2m;
563 563 Object.keys(keyNameJson).forEach(key => {
564 564 if (key.startsWith(`/${keyId}`)) {
... ...
... ... @@ -17,7 +17,7 @@
17 17 -->
18 18 <form [formGroup]="instancesFormGroup" (ngSubmit)="add()" style="min-width: 400px;">
19 19 <mat-toolbar fxLayout="row" color="primary">
20   - <b><i>{{data.objectName}}</i></b> (object <<b>{{data.objectKeyId}}</b>>)
  20 + <b><i>{{data.objectName}}</i></b> (object <<b>{{data.objectId}}</b>>)
21 21 <span fxFlex></span>
22 22 <button mat-button mat-icon-button
23 23 (click)="cancel()"
... ...
... ... @@ -23,9 +23,9 @@ import { Router } from '@angular/router';
23 23 import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
24 24
25 25 export interface Lwm2mObjectAddInstancesData {
26   - instancesIds: Set<number>;
  26 + instancesId: Set<number>;
27 27 objectName?: string;
28   - objectKeyId?: string;
  28 + objectId?: number;
29 29 }
30 30
31 31 @Component({
... ... @@ -48,16 +48,15 @@ export class Lwm2mObjectAddInstancesDialogComponent extends DialogComponent<Lwm2
48 48
49 49 ngOnInit(): void {
50 50 this.instancesFormGroup = this.fb.group({
51   - instancesIds: this.data.instancesIds
  51 + instancesIds: [this.data.instancesId]
52 52 });
53 53 }
54 54
55 55 cancel(): void {
56   - this.dialogRef.close(undefined);
  56 + this.dialogRef.close(null);
57 57 }
58 58
59 59 add(): void {
60   - this.data.instancesIds = this.instancesFormGroup.get('instancesIds').value;
61   - this.dialogRef.close(this.data);
  60 + this.dialogRef.close(this.instancesFormGroup.get('instancesIds').value);
62 61 }
63 62 }
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2021 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<section [formGroup]="instancesFormGroup">
  19 + <mat-accordion multi="true" formArrayName="instances">
  20 + <mat-expansion-panel
  21 + class="instance-row"
  22 + *ngFor="let instances of instancesFormArray.controls; let $index = index; trackBy: trackByParams"
  23 + [formGroupName]="$index"
  24 + [expanded]="isExpend"
  25 + [disabled]="isExpend">
  26 + <mat-expansion-panel-header>
  27 + <mat-panel-title class="tb-panel-title-height" fxLayout="row">
  28 + <div fxFlex="30" fxLayoutAlign="start center">
  29 + {{ 'device-profile.lwm2m.instance' | translate }} <<b>{{instances.get('id').value}}</b>>
  30 + </div>
  31 + <div fxLayoutAlign="center center" fxFlex="10">
  32 + <mat-checkbox color="warn"
  33 + [disabled]="this.disabled"
  34 + [checked]="getChecked(instances, 'attribute')"
  35 + (change)="changeInstanceResourcesCheckBox($event.checked, instances, 'attribute')"
  36 + [indeterminate]="getIndeterminate(instances, 'attribute')">
  37 + </mat-checkbox>
  38 + </div>
  39 + <div fxLayoutAlign="center center" fxFlex="10">
  40 + <mat-checkbox color="primary"
  41 + [disabled]="this.disabled"
  42 + [checked]="getChecked(instances, 'telemetry')"
  43 + (change)="changeInstanceResourcesCheckBox($event.checked, instances, 'telemetry')"
  44 + [indeterminate]="getIndeterminate(instances, 'telemetry')">
  45 + </mat-checkbox>
  46 + </div>
  47 + <div fxLayoutAlign="center center" fxFlex="10">
  48 + <mat-checkbox color="primary"
  49 + [disabled]="this.disabled || !(getIndeterminate(instances, 'telemetry') || getIndeterminate(instances, 'attribute'))"
  50 + [checked]="getChecked(instances, 'observe')"
  51 + (change)="changeInstanceResourcesCheckBox($event.checked, instances, 'observe')"
  52 + [indeterminate]="getIndeterminate(instances, 'observe')">
  53 + </mat-checkbox>
  54 + </div>
  55 + <span fxFlex></span>
  56 + <tb-profile-lwm2m-attributes
  57 + formControlName="attributes"
  58 + [isAttributeTelemetry]="disableObserveInstance(instances)"
  59 + [modelName]="getNameInstance(instances.value)">
  60 + </tb-profile-lwm2m-attributes>
  61 + </mat-panel-title>
  62 + </mat-expansion-panel-header>
  63 + <ng-template matExpansionPanelContent>
  64 + <tb-profile-lwm2m-observe-attr-telemetry-resource
  65 + formControlName="resources">
  66 + </tb-profile-lwm2m-observe-attr-telemetry-resource>
  67 + </ng-template>
  68 + </mat-expansion-panel>
  69 + </mat-accordion>
  70 +</section>
... ...
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-instances.component.scss renamed from ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.scss
... ... @@ -13,21 +13,18 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -:host {
17   - .resource-name-lw-end{
18   - white-space: nowrap;
19   - overflow: hidden;
20   - text-overflow: ellipsis;
21   - text-align:end;
22   - //width: 80px;
23   - cursor: pointer;
  16 +:host{
  17 + .tb-panel-title-height {
  18 + min-height: 42px;
24 19 }
25 20
26   - .resource-name-lw{
27   - white-space: nowrap;
28   - overflow: hidden;
29   - text-overflow: ellipsis;
30   - cursor: pointer;
  21 + .instance-row {
  22 + mat-expansion-panel-header {
  23 + color: inherit;
  24 + }
31 25 }
32   -}
33 26
  27 + .mat-expansion-panel-header-title {
  28 + margin-right: 0;
  29 + }
  30 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, forwardRef, Input, OnDestroy } from '@angular/core';
  18 +import {
  19 + AbstractControl,
  20 + ControlValueAccessor,
  21 + FormArray,
  22 + FormBuilder,
  23 + FormGroup,
  24 + NG_VALIDATORS,
  25 + NG_VALUE_ACCESSOR,
  26 + ValidationErrors,
  27 + Validator,
  28 + Validators
  29 +} from '@angular/forms';
  30 +import { coerceBooleanProperty } from '@angular/cdk/coercion';
  31 +import { Instance, ResourceLwM2M, ResourceSettingTelemetry, } from './lwm2m-profile-config.models';
  32 +import { deepClone, isDefinedAndNotNull } from '@core/utils';
  33 +import { TranslateService } from '@ngx-translate/core';
  34 +import { Subscription } from 'rxjs';
  35 +
  36 +@Component({
  37 + selector: 'tb-profile-lwm2m-observe-attr-telemetry-instances',
  38 + templateUrl: './lwm2m-observe-attr-telemetry-instances.component.html',
  39 + styleUrls: [ './lwm2m-observe-attr-telemetry-instances.component.scss'],
  40 + providers: [
  41 + {
  42 + provide: NG_VALUE_ACCESSOR,
  43 + useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryInstancesComponent),
  44 + multi: true
  45 + },
  46 + {
  47 + provide: NG_VALIDATORS,
  48 + useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryInstancesComponent),
  49 + multi: true
  50 + }
  51 + ]
  52 +})
  53 +
  54 +export class Lwm2mObserveAttrTelemetryInstancesComponent implements ControlValueAccessor, Validator, OnDestroy {
  55 +
  56 + instancesFormGroup: FormGroup;
  57 +
  58 + private requiredValue: boolean;
  59 + get required(): boolean {
  60 + return this.requiredValue;
  61 + }
  62 +
  63 + @Input()
  64 + set required(value: boolean) {
  65 + const newVal = coerceBooleanProperty(value);
  66 + if (this.requiredValue !== newVal) {
  67 + this.requiredValue = newVal;
  68 + this.updateValidators();
  69 + }
  70 + }
  71 +
  72 + @Input()
  73 + disabled: boolean;
  74 +
  75 + private valueChange$: Subscription = null;
  76 + private propagateChange = (v: any) => { };
  77 +
  78 + constructor(private fb: FormBuilder,
  79 + public translate: TranslateService) {
  80 + this.instancesFormGroup = this.fb.group({
  81 + instances: this.fb.array([])
  82 + });
  83 + }
  84 +
  85 + ngOnDestroy() {
  86 + if (this.valueChange$) {
  87 + this.valueChange$.unsubscribe();
  88 + }
  89 + }
  90 +
  91 + registerOnChange(fn: any): void {
  92 + this.propagateChange = fn;
  93 + }
  94 +
  95 + registerOnTouched(fn: any): void {
  96 + }
  97 +
  98 + setDisabledState(isDisabled: boolean): void {
  99 + this.disabled = isDisabled;
  100 + if (isDisabled) {
  101 + this.instancesFormGroup.disable({emitEvent: false});
  102 + } else {
  103 + this.instancesFormGroup.enable({emitEvent: false});
  104 + }
  105 + }
  106 +
  107 + writeValue(value: Instance[]): void {
  108 + this.updateInstances(value);
  109 + }
  110 +
  111 + validate(control: AbstractControl): ValidationErrors | null {
  112 + return this.instancesFormGroup.valid ? null : {
  113 + instancesForm: false
  114 + };
  115 + }
  116 +
  117 + get instancesFormArray(): FormArray {
  118 + return this.instancesFormGroup.get('instances') as FormArray;
  119 + }
  120 +
  121 + private updateInstances(instances: Instance[]): void {
  122 + if (instances.length === this.instancesFormArray.length) {
  123 + this.instancesFormArray.patchValue(instances, {emitEvent: false});
  124 + } else {
  125 + if (this.valueChange$) {
  126 + this.valueChange$.unsubscribe();
  127 + }
  128 + const instancesControl: Array<AbstractControl> = [];
  129 + if (instances) {
  130 + instances.forEach((instance) => {
  131 + instancesControl.push(this.createInstanceFormGroup(instance));
  132 + });
  133 + }
  134 + this.instancesFormGroup.setControl('instances', this.fb.array(instancesControl));
  135 + if (this.disabled) {
  136 + this.instancesFormGroup.disable({emitEvent: false});
  137 + }
  138 + this.valueChange$ = this.instancesFormGroup.valueChanges.subscribe(value => {
  139 + this.updateModel(value.instances);
  140 + });
  141 + }
  142 + }
  143 +
  144 + private createInstanceFormGroup(instance: Instance): FormGroup {
  145 + return this.fb.group({
  146 + id: [instance.id],
  147 + attributes: [instance.attributes],
  148 + resources: [instance.resources]
  149 + });
  150 + }
  151 +
  152 + private updateModel(instances: Instance[]) {
  153 + if (instances && this.instancesFormGroup.valid) {
  154 + this.propagateChange(instances);
  155 + } else {
  156 + this.propagateChange(null);
  157 + }
  158 + }
  159 +
  160 + changeInstanceResourcesCheckBox = (value: boolean, instance: AbstractControl, type: ResourceSettingTelemetry): void => {
  161 + const resources = deepClone(instance.get('resources').value as ResourceLwM2M[]);
  162 + if (value && type === 'observe') {
  163 + resources.forEach(resource => resource[type] = resource.telemetry || resource.attribute);
  164 + } else {
  165 + resources.forEach(resource => resource[type] = value);
  166 + }
  167 + instance.get('resources').patchValue(resources);
  168 + }
  169 +
  170 + private updateValidators(): void {
  171 + this.instancesFormArray.setValidators(this.required ? Validators.required : []);
  172 + this.instancesFormArray.updateValueAndValidity();
  173 + }
  174 +
  175 + trackByParams = (index: number, instance: Instance): number => {
  176 + return instance.id;
  177 + }
  178 +
  179 + getIndeterminate = (instance: AbstractControl, type: ResourceSettingTelemetry): boolean => {
  180 + const resources = instance.get('resources').value as ResourceLwM2M[];
  181 + if (isDefinedAndNotNull(resources)) {
  182 + const checkedResource = resources.filter(resource => resource[type]);
  183 + return checkedResource.length !== 0 && checkedResource.length !== resources.length;
  184 + }
  185 + return false;
  186 + }
  187 +
  188 + getChecked = (instance: AbstractControl, type: ResourceSettingTelemetry): boolean => {
  189 + const resources = instance.get('resources').value as ResourceLwM2M[];
  190 + return isDefinedAndNotNull(resources) && resources.every(resource => resource[type]);
  191 + }
  192 +
  193 + get isExpend(): boolean {
  194 + return this.instancesFormArray.length === 1;
  195 + }
  196 +
  197 + getNameInstance(instance: Instance): string {
  198 + return `${this.translate.instant('device-profile.lwm2m.instance')} <${instance.id}>`;
  199 + }
  200 +
  201 + disableObserveInstance = (instance: AbstractControl): boolean => {
  202 + const checkedAttrTelemetry = this.observeInstance(instance);
  203 + if (checkedAttrTelemetry) {
  204 + instance.get('attributes').patchValue(null, {emitEvent: false});
  205 + }
  206 + return checkedAttrTelemetry;
  207 + }
  208 +
  209 +
  210 + observeInstance = (instance: AbstractControl): boolean => {
  211 + const resources = instance.get('resources').value as ResourceLwM2M[];
  212 + if (isDefinedAndNotNull(resources)) {
  213 + const checkedAttribute = resources.filter(resource => resource.attribute);
  214 + const checkedTelemetry = resources.filter(resource => resource.telemetry);
  215 + return checkedAttribute.length === 0 && checkedTelemetry.length === 0;
  216 + }
  217 + return false;
  218 + }
  219 +}
... ...
1   -///
2   -/// Copyright © 2016-2021 The Thingsboard Authors
3   -///
4   -/// Licensed under the Apache License, Version 2.0 (the "License");
5   -/// you may not use this file except in compliance with the License.
6   -/// You may obtain a copy of the License at
7   -///
8   -/// http://www.apache.org/licenses/LICENSE-2.0
9   -///
10   -/// Unless required by applicable law or agreed to in writing, software
11   -/// distributed under the License is distributed on an "AS IS" BASIS,
12   -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   -/// See the License for the specific language governing permissions and
14   -/// limitations under the License.
15   -///
16   -
17   -import { Component, forwardRef, Input } from '@angular/core';
18   -import { ControlValueAccessor, FormArray, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
19   -import { ResourceLwM2M, RESOURCES } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models';
20   -import _ from 'lodash';
21   -import { coerceBooleanProperty } from '@angular/cdk/coercion';
22   -
23   -@Component({
24   - selector: 'tb-profile-lwm2m-observe-attr-telemetry-resource',
25   - templateUrl: './lwm2m-observe-attr-telemetry-resource.component.html',
26   - styleUrls: ['./lwm2m-attributes.component.scss'],
27   - providers: [
28   - {
29   - provide: NG_VALUE_ACCESSOR,
30   - useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryResourceComponent),
31   - multi: true
32   - }
33   - ]
34   -})
35   -
36   -export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueAccessor {
37   -
38   - private requiredValue: boolean;
39   -
40   - resourceFormGroup: FormGroup;
41   - disabled = false;
42   -
43   - get required(): boolean {
44   - return this.requiredValue;
45   - }
46   -
47   - @Input()
48   - set required(value: boolean) {
49   - const newVal = coerceBooleanProperty(value);
50   - if (this.requiredValue !== newVal) {
51   - this.requiredValue = newVal;
52   - }
53   - }
54   -
55   - constructor(private fb: FormBuilder) {
56   - this.resourceFormGroup = this.fb.group({
57   - resources: this.fb.array([])
58   - });
59   - this.resourceFormGroup.valueChanges.subscribe(value => {
60   - if (!this.disabled) {
61   - this.propagateChangeState(value.resources);
62   - }
63   - });
64   - }
65   -
66   - registerOnTouched(fn: any): void {
67   - }
68   -
69   - writeValue(value: ResourceLwM2M[]): void {
70   - this.createResourceLwM2M(value);
71   - }
72   -
73   - get resourceFormArray(): FormArray{
74   - return this.resourceFormGroup.get(RESOURCES) as FormArray;
75   - }
76   -
77   - setDisabledState(isDisabled: boolean): void {
78   - this.disabled = isDisabled;
79   - if (isDisabled) {
80   - this.resourceFormGroup.disable();
81   - } else {
82   - this.resourceFormGroup.enable();
83   - }
84   - }
85   -
86   - updateValueKeyName = (event: Event, index: number): void => {
87   - this.resourceFormArray.at(index).patchValue({keyName: _.camelCase((event.target as HTMLInputElement).value)});
88   - }
89   -
90   - updateAttributeLwm2m = (event: Event, index: number): void => {
91   - this.resourceFormArray.at(index).patchValue({attributeLwm2m: event});
92   - }
93   -
94   - getNameResourceLwm2m = (resourceLwM2M: ResourceLwM2M): string => {
95   - return `<${resourceLwM2M.id}> ${resourceLwM2M.name}`;
96   - }
97   -
98   - createResourceLwM2M(resourcesLwM2M: ResourceLwM2M[]): void {
99   - if (resourcesLwM2M.length === this.resourceFormArray.length) {
100   - this.resourceFormArray.patchValue(resourcesLwM2M, {emitEvent: false});
101   - } else {
102   - this.resourceFormArray.clear();
103   - resourcesLwM2M.forEach(resourceLwM2M => {
104   - this.resourceFormArray.push(this.fb.group( {
105   - id: resourceLwM2M.id,
106   - name: resourceLwM2M.name,
107   - observe: resourceLwM2M.observe,
108   - attribute: resourceLwM2M.attribute,
109   - telemetry: resourceLwM2M.telemetry,
110   - keyName: [resourceLwM2M.keyName, Validators.required],
111   - attributeLwm2m: [resourceLwM2M.attributeLwm2m]
112   - }));
113   - });
114   - }
115   - }
116   -
117   - private propagateChange = (v: any) => { };
118   -
119   - registerOnChange(fn: any): void {
120   - this.propagateChange = fn;
121   - }
122   -
123   - private propagateChangeState = (value: any): void => {
124   - if (value && this.resourceFormGroup.valid) {
125   - this.propagateChange(value);
126   - } else {
127   - this.propagateChange(null);
128   - }
129   - }
130   -
131   - trackByParams = (index: number): number => {
132   - return index;
133   - }
134   -
135   - updateObserve = (index: number): void => {
136   - if (this.resourceFormArray.at(index).value.attribute === false && this.resourceFormArray.at(index).value.telemetry === false) {
137   - this.resourceFormArray.at(index).patchValue({observe: false});
138   - this.resourceFormArray.at(index).patchValue({attributeLwm2m: {}});
139   - }
140   - }
141   -
142   - disableObserve = (index: number): boolean => {
143   - return !this.resourceFormArray.at(index).value.telemetry && !this.resourceFormArray.at(index).value.attribute;
144   - }
145   -}
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.html renamed from ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html
... ... @@ -15,77 +15,61 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<section [formGroup]="resourceFormGroup">
19   - <div fxLayout="row" fxFill formArrayName="resources"
20   - *ngFor="let resourceLwM2M of resourceFormArray.controls; let i = index; trackBy: trackByParams">
21   - <div class="vertical-padding" fxLayout="column" fxFill [formGroupName]="i">
22   - <div fxLayout="row" fxFill fxLayoutAlign="start center" [fxShow]="!i">
23   - <div fxFlex="30">
24   - <mat-label translate>device-profile.lwm2m.resource-label</mat-label>
25   - </div>
26   - <div fxFlex="10" style="text-align: center">
27   - <mat-label translate>device-profile.lwm2m.attribute-label</mat-label>
28   - </div>
29   - <div fxFlex="10" style="text-align: center">
30   - <mat-label translate>device-profile.lwm2m.telemetry-label</mat-label>
31   - </div>
32   - <div fxFlex="10" style="text-align: center">
33   - <mat-label translate>device-profile.lwm2m.observe-label</mat-label>
34   - </div>
35   - <div fxFlex>
36   - <mat-label translate>device-profile.lwm2m.key-name-label</mat-label>
37   - </div>
  18 +<section [formGroup]="resourcesFormGroup">
  19 + <div fxLayout="row" fxLayoutAlign="start center">
  20 + <div fxFlex="30">
  21 + <mat-label translate>device-profile.lwm2m.resource-label</mat-label>
  22 + </div>
  23 + <div fxFlex="10" class="checkbox-column-title">
  24 + <mat-label translate>device-profile.lwm2m.attribute-label</mat-label>
  25 + </div>
  26 + <div fxFlex="10" class="checkbox-column-title">
  27 + <mat-label translate>device-profile.lwm2m.telemetry-label</mat-label>
  28 + </div>
  29 + <div fxFlex="10" class="checkbox-column-title">
  30 + <mat-label translate>device-profile.lwm2m.observe-label</mat-label>
  31 + </div>
  32 + <div fxFlex>
  33 + <mat-label translate>device-profile.lwm2m.key-name</mat-label>
  34 + </div>
  35 + </div>
  36 + <mat-divider></mat-divider>
  37 + <div formArrayName="resources"
  38 + *ngFor="let resourceLwM2M of resourcesFormArray.controls; let $index = index; trackBy: trackByParams">
  39 + <div [formGroupName]="$index" fxLayout="row" fxLayoutAlign="start center" class="resource-list">
  40 + <div class="resource-name" fxFlex="30">
  41 + <<b>{{resourceLwM2M.get('id').value}}</b>> <b>{{resourceLwM2M.get('name').value}}</b>
  42 + </div>
  43 + <div fxFlex="10" fxLayoutAlign="center center">
  44 + <mat-checkbox formControlName="attribute" color="warn">
  45 + </mat-checkbox>
  46 + </div>
  47 + <div fxFlex="10" fxLayoutAlign="center center">
  48 + <mat-checkbox formControlName="telemetry" color="primary">
  49 + </mat-checkbox>
38 50 </div>
39   - <div fxLayout="row" fxFill fxLayoutAlign="start center">
40   - <div class="resource-name-lw" fxFlex="30"
41   - matTooltip="{{'device-profile.lwm2m.resource-tip' | translate}}" matTooltipPosition="above">
42   - <<b>{{resourceLwM2M.get('id').value}}</b>> <b><i>{{resourceLwM2M.get('name').value}}</i></b>
43   - </div>
44   - <div fxFlex="10" fxLayoutAlign="center center">
45   - <mat-checkbox formControlName="attribute" color="warn"
46   - (change)="updateObserve(i)"
47   - matTooltip="{{'device-profile.lwm2m.is-attr-tip' | translate}}"
48   - matTooltipPosition="above">
49   - </mat-checkbox>
50   - </div>
51   - <div fxFlex="10" fxLayoutAlign="center center">
52   - <mat-checkbox formControlName="telemetry" color="primary"
53   - (change)="updateObserve(i)"
54   - matTooltip="{{'device-profile.lwm2m.is-telemetry-tip' | translate}}"
55   - matTooltipPosition="above">
56   - </mat-checkbox>
57   - </div>
58   - <div fxFlex="10" fxLayoutAlign="center center">
59   - <mat-checkbox formControlName="observe" color="primary"
60   - [disabled]="disableObserve(i)"
61   - matTooltip="{{(disableObserve(i) ? 'device-profile.lwm2m.not-observe-tip' : 'device-profile.lwm2m.is-observe-tip') | translate}}"
62   - matTooltipPosition="above">
63   - </mat-checkbox>
64   - </div>
65   - <mat-form-field fxFlex="33">
66   - <mat-label *ngIf="resourceLwM2M.get('keyName').hasError('required')">
67   - {{ 'device-profile.lwm2m.key-name-label' | translate }}</mat-label>
68   - <input class="resource-name-lw" matInput type="text" formControlName="keyName" required
69   - matTooltip="{{'device-profile.lwm2m.key-name-tip' | translate}}"
70   - (input)="updateValueKeyName($event, i)"
71   - matTooltipPosition="above">
72   - <mat-error *ngIf="resourceLwM2M.get('keyName').hasError('required')">
73   - {{ 'device-profile.lwm2m.key-name' | translate }}
74   - <strong>{{ 'device-profile.lwm2m.required' | translate }}</strong>
75   - </mat-error>
76   - </mat-form-field>
77   - <span fxFlex></span>
78   - <div class="resource-name-lw-end">
79   - <tb-profile-lwm2m-attributes
80   - formControlName="attributeLwm2m"
81   - [isAttributeTelemetry]="disableObserve(i)"
82   - isResource="true"
83   - [modelName]="getNameResourceLwm2m(resourceLwM2M.value)"
84   - [disabled]="this.disabled"
85   - (updateAttributeLwm2m)="updateAttributeLwm2m($event, i)">
86   - </tb-profile-lwm2m-attributes>
87   - </div>
  51 + <div fxFlex="10" fxLayoutAlign="center center">
  52 + <mat-checkbox fxFlex="10" formControlName="observe" color="primary"
  53 + matTooltip="{{ 'device-profile.lwm2m.edit-observe-select' | translate }}"
  54 + [matTooltipDisabled]="disabled || !isDisabledObserve($index)"
  55 + matTooltipPosition="above">
  56 + </mat-checkbox>
88 57 </div>
  58 + <mat-form-field fxFlex="33" hideRequiredMarker>
  59 + <mat-label></mat-label>
  60 + <input matInput type="text" formControlName="keyName" required>
  61 + <mat-error *ngIf="resourceLwM2M.get('keyName').hasError('required') ||
  62 + resourceLwM2M.get('keyName').hasError('pattern')">
  63 + {{ 'device-profile.lwm2m.key-name-required' | translate }}
  64 + </mat-error>
  65 + </mat-form-field>
  66 + <span fxFlex></span>
  67 + <tb-profile-lwm2m-attributes
  68 + formControlName="attributes"
  69 + [isAttributeTelemetry]="isDisabledObserve($index)"
  70 + isResource="true"
  71 + [modelName]="getNameResourceLwm2m(resourceLwM2M.value)">
  72 + </tb-profile-lwm2m-attributes>
89 73 </div>
90 74 </div>
91 75 </section>
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host {
  17 + .resource-name{
  18 + white-space: nowrap;
  19 + overflow: hidden;
  20 + text-overflow: ellipsis;
  21 + }
  22 +
  23 + .checkbox-column-title {
  24 + text-align: center;
  25 + }
  26 +
  27 + .mat-divider {
  28 + margin-bottom: 4px;
  29 + }
  30 +
  31 + .resource-list {
  32 + padding-bottom: 8px;
  33 + height: 42px;
  34 + }
  35 +}
  36 +
  37 +:host ::ng-deep {
  38 + .resource-list {
  39 + mat-form-field {
  40 + .mat-form-field-wrapper {
  41 + padding-bottom: 0;
  42 + .mat-form-field-infix {
  43 + border-top-width: 0.2em;
  44 + width: auto;
  45 + min-width: auto;
  46 + }
  47 + .mat-form-field-underline {
  48 + bottom: 0;
  49 + }
  50 + .mat-form-field-subscript-wrapper{
  51 + margin-top: 1.8em;
  52 + }
  53 + }
  54 + }
  55 + }
  56 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, forwardRef, Input, OnDestroy } from '@angular/core';
  18 +import {
  19 + AbstractControl,
  20 + ControlValueAccessor,
  21 + FormArray,
  22 + FormBuilder,
  23 + FormGroup,
  24 + NG_VALIDATORS,
  25 + NG_VALUE_ACCESSOR,
  26 + ValidationErrors,
  27 + Validator,
  28 + Validators
  29 +} from '@angular/forms';
  30 +import { ResourceLwM2M } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models';
  31 +import { coerceBooleanProperty } from '@angular/cdk/coercion';
  32 +import { combineLatest, Subject, Subscription } from 'rxjs';
  33 +import { startWith, takeUntil } from 'rxjs/operators';
  34 +
  35 +@Component({
  36 + selector: 'tb-profile-lwm2m-observe-attr-telemetry-resource',
  37 + templateUrl: './lwm2m-observe-attr-telemetry-resources.component.html',
  38 + styleUrls: ['./lwm2m-observe-attr-telemetry-resources.component.scss'],
  39 + providers: [
  40 + {
  41 + provide: NG_VALUE_ACCESSOR,
  42 + useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryResourcesComponent),
  43 + multi: true
  44 + },
  45 + {
  46 + provide: NG_VALIDATORS,
  47 + useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryResourcesComponent),
  48 + multi: true
  49 + }
  50 + ]
  51 +})
  52 +
  53 +export class Lwm2mObserveAttrTelemetryResourcesComponent implements ControlValueAccessor, OnDestroy, Validator {
  54 +
  55 + resourcesFormGroup: FormGroup;
  56 +
  57 + @Input()
  58 + disabled = false;
  59 +
  60 + private requiredValue: boolean;
  61 + get required(): boolean {
  62 + return this.requiredValue;
  63 + }
  64 +
  65 + @Input()
  66 + set required(value: boolean) {
  67 + const newVal = coerceBooleanProperty(value);
  68 + if (this.requiredValue !== newVal) {
  69 + this.requiredValue = newVal;
  70 + }
  71 + }
  72 +
  73 + private destroy$ = new Subject();
  74 + private valueChange$: Subscription = null;
  75 + private propagateChange = (v: any) => { };
  76 +
  77 + constructor(private fb: FormBuilder) {
  78 + this.resourcesFormGroup = this.fb.group({
  79 + resources: this.fb.array([])
  80 + });
  81 + }
  82 +
  83 + ngOnDestroy() {
  84 + if (this.valueChange$) {
  85 + this.valueChange$.unsubscribe();
  86 + }
  87 + this.destroy$.next();
  88 + this.destroy$.complete();
  89 + }
  90 +
  91 + registerOnTouched(fn: any): void {
  92 + }
  93 +
  94 + registerOnChange(fn: any): void {
  95 + this.propagateChange = fn;
  96 + }
  97 +
  98 + writeValue(value: ResourceLwM2M[]): void {
  99 + this.updatedResources(value);
  100 + }
  101 +
  102 + setDisabledState(isDisabled: boolean): void {
  103 + this.disabled = isDisabled;
  104 + if (isDisabled) {
  105 + this.resourcesFormGroup.disable({emitEvent: false});
  106 + } else {
  107 + this.resourcesFormArray.controls.forEach(resource => {
  108 + resource.get('keyName').enable({emitEvent: false});
  109 + resource.get('attribute').enable({emitEvent: false});
  110 + resource.get('telemetry').enable({onlySelf: true});
  111 + resource.get('attributes').enable({emitEvent: false});
  112 + });
  113 + }
  114 + }
  115 +
  116 + validate(): ValidationErrors | null {
  117 + return this.resourcesFormGroup.valid ? null : {
  118 + resources: false
  119 + };
  120 + }
  121 +
  122 + get resourcesFormArray(): FormArray {
  123 + return this.resourcesFormGroup.get('resources') as FormArray;
  124 + }
  125 +
  126 + getNameResourceLwm2m(resourceLwM2M: ResourceLwM2M): string {
  127 + return `<${resourceLwM2M.id}> ${resourceLwM2M.name}`;
  128 + }
  129 +
  130 + private updatedResources(resources: ResourceLwM2M[]): void {
  131 + if (resources.length === this.resourcesFormArray.length) {
  132 + this.resourcesFormArray.patchValue(resources, {emitEvent: false});
  133 + } else {
  134 + if (this.valueChange$) {
  135 + this.valueChange$.unsubscribe();
  136 + }
  137 + const resourcesControl: Array<AbstractControl> = [];
  138 + if (resources) {
  139 + resources.forEach((resource) => {
  140 + resourcesControl.push(this.createdResourceFormGroup(resource));
  141 + });
  142 + }
  143 + this.resourcesFormGroup.setControl('resources', this.fb.array(resourcesControl));
  144 + if (this.disabled) {
  145 + this.resourcesFormGroup.disable({emitEvent: false});
  146 + }
  147 + this.valueChange$ = this.resourcesFormGroup.valueChanges.subscribe(value => {
  148 + this.updateModel(this.resourcesFormGroup.getRawValue().resources);
  149 + });
  150 + }
  151 + }
  152 +
  153 + private createdResourceFormGroup(resource: ResourceLwM2M): FormGroup {
  154 + const form = this.fb.group( {
  155 + id: [resource.id],
  156 + name: [resource.name],
  157 + attribute: [resource.attribute],
  158 + telemetry: [resource.telemetry],
  159 + observe: [resource.observe],
  160 + keyName: [resource.keyName, [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]],
  161 + attributes: [resource.attributes]
  162 + });
  163 + combineLatest([
  164 + form.get('attribute').valueChanges.pipe(startWith(resource.attribute), takeUntil(this.destroy$)),
  165 + form.get('telemetry').valueChanges.pipe(startWith(resource.telemetry), takeUntil(this.destroy$))
  166 + ]).subscribe(([attribute, telemetry]) => {
  167 + if (attribute || telemetry) {
  168 + form.get('observe').enable({emitEvent: false});
  169 + } else {
  170 + form.get('observe').disable({emitEvent: false});
  171 + form.get('observe').patchValue(false, {emitEvent: false});
  172 + form.get('attributes').patchValue({}, {emitEvent: false});
  173 + }
  174 + });
  175 + return form;
  176 + }
  177 +
  178 + private updateModel(value: ResourceLwM2M[]) {
  179 + if (value && this.resourcesFormGroup.valid) {
  180 + this.propagateChange(value);
  181 + } else {
  182 + this.propagateChange(null);
  183 + }
  184 + }
  185 +
  186 + trackByParams(index: number, resource: ResourceLwM2M): number {
  187 + return resource.id;
  188 + }
  189 +
  190 + isDisabledObserve(index: number): boolean{
  191 + return this.resourcesFormArray.at(index).get('observe').disabled;
  192 + }
  193 +}
... ...
... ... @@ -15,107 +15,32 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<section [formGroup]="observeAttrTelemetryFormGroup">
19   - <mat-accordion multi="true" formArrayName="clientLwM2M">
  18 +<section [formGroup]="modelsFormGroup">
  19 + <mat-accordion multi="true" formArrayName="models">
20 20 <mat-expansion-panel
21   - *ngFor="let objectLwM2M of clientLwM2MFormArray.controls; let i = index;"
22   - [formGroupName]="i">
  21 + *ngFor="let objectLwM2M of modelsFormArray.controls; let $index = index; trackBy: trackByParams"
  22 + [formGroupName]="$index">
23 23 <mat-expansion-panel-header >
24 24 <mat-panel-title fxLayoutAlign="start center" >
25   - <b><i>{{ objectLwM2M.get('name').value}}</i></b>&nbsp;&lt;{{ objectLwM2M.get('keyId').value}}>
26   - <div fxFlex class="resource-name-lw-end">
27   - <tb-profile-lwm2m-attributes
28   - formControlName="attributeLwm2m"
29   - [isAttributeTelemetry]="disableObserveObject(i)"
30   - [modelName]="getNameObjectLwm2m( objectLwM2M.get('name').value, objectLwM2M.get('keyId').value)"
31   - [disabled]="this.disabled"
32   - (updateAttributeLwm2m)="updateAttributeLwm2mObject($event, objectLwM2M.get('keyId').value)">
33   - </tb-profile-lwm2m-attributes>
34   - </div>
35   - </mat-panel-title>
36   - <mat-panel-description fxFlex="5" fxLayoutAlign="end center" *ngIf="!disabled && objectLwM2M.get('multiple').value">
  25 + <b><i>{{ objectLwM2M.get('name').value}}</i></b>&nbsp;&lt;{{ objectLwM2M.get('id').value}}>
  26 + <span fxFlex></span>
  27 + <tb-profile-lwm2m-attributes
  28 + formControlName="attributes"
  29 + [modelName]="getNameObject(objectLwM2M.value)">
  30 + </tb-profile-lwm2m-attributes>
37 31 <button type="button"
38   - mat-button mat-icon-button (click)="addInstances($event, objectLwM2M.value)"
39   - matTooltip="{{'device-profile.lwm2m.add-instances-tip' | translate}}"
  32 + *ngIf="!disabled && objectLwM2M.get('multiple').value"
  33 + mat-button mat-icon-button (click)="addInstances($event, objectLwM2M)"
  34 + matTooltip="{{'device-profile.lwm2m.add-new-instances' | translate}}"
40 35 matTooltipPosition="above">
41   - <mat-icon class="material-icons">{{'note_add'}}</mat-icon>
  36 + <mat-icon class="material-icons">note_add</mat-icon>
42 37 </button>
43   - </mat-panel-description>
  38 + </mat-panel-title>
44 39 </mat-expansion-panel-header>
45 40 <ng-template matExpansionPanelContent>
46   - <div fxLayout="column" fxLayoutGap="8px" formArrayName="instances">
47   - <mat-expansion-panel
48   - class="instance-list"
49   - *ngFor="let instances of instancesLwm2mFormArray(objectLwM2M).controls; let y = index;"
50   - [formGroupName]="y"
51   - [expanded]="getExpended(objectLwM2M)"
52   - [disabled]="getExpended(objectLwM2M)">
53   - <mat-expansion-panel-header>
54   - <mat-panel-title>
55   - <div class="tb-panel-title-height" fxFlex="100">
56   - <div fxLayout="row" fxFill>
57   - <div fxFlex="30">
58   - {{'device-profile.lwm2m.instance-label' | translate}} <<b>{{instances.get('id').value}}</b>>
59   - </div>
60   - <div class="checkbox-padding" fxFlex="10">
61   - <mat-checkbox color="warn"
62   - [disabled]="this.disabled"
63   - [checked]="getChecked(instances, 'attribute')"
64   - (click)="$event.stopPropagation()"
65   - (change)="changeInstanceResourcesCheckBox($event.checked, instances, 'attribute')"
66   - (keydown)="$event.stopPropagation()"
67   - [indeterminate]="getIndeterminate(instances, 'attribute')"
68   - matTooltip="{{'device-profile.lwm2m.is-attr-tip' | translate}}"
69   - matTooltipPosition="above">
70   - </mat-checkbox>
71   - </div>
72   - <div class="checkbox-padding" fxFlex="10">
73   - <mat-checkbox color="primary"
74   - [disabled]="this.disabled"
75   - [checked]="getChecked(instances, 'telemetry')"
76   - (click)="$event.stopPropagation()"
77   - (change)="changeInstanceResourcesCheckBox($event.checked, instances, 'telemetry')"
78   - (keydown)="$event.stopPropagation()"
79   - [indeterminate]="getIndeterminate(instances, 'telemetry')"
80   - matTooltip="{{'device-profile.lwm2m.is-telemetry-tip' | translate}}"
81   - matTooltipPosition="above">
82   - </mat-checkbox>
83   - </div>
84   - <div class="checkbox-padding" fxFlex="10">
85   - <mat-checkbox color="primary"
86   - [disabled]="this.disabled"
87   - [checked]="getChecked(instances, 'observe')"
88   - (click)="$event.stopPropagation()"
89   - (change)="changeInstanceResourcesCheckBox($event.checked, instances, 'observe')"
90   - (keydown)="$event.stopPropagation()"
91   - [indeterminate]="getIndeterminate(instances, 'observe')"
92   - matTooltip="{{'device-profile.lwm2m.is-observe-tip' | translate}}"
93   - matTooltipPosition="above">
94   - </mat-checkbox>
95   - </div>
96   - <div fxFlex="7">
97   - </div>
98   - <div fxFlex="37" class="resource-name-lw-end" fxFlexOffset="5">
99   - <tb-profile-lwm2m-attributes
100   - formControlName="attributeLwm2m"
101   - [isAttributeTelemetry]="disableObserveInstance(instances)"
102   - [modelName]="getNameInstanceLwm2m(instances.value, objectLwM2M.get('keyId').value)"
103   - [disabled]="this.disabled"
104   - (updateAttributeLwm2m)="updateAttributeLwm2mInstance($event, y, objectLwM2M.get('keyId').value)">
105   - </tb-profile-lwm2m-attributes>
106   - </div>
107   - </div>
108   - </div>
109   - </mat-panel-title>
110   - </mat-expansion-panel-header>
111   - <ng-template matExpansionPanelContent>
112   - <tb-profile-lwm2m-observe-attr-telemetry-resource
113   - formControlName="resources"
114   - [required]="required">
115   - </tb-profile-lwm2m-observe-attr-telemetry-resource>
116   - </ng-template>
117   - </mat-expansion-panel>
118   - </div>
  41 + <tb-profile-lwm2m-observe-attr-telemetry-instances
  42 + formControlName="instances">
  43 + </tb-profile-lwm2m-observe-attr-telemetry-instances>
119 44 </ng-template>
120 45 </mat-expansion-panel>
121 46 </mat-accordion>
... ...
... ... @@ -13,23 +13,13 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -.tb-panel-title-height {
17   - user-select: none;
18   - min-height: 32px;
19   -}
20   -
21   -.checkbox-padding {
22   - padding-left: 22px;
23   - text-align:center;
24   -}
25   -
26 16 :host{
27 17 section {
28 18 padding: 2px;
29 19 }
30   - .instance-list {
31   - mat-expansion-panel-header {
32   - color: inherit;
33   - }
  20 +
  21 + .tb-panel-title-height {
  22 + user-select: none;
  23 + min-height: 32px;
34 24 }
35 25 }
... ...
... ... @@ -14,38 +14,29 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { Component, forwardRef, Input } from '@angular/core';
  17 +import { ChangeDetectorRef, Component, forwardRef, Input, OnDestroy } from '@angular/core';
18 18 import {
19 19 AbstractControl,
20 20 ControlValueAccessor,
21 21 FormArray,
22 22 FormBuilder,
23 23 FormGroup,
  24 + NG_VALIDATORS,
24 25 NG_VALUE_ACCESSOR,
  26 + ValidationErrors,
  27 + Validator,
25 28 Validators
26 29 } from '@angular/forms';
27   -import { Store } from '@ngrx/store';
28   -import { AppState } from '@core/core.state';
29 30 import { coerceBooleanProperty } from '@angular/cdk/coercion';
30   -import {
31   - ATTRIBUTE,
32   - ATTRIBUTE_LWM2M,
33   - CLIENT_LWM2M,
34   - Instance,
35   - INSTANCES,
36   - ObjectLwM2M,
37   - ResourceLwM2M,
38   - RESOURCES,
39   - TELEMETRY
40   -} from './lwm2m-profile-config.models';
41   -import { deepClone, isDefinedAndNotNull, isEqual, isUndefined } from '@core/utils';
  31 +import { Instance, ObjectLwM2M } from './lwm2m-profile-config.models';
  32 +import { deepClone, isDefinedAndNotNull, isEqual } from '@core/utils';
42 33 import { MatDialog } from '@angular/material/dialog';
43   -import { TranslateService } from '@ngx-translate/core';
44 34 import {
45 35 Lwm2mObjectAddInstancesData,
46 36 Lwm2mObjectAddInstancesDialogComponent
47 37 } from '@home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component';
48 38 import _ from 'lodash';
  39 +import { Subscription } from 'rxjs';
49 40
50 41 @Component({
51 42 selector: 'tb-profile-lwm2m-observe-attr-telemetry',
... ... @@ -56,18 +47,20 @@ import _ from 'lodash';
56 47 provide: NG_VALUE_ACCESSOR,
57 48 useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryComponent),
58 49 multi: true
  50 + },
  51 + {
  52 + provide: NG_VALIDATORS,
  53 + useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryComponent),
  54 + multi: true
59 55 }
60 56 ]
61 57 })
62 58
63   -export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor {
  59 +export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor, OnDestroy, Validator {
64 60
65   - private requiredValue: boolean;
66   -
67   - valuePrev = null as any;
68   - observeAttrTelemetryFormGroup: FormGroup;
69   - resources = RESOURCES;
  61 + modelsFormGroup: FormGroup;
70 62
  63 + private requiredValue: boolean;
71 64 get required(): boolean {
72 65 return this.requiredValue;
73 66 }
... ... @@ -84,132 +77,103 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor
84 77 @Input()
85 78 disabled: boolean;
86 79
87   - constructor(private store: Store<AppState>,
88   - private fb: FormBuilder,
  80 + private valueChange$: Subscription = null;
  81 + private propagateChange = (v: any) => { };
  82 +
  83 + constructor(private fb: FormBuilder,
89 84 private dialog: MatDialog,
90   - public translate: TranslateService) {
91   - this.observeAttrTelemetryFormGroup = this.fb.group({
92   - [CLIENT_LWM2M]: this.fb.array([])
93   - });
94   - this.observeAttrTelemetryFormGroup.valueChanges.subscribe(value => {
95   - if (isUndefined(this.disabled) || !this.disabled) {
96   - this.propagateChangeState(value);
97   - }
  85 + private cd: ChangeDetectorRef) {
  86 + this.modelsFormGroup = this.fb.group({
  87 + models: this.fb.array([])
98 88 });
99 89 }
100 90
101   - private propagateChange = (v: any) => {
102   - };
  91 + ngOnDestroy() {
  92 + if (this.valueChange$) {
  93 + this.valueChange$.unsubscribe();
  94 + }
  95 + }
103 96
104 97 registerOnChange(fn: any): void {
105 98 this.propagateChange = fn;
106 99 }
107 100
108   - private propagateChangeState = (value: any): void => {
109   - if (value) {
110   - if (this.valuePrev === null) {
111   - this.valuePrev = 'init';
112   - } else if (this.valuePrev === 'init') {
113   - this.valuePrev = value;
114   - } else if (JSON.stringify(value) !== JSON.stringify(this.valuePrev)) {
115   - this.valuePrev = value;
116   - if (this.observeAttrTelemetryFormGroup.valid) {
117   - this.propagateChange(value);
118   - } else {
119   - this.propagateChange(null);
120   - }
121   - }
122   - }
123   - }
124   -
125 101 registerOnTouched(fn: any): void {
126 102 }
127 103
128 104 setDisabledState(isDisabled: boolean): void {
129 105 this.disabled = isDisabled;
130   - this.valuePrev = null;
131 106 if (isDisabled) {
132   - this.observeAttrTelemetryFormGroup.disable();
  107 + this.modelsFormGroup.disable({emitEvent: false});
133 108 } else {
134   - this.observeAttrTelemetryFormGroup.enable();
  109 + this.modelsFormGroup.enable({emitEvent: false});
135 110 }
136 111 }
137 112
138   - writeValue(value: {}): void {
  113 + writeValue(value: ObjectLwM2M[]): void {
139 114 if (isDefinedAndNotNull(value)) {
140   - this.buildClientObjectsLwM2M(value[CLIENT_LWM2M]);
  115 + this.updateModels(value);
141 116 }
142 117 }
143 118
144   - private buildClientObjectsLwM2M = (objectsLwM2M: ObjectLwM2M []): void => {
145   - this.observeAttrTelemetryFormGroup.setControl(CLIENT_LWM2M,
146   - this.createObjectsLwM2M(objectsLwM2M)
147   - );
  119 + validate(): ValidationErrors | null {
  120 + return this.modelsFormGroup.valid ? null : {
  121 + modelsFormGroup: false
  122 + };
148 123 }
149 124
150   - private createObjectsLwM2M = (objectsLwM2M: ObjectLwM2M[]): FormArray => {
151   - return this.fb.array(objectsLwM2M.map((objectLwM2M) => {
152   - return this.fb.group({
153   - keyId: objectLwM2M.keyId,
154   - name: objectLwM2M.name,
155   - multiple: objectLwM2M.multiple,
156   - mandatory: objectLwM2M.mandatory,
157   - attributeLwm2m: objectLwM2M.attributeLwm2m,
158   - instances: this.createInstanceLwM2M(objectLwM2M.instances)
159   - });
160   - }));
  125 + get modelsFormArray(): FormArray {
  126 + return this.modelsFormGroup.get('models') as FormArray;
161 127 }
162 128
163   - private createInstanceLwM2M = (instancesLwM2M: Instance[]): FormArray => {
164   - return this.fb.array(instancesLwM2M.map((instanceLwM2M) => {
165   - return this.fb.group({
166   - id: instanceLwM2M.id,
167   - attributeLwm2m: {value: instanceLwM2M.attributeLwm2m, disabled: this.disabled},
168   - resources: {value: instanceLwM2M.resources, disabled: this.disabled}
  129 + private updateModels(models: ObjectLwM2M[]) {
  130 + if (models.length === this.modelsFormArray.length) {
  131 + this.modelsFormArray.patchValue(models, {emitEvent: false});
  132 + } else {
  133 + if (this.valueChange$) {
  134 + this.valueChange$.unsubscribe();
  135 + }
  136 + const modelControls: Array<AbstractControl> = [];
  137 + models.forEach(model => {
  138 + modelControls.push(this.createModelFormGroup(model));
169 139 });
170   - }));
171   - }
172   -
173   - get clientLwM2MFormArray(): FormArray {
174   - return this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray;
175   - }
176   -
177   - instancesLwm2mFormArray = (objectLwM2M: AbstractControl): FormArray => {
178   - return objectLwM2M.get(INSTANCES) as FormArray;
179   - }
180   -
181   - changeInstanceResourcesCheckBox = (value: boolean, instance: AbstractControl, type: string): void => {
182   - const resources = deepClone(instance.get(RESOURCES).value as ResourceLwM2M[]);
183   - resources.forEach(resource => resource[type] = value);
184   - instance.get(RESOURCES).patchValue(resources);
185   - this.propagateChange(this.observeAttrTelemetryFormGroup.value);
186   - }
187   -
188   - private updateValidators = (): void => {
189   - this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M).setValidators(this.required ? Validators.required : []);
190   - this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M).updateValueAndValidity();
  140 + this.modelsFormGroup.setControl('models', this.fb.array(modelControls));
  141 + if (this.disabled) {
  142 + this.modelsFormGroup.disable({emitEvent: false});
  143 + }
  144 + this.valueChange$ = this.modelsFormGroup.valueChanges.subscribe(value => {
  145 + this.updateModel(value.models);
  146 + });
  147 + }
191 148 }
192 149
193   - trackByParams = (index: number, element: any): number => {
194   - return index;
  150 + private createModelFormGroup(objectLwM2M: ObjectLwM2M): FormGroup {
  151 + return this.fb.group({
  152 + id: [objectLwM2M.id],
  153 + keyId: [objectLwM2M.keyId],
  154 + name: [objectLwM2M.name],
  155 + multiple: [objectLwM2M.multiple],
  156 + mandatory: [objectLwM2M.mandatory],
  157 + attributes: [objectLwM2M.attributes],
  158 + instances: [objectLwM2M.instances]
  159 + });
195 160 }
196 161
197   - getIndeterminate = (instance: AbstractControl, type: string): boolean => {
198   - const resources = instance.get(RESOURCES).value as ResourceLwM2M[];
199   - if (isDefinedAndNotNull(resources)) {
200   - const checkedResource = resources.filter(resource => resource[type]);
201   - return checkedResource.length !== 0 && checkedResource.length !== resources.length;
  162 + private updateModel = (value: ObjectLwM2M[]): void => {
  163 + if (value && this.modelsFormGroup.valid) {
  164 + this.propagateChange(value);
  165 + } else {
  166 + this.propagateChange(null);
202 167 }
203   - return false;
204 168 }
205 169
206   - getChecked = (instance: AbstractControl, type: string): boolean => {
207   - const resources = instance.get(RESOURCES).value as ResourceLwM2M[];
208   - return isDefinedAndNotNull(resources) && resources.every(resource => resource[type]);
  170 + private updateValidators = (): void => {
  171 + this.modelsFormArray.setValidators(this.required ? Validators.required : []);
  172 + this.modelsFormArray.updateValueAndValidity();
209 173 }
210 174
211   - getExpended = (objectLwM2M: AbstractControl): boolean => {
212   - return this.instancesLwm2mFormArray(objectLwM2M).length === 1;
  175 + trackByParams = (index: number, objectLwM2M: ObjectLwM2M): number => {
  176 + return objectLwM2M.id;
213 177 }
214 178
215 179 /**
... ... @@ -230,137 +194,76 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor
230 194 * Object Instance ID cnt_max = cnt_min = 1 (всегда есть один)
231 195 */
232 196
233   - addInstances = ($event: Event, object: ObjectLwM2M): void => {
  197 + addInstances = ($event: Event, control: AbstractControl): void => {
234 198 if ($event) {
235 199 $event.stopPropagation();
236 200 $event.preventDefault();
237 201 }
238   - this.dialog.open<Lwm2mObjectAddInstancesDialogComponent, Lwm2mObjectAddInstancesData, object>(Lwm2mObjectAddInstancesDialogComponent, {
  202 + const object: ObjectLwM2M = control.value;
  203 + const instancesId: Set<number> = this.instancesToSetId(object.instances);
  204 + this.dialog.open<Lwm2mObjectAddInstancesDialogComponent, Lwm2mObjectAddInstancesData>(Lwm2mObjectAddInstancesDialogComponent, {
239 205 disableClose: true,
240 206 panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
241 207 data: {
242   - instancesIds: this.instancesToSetId(object.instances),
  208 + instancesId: new Set(instancesId),
243 209 objectName: object.name,
244   - objectKeyId: object.keyId
  210 + objectId: object.id
245 211 }
246 212 }).afterClosed().subscribe(
247   - (res: Lwm2mObjectAddInstancesData | undefined) => {
  213 + (res: Set<number> | null) => {
248 214 if (isDefinedAndNotNull(res)) {
249   - this.updateInstancesIds(res);
  215 + this.updateInstancesIds(res, control, instancesId);
250 216 }
251 217 }
252 218 );
253 219 }
254 220
255   - private updateInstancesIds = (data: Lwm2mObjectAddInstancesData): void => {
256   - const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).controls
257   - .find(e => e.value.keyId === data.objectKeyId) as FormGroup;
258   - const instancesArray = objectLwM2MFormGroup.value.instances as Instance [];
259   - const instancesFormArray = objectLwM2MFormGroup.get(INSTANCES) as FormArray;
260   - const instance0 = deepClone(instancesFormArray.at(0).value as Instance);
261   - instance0.resources.forEach(r => {
262   - r.attribute = false;
263   - r.telemetry = false;
264   - r.observe = false;
265   - r.attributeLwm2m = {};
  221 + private updateInstancesIds(instancesId: Set<number>, control: AbstractControl, prevInstancesId: Set<number>) {
  222 + let instancesValue: Instance[] = control.get('instances').value;
  223 + const instanceTemplate = deepClone(instancesValue[0]);
  224 + instanceTemplate.attributes = {};
  225 + instanceTemplate.resources.forEach(resource => {
  226 + resource.attribute = false;
  227 + resource.telemetry = false;
  228 + resource.observe = false;
  229 + resource.keyName = _.camelCase(resource.name);
  230 + resource.attributes = {};
266 231 });
267   - const valueOld = this.instancesToSetId(instancesArray);
268   - if (!isEqual(valueOld, data.instancesIds)) {
269   - const idsDel = this.diffBetweenSet(valueOld, data.instancesIds);
270   - const idsAdd = this.diffBetweenSet(data.instancesIds, valueOld);
  232 + if (!isEqual(prevInstancesId, instancesId)) {
  233 + const idsDel = this.diffBetweenSet(prevInstancesId, instancesId);
  234 + const idsAdd = this.diffBetweenSet(instancesId, prevInstancesId);
271 235 if (idsAdd.size) {
272   - this.addInstancesNew(idsAdd, objectLwM2MFormGroup, instancesFormArray, instance0);
  236 + idsAdd.forEach(id => {
  237 + const template = deepClone(instanceTemplate);
  238 + template.resources.forEach(resource => resource.keyName += id);
  239 + template.id = id;
  240 + instancesValue.push(template);
  241 + });
273 242 }
274 243 if (idsDel.size) {
275   - this.deleteInstances(idsDel, objectLwM2MFormGroup, instancesFormArray, instance0);
  244 + instancesValue = instancesValue.filter(instance => !idsDel.has(instance.id));
  245 + if (instancesValue.length === 0) {
  246 + instanceTemplate.id = 0;
  247 + instancesValue.push(instanceTemplate);
  248 + }
  249 + }
  250 + if (idsAdd.size || idsDel.size) {
  251 + instancesValue.sort((a, b) => a.id - b.id);
  252 + control.get('instances').patchValue(instancesValue);
  253 + this.cd.markForCheck();
276 254 }
277 255 }
278 256 }
279 257
280   - private addInstancesNew = (idsAdd: Set<number>, objectLwM2MFormGroup: FormGroup, instancesFormArray: FormArray,
281   - instanceNew: Instance): void => {
282   - idsAdd.forEach(x => {
283   - instanceNew.resources.forEach(resource => {resource.keyName = _.camelCase(resource.name + x);});
284   - this.pushInstance(instancesFormArray, x, deepClone(instanceNew as Instance));
285   - });
286   - (instancesFormArray.controls as FormGroup[]).sort((a, b) => a.value.id - b.value.id);
287   - }
288   -
289   - private deleteInstances = (idsDel: Set<number>, objectLwM2MFormGroup: FormGroup, instancesFormArray: FormArray,
290   - instance0: Instance): void => {
291   - idsDel.forEach(x => {
292   - const instanceIndex = instancesFormArray.value.findIndex(element => element.id === x);
293   - instancesFormArray.removeAt(instanceIndex);
294   - });
295   - if (instancesFormArray.length === 0) {
296   - this.pushInstance(instancesFormArray, 0, instance0);
297   - }
298   - (instancesFormArray.controls as FormGroup[]).sort((a, b) => a.value.id - b.value.id);
299   - }
300   -
301   - private pushInstance = (instancesFormArray: FormArray, x: number, instanceNew: Instance): void => {
302   - instancesFormArray.push(this.fb.group({
303   - id: x,
304   - attributeLwm2m: instanceNew.attributeLwm2m,
305   - resources: {value: instanceNew.resources, disabled: this.disabled}
306   - }));
307   - }
308   -
309 258 private diffBetweenSet<T>(firstSet: Set<T>, secondSet: Set<T>): Set<T> {
310 259 return new Set([...Array.from(firstSet)].filter(x => !secondSet.has(x)));
311 260 }
312 261
313   - private instancesToSetId = (instances: Instance[]): Set<number> => {
  262 + private instancesToSetId(instances: Instance[]): Set<number> {
314 263 return new Set(instances.map(x => x.id));
315 264 }
316 265
317   - getNameObjectLwm2m = (objectName: string, idVerObj: string): string => {
318   - return objectName + ' <' + idVerObj + '>';
319   - }
320   - getNameInstanceLwm2m = (instance: Instance, idVerObj: string): string => {
321   - return ' instance <' + idVerObj + '/' + instance.id +'>';
322   - }
323   -
324   - updateAttributeLwm2mObject = (event: Event, objectKeyId: number): void => {
325   - const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).controls
326   - .find(e => e.value.keyId === objectKeyId) as FormGroup;
327   - objectLwM2MFormGroup.patchValue({attributeLwm2m: event});
328   - }
329   -
330   - updateAttributeLwm2mInstance = (event: Event, indexInstance: number, objectKeyId: number): void => {
331   -
332   - const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).controls
333   - .find(e => e.value.keyId === objectKeyId) as FormGroup;
334   - const instancesFormArray = objectLwM2MFormGroup.get(INSTANCES) as FormArray;
335   - instancesFormArray.at(indexInstance).patchValue({attributeLwm2m: event});
336   - }
337   -
338   - disableObserveInstance = (instance: AbstractControl): boolean => {
339   - const checkedAttrTelemetry = this.observeInstance(instance);
340   - if (checkedAttrTelemetry) {
341   - instance.get(ATTRIBUTE_LWM2M).patchValue(null);
342   - }
343   - return checkedAttrTelemetry;
344   - }
345   -
346   - disableObserveObject = (index: number): boolean => {
347   - const object = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).at(index) as FormGroup;
348   - const instances = object.controls.instances as FormArray;
349   - const checkedAttrTelemetry = instances.controls.filter(instance => !this.disableObserveInstance(instance));
350   - if (checkedAttrTelemetry.length === 0) {
351   - object.controls.attributeLwm2m.patchValue(null);
352   - }
353   - return checkedAttrTelemetry.length === 0;
354   - }
355   -
356   -
357   - observeInstance = (instance: AbstractControl): boolean => {
358   - const resources = instance.get(RESOURCES).value as ResourceLwM2M[];
359   - if (isDefinedAndNotNull(resources)) {
360   - const checkedAttribute = resources.filter(resource => resource[ATTRIBUTE]);
361   - const checkedTelemetry = resources.filter(resource => resource[TELEMETRY]);
362   - return checkedAttribute.length === 0 && checkedTelemetry.length === 0;
363   - }
364   - return false;
  266 + getNameObject = (objectLwM2M: ObjectLwM2M): string => {
  267 + return `${objectLwM2M.name} <${objectLwM2M.id}>`;
365 268 }
366 269 }
... ...
... ... @@ -18,7 +18,7 @@ import { NgModule } from '@angular/core';
18 18 import { Lwm2mDeviceProfileTransportConfigurationComponent } from './lwm2m-device-profile-transport-configuration.component';
19 19 import { Lwm2mObjectListComponent } from './lwm2m-object-list.component';
20 20 import { Lwm2mObserveAttrTelemetryComponent } from './lwm2m-observe-attr-telemetry.component';
21   -import { Lwm2mObserveAttrTelemetryResourceComponent } from './lwm2m-observe-attr-telemetry-resource.component';
  21 +import { Lwm2mObserveAttrTelemetryResourcesComponent } from './lwm2m-observe-attr-telemetry-resources.component';
22 22 import { Lwm2mAttributesDialogComponent } from './lwm2m-attributes-dialog.component';
23 23 import { Lwm2mAttributesComponent } from './lwm2m-attributes.component';
24 24 import { Lwm2mAttributesKeyListComponent } from './lwm2m-attributes-key-list.component';
... ... @@ -27,6 +27,7 @@ import { Lwm2mObjectAddInstancesDialogComponent } from './lwm2m-object-add-insta
27 27 import { Lwm2mObjectAddInstancesListComponent } from './lwm2m-object-add-instances-list.component';
28 28 import { CommonModule } from '@angular/common';
29 29 import { SharedModule } from '@app/shared/shared.module';
  30 +import { Lwm2mObserveAttrTelemetryInstancesComponent } from './lwm2m-observe-attr-telemetry-instances.component';
30 31
31 32 @NgModule({
32 33 declarations:
... ... @@ -34,13 +35,14 @@ import { SharedModule } from '@app/shared/shared.module';
34 35 Lwm2mDeviceProfileTransportConfigurationComponent,
35 36 Lwm2mObjectListComponent,
36 37 Lwm2mObserveAttrTelemetryComponent,
37   - Lwm2mObserveAttrTelemetryResourceComponent,
  38 + Lwm2mObserveAttrTelemetryResourcesComponent,
38 39 Lwm2mAttributesDialogComponent,
39 40 Lwm2mAttributesComponent,
40 41 Lwm2mAttributesKeyListComponent,
41 42 Lwm2mDeviceConfigServerComponent,
42 43 Lwm2mObjectAddInstancesDialogComponent,
43   - Lwm2mObjectAddInstancesListComponent
  44 + Lwm2mObjectAddInstancesListComponent,
  45 + Lwm2mObserveAttrTelemetryInstancesComponent
44 46 ],
45 47 imports: [
46 48 CommonModule,
... ... @@ -50,13 +52,14 @@ import { SharedModule } from '@app/shared/shared.module';
50 52 Lwm2mDeviceProfileTransportConfigurationComponent,
51 53 Lwm2mObjectListComponent,
52 54 Lwm2mObserveAttrTelemetryComponent,
53   - Lwm2mObserveAttrTelemetryResourceComponent,
  55 + Lwm2mObserveAttrTelemetryResourcesComponent,
54 56 Lwm2mAttributesDialogComponent,
55 57 Lwm2mAttributesComponent,
56 58 Lwm2mAttributesKeyListComponent,
57 59 Lwm2mDeviceConfigServerComponent,
58 60 Lwm2mObjectAddInstancesDialogComponent,
59   - Lwm2mObjectAddInstancesListComponent
  61 + Lwm2mObjectAddInstancesListComponent,
  62 + Lwm2mObserveAttrTelemetryInstancesComponent
60 63 ],
61 64 providers: [
62 65 ]
... ...
... ... @@ -20,8 +20,6 @@ export const PAGE_SIZE_LIMIT = 50;
20 20 export const INSTANCES = 'instances';
21 21 export const INSTANCE = 'instance';
22 22 export const RESOURCES = 'resources';
23   -export const ATTRIBUTE_LWM2M = 'attributeLwm2m';
24   -export const CLIENT_LWM2M = 'clientLwM2M';
25 23 export const OBSERVE_ATTR_TELEMETRY = 'observeAttrTelemetry';
26 24 export const OBSERVE = 'observe';
27 25 export const ATTRIBUTE = 'attribute';
... ... @@ -184,7 +182,7 @@ export interface ObservableAttributes {
184 182 attribute: string[];
185 183 telemetry: string[];
186 184 keyName: {};
187   - attributeLwm2m?: AttributesNameValueMap;
  185 + attributeLwm2m?: AttributesNameValueMap[];
188 186 }
189 187
190 188 export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecurityConfig {
... ... @@ -222,7 +220,7 @@ export function getDefaultProfileObserveAttrConfig(): ObservableAttributes {
222 220 attribute: [],
223 221 telemetry: [],
224 222 keyName: {},
225   - attributeLwm2m: {}
  223 + attributeLwm2m: []
226 224 };
227 225 }
228 226
... ... @@ -237,6 +235,8 @@ export function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSetting
237 235 };
238 236 }
239 237
  238 +export type ResourceSettingTelemetry = 'observe' | 'attribute' | 'telemetry';
  239 +
240 240 export interface ResourceLwM2M {
241 241 id: number;
242 242 name: string;
... ... @@ -244,12 +244,12 @@ export interface ResourceLwM2M {
244 244 attribute: boolean;
245 245 telemetry: boolean;
246 246 keyName: string;
247   - attributeLwm2m?: AttributesNameValueMap;
  247 + attributes?: AttributesNameValueMap;
248 248 }
249 249
250 250 export interface Instance {
251 251 id: number;
252   - attributeLwm2m?: AttributesNameValueMap;
  252 + attributes?: AttributesNameValueMap;
253 253 resources: ResourceLwM2M[];
254 254 }
255 255
... ... @@ -265,7 +265,7 @@ export interface ObjectLwM2M {
265 265 name: string;
266 266 multiple?: boolean;
267 267 mandatory?: boolean;
268   - attributeLwm2m?: AttributesNameValueMap;
  268 + attributes?: AttributesNameValueMap;
269 269 instances?: Instance [];
270 270 }
271 271
... ...
... ... @@ -1228,25 +1228,20 @@
1228 1228 "valid-id-instance-no-max": "Instance number '{{instance}}' no validated. Max value='{{max}}'",
1229 1229 "valid-id-instance": "Instance number '{{instance}}' no validated. { count, plural, 1 {Max value='{{max}}'} 2 {Min value='{{min}}'} other {Must be only number} }",
1230 1230 "model-tab": "LWM2M Model",
1231   - "add-instances-tip": "Add new instances",
  1231 + "add-new-instances": "Add new instances",
1232 1232 "instances-list": "Instances list",
1233 1233 "instances-input": "Input Instance Id value",
1234 1234 "instances-input-holder": "Input Instance number...",
1235   - "instance-label": "Instance",
  1235 + "instance": "Instance",
1236 1236 "resource-label": "<id> Resource name",
1237 1237 "observe-label": "Observe",
1238 1238 "attribute-label": "Attribute",
1239 1239 "telemetry-label": "Telemetry",
1240   - "key-name-label": "Key Name",
1241   - "resource-tip": "ID & Original Name of the Resource (only Operations isReadable)",
1242   - "is-observe-tip": "Is Observe",
1243   - "not-observe-tip": "To observe select telemetry or attributes first",
1244   - "is-attr-tip": "Is Attribute",
1245   - "is-telemetry-tip": "Is Telemetry",
1246   - "key-name-tip": "Key Name in Camel format",
1247   - "edit-attributes-select": "To edit attributes select telemetry or attributes",
  1240 + "edit-observe-select": "To edit observe select telemetry or attribute",
  1241 + "edit-attributes-select": "To edit attributes select telemetry or attribute",
1248 1242 "no-attributes-set": "No attributes set",
1249   - "key-name": "Key Name",
  1243 + "key-name": "Key name",
  1244 + "key-name-required": "Key name is required",
1250 1245 "attribute-name": "Name attribute",
1251 1246 "attribute-name-required": "Name attribute is required.",
1252 1247 "attribute-value": "Attribute value",
... ...