diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 74c29bf00e..c37706b5e0 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -59,7 +59,7 @@ "flot.curvedlines": "https://github.com/MichaelZinsmaier/CurvedLines.git#master", "font-awesome": "^4.7.0", "html2canvas": "^1.4.1", - "jquery": "^3.6.3", + "jquery": "^3.7.1", "jquery.terminal": "^2.35.3", "js-beautify": "1.14.7", "json-schema-defaults": "^0.4.0", @@ -127,7 +127,7 @@ "@types/flowjs": "^2.13.9", "@types/jasmine": "~3.10.2", "@types/jasminewd2": "^2.0.10", - "@types/jquery": "^3.5.16", + "@types/jquery": "^3.5.30", "@types/js-beautify": "^1.13.3", "@types/leaflet": "1.8.0", "@types/leaflet-polylinedecorator": "1.6.4", diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index 9ac5bc79a1..dcc33dcbd9 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -32,6 +32,7 @@ import { AuthService } from '@core/auth/auth.service'; import { svgIcons, svgIconsUrl } from '@shared/models/icon.models'; import { ActionSettingsChangeLanguage } from '@core/settings/settings.actions'; import { SETTINGS_KEY } from '@core/settings/settings.effects'; +import { initCustomJQueryEvents } from '@shared/models/jquery-event.models'; @Component({ selector: 'tb-root', @@ -74,6 +75,8 @@ export class AppComponent implements OnInit { this.setupTranslate(); this.setupAuth(); + + initCustomJQueryEvents(); } setupTranslate() { diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index 33c8d659f6..16498100ae 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -27,7 +27,7 @@ [class.center-vertical]="centerVertical" [class.center-horizontal]="centerHorizontal" (mousedown)="onDashboardMouseDown($event)" - (contextmenu)="openDashboardContextMenu($event)"> + (tbcontextmenu)="openDashboardContextMenu($event)">
((_, item) => item), @@ -395,7 +396,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } } - openDashboardContextMenu($event: MouseEvent) { + openDashboardContextMenu($event: TbContextMenuEvent) { if (this.callbacks && this.callbacks.prepareDashboardContextMenu) { const items = this.callbacks.prepareDashboardContextMenu($event); if (items && items.length) { @@ -410,7 +411,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } } - private openWidgetContextMenu($event: MouseEvent, widget: DashboardWidget) { + private openWidgetContextMenu($event: TbContextMenuEvent, widget: DashboardWidget) { if (this.callbacks && this.callbacks.prepareWidgetContextMenu) { const items = this.callbacks.prepareWidgetContextMenu($event, widget.widget, widget.isReference); if (items && items.length) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.html similarity index 77% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.html index c2dead1abc..7122bcd52d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.html @@ -16,20 +16,9 @@ --> -
- - {{ 'gateway.key' | translate }} - - - warning - - +
+ {{ 'gateway.rpc.hint.modbus-response-reading' | translate }}
+ {{ 'gateway.rpc.hint.modbus-writing-functions' | translate }}
@@ -45,21 +34,6 @@
-
- - {{ 'gateway.rpc.value' | translate }} - - - warning - - -
{{ 'gateway.rpc.address' | translate }} @@ -88,5 +62,20 @@ />
+
+ + {{ 'gateway.rpc.value' | translate }} + + + warning + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.scss new file mode 100644 index 0000000000..62eaca664f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.scss @@ -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; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.ts similarity index 94% rename from ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.ts index 17c48cc595..ae110b9418 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/modbus-rpc-parameters/modbus-rpc-parameters.component.ts @@ -39,13 +39,14 @@ import { ModbusEditableDataTypes, ModbusFunctionCodeTranslationsMap, ModbusObjectCountByDataType, - ModbusValue, noLeadTrailSpacesRegex, + RPCTemplateConfigModbus, } from '@home/components/widget/lib/gateway/gateway-widget.models'; @Component({ selector: 'tb-modbus-rpc-parameters', templateUrl: './modbus-rpc-parameters.component.html', + styleUrls: ['./modbus-rpc-parameters.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, providers: [ { @@ -80,14 +81,13 @@ export class ModbusRpcParametersComponent implements ControlValueAccessor, Valid private readonly readFunctionCodes = [1, 2, 3, 4]; private readonly bitsFunctionCodes = [...this.readFunctionCodes, ...this.writeFunctionCodes]; - private onChange: (value: ModbusValue) => void; + private onChange: (value: RPCTemplateConfigModbus) => void; private onTouched: () => void; private destroy$ = new Subject(); constructor(private fb: FormBuilder) { this.rpcParametersFormGroup = this.fb.group({ - tag: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], type: [ModbusDataType.BYTES, [Validators.required]], functionCode: [this.defaultFunctionCodes[0], [Validators.required]], value: [{value: '', disabled: true}, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], @@ -106,7 +106,7 @@ export class ModbusRpcParametersComponent implements ControlValueAccessor, Valid this.destroy$.complete(); } - registerOnChange(fn: (value: ModbusValue) => void): void { + registerOnChange(fn: (value: RPCTemplateConfigModbus) => void): void { this.onChange = fn; } @@ -120,7 +120,7 @@ export class ModbusRpcParametersComponent implements ControlValueAccessor, Valid }; } - writeValue(value: ModbusValue): void { + writeValue(value: RPCTemplateConfigModbus): void { this.rpcParametersFormGroup.patchValue(value, {emitEvent: false}); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.html new file mode 100644 index 0000000000..eb66a6df8c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.html @@ -0,0 +1,48 @@ + + + + {{ 'gateway.rpc.method-name' | translate }} + + + + {{ 'gateway.rpc.requestTopicExpression' | translate }} + + + + {{ 'gateway.rpc.withResponse' | translate }} + + + {{ 'gateway.rpc.responseTopicExpression' | translate }} + + + + {{ 'gateway.rpc.responseTimeout' | translate }} + + + + {{ 'gateway.rpc.valueExpression' | translate }} + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.scss new file mode 100644 index 0000000000..a2dddebc47 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.scss @@ -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; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.ts new file mode 100644 index 0000000000..56d9510e7d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/mqtt-rpc-parameters/mqtt-rpc-parameters.component.ts @@ -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(); + + 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(); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.html new file mode 100644 index 0000000000..ec5c20cfb1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.html @@ -0,0 +1,93 @@ + + +
+ {{ 'gateway.rpc.hint.opc-method' | translate }} +
+ + {{ 'gateway.rpc.method' | translate }} + + +
+ + {{ 'gateway.rpc.arguments' | translate }} + +
+
+
gateway.type
+
+ + + +
+ + + {{ valueTypes.get(argumentFormGroup.get('type').value)?.name | translate }} +
+
+ + + + {{ valueTypes.get(valueType).name | translate }} + +
+
+
+
+
+
gateway.value
+ + + + + + + true + false + + + + warning + + +
+ +
+ +
+
+ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.scss new file mode 100644 index 0000000000..5108cc70b1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.scss @@ -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; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.ts new file mode 100644 index 0000000000..0c0dbea3bd --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/rpc-parameters/opc-rpc-parameters/opc-rpc-parameters.component.ts @@ -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(); + + 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}); + }); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html index f72a2c18af..f40914f3d0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.html @@ -70,8 +70,8 @@ matTooltipPosition="above" matTooltipClass="tb-error-tooltip" [matTooltip]="('gateway.value-required') | translate" - *ngIf="keyControl.get(keyControl.get('type').value).hasError('required') - && keyControl.get(keyControl.get('type').value).touched" + *ngIf="keyControl.get(keyControl.get('value').value).hasError('required') + && keyControl.get(keyControl.get('value').value).touched" class="tb-error"> warning diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts index 8e1c6fdb57..27096ea25a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component.ts @@ -104,7 +104,7 @@ export class TypeValuePanelComponent implements ControlValueAccessor, Validator, dataKeyFormGroup.disable({emitEvent: false}); dataKeyFormGroup.get('type').enable({emitEvent: false}); dataKeyFormGroup.get(type).enable({emitEvent: false}); - }) + }); } deleteKey($event: Event, index: number): void { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html index ee644c54c7..0d01a1c1d7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.html @@ -44,7 +44,11 @@
{{!innerValue ? ('gateway.rpc.' + config.key | translate) : config.key}}
-
+ {{ config.value | getRpcTemplateArrayView }} +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss index 27ff7843ee..bf037a7249 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.scss @@ -104,6 +104,10 @@ flex: 1; margin: 0; } + + .array-value { + margin-left: 10px; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts index 466ab5fdb5..9c2e551a22 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector-templates.component.ts @@ -49,7 +49,8 @@ export class GatewayServiceRPCConnectorTemplatesComponent implements OnInit { rpcTemplates: Array; public readonly originalOrder = (): number => 0; - public readonly isObject = (value: any) => isLiteralObject(value); + public readonly isObject = (value: unknown) => isLiteralObject(value); + public readonly isArray = (value: unknown) => Array.isArray(value); public readonly SNMPMethodsTranslations = SNMPMethodsTranslations; constructor(private attributeService: AttributeService) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html index 3e94ae96d7..fde4ab28ac 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.html @@ -20,36 +20,6 @@ class="mat-subtitle-1 title">{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}
- - - {{ 'gateway.rpc.methodFilter' | translate }} - - - - {{ 'gateway.rpc.requestTopicExpression' | translate }} - - - - {{ 'gateway.rpc.withResponse' | translate }} - - - {{ 'gateway.rpc.responseTopicExpression' | translate }} - - - - {{ 'gateway.rpc.responseTimeout' | translate }} - - - - {{ 'gateway.rpc.valueExpression' | translate }} - - - {{ 'gateway.rpc.methodRPC' | translate }} @@ -407,31 +377,6 @@ - - - {{ 'gateway.rpc.method' | translate }} - - -
- {{ 'gateway.rpc.arguments' | translate }} -
- - - - delete - -
- -
-
{{ 'gateway.statistics.command' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts index 989f9daeed..f77e877aea 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc-connector.component.ts @@ -51,7 +51,6 @@ import { } from '@shared/components/dialog/json-object-edit-dialog.component'; import { jsonRequired } from '@shared/components/json-object-edit.component'; import { deepClone } from '@core/utils'; -import { takeUntil, tap } from "rxjs/operators"; import { Subject } from "rxjs"; @Component({ @@ -129,7 +128,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C this.propagateChange({...this.commandForm.value, ...value}); } }); - this.observeMQTTWithResponse(); } ngOnDestroy(): void { @@ -141,16 +139,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C let formGroup: FormGroup; switch (type) { - case ConnectorType.MQTT: - formGroup = 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(this.numbersOnlyPattern)]], - valueExpression: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - withResponse: [false, []], - }); - break; case ConnectorType.BACNET: formGroup = this.fb.group({ method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], @@ -246,12 +234,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C httpHeaders: this.fb.array([]), }) break; - case ConnectorType.OPCUA: - formGroup = this.fb.group({ - method: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], - arguments: this.fb.array([]), - }) - break; default: formGroup = this.fb.group({ command: [null, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], @@ -293,18 +275,6 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C return (this.commandForm.get(path) as FormArray).controls as FormControl[]; } - addOCPUAArguments(value: string = null) { - const oidsFA = this.commandForm.get('arguments') as FormArray; - if (oidsFA) { - oidsFA.push(this.fb.control(value, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]), {emitEvent: false}); - } - } - - removeOCPUAArguments(index: number) { - const oidsFA = this.commandForm.get('arguments') as FormArray; - oidsFA.removeAt(index); - } - openEditJSONDialog($event: Event) { if ($event) { $event.stopPropagation(); @@ -368,34 +338,8 @@ export class GatewayServiceRPCConnectorComponent implements OnInit, OnDestroy, C }) delete value.httpHeaders; break; - case ConnectorType.OPCUA: - this.clearFromArrayByName("arguments"); - value.arguments.forEach(value => { - this.addOCPUAArguments(value) - }) - delete value.arguments; - break; } this.commandForm.patchValue(value, {onlySelf: false}); } } - - private observeMQTTWithResponse(): void { - if (this.connectorType === ConnectorType.MQTT) { - this.commandForm.get('withResponse').valueChanges.pipe( - tap((isActive: boolean) => { - const responseTopicControl = this.commandForm.get('responseTopicExpression'); - const responseTimeoutControl = this.commandForm.get('responseTimeout'); - if (isActive) { - responseTopicControl.enable(); - responseTimeoutControl.enable(); - } else { - responseTopicControl.disable(); - responseTimeoutControl.disable(); - } - }), - takeUntil(this.destroy$), - ).subscribe(); - } - } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html index 41dea0b656..41a655d93e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.html @@ -42,16 +42,20 @@
- +
{{ 'gateway.rpc.title' | translate: {type: gatewayConnectorDefaultTypesTranslates.get(connectorType)} }}
- + + + + +
-
+
SVG.Svg', - description: 'A root svg node. Instance of SVG.Svg.' + type: 'SVG.Svg', + description: 'A root svg node. Instance of SVG.Svg.' } }); @@ -1114,8 +1115,9 @@ export const elementStateRenderFunctionCompletions = (ctxCompletion: TbEditorCom meta: 'argument', type: 'Element', description: 'SVG element.
' + - 'See Manipulating section to manipulate the element.
' + - 'See Animating section to animate the element.' + 'See Manipulating section to manipulate the element.
' + + 'See Animating section to animate the element.' } }); @@ -1133,7 +1135,7 @@ export const scadaSymbolContextCompletion = (metadata: ScadaSymbolMetadata, tags customTranslate: CustomTranslatePipe): TbEditorCompletion => { const scadaSymbolAnimationLink = HelpLinks.linksMap.scadaSymbolDevAnimation; - const scadaSymbolAnimation = `ScadaSymbolAnimation`; + const scadaSymbolAnimation = `ScadaSymbolAnimation`; const properties: TbEditorCompletion = { meta: 'object', @@ -1282,8 +1284,8 @@ export const scadaSymbolContextCompletion = (metadata: ScadaSymbolMetadata, tags text: { meta: 'function', description: 'Set text to element(s). Only applicable for elements of type ' + - 'SVG.Text or ' + - 'SVG.Tspan.', + 'SVG.Text or ' + + 'SVG.Tspan.', args: [ { name: 'element', @@ -1300,8 +1302,8 @@ export const scadaSymbolContextCompletion = (metadata: ScadaSymbolMetadata, tags font: { meta: 'function', description: 'Set element(s) text font and color. Only applicable for elements of type ' + - 'SVG.Text or ' + - 'SVG.Tspan.', + 'SVG.Text or ' + + 'SVG.Tspan.', args: [ { name: 'element', @@ -1323,7 +1325,7 @@ export const scadaSymbolContextCompletion = (metadata: ScadaSymbolMetadata, tags icon: { meta: 'function', description: 'Draws icon inside element(s). Only applicable for elements of type ' + - 'SVG.G.', + 'SVG.G.', args: [ { name: 'element', @@ -1431,8 +1433,9 @@ export const scadaSymbolContextCompletion = (metadata: ScadaSymbolMetadata, tags tags: tagsCompletions, svg: { meta: 'argument', - type: 'SVG.Svg', - description: 'A root svg node. Instance of SVG.Svg.' + type: 'SVG.Svg', + description: 'A root svg node. Instance of SVG.Svg.' } } }; diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.models.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.models.ts index e4d2af97af..cb4a3805f3 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.models.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.models.ts @@ -21,7 +21,7 @@ import { serviceCompletions } from '@shared/models/ace/service-completion.models const widgetEditorCompletions: TbEditorCompletions = { ... {self: { description: 'Built-in variable self that is a reference to the widget instance', - type: 'WidgetTypeInstance', + type: 'WidgetTypeInstance', meta: 'object', children: { ...{ @@ -31,19 +31,19 @@ const widgetEditorCompletions: TbEditorCompletions = { }, onDataUpdated: { description: 'Called when the new data is available from the widget subscription.
Latest data can be accessed from ' + - 'the defaultSubscription property of widget context (ctx).', + 'the defaultSubscription property of widget context (ctx).', meta: 'function' }, onResize: { - description: 'Called when widget container is resized. Latest width and height can be obtained from widget context (ctx).', + description: 'Called when widget container is resized. Latest width and height can be obtained from widget context (ctx).', meta: 'function' }, onEditModeChanged: { - description: 'Called when dashboard editing mode is changed. Latest mode is handled by isEdit property of widget context (ctx).', + description: 'Called when dashboard editing mode is changed. Latest mode is handled by isEdit property of widget context (ctx).', meta: 'function' }, onMobileModeChanged: { - description: 'Called when dashboard view width crosses mobile breakpoint. Latest state is handled by isMobile property of widget context (ctx).', + description: 'Called when dashboard view width crosses mobile breakpoint. Latest state is handled by isMobile property of widget context (ctx).', meta: 'function' }, onDestroy: { @@ -51,7 +51,7 @@ const widgetEditorCompletions: TbEditorCompletions = { meta: 'function' }, getSettingsSchema: { - description: 'Optional function returning widget settings schema json as alternative to Settings tab of Settings schema section.', + description: 'Optional function returning widget settings schema json as alternative to Settings tab of Settings schema section.', meta: 'function', return: { description: 'An widget settings schema json', @@ -59,7 +59,7 @@ const widgetEditorCompletions: TbEditorCompletions = { } }, getDataKeySettingsSchema: { - description: 'Optional function returning particular data key settings schema json as alternative to Data key settings schema of Settings schema section.', + description: 'Optional function returning particular data key settings schema json as alternative to Data key settings schema of Settings schema section.', meta: 'function', return: { description: 'A particular data key settings schema json', @@ -71,7 +71,7 @@ const widgetEditorCompletions: TbEditorCompletions = { meta: 'function', return: { description: 'An object describing widget datasource parameters.', - type: 'WidgetTypeParameters' + type: 'WidgetTypeParameters' } }, actionSources: { @@ -79,7 +79,7 @@ const widgetEditorCompletions: TbEditorCompletions = { meta: 'function', return: { description: 'A map of action sources by action source id.', - type: '{[actionSourceId: string]: WidgetActionSource}' + type: '{[actionSourceId: string]: WidgetActionSource}' } } }, diff --git a/ui-ngx/src/app/shared/directives/context-menu.directive.ts b/ui-ngx/src/app/shared/directives/context-menu.directive.ts new file mode 100644 index 0000000000..ada727cfe2 --- /dev/null +++ b/ui-ngx/src/app/shared/directives/context-menu.directive.ts @@ -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(); + + constructor(private el: ElementRef) { + $(this.el.nativeElement).on('tbcontextmenu', (e: TbContextMenuEvent) => this.tbcontextmenu.emit(e)); + } + + ngOnDestroy() { + $(this.el.nativeElement).off('tbcontextmenu'); + } +} diff --git a/ui-ngx/src/app/shared/directives/public-api.ts b/ui-ngx/src/app/shared/directives/public-api.ts index 6f25320d90..7e0902e223 100644 --- a/ui-ngx/src/app/shared/directives/public-api.ts +++ b/ui-ngx/src/app/shared/directives/public-api.ts @@ -16,3 +16,4 @@ export * from './truncate-with-tooltip.directive'; export * from './ellipsis-chip-list.directive'; +export * from './context-menu.directive'; diff --git a/ui-ngx/src/app/shared/models/ace/service-completion.models.ts b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts index dd7fb74dc8..5efc428194 100644 --- a/ui-ngx/src/app/shared/models/ace/service-completion.models.ts +++ b/ui-ngx/src/app/shared/models/ace/service-completion.models.ts @@ -16,101 +16,101 @@ import { FunctionArg, FunctionArgType, TbEditorCompletions } from '@shared/models/ace/completion.models'; -export const entityIdHref = 'EntityId'; +export const entityIdHref = 'EntityId'; -export const baseDataHref = 'Base data'; +export const baseDataHref = 'Base data'; -export const alarmDataHref = 'Alarm data'; +export const alarmDataHref = 'Alarm data'; -export const alarmDataQueryHref = 'Alarm data query'; +export const alarmDataQueryHref = 'Alarm data query'; -export const attributeScopeHref = 'Attribute scope'; +export const attributeScopeHref = 'Attribute scope'; -export const entityTypeHref = 'EntityType'; +export const entityTypeHref = 'EntityType'; -export const pageDataHref = 'PageData'; +export const pageDataHref = 'PageData'; -export const deviceInfoHref = 'DeviceInfo'; +export const deviceInfoHref = 'DeviceInfo'; -export const assetInfoHref = 'AssetInfo'; +export const assetInfoHref = 'AssetInfo'; -export const entityViewInfoHref = 'EntityViewInfo'; +export const entityViewInfoHref = 'EntityViewInfo'; -export const entityRelationsQueryHref = 'EntityRelationsQuery'; +export const entityRelationsQueryHref = 'EntityRelationsQuery'; -export const entityRelationInfoHref = 'EntityRelationInfo'; +export const entityRelationInfoHref = 'EntityRelationInfo'; -export const dashboardInfoHref = 'DashboardInfo'; +export const dashboardInfoHref = 'DashboardInfo'; -export const deviceHref = 'Device'; +export const deviceHref = 'Device'; -export const assetHref = 'Asset'; +export const assetHref = 'Asset'; -export const entityViewHref = 'entityView'; +export const entityViewHref = 'entityView'; -export const entityRelationHref = 'Entity relation'; +export const entityRelationHref = 'Entity relation'; -export const dashboardHref = 'Dashboard'; +export const dashboardHref = 'Dashboard'; -export const customerHref = 'Customer'; +export const customerHref = 'Customer'; -export const attributeDataHref = 'Attribute Data'; +export const attributeDataHref = 'Attribute Data'; -export const timeseriesDataHref = 'Timeseries Data'; +export const timeseriesDataHref = 'Timeseries Data'; -export const aggregationTypeHref = 'Aggregation Type'; +export const aggregationTypeHref = 'Aggregation Type'; -export const dataSortOrderHref = 'Data Sort Order'; +export const dataSortOrderHref = 'Data Sort Order'; -export const userHref = 'User'; +export const userHref = 'User'; -export const entityDataHref = 'Entity data'; +export const entityDataHref = 'Entity data'; -export const entityDataQueryHref = 'Entity Data Query'; +export const entityDataQueryHref = 'Entity Data Query'; -export const deviceCredentialsHref = 'DeviceCredentials'; +export const deviceCredentialsHref = 'DeviceCredentials'; -export const entityFilterHref = 'Entity filter'; +export const entityFilterHref = 'Entity filter'; -export const entityInfoHref = 'Entity info'; +export const entityInfoHref = 'Entity info'; -export const aliasEntityTypeHref = 'Alias Entity Type'; +export const aliasEntityTypeHref = 'Alias Entity Type'; -export const aliasFilterTypeHref = 'Alias filter type'; +export const aliasFilterTypeHref = 'Alias filter type'; -export const entityAliasHref = 'Entity alias'; +export const entityAliasHref = 'Entity alias'; -export const dataKeyTypeHref = 'Data key type'; +export const dataKeyTypeHref = 'Data key type'; -export const subscriptionInfoHref = 'Subscription info'; +export const subscriptionInfoHref = 'Subscription info'; -export const dataSourceHref = 'Datasource'; +export const dataSourceHref = 'Datasource'; -export const stateParamsHref = 'State params'; +export const stateParamsHref = 'State params'; -export const aliasInfoHref = 'Alias info'; +export const aliasInfoHref = 'Alias info'; -export const entityAliasFilterHref = 'Entity alias filter'; +export const entityAliasFilterHref = 'Entity alias filter'; -export const entityAliasFilterResultHref = 'Entity alias filter result'; +export const entityAliasFilterResultHref = 'Entity alias filter result'; -export const importEntityDataHref = 'Import entity data'; +export const importEntityDataHref = 'Import entity data'; -export const importEntitiesResultInfoHref = 'Import entities result info'; +export const importEntitiesResultInfoHref = 'Import entities result info'; -export const customDialogComponentHref = 'CustomDialogComponent'; +export const customDialogComponentHref = 'CustomDialogComponent'; -export const resourceInfoHref = 'Resource info'; +export const resourceInfoHref = 'Resource info'; export const pageLinkArg: FunctionArg = { name: 'pageLink', - type: 'PageLink', + type: 'PageLink', description: 'Page link object used to perform paginated request.' }; export const requestConfigArg: FunctionArg = { name: 'config', - type: 'RequestConfig', + type: 'RequestConfig', description: 'HTTP request configuration.', optional: true }; @@ -167,9 +167,9 @@ export function observablePageDataReturnType(objectType: string): FunctionArgTyp export const serviceCompletions: TbEditorCompletions = { deviceService: { description: 'Device Service API
' + - 'See DeviceService for API reference.', + 'See DeviceService for API reference.', meta: 'service', - type: 'DeviceService', + type: 'DeviceService', children: { getTenantDeviceInfos: { description: 'Get tenant devices', @@ -243,7 +243,7 @@ export const serviceCompletions: TbEditorCompletions = { args: [ requestConfigArg ], - return: observableArrayReturnType('EntitySubtype') + return: observableArrayReturnType('EntitySubtype') }, getDeviceCredentials: { description: 'Get device credentials by device id', @@ -322,7 +322,7 @@ export const serviceCompletions: TbEditorCompletions = { description: 'Find devices by search query', meta: 'function', args: [ - { name: 'query', type: 'DeviceSearchQuery', + { name: 'query', type: 'DeviceSearchQuery', description: 'Device search query object'}, requestConfigArg ], @@ -346,7 +346,7 @@ export const serviceCompletions: TbEditorCompletions = { description: 'Claiming device name'}, requestConfigArg ], - return: observableReturnType('ClaimResult') + return: observableReturnType('ClaimResult') }, unclaimDevice: { description: 'Send un-claim device request', @@ -362,9 +362,9 @@ export const serviceCompletions: TbEditorCompletions = { }, assetService: { description: 'Asset Service API
' + - 'See AssetService for API reference.', + 'See AssetService for API reference.', meta: 'service', - type: 'AssetService', + type: 'AssetService', children: { getTenantAssetInfos: { description: 'Get tenant assets', @@ -438,7 +438,7 @@ export const serviceCompletions: TbEditorCompletions = { args: [ requestConfigArg ], - return: observableArrayReturnType('EntitySubtype') + return: observableArrayReturnType('EntitySubtype') }, makeAssetPublic: { description: 'Make asset public (available from public dashboard)', @@ -474,7 +474,7 @@ export const serviceCompletions: TbEditorCompletions = { args: [ { name: 'query', - type: 'AssetSearchQuery', + type: 'AssetSearchQuery', description: 'Asset search query object' }, requestConfigArg @@ -497,9 +497,9 @@ export const serviceCompletions: TbEditorCompletions = { }, entityViewService: { description: 'EntityView Service API
' + - 'See EntityViewService for API reference.', + 'See EntityViewService for API reference.', meta: 'service', - type: 'EntityViewService', + type: 'EntityViewService', children: { getTenantEntityViewInfos: { description: 'Get tenant entity view infos', @@ -564,7 +564,7 @@ export const serviceCompletions: TbEditorCompletions = { args: [ requestConfigArg ], - return: observableArrayReturnType('EntitySubtype') + return: observableArrayReturnType('EntitySubtype') }, makeEntityViewPublic: { description: 'Make entity view public (available from public dashboard)', @@ -600,7 +600,7 @@ export const serviceCompletions: TbEditorCompletions = { args: [ { name: 'query', - type: 'AssetSearchQuery', + type: 'AssetSearchQuery', description: 'Entity view search query object' }, requestConfigArg @@ -611,9 +611,9 @@ export const serviceCompletions: TbEditorCompletions = { }, customerService: { description: 'Customer Service API
' + - 'See CustomerService for API reference.', + 'See CustomerService for API reference.', meta: 'service', - type: 'CustomerService', + type: 'CustomerService', children: { getCustomer: { description: 'Get customer by id', @@ -655,9 +655,9 @@ export const serviceCompletions: TbEditorCompletions = { }, dashboardService: { description: 'Dashboard Service API
' + - 'See DashboardService for API reference.', + 'See DashboardService for API reference.', meta: 'service', - type: 'DashboardService', + type: 'DashboardService', children: { getTenantDashboards: { description: 'Get tenant dashboards', @@ -814,9 +814,9 @@ export const serviceCompletions: TbEditorCompletions = { }, userService: { description: 'User Service API
' + - 'See UserService for API reference.', + 'See UserService for API reference.', meta: 'service', - type: 'UserService', + type: 'UserService', children: { getUsers: { description: 'Get users', @@ -907,9 +907,9 @@ export const serviceCompletions: TbEditorCompletions = { }, entityRelationService: { description: 'Entity Relation Service API
' + - 'See EntityRelationService for API reference.', + 'See EntityRelationService for API reference.', meta: 'service', - type: 'EntityRelationService', + type: 'EntityRelationService', children: { saveRelation: { description: 'Save relation', @@ -1029,9 +1029,9 @@ export const serviceCompletions: TbEditorCompletions = { }, attributeService: { description: 'Attribute Service API
' + - 'See AttributeService for API reference.', + 'See AttributeService for API reference.', meta: 'service', - type: 'AttributeService', + type: 'AttributeService', children: { getEntityAttributes: { description: 'Get entity attributes by id', @@ -1109,9 +1109,9 @@ export const serviceCompletions: TbEditorCompletions = { }, entityService: { description: 'Entity Service API
' + - 'See EntityService for API reference.', + 'See EntityService for API reference.', meta: 'service', - type: 'EntityService', + type: 'EntityService', children: { getEntity: { description: 'Get entity by id', @@ -1304,9 +1304,9 @@ export const serviceCompletions: TbEditorCompletions = { }, resourceService: { description: 'Resource Service API
' + - 'See ResourceService for API reference.', + 'See ResourceService for API reference.', meta: 'service', - type: 'ResourceService', + type: 'ResourceService', children: { getResources: { description: 'Find resources by search text', @@ -1321,9 +1321,9 @@ export const serviceCompletions: TbEditorCompletions = { }, dialogs: { description: 'Dialogs Service API
' + - 'See DialogService for API reference.', + 'See DialogService for API reference.', meta: 'service', - type: 'DialogService', + type: 'DialogService', children: { confirm: { description: 'Confirm', @@ -1382,9 +1382,9 @@ export const serviceCompletions: TbEditorCompletions = { }, customDialog: { description: 'Custom Dialog Service API
' + - 'See CustomDialogService for API reference.', + 'See CustomDialogService for API reference.', meta: 'service', - type: 'CustomDialogService', + type: 'CustomDialogService', children: { customDialog: { description: 'Custom Dialog', @@ -1400,32 +1400,32 @@ export const serviceCompletions: TbEditorCompletions = { }, date: { description: 'Date Pipe
Formats a date value according to locale rules.
' + - 'See DatePipe for API reference.', + 'See DatePipe for API reference.', meta: 'service', - type: 'DatePipe' + type: 'DatePipe' }, translate: { description: 'Translate Service API
' + - 'See TranslateService for API reference.', + 'See TranslateService for API reference.', meta: 'service', - type: 'TranslateService' + type: 'TranslateService' }, http: { description: 'HTTP Client Service
' + - 'See HttpClient for API reference.', + 'See HttpClient for API reference.', meta: 'service', - type: 'HttpClient' + type: 'HttpClient' }, sanitizer: { description: 'DomSanitizer Service
' + - 'See DomSanitizer for API reference.', + 'See DomSanitizer for API reference.', meta: 'service', - type: 'DomSanitizer' + type: 'DomSanitizer' }, router: { description: 'Router Service
' + - 'See Router for API reference.', + 'See Router for API reference.', meta: 'service', - type: 'Router' + type: 'Router' } }; diff --git a/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts index e54e52bdbc..b2b0b6235d 100644 --- a/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts +++ b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts @@ -20,7 +20,7 @@ import { entityIdHref, serviceCompletions } from '@shared/models/ace/service-com export const timewindowCompletion: TbEditorCompletion = { description: 'Timewindow configuration object', meta: 'property', - type: 'Timewindow', + type: 'Timewindow', children: { displayValue: { description: 'Current timewindow display value.', @@ -50,7 +50,7 @@ export const timewindowCompletion: TbEditorCompletion = { realtime: { description: 'Realtime timewindow configuration object.', meta: 'property', - type: 'IntervalWindow', + type: 'IntervalWindow', children: { interval: { description: 'Timewindow aggregation interval in milliseconds', @@ -67,7 +67,7 @@ export const timewindowCompletion: TbEditorCompletion = { history: { description: 'History timewindow configuration object.', meta: 'property', - type: 'HistoryWindow', + type: 'HistoryWindow', children: { historyType: { description: 'History timewindow type (0 - last interval, 1 - fixed)', @@ -87,7 +87,7 @@ export const timewindowCompletion: TbEditorCompletion = { fixedTimewindow: { description: 'Fixed history timewindow configuration object', meta: 'property', - type: 'FixedWindow', + type: 'FixedWindow', children: { startTimeMs: { description: 'Timewindow start time in UTC milliseconds', @@ -106,7 +106,7 @@ export const timewindowCompletion: TbEditorCompletion = { aggregation: { description: 'Timewindow aggregation configuration object.', meta: 'property', - type: 'Aggregation', + type: 'Aggregation', children: { interval: { description: 'Aggregation interval in milliseconds', @@ -116,7 +116,7 @@ export const timewindowCompletion: TbEditorCompletion = { type: { description: 'Aggregation type', meta: 'property', - type: 'AggregationType' + type: 'AggregationType' }, limit: { description: 'Maximum allowed datapoints when aggregation is disabled (AggregationType == \'NONE\')', @@ -132,7 +132,7 @@ export const widgetContextCompletions: TbEditorCompletions = { ctx: { description: 'A reference to widget context that has all necessary API
and data used by widget instance.', meta: 'object', - type: 'WidgetContext', + type: 'WidgetContext', children: { ...{ $container: { @@ -143,7 +143,7 @@ export const widgetContextCompletions: TbEditorCompletions = { $scope: { description: 'Reference to the current widget component.
Can be used to access/modify component properties when widget is built using Angular approach.', meta: 'property', - type: 'IDynamicWidgetComponent' + type: 'IDynamicWidgetComponent' }, width: { description: 'Current width of widget container in pixels.', @@ -168,7 +168,7 @@ export const widgetContextCompletions: TbEditorCompletions = { widgetConfig: { description: 'Common widget configuration containing properties such as color (text color), backgroundColor (widget background color), etc.', meta: 'property', - type: 'WidgetConfig', + type: 'WidgetConfig', children: { title: { description: 'Widget title.', @@ -233,17 +233,17 @@ export const widgetContextCompletions: TbEditorCompletions = { legendConfig: { description: 'Legend configuration.', meta: 'property', - type: 'LegendConfig', + type: 'LegendConfig', children: { position: { description: 'Legend position. Possible values: \'top\', \'bottom\', \'left\', \'right\'', meta: 'property', - type: 'LegendPosition', + type: 'LegendPosition', }, direction: { description: 'Legend direction. Possible values: \'column\', \'row\'', meta: 'property', - type: 'LegendDirection', + type: 'LegendDirection', }, showMin: { description: 'Whether to display aggregated min values.', @@ -331,12 +331,12 @@ export const widgetContextCompletions: TbEditorCompletions = { alarmSource: { description: 'Configured alarm source for alarm widget type.', meta: 'property', - type: 'Datasource' + type: 'Datasource' }, alarmSearchStatus: { description: 'Configured default alarm search status for alarm widget type.', meta: 'property', - type: 'AlarmSearchStatus' + type: 'AlarmSearchStatus' }, alarmsPollingInterval: { description: 'Configured alarms polling interval for alarm widget type.', @@ -356,29 +356,29 @@ export const widgetContextCompletions: TbEditorCompletions = { datasources: { description: 'Array of configured widget datasources.', meta: 'property', - type: 'Array<Datasource>' + type: 'Array<Datasource>' } } }, settings: { - description: 'Widget settings containing widget specific properties according to the defined settings json schema', + description: 'Widget settings containing widget specific properties according to the defined settings json schema', meta: 'property', type: 'object' }, datasources: { description: 'Array of resolved widget datasources.', meta: 'property', - type: 'Array<Datasource>' + type: 'Array<Datasource>' }, data: { description: 'Array of latest datasources data.', meta: 'property', - type: 'Array<DatasourceData>' + type: 'Array<DatasourceData>' }, timeWindow: { description: 'Current widget timewindow (applicable for timeseries widgets).', meta: 'property', - type: 'WidgetTimewindow' + type: 'WidgetTimewindow' }, units: { description: 'Optional property defining units text of values displayed by widget. Useful for simple widgets like cards or gauges.', @@ -393,7 +393,7 @@ export const widgetContextCompletions: TbEditorCompletions = { currentUser: { description: 'Current user object.', meta: 'property', - type: 'AuthUser', + type: 'AuthUser', children: { sub: { description: 'User subject (email).', @@ -443,7 +443,7 @@ export const widgetContextCompletions: TbEditorCompletions = { authority: { description: 'User authority. Possible values: SYS_ADMIN, TENANT_ADMIN, CUSTOMER_USER', meta: 'property', - type: 'Authority' + type: 'Authority' } } }, @@ -468,12 +468,12 @@ export const widgetContextCompletions: TbEditorCompletions = { defaultSubscription: { description: 'Default widget subscription object contains all subscription information,
including current data, according to the widget type.', meta: 'property', - type: 'IWidgetSubscription' + type: 'IWidgetSubscription' }, timewindowFunctions: { description: 'Object with timewindow functions used to manage widget data time frame. Can by used by Time-series or Alarm widgets.', meta: 'property', - type: 'TimewindowFunctions', + type: 'TimewindowFunctions', children: { onUpdateTimewindow: { description: 'This function can be used to update current subscription time frame
to historical one identified by startTimeMs and endTimeMs arguments.', @@ -500,7 +500,7 @@ export const widgetContextCompletions: TbEditorCompletions = { controlApi: { description: 'Object that provides API functions for RPC (Control) widgets.', meta: 'property', - type: 'RpcApi', + type: 'RpcApi', children: { sendOneWayCommand: { description: 'Sends one way (without response) RPC command to the device.', @@ -585,7 +585,7 @@ export const widgetContextCompletions: TbEditorCompletions = { actionsApi: { description: 'Set of API functions to work with user defined actions.', meta: 'property', - type: 'WidgetActionsApi', + type: 'WidgetActionsApi', children: { getActionDescriptors: { description: 'Get list of action descriptors for provided actionSourceId.', @@ -599,7 +599,7 @@ export const widgetContextCompletions: TbEditorCompletions = { ], return: { description: 'The list of action descriptors', - type: 'Array<WidgetActionDescriptor>' + type: 'Array<WidgetActionDescriptor>' } }, handleWidgetAction: { @@ -614,7 +614,7 @@ export const widgetContextCompletions: TbEditorCompletions = { { name: 'descriptor', description: 'An action descriptor.', - type: 'WidgetActionDescriptor' + type: 'WidgetActionDescriptor' }, { name: 'entityId', @@ -635,7 +635,7 @@ export const widgetContextCompletions: TbEditorCompletions = { stateController: { description: 'Reference to Dashboard state controller, providing API to manage current dashboard state.', meta: 'property', - type: 'IStateController', + type: 'IStateController', children: { openState: { description: 'Navigate to new dashboard state.', @@ -649,7 +649,7 @@ export const widgetContextCompletions: TbEditorCompletions = { { name: 'params', description: 'An object with state parameters to use by the new state.', - type: 'StateParams', + type: 'StateParams', optional: true }, { @@ -667,7 +667,7 @@ export const widgetContextCompletions: TbEditorCompletions = { { name: 'id', description: 'An array state object of the target dashboard state.', - type: 'Array StateObject', + type: 'Array StateObject', }, { name: 'openRightLayout', @@ -690,7 +690,7 @@ export const widgetContextCompletions: TbEditorCompletions = { { name: 'params', description: 'An object with state parameters to update current state parameters.', - type: 'StateParams', + type: 'StateParams', optional: true }, { @@ -714,7 +714,7 @@ export const widgetContextCompletions: TbEditorCompletions = { meta: 'function', return: { description: 'current dashboard state parameters.', - type: 'StateParams' + type: 'StateParams' } }, getStateParamsByStateId: { @@ -729,7 +729,7 @@ export const widgetContextCompletions: TbEditorCompletions = { ], return: { description: 'current dashboard state parameters.', - type: 'StateParams' + type: 'StateParams' } } } diff --git a/ui-ngx/src/app/shared/models/jquery-event.models.ts b/ui-ngx/src/app/shared/models/jquery-event.models.ts new file mode 100644 index 0000000000..5657beb60b --- /dev/null +++ b/ui-ngx/src/app/shared/models/jquery-event.models.ts @@ -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'); + } + } + }; +}; diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 130e272c21..309a3e2bf3 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -68,6 +68,7 @@ import { NgxHmCarouselModule } from 'ngx-hm-carousel'; import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular'; import { UserMenuComponent } from '@shared/components/user-menu.component'; import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive'; +import { ContextMenuDirective } from '@shared/directives/context-menu.directive'; import { NospacePipe } from '@shared/pipe/nospace.pipe'; import { TranslateModule } from '@ngx-translate/core'; import { TbCheckboxComponent } from '@shared/components/tb-checkbox.component'; @@ -371,6 +372,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) LedLightComponent, MarkdownEditorComponent, TruncateWithTooltipDirective, + ContextMenuDirective, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -633,6 +635,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) LedLightComponent, MarkdownEditorComponent, TruncateWithTooltipDirective, + ContextMenuDirective, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, diff --git a/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json b/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json index e3277ac88a..59eb61a935 100644 --- a/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json +++ b/ui-ngx/src/assets/dashboard/tenant_admin_home_page.json @@ -1208,7 +1208,7 @@ "dropShadow": false, "enableFullscreen": false, "widgetStyle": {}, - "widgetCss": " .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding: 0;\n}\n\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding-bottom: 8px;\n font-weight: 600;\n font-size: 20px;\n line-height: 24px;\n letter-spacing: 0.1px;\n color: rgba(0, 0, 0, 0.76);\n}\n\n.tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel {\n padding: 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding-bottom: 0;\n font-weight: 500;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1182px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel {\n gap: 0;\n }\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n min-width: 150px;\n }\n}\n\n@media screen and (max-width: 960px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n max-width: 120px;\n }\n \n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-badges {\n max-width: 160px;\n }\n}\n\n@media screen and (min-width: 1819px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n max-width: 125px;\n }\n \n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-badges {\n max-width: 160px;\n }\n}\n\n@media screen and (min-width: 960px) and (max-height: 960px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode-label {\n display: none;\n }\n}", + "widgetCss": " .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding: 0;\n}\n\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding-bottom: 8px;\n font-weight: 600;\n font-size: 20px;\n line-height: 24px;\n letter-spacing: 0.1px;\n color: rgba(0, 0, 0, 0.76);\n}\n\n.tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel {\n padding: 0;\n}\n\n@media screen and (min-width: 960px) and (max-width: 1819px) {\n .tb-widget-container > .tb-widget > .tb-widget-content .tb-widget-title > .title-row > .title {\n padding-bottom: 0;\n font-weight: 500;\n font-size: 14px;\n line-height: 20px;\n letter-spacing: 0.25px;\n }\n}\n\n@media screen and (min-width: 960px) and (max-width: 1182px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel {\n gap: 0;\n }\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n min-width: 150px;\n }\n}\n\n@media screen and (max-width: 960px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n max-width: 120px;\n }\n \n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-badges {\n max-width: 160px;\n }\n}\n\n@media screen and (min-width: 1819px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode {\n max-width: 125px;\n }\n \n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-badges {\n max-width: 160px;\n }\n}\n\n@media screen and (min-width: 960px) and (max-height: 965px) {\n .tb-widget-container > .tb-widget > .tb-widget-content > .tb-widget .tb-mobile-app-qrcode-panel .tb-qrcode-label {\n display: none;\n }\n}", "showTitleIcon": false, "titleTooltip": "", "titleStyle": null, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 95314ded5c..c66cc6dbd5 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3142,10 +3142,11 @@ "title": "{{type}} Connector RPC parameters", "templates-title": "Connector RPC Templates", "methodFilter": "Method filter", + "method-name": "Method name", "requestTopicExpression": "Request topic expression", "responseTopicExpression": "Response topic expression", - "responseTimeout": "Response Time", - "valueExpression": "Value Expression", + "responseTimeout": "Response timeout", + "valueExpression": "Value expression", "tag": "Tag", "type": "Type", "functionCode": "Function Code", @@ -3184,6 +3185,11 @@ "tries": "Tries", "httpHeaders": "HTTP Headers", "header-name": "Header name", + "hint": { + "modbus-response-reading": "RPC response will return all subtracted values from all connected devices when the reading functions are selected.", + "modbus-writing-functions": "RPC will write a filled value to all connected devices when the writing functions are selected.", + "opc-method": "A filled method name is the OPC-UA method that will processed on the server side (make sure your node has the requested method)." + }, "security-name": "Security name", "value": "Value", "security": "Security", diff --git a/ui-ngx/src/typings/jquery.typings.d.ts b/ui-ngx/src/typings/jquery.typings.d.ts index d457729ace..a058373022 100644 --- a/ui-ngx/src/typings/jquery.typings.d.ts +++ b/ui-ngx/src/typings/jquery.typings.d.ts @@ -14,6 +14,9 @@ /// limitations under the License. /// +import { TbContextMenuEvent } from '@shared/models/jquery-event.models'; + interface JQuery { terminal(options?: any): any; + on(events: 'tbcontextmenu', handler: (e: TbContextMenuEvent) => void): this; } diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 51bfe8f56e..cc6762eb57 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -3052,10 +3052,10 @@ dependencies: "@types/jasmine" "*" -"@types/jquery@*", "@types/jquery@^3.5.16", "@types/jquery@^3.5.29": - version "3.5.29" - resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.29.tgz#3c06a1f519cd5fc3a7a108971436c00685b5dcea" - integrity sha512-oXQQC9X9MOPRrMhPHHOsXqeQDnWeCDT3PelUIg/Oy8FAbzSZtFHRjc7IpbfFVmpLtJ+UOoywpRsuO5Jxjybyeg== +"@types/jquery@*", "@types/jquery@^3.5.29", "@types/jquery@^3.5.30": + version "3.5.30" + resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.30.tgz#888d584cbf844d3df56834b69925085038fd80f7" + integrity sha512-nbWKkkyb919DOUxjmRVk8vwtDb0/k8FKncmUKFi+NY+QXqWltooxTrswvz4LspQwxvLdvzBN1TImr6cw3aQx2A== dependencies: "@types/sizzle" "*" @@ -7377,7 +7377,7 @@ jquery.terminal@^2.35.3: optionalDependencies: fsevents "^2.3.2" -jquery@>=1.9.1, jquery@^3.5.0, jquery@^3.6.3, jquery@^3.7.1: +jquery@>=1.9.1, jquery@^3.5.0, jquery@^3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==