committed by
Andrew Shvayka
20 changed files with 836 additions and 641 deletions
@ -0,0 +1,70 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 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. |
||||
|
|
||||
|
--> |
||||
|
<section [formGroup]="instancesFormGroup"> |
||||
|
<mat-accordion multi="true" formArrayName="instances"> |
||||
|
<mat-expansion-panel |
||||
|
class="instance-row" |
||||
|
*ngFor="let instances of instancesFormArray.controls; let $index = index; trackBy: trackByParams" |
||||
|
[formGroupName]="$index" |
||||
|
[expanded]="isExpend" |
||||
|
[disabled]="isExpend"> |
||||
|
<mat-expansion-panel-header> |
||||
|
<mat-panel-title class="tb-panel-title-height" fxLayout="row"> |
||||
|
<div fxFlex="30" fxLayoutAlign="start center"> |
||||
|
{{ 'device-profile.lwm2m.instance' | translate }} <<b>{{instances.get('id').value}}</b>> |
||||
|
</div> |
||||
|
<div fxLayoutAlign="center center" fxFlex="10"> |
||||
|
<mat-checkbox color="warn" |
||||
|
[disabled]="this.disabled" |
||||
|
[checked]="getChecked(instances, 'attribute')" |
||||
|
(change)="changeInstanceResourcesCheckBox($event.checked, instances, 'attribute')" |
||||
|
[indeterminate]="getIndeterminate(instances, 'attribute')"> |
||||
|
</mat-checkbox> |
||||
|
</div> |
||||
|
<div fxLayoutAlign="center center" fxFlex="10"> |
||||
|
<mat-checkbox color="primary" |
||||
|
[disabled]="this.disabled" |
||||
|
[checked]="getChecked(instances, 'telemetry')" |
||||
|
(change)="changeInstanceResourcesCheckBox($event.checked, instances, 'telemetry')" |
||||
|
[indeterminate]="getIndeterminate(instances, 'telemetry')"> |
||||
|
</mat-checkbox> |
||||
|
</div> |
||||
|
<div fxLayoutAlign="center center" fxFlex="10"> |
||||
|
<mat-checkbox color="primary" |
||||
|
[disabled]="this.disabled || !(getIndeterminate(instances, 'telemetry') || getIndeterminate(instances, 'attribute'))" |
||||
|
[checked]="getChecked(instances, 'observe')" |
||||
|
(change)="changeInstanceResourcesCheckBox($event.checked, instances, 'observe')" |
||||
|
[indeterminate]="getIndeterminate(instances, 'observe')"> |
||||
|
</mat-checkbox> |
||||
|
</div> |
||||
|
<span fxFlex></span> |
||||
|
<tb-profile-lwm2m-attributes |
||||
|
formControlName="attributes" |
||||
|
[isAttributeTelemetry]="disableObserveInstance(instances)" |
||||
|
[modelName]="getNameInstance(instances.value)"> |
||||
|
</tb-profile-lwm2m-attributes> |
||||
|
</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<ng-template matExpansionPanelContent> |
||||
|
<tb-profile-lwm2m-observe-attr-telemetry-resource |
||||
|
formControlName="resources"> |
||||
|
</tb-profile-lwm2m-observe-attr-telemetry-resource> |
||||
|
</ng-template> |
||||
|
</mat-expansion-panel> |
||||
|
</mat-accordion> |
||||
|
</section> |
||||
@ -0,0 +1,219 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2021 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, forwardRef, Input, OnDestroy } from '@angular/core'; |
||||
|
import { |
||||
|
AbstractControl, |
||||
|
ControlValueAccessor, |
||||
|
FormArray, |
||||
|
FormBuilder, |
||||
|
FormGroup, |
||||
|
NG_VALIDATORS, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
ValidationErrors, |
||||
|
Validator, |
||||
|
Validators |
||||
|
} from '@angular/forms'; |
||||
|
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
import { Instance, ResourceLwM2M, ResourceSettingTelemetry, } from './lwm2m-profile-config.models'; |
||||
|
import { deepClone, isDefinedAndNotNull } from '@core/utils'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { Subscription } from 'rxjs'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-profile-lwm2m-observe-attr-telemetry-instances', |
||||
|
templateUrl: './lwm2m-observe-attr-telemetry-instances.component.html', |
||||
|
styleUrls: [ './lwm2m-observe-attr-telemetry-instances.component.scss'], |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryInstancesComponent), |
||||
|
multi: true |
||||
|
}, |
||||
|
{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryInstancesComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
] |
||||
|
}) |
||||
|
|
||||
|
export class Lwm2mObserveAttrTelemetryInstancesComponent implements ControlValueAccessor, Validator, OnDestroy { |
||||
|
|
||||
|
instancesFormGroup: FormGroup; |
||||
|
|
||||
|
private requiredValue: boolean; |
||||
|
get required(): boolean { |
||||
|
return this.requiredValue; |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
set required(value: boolean) { |
||||
|
const newVal = coerceBooleanProperty(value); |
||||
|
if (this.requiredValue !== newVal) { |
||||
|
this.requiredValue = newVal; |
||||
|
this.updateValidators(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
private valueChange$: Subscription = null; |
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private fb: FormBuilder, |
||||
|
public translate: TranslateService) { |
||||
|
this.instancesFormGroup = this.fb.group({ |
||||
|
instances: this.fb.array([]) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
if (this.valueChange$) { |
||||
|
this.valueChange$.unsubscribe(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (isDisabled) { |
||||
|
this.instancesFormGroup.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.instancesFormGroup.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writeValue(value: Instance[]): void { |
||||
|
this.updateInstances(value); |
||||
|
} |
||||
|
|
||||
|
validate(control: AbstractControl): ValidationErrors | null { |
||||
|
return this.instancesFormGroup.valid ? null : { |
||||
|
instancesForm: false |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
get instancesFormArray(): FormArray { |
||||
|
return this.instancesFormGroup.get('instances') as FormArray; |
||||
|
} |
||||
|
|
||||
|
private updateInstances(instances: Instance[]): void { |
||||
|
if (instances.length === this.instancesFormArray.length) { |
||||
|
this.instancesFormArray.patchValue(instances, {emitEvent: false}); |
||||
|
} else { |
||||
|
if (this.valueChange$) { |
||||
|
this.valueChange$.unsubscribe(); |
||||
|
} |
||||
|
const instancesControl: Array<AbstractControl> = []; |
||||
|
if (instances) { |
||||
|
instances.forEach((instance) => { |
||||
|
instancesControl.push(this.createInstanceFormGroup(instance)); |
||||
|
}); |
||||
|
} |
||||
|
this.instancesFormGroup.setControl('instances', this.fb.array(instancesControl)); |
||||
|
if (this.disabled) { |
||||
|
this.instancesFormGroup.disable({emitEvent: false}); |
||||
|
} |
||||
|
this.valueChange$ = this.instancesFormGroup.valueChanges.subscribe(value => { |
||||
|
this.updateModel(value.instances); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private createInstanceFormGroup(instance: Instance): FormGroup { |
||||
|
return this.fb.group({ |
||||
|
id: [instance.id], |
||||
|
attributes: [instance.attributes], |
||||
|
resources: [instance.resources] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private updateModel(instances: Instance[]) { |
||||
|
if (instances && this.instancesFormGroup.valid) { |
||||
|
this.propagateChange(instances); |
||||
|
} else { |
||||
|
this.propagateChange(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
changeInstanceResourcesCheckBox = (value: boolean, instance: AbstractControl, type: ResourceSettingTelemetry): void => { |
||||
|
const resources = deepClone(instance.get('resources').value as ResourceLwM2M[]); |
||||
|
if (value && type === 'observe') { |
||||
|
resources.forEach(resource => resource[type] = resource.telemetry || resource.attribute); |
||||
|
} else { |
||||
|
resources.forEach(resource => resource[type] = value); |
||||
|
} |
||||
|
instance.get('resources').patchValue(resources); |
||||
|
} |
||||
|
|
||||
|
private updateValidators(): void { |
||||
|
this.instancesFormArray.setValidators(this.required ? Validators.required : []); |
||||
|
this.instancesFormArray.updateValueAndValidity(); |
||||
|
} |
||||
|
|
||||
|
trackByParams = (index: number, instance: Instance): number => { |
||||
|
return instance.id; |
||||
|
} |
||||
|
|
||||
|
getIndeterminate = (instance: AbstractControl, type: ResourceSettingTelemetry): boolean => { |
||||
|
const resources = instance.get('resources').value as ResourceLwM2M[]; |
||||
|
if (isDefinedAndNotNull(resources)) { |
||||
|
const checkedResource = resources.filter(resource => resource[type]); |
||||
|
return checkedResource.length !== 0 && checkedResource.length !== resources.length; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
getChecked = (instance: AbstractControl, type: ResourceSettingTelemetry): boolean => { |
||||
|
const resources = instance.get('resources').value as ResourceLwM2M[]; |
||||
|
return isDefinedAndNotNull(resources) && resources.every(resource => resource[type]); |
||||
|
} |
||||
|
|
||||
|
get isExpend(): boolean { |
||||
|
return this.instancesFormArray.length === 1; |
||||
|
} |
||||
|
|
||||
|
getNameInstance(instance: Instance): string { |
||||
|
return `${this.translate.instant('device-profile.lwm2m.instance')} <${instance.id}>`; |
||||
|
} |
||||
|
|
||||
|
disableObserveInstance = (instance: AbstractControl): boolean => { |
||||
|
const checkedAttrTelemetry = this.observeInstance(instance); |
||||
|
if (checkedAttrTelemetry) { |
||||
|
instance.get('attributes').patchValue(null, {emitEvent: false}); |
||||
|
} |
||||
|
return checkedAttrTelemetry; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
observeInstance = (instance: AbstractControl): boolean => { |
||||
|
const resources = instance.get('resources').value as ResourceLwM2M[]; |
||||
|
if (isDefinedAndNotNull(resources)) { |
||||
|
const checkedAttribute = resources.filter(resource => resource.attribute); |
||||
|
const checkedTelemetry = resources.filter(resource => resource.telemetry); |
||||
|
return checkedAttribute.length === 0 && checkedTelemetry.length === 0; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
@ -1,91 +0,0 @@ |
|||||
<!-- |
|
||||
|
|
||||
Copyright © 2016-2021 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. |
|
||||
|
|
||||
--> |
|
||||
<section [formGroup]="resourceFormGroup"> |
|
||||
<div fxLayout="row" fxFill formArrayName="resources" |
|
||||
*ngFor="let resourceLwM2M of resourceFormArray.controls; let i = index; trackBy: trackByParams"> |
|
||||
<div class="vertical-padding" fxLayout="column" fxFill [formGroupName]="i"> |
|
||||
<div fxLayout="row" fxFill fxLayoutAlign="start center" [fxShow]="!i"> |
|
||||
<div fxFlex="30"> |
|
||||
<mat-label translate>device-profile.lwm2m.resource-label</mat-label> |
|
||||
</div> |
|
||||
<div fxFlex="10" style="text-align: center"> |
|
||||
<mat-label translate>device-profile.lwm2m.attribute-label</mat-label> |
|
||||
</div> |
|
||||
<div fxFlex="10" style="text-align: center"> |
|
||||
<mat-label translate>device-profile.lwm2m.telemetry-label</mat-label> |
|
||||
</div> |
|
||||
<div fxFlex="10" style="text-align: center"> |
|
||||
<mat-label translate>device-profile.lwm2m.observe-label</mat-label> |
|
||||
</div> |
|
||||
<div fxFlex> |
|
||||
<mat-label translate>device-profile.lwm2m.key-name-label</mat-label> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div fxLayout="row" fxFill fxLayoutAlign="start center"> |
|
||||
<div class="resource-name-lw" fxFlex="30" |
|
||||
matTooltip="{{'device-profile.lwm2m.resource-tip' | translate}}" matTooltipPosition="above"> |
|
||||
<<b>{{resourceLwM2M.get('id').value}}</b>> <b><i>{{resourceLwM2M.get('name').value}}</i></b> |
|
||||
</div> |
|
||||
<div fxFlex="10" fxLayoutAlign="center center"> |
|
||||
<mat-checkbox formControlName="attribute" color="warn" |
|
||||
(change)="updateObserve(i)" |
|
||||
matTooltip="{{'device-profile.lwm2m.is-attr-tip' | translate}}" |
|
||||
matTooltipPosition="above"> |
|
||||
</mat-checkbox> |
|
||||
</div> |
|
||||
<div fxFlex="10" fxLayoutAlign="center center"> |
|
||||
<mat-checkbox formControlName="telemetry" color="primary" |
|
||||
(change)="updateObserve(i)" |
|
||||
matTooltip="{{'device-profile.lwm2m.is-telemetry-tip' | translate}}" |
|
||||
matTooltipPosition="above"> |
|
||||
</mat-checkbox> |
|
||||
</div> |
|
||||
<div fxFlex="10" fxLayoutAlign="center center"> |
|
||||
<mat-checkbox formControlName="observe" color="primary" |
|
||||
[disabled]="disableObserve(i)" |
|
||||
matTooltip="{{(disableObserve(i) ? 'device-profile.lwm2m.not-observe-tip' : 'device-profile.lwm2m.is-observe-tip') | translate}}" |
|
||||
matTooltipPosition="above"> |
|
||||
</mat-checkbox> |
|
||||
</div> |
|
||||
<mat-form-field fxFlex="33"> |
|
||||
<mat-label *ngIf="resourceLwM2M.get('keyName').hasError('required')"> |
|
||||
{{ 'device-profile.lwm2m.key-name-label' | translate }}</mat-label> |
|
||||
<input class="resource-name-lw" matInput type="text" formControlName="keyName" required |
|
||||
matTooltip="{{'device-profile.lwm2m.key-name-tip' | translate}}" |
|
||||
(input)="updateValueKeyName($event, i)" |
|
||||
matTooltipPosition="above"> |
|
||||
<mat-error *ngIf="resourceLwM2M.get('keyName').hasError('required')"> |
|
||||
{{ 'device-profile.lwm2m.key-name' | translate }} |
|
||||
<strong>{{ 'device-profile.lwm2m.required' | translate }}</strong> |
|
||||
</mat-error> |
|
||||
</mat-form-field> |
|
||||
<span fxFlex></span> |
|
||||
<div class="resource-name-lw-end"> |
|
||||
<tb-profile-lwm2m-attributes |
|
||||
formControlName="attributeLwm2m" |
|
||||
[isAttributeTelemetry]="disableObserve(i)" |
|
||||
isResource="true" |
|
||||
[modelName]="getNameResourceLwm2m(resourceLwM2M.value)" |
|
||||
[disabled]="this.disabled" |
|
||||
(updateAttributeLwm2m)="updateAttributeLwm2m($event, i)"> |
|
||||
</tb-profile-lwm2m-attributes> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</section> |
|
||||
@ -1,145 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2021 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, forwardRef, Input } from '@angular/core'; |
|
||||
import { ControlValueAccessor, FormArray, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; |
|
||||
import { ResourceLwM2M, RESOURCES } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; |
|
||||
import _ from 'lodash'; |
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'tb-profile-lwm2m-observe-attr-telemetry-resource', |
|
||||
templateUrl: './lwm2m-observe-attr-telemetry-resource.component.html', |
|
||||
styleUrls: ['./lwm2m-attributes.component.scss'], |
|
||||
providers: [ |
|
||||
{ |
|
||||
provide: NG_VALUE_ACCESSOR, |
|
||||
useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryResourceComponent), |
|
||||
multi: true |
|
||||
} |
|
||||
] |
|
||||
}) |
|
||||
|
|
||||
export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueAccessor { |
|
||||
|
|
||||
private requiredValue: boolean; |
|
||||
|
|
||||
resourceFormGroup: FormGroup; |
|
||||
disabled = false; |
|
||||
|
|
||||
get required(): boolean { |
|
||||
return this.requiredValue; |
|
||||
} |
|
||||
|
|
||||
@Input() |
|
||||
set required(value: boolean) { |
|
||||
const newVal = coerceBooleanProperty(value); |
|
||||
if (this.requiredValue !== newVal) { |
|
||||
this.requiredValue = newVal; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
constructor(private fb: FormBuilder) { |
|
||||
this.resourceFormGroup = this.fb.group({ |
|
||||
resources: this.fb.array([]) |
|
||||
}); |
|
||||
this.resourceFormGroup.valueChanges.subscribe(value => { |
|
||||
if (!this.disabled) { |
|
||||
this.propagateChangeState(value.resources); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
registerOnTouched(fn: any): void { |
|
||||
} |
|
||||
|
|
||||
writeValue(value: ResourceLwM2M[]): void { |
|
||||
this.createResourceLwM2M(value); |
|
||||
} |
|
||||
|
|
||||
get resourceFormArray(): FormArray{ |
|
||||
return this.resourceFormGroup.get(RESOURCES) as FormArray; |
|
||||
} |
|
||||
|
|
||||
setDisabledState(isDisabled: boolean): void { |
|
||||
this.disabled = isDisabled; |
|
||||
if (isDisabled) { |
|
||||
this.resourceFormGroup.disable(); |
|
||||
} else { |
|
||||
this.resourceFormGroup.enable(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
updateValueKeyName = (event: Event, index: number): void => { |
|
||||
this.resourceFormArray.at(index).patchValue({keyName: _.camelCase((event.target as HTMLInputElement).value)}); |
|
||||
} |
|
||||
|
|
||||
updateAttributeLwm2m = (event: Event, index: number): void => { |
|
||||
this.resourceFormArray.at(index).patchValue({attributeLwm2m: event}); |
|
||||
} |
|
||||
|
|
||||
getNameResourceLwm2m = (resourceLwM2M: ResourceLwM2M): string => { |
|
||||
return `<${resourceLwM2M.id}> ${resourceLwM2M.name}`; |
|
||||
} |
|
||||
|
|
||||
createResourceLwM2M(resourcesLwM2M: ResourceLwM2M[]): void { |
|
||||
if (resourcesLwM2M.length === this.resourceFormArray.length) { |
|
||||
this.resourceFormArray.patchValue(resourcesLwM2M, {emitEvent: false}); |
|
||||
} else { |
|
||||
this.resourceFormArray.clear(); |
|
||||
resourcesLwM2M.forEach(resourceLwM2M => { |
|
||||
this.resourceFormArray.push(this.fb.group( { |
|
||||
id: resourceLwM2M.id, |
|
||||
name: resourceLwM2M.name, |
|
||||
observe: resourceLwM2M.observe, |
|
||||
attribute: resourceLwM2M.attribute, |
|
||||
telemetry: resourceLwM2M.telemetry, |
|
||||
keyName: [resourceLwM2M.keyName, Validators.required], |
|
||||
attributeLwm2m: [resourceLwM2M.attributeLwm2m] |
|
||||
})); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private propagateChange = (v: any) => { }; |
|
||||
|
|
||||
registerOnChange(fn: any): void { |
|
||||
this.propagateChange = fn; |
|
||||
} |
|
||||
|
|
||||
private propagateChangeState = (value: any): void => { |
|
||||
if (value && this.resourceFormGroup.valid) { |
|
||||
this.propagateChange(value); |
|
||||
} else { |
|
||||
this.propagateChange(null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
trackByParams = (index: number): number => { |
|
||||
return index; |
|
||||
} |
|
||||
|
|
||||
updateObserve = (index: number): void => { |
|
||||
if (this.resourceFormArray.at(index).value.attribute === false && this.resourceFormArray.at(index).value.telemetry === false) { |
|
||||
this.resourceFormArray.at(index).patchValue({observe: false}); |
|
||||
this.resourceFormArray.at(index).patchValue({attributeLwm2m: {}}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
disableObserve = (index: number): boolean => { |
|
||||
return !this.resourceFormArray.at(index).value.telemetry && !this.resourceFormArray.at(index).value.attribute; |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,75 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 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. |
||||
|
|
||||
|
--> |
||||
|
<section [formGroup]="resourcesFormGroup"> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center"> |
||||
|
<div fxFlex="30"> |
||||
|
<mat-label translate>device-profile.lwm2m.resource-label</mat-label> |
||||
|
</div> |
||||
|
<div fxFlex="10" class="checkbox-column-title"> |
||||
|
<mat-label translate>device-profile.lwm2m.attribute-label</mat-label> |
||||
|
</div> |
||||
|
<div fxFlex="10" class="checkbox-column-title"> |
||||
|
<mat-label translate>device-profile.lwm2m.telemetry-label</mat-label> |
||||
|
</div> |
||||
|
<div fxFlex="10" class="checkbox-column-title"> |
||||
|
<mat-label translate>device-profile.lwm2m.observe-label</mat-label> |
||||
|
</div> |
||||
|
<div fxFlex> |
||||
|
<mat-label translate>device-profile.lwm2m.key-name</mat-label> |
||||
|
</div> |
||||
|
</div> |
||||
|
<mat-divider></mat-divider> |
||||
|
<div formArrayName="resources" |
||||
|
*ngFor="let resourceLwM2M of resourcesFormArray.controls; let $index = index; trackBy: trackByParams"> |
||||
|
<div [formGroupName]="$index" fxLayout="row" fxLayoutAlign="start center" class="resource-list"> |
||||
|
<div class="resource-name" fxFlex="30"> |
||||
|
<<b>{{resourceLwM2M.get('id').value}}</b>> <b>{{resourceLwM2M.get('name').value}}</b> |
||||
|
</div> |
||||
|
<div fxFlex="10" fxLayoutAlign="center center"> |
||||
|
<mat-checkbox formControlName="attribute" color="warn"> |
||||
|
</mat-checkbox> |
||||
|
</div> |
||||
|
<div fxFlex="10" fxLayoutAlign="center center"> |
||||
|
<mat-checkbox formControlName="telemetry" color="primary"> |
||||
|
</mat-checkbox> |
||||
|
</div> |
||||
|
<div fxFlex="10" fxLayoutAlign="center center"> |
||||
|
<mat-checkbox fxFlex="10" formControlName="observe" color="primary" |
||||
|
matTooltip="{{ 'device-profile.lwm2m.edit-observe-select' | translate }}" |
||||
|
[matTooltipDisabled]="disabled || !isDisabledObserve($index)" |
||||
|
matTooltipPosition="above"> |
||||
|
</mat-checkbox> |
||||
|
</div> |
||||
|
<mat-form-field fxFlex="33" hideRequiredMarker> |
||||
|
<mat-label></mat-label> |
||||
|
<input matInput type="text" formControlName="keyName" required> |
||||
|
<mat-error *ngIf="resourceLwM2M.get('keyName').hasError('required') || |
||||
|
resourceLwM2M.get('keyName').hasError('pattern')"> |
||||
|
{{ 'device-profile.lwm2m.key-name-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<span fxFlex></span> |
||||
|
<tb-profile-lwm2m-attributes |
||||
|
formControlName="attributes" |
||||
|
[isAttributeTelemetry]="isDisabledObserve($index)" |
||||
|
isResource="true" |
||||
|
[modelName]="getNameResourceLwm2m(resourceLwM2M.value)"> |
||||
|
</tb-profile-lwm2m-attributes> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
@ -0,0 +1,56 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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 { |
||||
|
.resource-name{ |
||||
|
white-space: nowrap; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
} |
||||
|
|
||||
|
.checkbox-column-title { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.mat-divider { |
||||
|
margin-bottom: 4px; |
||||
|
} |
||||
|
|
||||
|
.resource-list { |
||||
|
padding-bottom: 8px; |
||||
|
height: 42px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
.resource-list { |
||||
|
mat-form-field { |
||||
|
.mat-form-field-wrapper { |
||||
|
padding-bottom: 0; |
||||
|
.mat-form-field-infix { |
||||
|
border-top-width: 0.2em; |
||||
|
width: auto; |
||||
|
min-width: auto; |
||||
|
} |
||||
|
.mat-form-field-underline { |
||||
|
bottom: 0; |
||||
|
} |
||||
|
.mat-form-field-subscript-wrapper{ |
||||
|
margin-top: 1.8em; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,193 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2021 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, forwardRef, Input, OnDestroy } from '@angular/core'; |
||||
|
import { |
||||
|
AbstractControl, |
||||
|
ControlValueAccessor, |
||||
|
FormArray, |
||||
|
FormBuilder, |
||||
|
FormGroup, |
||||
|
NG_VALIDATORS, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
ValidationErrors, |
||||
|
Validator, |
||||
|
Validators |
||||
|
} from '@angular/forms'; |
||||
|
import { ResourceLwM2M } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; |
||||
|
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
import { combineLatest, Subject, Subscription } from 'rxjs'; |
||||
|
import { startWith, takeUntil } from 'rxjs/operators'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-profile-lwm2m-observe-attr-telemetry-resource', |
||||
|
templateUrl: './lwm2m-observe-attr-telemetry-resources.component.html', |
||||
|
styleUrls: ['./lwm2m-observe-attr-telemetry-resources.component.scss'], |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryResourcesComponent), |
||||
|
multi: true |
||||
|
}, |
||||
|
{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryResourcesComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
] |
||||
|
}) |
||||
|
|
||||
|
export class Lwm2mObserveAttrTelemetryResourcesComponent implements ControlValueAccessor, OnDestroy, Validator { |
||||
|
|
||||
|
resourcesFormGroup: FormGroup; |
||||
|
|
||||
|
@Input() |
||||
|
disabled = false; |
||||
|
|
||||
|
private requiredValue: boolean; |
||||
|
get required(): boolean { |
||||
|
return this.requiredValue; |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
set required(value: boolean) { |
||||
|
const newVal = coerceBooleanProperty(value); |
||||
|
if (this.requiredValue !== newVal) { |
||||
|
this.requiredValue = newVal; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private destroy$ = new Subject(); |
||||
|
private valueChange$: Subscription = null; |
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private fb: FormBuilder) { |
||||
|
this.resourcesFormGroup = this.fb.group({ |
||||
|
resources: this.fb.array([]) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
if (this.valueChange$) { |
||||
|
this.valueChange$.unsubscribe(); |
||||
|
} |
||||
|
this.destroy$.next(); |
||||
|
this.destroy$.complete(); |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
writeValue(value: ResourceLwM2M[]): void { |
||||
|
this.updatedResources(value); |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (isDisabled) { |
||||
|
this.resourcesFormGroup.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.resourcesFormArray.controls.forEach(resource => { |
||||
|
resource.get('keyName').enable({emitEvent: false}); |
||||
|
resource.get('attribute').enable({emitEvent: false}); |
||||
|
resource.get('telemetry').enable({onlySelf: true}); |
||||
|
resource.get('attributes').enable({emitEvent: false}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
validate(): ValidationErrors | null { |
||||
|
return this.resourcesFormGroup.valid ? null : { |
||||
|
resources: false |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
get resourcesFormArray(): FormArray { |
||||
|
return this.resourcesFormGroup.get('resources') as FormArray; |
||||
|
} |
||||
|
|
||||
|
getNameResourceLwm2m(resourceLwM2M: ResourceLwM2M): string { |
||||
|
return `<${resourceLwM2M.id}> ${resourceLwM2M.name}`; |
||||
|
} |
||||
|
|
||||
|
private updatedResources(resources: ResourceLwM2M[]): void { |
||||
|
if (resources.length === this.resourcesFormArray.length) { |
||||
|
this.resourcesFormArray.patchValue(resources, {emitEvent: false}); |
||||
|
} else { |
||||
|
if (this.valueChange$) { |
||||
|
this.valueChange$.unsubscribe(); |
||||
|
} |
||||
|
const resourcesControl: Array<AbstractControl> = []; |
||||
|
if (resources) { |
||||
|
resources.forEach((resource) => { |
||||
|
resourcesControl.push(this.createdResourceFormGroup(resource)); |
||||
|
}); |
||||
|
} |
||||
|
this.resourcesFormGroup.setControl('resources', this.fb.array(resourcesControl)); |
||||
|
if (this.disabled) { |
||||
|
this.resourcesFormGroup.disable({emitEvent: false}); |
||||
|
} |
||||
|
this.valueChange$ = this.resourcesFormGroup.valueChanges.subscribe(value => { |
||||
|
this.updateModel(this.resourcesFormGroup.getRawValue().resources); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private createdResourceFormGroup(resource: ResourceLwM2M): FormGroup { |
||||
|
const form = this.fb.group( { |
||||
|
id: [resource.id], |
||||
|
name: [resource.name], |
||||
|
attribute: [resource.attribute], |
||||
|
telemetry: [resource.telemetry], |
||||
|
observe: [resource.observe], |
||||
|
keyName: [resource.keyName, [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]], |
||||
|
attributes: [resource.attributes] |
||||
|
}); |
||||
|
combineLatest([ |
||||
|
form.get('attribute').valueChanges.pipe(startWith(resource.attribute), takeUntil(this.destroy$)), |
||||
|
form.get('telemetry').valueChanges.pipe(startWith(resource.telemetry), takeUntil(this.destroy$)) |
||||
|
]).subscribe(([attribute, telemetry]) => { |
||||
|
if (attribute || telemetry) { |
||||
|
form.get('observe').enable({emitEvent: false}); |
||||
|
} else { |
||||
|
form.get('observe').disable({emitEvent: false}); |
||||
|
form.get('observe').patchValue(false, {emitEvent: false}); |
||||
|
form.get('attributes').patchValue({}, {emitEvent: false}); |
||||
|
} |
||||
|
}); |
||||
|
return form; |
||||
|
} |
||||
|
|
||||
|
private updateModel(value: ResourceLwM2M[]) { |
||||
|
if (value && this.resourcesFormGroup.valid) { |
||||
|
this.propagateChange(value); |
||||
|
} else { |
||||
|
this.propagateChange(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
trackByParams(index: number, resource: ResourceLwM2M): number { |
||||
|
return resource.id; |
||||
|
} |
||||
|
|
||||
|
isDisabledObserve(index: number): boolean{ |
||||
|
return this.resourcesFormArray.at(index).get('observe').disabled; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue