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