|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
47
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.html → ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.html
@ -0,0 +1,20 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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 { |
||||
|
.hint-container { |
||||
|
margin-bottom: 12px; |
||||
|
} |
||||
|
} |
||||
10
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.ts → ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.ts
@ -0,0 +1,48 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2024 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. |
||||
|
|
||||
|
--> |
||||
|
<ng-container [formGroup]="rpcParametersFormGroup"> |
||||
|
<mat-form-field> |
||||
|
<mat-label>{{ 'gateway.rpc.method-name' | translate }}</mat-label> |
||||
|
<input matInput formControlName="methodFilter" |
||||
|
placeholder="echo"/> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field> |
||||
|
<mat-label>{{ 'gateway.rpc.requestTopicExpression' | translate }}</mat-label> |
||||
|
<input matInput formControlName="requestTopicExpression" |
||||
|
placeholder="sensor/${deviceName}/request/${methodName}/${requestId}"/> |
||||
|
</mat-form-field> |
||||
|
<mat-slide-toggle class="margin" (click)="$event.stopPropagation()" formControlName="withResponse"> |
||||
|
{{ 'gateway.rpc.withResponse' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<mat-form-field *ngIf="rpcParametersFormGroup.get('withResponse')?.value"> |
||||
|
<mat-label>{{ 'gateway.rpc.responseTopicExpression' | translate }}</mat-label> |
||||
|
<input matInput formControlName="responseTopicExpression" |
||||
|
placeholder="sensor/${deviceName}/response/${methodName}/${requestId}"/> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="rpcParametersFormGroup.get('withResponse')?.value"> |
||||
|
<mat-label>{{ 'gateway.rpc.responseTimeout' | translate }}</mat-label> |
||||
|
<input matInput formControlName="responseTimeout" type="number" |
||||
|
placeholder="10000" min="10" step="1"/> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field> |
||||
|
<mat-label>{{ 'gateway.rpc.valueExpression' | translate }}</mat-label> |
||||
|
<input matInput formControlName="valueExpression" |
||||
|
placeholder="${params}"/> |
||||
|
</mat-form-field> |
||||
|
</ng-container> |
||||
|
|
||||
@ -0,0 +1,24 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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 { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
|
||||
|
.mat-mdc-slide-toggle.margin { |
||||
|
margin-bottom: 10px; |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,139 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { |
||||
|
ChangeDetectionStrategy, |
||||
|
Component, |
||||
|
forwardRef, |
||||
|
OnDestroy, |
||||
|
} from '@angular/core'; |
||||
|
import { |
||||
|
ControlValueAccessor, |
||||
|
FormBuilder, |
||||
|
NG_VALIDATORS, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
UntypedFormGroup, |
||||
|
ValidationErrors, |
||||
|
Validator, Validators, |
||||
|
} from '@angular/forms'; |
||||
|
import { SharedModule } from '@shared/shared.module'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { takeUntil, tap } from 'rxjs/operators'; |
||||
|
import { |
||||
|
integerRegex, |
||||
|
noLeadTrailSpacesRegex, |
||||
|
RPCTemplateConfigMQTT |
||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-mqtt-rpc-parameters', |
||||
|
templateUrl: './mqtt-rpc-parameters.component.html', |
||||
|
styleUrls: ['./mqtt-rpc-parameters.component.scss'], |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => MqttRpcParametersComponent), |
||||
|
multi: true |
||||
|
}, |
||||
|
{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => MqttRpcParametersComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
], |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
SharedModule, |
||||
|
], |
||||
|
}) |
||||
|
export class MqttRpcParametersComponent implements ControlValueAccessor, Validator, OnDestroy { |
||||
|
|
||||
|
rpcParametersFormGroup: UntypedFormGroup; |
||||
|
|
||||
|
private onChange: (value: RPCTemplateConfigMQTT) => void = (_) => {}; |
||||
|
private onTouched: () => void = () => {}; |
||||
|
|
||||
|
private destroy$ = new Subject<void>(); |
||||
|
|
||||
|
constructor(private fb: FormBuilder) { |
||||
|
this.rpcParametersFormGroup = this.fb.group({ |
||||
|
methodFilter: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], |
||||
|
requestTopicExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], |
||||
|
responseTopicExpression: [{ value: null, disabled: true }, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], |
||||
|
responseTimeout: [{ value: null, disabled: true }, [Validators.min(10), Validators.pattern(integerRegex)]], |
||||
|
valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], |
||||
|
withResponse: [false, []], |
||||
|
}); |
||||
|
|
||||
|
this.observeValueChanges(); |
||||
|
this.observeWithResponse(); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
this.destroy$.next(); |
||||
|
this.destroy$.complete(); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: (value: RPCTemplateConfigMQTT) => void): void { |
||||
|
this.onChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: () => void): void { |
||||
|
this.onTouched = fn; |
||||
|
} |
||||
|
|
||||
|
validate(): ValidationErrors | null { |
||||
|
return this.rpcParametersFormGroup.valid ? null : { |
||||
|
rpcParametersFormGroup: { valid: false } |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
writeValue(value: RPCTemplateConfigMQTT): void { |
||||
|
this.rpcParametersFormGroup.patchValue(value, {emitEvent: false}); |
||||
|
this.toggleResponseFields(value.withResponse); |
||||
|
} |
||||
|
|
||||
|
private observeValueChanges(): void { |
||||
|
this.rpcParametersFormGroup.valueChanges.pipe( |
||||
|
takeUntil(this.destroy$) |
||||
|
).subscribe((value) => { |
||||
|
this.onChange(value); |
||||
|
this.onTouched(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private observeWithResponse(): void { |
||||
|
this.rpcParametersFormGroup.get('withResponse').valueChanges.pipe( |
||||
|
tap((isActive: boolean) => this.toggleResponseFields(isActive)), |
||||
|
takeUntil(this.destroy$), |
||||
|
).subscribe(); |
||||
|
} |
||||
|
|
||||
|
private toggleResponseFields(enabled: boolean): void { |
||||
|
const responseTopicControl = this.rpcParametersFormGroup.get('responseTopicExpression'); |
||||
|
const responseTimeoutControl = this.rpcParametersFormGroup.get('responseTimeout'); |
||||
|
if (enabled) { |
||||
|
responseTopicControl.enable(); |
||||
|
responseTimeoutControl.enable(); |
||||
|
} else { |
||||
|
responseTopicControl.disable(); |
||||
|
responseTimeoutControl.disable(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2024 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. |
||||
|
|
||||
|
--> |
||||
|
<ng-container [formGroup]="rpcParametersFormGroup"> |
||||
|
<div class="tb-form-hint tb-primary-fill tb-flex no-padding-top hint-container"> |
||||
|
{{ 'gateway.rpc.hint.opc-method' | translate }} |
||||
|
</div> |
||||
|
<mat-form-field class="tb-flex"> |
||||
|
<mat-label>{{ 'gateway.rpc.method' | translate }}</mat-label> |
||||
|
<input matInput formControlName="method" placeholder="multiply"/> |
||||
|
</mat-form-field> |
||||
|
<fieldset class="tb-form-panel stroked arguments-container" fxLayout="column" formArrayName="arguments"> |
||||
|
<strong> |
||||
|
<span class="fields-label">{{ 'gateway.rpc.arguments' | translate }}</span> |
||||
|
</strong> |
||||
|
<div fxFlex fxLayout="row" fxLayoutGap="10px" fxLayoutAlign="center center" |
||||
|
*ngFor="let argumentFormGroup of rpcParametersFormGroup.get('arguments')['controls']; let i = index" [formGroup]="argumentFormGroup"> |
||||
|
<div class="tb-form-row column-xs type-container" fxLayoutAlign="space-between center"> |
||||
|
<div class="tb-required" translate>gateway.type</div> |
||||
|
<div class="tb-flex no-gap"> |
||||
|
<mat-form-field class="tb-flex no-gap fill-width" appearance="outline" subscriptSizing="dynamic"> |
||||
|
<mat-select formControlName="type"> |
||||
|
<mat-select-trigger> |
||||
|
<div class="tb-flex align-center"> |
||||
|
<mat-icon class="tb-mat-18" [svgIcon]="valueTypes.get(argumentFormGroup.get('type').value)?.icon"> |
||||
|
</mat-icon> |
||||
|
<span>{{ valueTypes.get(argumentFormGroup.get('type').value)?.name | translate }}</span> |
||||
|
</div> |
||||
|
</mat-select-trigger> |
||||
|
<mat-option *ngFor="let valueType of valueTypeKeys" [value]="valueType"> |
||||
|
<mat-icon class="tb-mat-20" svgIcon="{{ valueTypes.get(valueType).icon }}"> |
||||
|
</mat-icon> |
||||
|
<span>{{ valueTypes.get(valueType).name | translate }}</span> |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row column-xs value-container" fxLayoutAlign="space-between center"> |
||||
|
<div class="tb-required" translate>gateway.value</div> |
||||
|
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic" class="tb-inline-field flex tb-suffix-absolute"> |
||||
|
<ng-container [ngSwitch]="argumentFormGroup.get('type').value"> |
||||
|
<input *ngSwitchCase="MappingValueType.STRING" matInput required formControlName="string" |
||||
|
placeholder="{{ 'gateway.set' | translate }}" /> |
||||
|
<input *ngSwitchCase="MappingValueType.INTEGER" matInput required formControlName="integer" type="number" |
||||
|
placeholder="{{ 'gateway.set' | translate }}" /> |
||||
|
<input *ngSwitchCase="MappingValueType.DOUBLE" matInput required formControlName="double" type="number" |
||||
|
placeholder="{{ 'gateway.set' | translate }}" /> |
||||
|
<mat-select *ngSwitchCase="MappingValueType.BOOLEAN" formControlName="boolean"> |
||||
|
<mat-option [value]="true">true</mat-option> |
||||
|
<mat-option [value]="false">false</mat-option> |
||||
|
</mat-select> |
||||
|
</ng-container> |
||||
|
<mat-icon matSuffix |
||||
|
matTooltipPosition="above" |
||||
|
matTooltipClass="tb-error-tooltip" |
||||
|
[matTooltip]="('gateway.value-required') | translate" |
||||
|
*ngIf="argumentFormGroup.get(argumentFormGroup.get('type').value).hasError('required') |
||||
|
&& argumentFormGroup.get(argumentFormGroup.get('type').value).touched" |
||||
|
class="tb-error"> |
||||
|
warning |
||||
|
</mat-icon> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<button mat-icon-button (click)="removeArgument(i)" |
||||
|
class="tb-box-button" |
||||
|
matTooltip="{{ 'gateway.rpc.remove' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>delete</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
<button mat-raised-button |
||||
|
fxFlexAlign="start" |
||||
|
(click)="addArgument()"> |
||||
|
{{ 'gateway.rpc.add-argument' | translate }} |
||||
|
</button> |
||||
|
</fieldset> |
||||
|
</ng-container> |
||||
|
|
||||
@ -0,0 +1,32 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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 { |
||||
|
.arguments-container { |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
|
||||
|
.type-container { |
||||
|
width: 40%; |
||||
|
} |
||||
|
|
||||
|
.value-container { |
||||
|
width: 50%; |
||||
|
} |
||||
|
|
||||
|
.hint-container { |
||||
|
margin-bottom: 12px; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,169 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { |
||||
|
ChangeDetectionStrategy, |
||||
|
ChangeDetectorRef, |
||||
|
Component, |
||||
|
forwardRef, |
||||
|
OnDestroy, |
||||
|
} from '@angular/core'; |
||||
|
import { |
||||
|
ControlValueAccessor, |
||||
|
FormArray, |
||||
|
FormBuilder, |
||||
|
FormGroup, |
||||
|
NG_VALIDATORS, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
UntypedFormGroup, |
||||
|
ValidationErrors, |
||||
|
Validator, Validators, |
||||
|
} from '@angular/forms'; |
||||
|
import { SharedModule } from '@shared/shared.module'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
import { |
||||
|
integerRegex, |
||||
|
MappingValueType, |
||||
|
mappingValueTypesMap, |
||||
|
noLeadTrailSpacesRegex, |
||||
|
OPCTypeValue, |
||||
|
RPCTemplateConfigOPC |
||||
|
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
||||
|
import { isDefinedAndNotNull, isEqual } from '@core/utils'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-opc-rpc-parameters', |
||||
|
templateUrl: './opc-rpc-parameters.component.html', |
||||
|
styleUrls: ['./opc-rpc-parameters.component.scss'], |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => OpcRpcParametersComponent), |
||||
|
multi: true |
||||
|
}, |
||||
|
{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => OpcRpcParametersComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
], |
||||
|
standalone: true, |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
SharedModule, |
||||
|
], |
||||
|
}) |
||||
|
export class OpcRpcParametersComponent implements ControlValueAccessor, Validator, OnDestroy { |
||||
|
|
||||
|
rpcParametersFormGroup: UntypedFormGroup; |
||||
|
|
||||
|
readonly valueTypeKeys: MappingValueType[] = Object.values(MappingValueType); |
||||
|
readonly MappingValueType = MappingValueType; |
||||
|
readonly valueTypes = mappingValueTypesMap; |
||||
|
|
||||
|
private onChange: (value: RPCTemplateConfigOPC) => void = (_) => {} ; |
||||
|
private onTouched: () => void = () => {}; |
||||
|
|
||||
|
private destroy$ = new Subject<void>(); |
||||
|
|
||||
|
constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) { |
||||
|
this.rpcParametersFormGroup = this.fb.group({ |
||||
|
method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], |
||||
|
arguments: this.fb.array([]), |
||||
|
}); |
||||
|
|
||||
|
this.observeValueChanges(); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
this.destroy$.next(); |
||||
|
this.destroy$.complete(); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: (value: RPCTemplateConfigOPC) => void): void { |
||||
|
this.onChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: () => void): void { |
||||
|
this.onTouched = fn; |
||||
|
} |
||||
|
|
||||
|
validate(): ValidationErrors | null { |
||||
|
return this.rpcParametersFormGroup.valid ? null : { |
||||
|
rpcParametersFormGroup: { valid: false } |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
writeValue(params: RPCTemplateConfigOPC): void { |
||||
|
this.clearArguments(); |
||||
|
params.arguments?.map(({type, value}) => ({type, [type]: value })) |
||||
|
.forEach(argument => this.addArgument(argument as OPCTypeValue)); |
||||
|
this.cdr.markForCheck(); |
||||
|
this.rpcParametersFormGroup.get('method').patchValue(params.method); |
||||
|
} |
||||
|
|
||||
|
private observeValueChanges(): void { |
||||
|
this.rpcParametersFormGroup.valueChanges.pipe( |
||||
|
takeUntil(this.destroy$) |
||||
|
).subscribe(params => { |
||||
|
const updatedArguments = params.arguments.map(({type, ...config}) => ({type, value: config[type]})); |
||||
|
this.onChange({method: params.method, arguments: updatedArguments}); |
||||
|
this.onTouched(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
removeArgument(index: number): void { |
||||
|
(this.rpcParametersFormGroup.get('arguments') as FormArray).removeAt(index); |
||||
|
} |
||||
|
|
||||
|
addArgument(value: OPCTypeValue = {} as OPCTypeValue): void { |
||||
|
const fromGroup = this.fb.group({ |
||||
|
type: [value.type ?? MappingValueType.STRING], |
||||
|
string: [ |
||||
|
value.string ?? { value: '', disabled: !(isEqual(value, {}) || value.string)}, |
||||
|
[Validators.required, Validators.pattern(noLeadTrailSpacesRegex)] |
||||
|
], |
||||
|
integer: [ |
||||
|
{value: value.integer ?? 0, disabled: !isDefinedAndNotNull(value.integer)}, |
||||
|
[Validators.required, Validators.pattern(integerRegex)] |
||||
|
], |
||||
|
double: [{value: value.double ?? 0, disabled: !isDefinedAndNotNull(value.double)}, [Validators.required]], |
||||
|
boolean: [{value: value.boolean ?? false, disabled: !isDefinedAndNotNull(value.boolean)}, [Validators.required]], |
||||
|
}); |
||||
|
this.observeTypeChange(fromGroup); |
||||
|
(this.rpcParametersFormGroup.get('arguments') as FormArray).push(fromGroup, {emitEvent: false}); |
||||
|
} |
||||
|
|
||||
|
clearArguments(): void { |
||||
|
const formArray = this.rpcParametersFormGroup.get('arguments') as FormArray; |
||||
|
while (formArray.length !== 0) { |
||||
|
formArray.removeAt(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private observeTypeChange(dataKeyFormGroup: FormGroup): void { |
||||
|
dataKeyFormGroup.get('type').valueChanges |
||||
|
.pipe(takeUntil(this.destroy$)) |
||||
|
.subscribe(type => { |
||||
|
dataKeyFormGroup.disable({emitEvent: false}); |
||||
|
dataKeyFormGroup.get('type').enable({emitEvent: false}); |
||||
|
dataKeyFormGroup.get(type).enable({emitEvent: false}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { Pipe, PipeTransform } from '@angular/core'; |
||||
|
|
||||
|
@Pipe({ |
||||
|
name: 'getRpcTemplateArrayView', |
||||
|
standalone: true, |
||||
|
}) |
||||
|
export class RpcTemplateArrayViewPipe implements PipeTransform { |
||||
|
|
||||
|
transform(values: {value: string | boolean | number}[]): string { |
||||
|
return values.map(({value}) => value.toString()).join(', '); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 { Directive, ElementRef, EventEmitter, OnDestroy, Output } from '@angular/core'; |
||||
|
import { TbContextMenuEvent } from '@shared/models/jquery-event.models'; |
||||
|
|
||||
|
@Directive({ |
||||
|
selector: '[tbcontextmenu]' |
||||
|
}) |
||||
|
export class ContextMenuDirective implements OnDestroy { |
||||
|
|
||||
|
@Output() |
||||
|
tbcontextmenu = new EventEmitter<TbContextMenuEvent>(); |
||||
|
|
||||
|
constructor(private el: ElementRef) { |
||||
|
$(this.el.nativeElement).on('tbcontextmenu', (e: TbContextMenuEvent) => this.tbcontextmenu.emit(e)); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
$(this.el.nativeElement).off('tbcontextmenu'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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 Timeout = NodeJS.Timeout; |
||||
|
|
||||
|
export interface TbContextMenuEvent extends Event { |
||||
|
clientX: number; |
||||
|
clientY: number; |
||||
|
ctrlKey: boolean; |
||||
|
metaKey: boolean; |
||||
|
} |
||||
|
|
||||
|
const isIOSDevice = (): boolean => |
||||
|
/iPhone|iPad|iPod/i.test(navigator.userAgent) || (navigator.userAgent.includes('Mac') && 'ontouchend' in document); |
||||
|
|
||||
|
export const initCustomJQueryEvents = () => { |
||||
|
$.event.special.tbcontextmenu = { |
||||
|
setup(this: HTMLElement) { |
||||
|
const el = $(this); |
||||
|
if (isIOSDevice()) { |
||||
|
let timeoutId: Timeout; |
||||
|
|
||||
|
el.on('touchstart', (e) => { |
||||
|
e.stopPropagation(); |
||||
|
timeoutId = setTimeout(() => { |
||||
|
timeoutId = null; |
||||
|
e.stopPropagation(); |
||||
|
const touch = e.originalEvent.changedTouches[0]; |
||||
|
const event = $.Event('tbcontextmenu', { |
||||
|
clientX: touch.clientX, |
||||
|
clientY: touch.clientY, |
||||
|
ctrlKey: false, |
||||
|
metaKey: false |
||||
|
}); |
||||
|
el.trigger(event, e); |
||||
|
}, 500); |
||||
|
}); |
||||
|
|
||||
|
el.on('touchend touchmove', () => { |
||||
|
if (timeoutId) { |
||||
|
clearTimeout(timeoutId); |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
el.on('contextmenu', (e) => { |
||||
|
e.preventDefault(); |
||||
|
e.stopPropagation(); |
||||
|
const event = $.Event('tbcontextmenu', { |
||||
|
clientX: e.originalEvent.clientX, |
||||
|
clientY: e.originalEvent.clientY, |
||||
|
ctrlKey: e.originalEvent.ctrlKey, |
||||
|
metaKey: e.originalEvent.metaKey, |
||||
|
}); |
||||
|
el.trigger(event, e); |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
teardown(this: HTMLElement) { |
||||
|
const el = $(this); |
||||
|
if (isIOSDevice()) { |
||||
|
el.off('touchstart touchend touchmove'); |
||||
|
} else { |
||||
|
el.off('contextmenu'); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
}; |
||||