diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index a398c2fc43..c5c3773e56 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -37,8 +37,10 @@ "templateHtml": "\n", "templateCss": ".tb-toast {\n min-width: 0;\n font-size: 14px !important;\n}", "controllerScript": "self.onInit = function() {\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.multipleInputWidget.onDataUpdated();\r\n}\r\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"updateAllValues\": {\n \"title\":\"Update all values, not only modified\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"saveButtonLabel\": {\n \"title\": \"'SAVE' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"resetButtonLabel\": {\n \"title\": \"'UNDO' button label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showGroupTitle\": {\n \"title\":\"Show title for group of fields, related to different entities\",\n \"type\":\"boolean\",\n \"default\": false\n },\n \"groupTitle\": {\n \"title\": \"Group title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n {\n \"key\": \"updateAllValues\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"saveButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n {\n \"key\": \"resetButtonLabel\",\n \"condition\": \"model.showActionButtons === true\"\n },\n \"showResultMessage\",\n \"showGroupTitle\",\n \"groupTitle\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n {\n \"key\": \"fieldsInRow\",\n \"condition\": \"model.fieldsAlignment === 'row'\"\n }\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"slideToggleLabelPosition\": {\n \"title\": \"Label appears after or before the slide-toggle\",\n \"type\": \"string\",\n \"default\": \"after\"\n },\n \"selectOptions\": {\n \"title\": \"Select options\",\n \"type\": \"array\",\n \"default\": [],\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"value\": {\n \"title\": \"Value (write 'null' for create empty option)\",\n \"type\": \"string\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n }\n },\n \"required\": [\"value\"]\n }\n },\n \"step\": {\n \"title\": \"Step interval between values\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\"\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"minValueErrorMessage\": {\n \"title\": \"'Min Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValueErrorMessage\": {\n \"title\": \"'Max Value' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"invalidDateErrorMessage\": {\n \"title\": \"'Invalid Date' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useCustomIcon\": {\n \"title\": \"Use custom icon\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"customIcon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useGetValueFunction\": {\n \"title\": \"use getValue function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"getValueFunctionBody\": {\n \"title\": \"getValue function: f(value, ctx)\",\n \"type\": \"string\"\n },\n \"useSetValueFunction\": {\n \"title\": \"use setValue function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"setValueFunctionBody\": {\n \"title\": \"setValue function: f(value, originValue, ctx)\",\n \"type\": \"string\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n },\n {\n \"value\": \"select\",\n \"label\": \"Select\"\n }\n ]\n },\n {\n \"key\": \"slideToggleLabelPosition\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"condition\": \"model.dataKeyValueType === 'booleanSwitch'\",\n \"items\": [\n {\n \"value\": \"after\",\n \"label\": \"After\"\n },\n {\n \"value\": \"before\",\n \"label\": \"Before\"\n }\n ]\n },\n {\n \"key\": \"selectOptions\",\n \"condition\": \"model.dataKeyValueType === 'select'\",\n \"items\": [\"selectOptions[].value\", \"selectOptions[].label\"]\n },\n {\n \"key\": \"step\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"minValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValue\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n \"required\",\n {\n \"key\": \"requiredErrorMessage\",\n \"condition\": \"model.required === true\"\n },\n {\n \"key\": \"invalidDateErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'dateTime' || model.dataKeyValueType === 'date' || model.dataKeyValueType === 'time'\"\n },\n {\n \"key\": \"minValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"maxValueErrorMessage\",\n \"condition\": \"model.dataKeyValueType === 'double' || model.dataKeyValueType === 'integer'\"\n },\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n \"useCustomIcon\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\",\n\t\t\t\"condition\": \"model.useCustomIcon !== true\"\n\t\t},\n\t\t{\n \t\t\"key\": \"customIcon\",\n\t\t\t\"type\": \"image\",\n\t\t\t\"condition\": \"model.useCustomIcon === true\"\n\t\t},\n\t\t\"useGetValueFunction\",\n\t\t{\n\t\t \"key\": \"getValueFunctionBody\",\n\t\t \"type\": \"javascript\",\n\t\t \"condition\": \"model.useGetValueFunction === true\"\n\t\t},\n\t\t\"useSetValueFunction\",\n\t\t{\n\t\t \"key\": \"setValueFunctionBody\",\n\t\t \"type\": \"javascript\",\n\t\t \"condition\": \"model.useSetValueFunction === true\"\n\t\t}\n ]\n}\n", + "settingsSchema": "", + "dataKeySettingsSchema": "", + "settingsDirective": "tb-update-multiple-attributes-widget-settings", + "dataKeySettingsDirective": "tb-update-multiple-attributes-key-settings", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } }, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.html new file mode 100644 index 0000000000..68a1a6c5dc --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.html @@ -0,0 +1,53 @@ + + + +
+ +
+
+
+
+ + +
+
+ +
+ +
+
+ + widgets.input-widgets.option-value + + + + widgets.input-widgets.option-label + + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.scss new file mode 100644 index 0000000000..36c8d7c154 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.scss @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2022 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: block; + .mat-expansion-panel { + box-shadow: none; + &.datakey-select-option { + border: 1px groove rgba(0, 0, 0, .25); + .mat-expansion-panel-header { + padding: 0 24px 0 8px; + &.mat-expanded { + height: 48px; + } + } + } + } +} + +:host ::ng-deep { + .mat-expansion-panel { + &.datakey-select-option { + .mat-expansion-panel-body { + padding: 0 8px 8px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.ts new file mode 100644 index 0000000000..6a1033a4e2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.ts @@ -0,0 +1,128 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALUE_ACCESSOR, + Validators +} from '@angular/forms'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; + +export interface DataKeySelectOption { + value: string; + label?: string; +} + +export function dataKeySelectOptionValidator(control: AbstractControl) { + const selectOption: DataKeySelectOption = control.value; + if (!selectOption || !selectOption.value) { + return { + dataKeySelectOption: true + }; + } + return null; +} + +@Component({ + selector: 'tb-datakey-select-option', + templateUrl: './datakey-select-option.component.html', + styleUrls: ['./datakey-select-option.component.scss', './../widget-settings.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DataKeySelectOptionComponent), + multi: true + } + ] +}) +export class DataKeySelectOptionComponent extends PageComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled: boolean; + + @Input() + expanded = false; + + @Output() + removeSelectOption = new EventEmitter(); + + private modelValue: DataKeySelectOption; + + private propagateChange = null; + + public selectOptionFormGroup: FormGroup; + + constructor(protected store: Store, + private translate: TranslateService, + private domSanitizer: DomSanitizer, + private fb: FormBuilder) { + super(store); + } + + ngOnInit(): void { + this.selectOptionFormGroup = this.fb.group({ + value: [null, [Validators.required]], + label: [null, []] + }); + this.selectOptionFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.selectOptionFormGroup.disable({emitEvent: false}); + } else { + this.selectOptionFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: DataKeySelectOption): void { + this.modelValue = value; + this.selectOptionFormGroup.patchValue( + value, {emitEvent: false} + ); + } + + selectOptionHtml(): SafeHtml { + const selectOption: DataKeySelectOption = this.selectOptionFormGroup.value; + const value = selectOption?.value || 'Undefined'; + const label = selectOption?.label || ''; + return this.domSanitizer.bypassSecurityTrustHtml(`${value} ${label ? '(' + label + ')' : ''}`); + } + + private updateModel() { + const value: DataKeySelectOption = this.selectOptionFormGroup.value; + this.modelValue = value; + this.propagateChange(this.modelValue); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html new file mode 100644 index 0000000000..2f47998fba --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.html @@ -0,0 +1,246 @@ + +
+ + {{ 'widgets.input-widgets.hide-input-field' | translate }} + +
+ widgets.input-widgets.general-settings +
+ + widgets.input-widgets.datakey-type + + + {{ 'widgets.input-widgets.datakey-type-server' | translate }} + + + {{ 'widgets.input-widgets.datakey-type-shared' | translate }} + + + {{ 'widgets.input-widgets.datakey-type-timeseries' | translate }} + + + + + widgets.input-widgets.datakey-value-type + + + {{ 'widgets.input-widgets.datakey-value-type-string' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-double' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-integer' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-boolean-checkbox' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-boolean-switch' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-date-time' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-date' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-time' | translate }} + + + {{ 'widgets.input-widgets.datakey-value-type-select' | translate }} + + + +
+ + {{ 'widgets.input-widgets.value-is-required' | translate }} + +
+ + widgets.input-widgets.ability-to-edit-attribute + + + {{ 'widgets.input-widgets.ability-to-edit-attribute-editable' | translate }} + + + {{ 'widgets.input-widgets.ability-to-edit-attribute-disabled' | translate }} + + + {{ 'widgets.input-widgets.ability-to-edit-attribute-readonly' | translate }} + + + + + widgets.input-widgets.disable-on-datakey-name + + +
+
+
+ widgets.input-widgets.slide-toggle-settings + + widgets.input-widgets.slide-toggle-label-position + + + {{ 'widgets.input-widgets.slide-toggle-label-position-after' | translate }} + + + {{ 'widgets.input-widgets.slide-toggle-label-position-before' | translate }} + + + +
+
+ widgets.input-widgets.select-options +
+
+
+ + +
+
+
+ widgets.input-widgets.no-select-options +
+
+ +
+
+
+
+ widgets.input-widgets.numeric-field-settings +
+ + widgets.input-widgets.step-interval + + + + widgets.input-widgets.min-value + + + + widgets.input-widgets.max-value + + +
+
+
+ widgets.input-widgets.error-messages + + widgets.input-widgets.required-error-message + + +
+ + widgets.input-widgets.min-value-error-message + + + + widgets.input-widgets.max-value-error-message + + +
+ + widgets.input-widgets.invalid-date-error-message + + +
+
+ widgets.input-widgets.icon-settings + + {{ 'widgets.input-widgets.use-custom-icon' | translate }} + + + + + +
+
+ widgets.input-widgets.value-conversion-settings +
+ widgets.input-widgets.get-value-settings + + + + + {{ 'widgets.input-widgets.use-get-value-function' | translate }} + + + + widget-config.advanced-settings + + + + + + + +
+
+ widgets.input-widgets.set-value-settings + + + + + {{ 'widgets.input-widgets.use-set-value-function' | translate }} + + + + widget-config.advanced-settings + + + + + + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.ts new file mode 100644 index 0000000000..4884166be5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component.ts @@ -0,0 +1,250 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { + DataKeySelectOption, + dataKeySelectOptionValidator +} from '@home/components/widget/lib/settings/input/datakey-select-option.component'; +import { CdkDragDrop } from '@angular/cdk/drag-drop'; + +@Component({ + selector: 'tb-update-multiple-attributes-key-settings', + templateUrl: './update-multiple-attributes-key-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettingsComponent { + + updateMultipleAttributesKeySettingsForm: FormGroup; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + } + + protected settingsForm(): FormGroup { + return this.updateMultipleAttributesKeySettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return { + dataKeyHidden: false, + dataKeyType: 'server', + dataKeyValueType: 'string', + required: false, + isEditable: 'editable', + disabledOnDataKey: '', + + slideToggleLabelPosition: 'after', + selectOptions: [], + step: 1, + minValue: null, + maxValue: null, + + requiredErrorMessage: '', + minValueErrorMessage: '', + maxValueErrorMessage: '', + invalidDateErrorMessage: '', + + useCustomIcon: false, + icon: '', + customIcon: '', + + useGetValueFunction: false, + getValueFunctionBody: '', + useSetValueFunction: false, + setValueFunctionBody: '' + }; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.updateMultipleAttributesKeySettingsForm = this.fb.group({ + + dataKeyHidden: [settings.dataKeyHidden, []], + + // General settings + + dataKeyType: [settings.dataKeyType, []], + dataKeyValueType: [settings.dataKeyValueType, []], + required: [settings.required, []], + isEditable: [settings.isEditable, []], + disabledOnDataKey: [settings.disabledOnDataKey, []], + + // Slide toggle settings + + slideToggleLabelPosition: [settings.slideToggleLabelPosition, []], + + // Select options + + selectOptions: this.prepareSelectOptionsFormArray(settings.selectOptions), + + // Numeric field settings + + step: [settings.step, [Validators.min(0)]], + minValue: [settings.minValue, []], + maxValue: [settings.maxValue, []], + + // Error messages + + requiredErrorMessage: [settings.requiredErrorMessage, []], + minValueErrorMessage: [settings.minValueErrorMessage, []], + maxValueErrorMessage: [settings.maxValueErrorMessage, []], + invalidDateErrorMessage: [settings.invalidDateErrorMessage, []], + + // Icon settings + + useCustomIcon: [settings.useCustomIcon, []], + icon: [settings.icon, []], + customIcon: [settings.customIcon, []], + + // Value conversion settings + + useGetValueFunction: [settings.useGetValueFunction, []], + getValueFunctionBody: [settings.getValueFunctionBody, []], + useSetValueFunction: [settings.useSetValueFunction, []], + setValueFunctionBody: [settings.setValueFunctionBody, []] + + }); + } + + protected validatorTriggers(): string[] { + return ['dataKeyHidden', 'dataKeyValueType', 'required', 'isEditable', 'useCustomIcon', 'useGetValueFunction', 'useSetValueFunction']; + } + + protected updateValidators(emitEvent: boolean) { + const dataKeyHidden: boolean = this.updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value; + const dataKeyValueType: string = this.updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value; + const required: boolean = this.updateMultipleAttributesKeySettingsForm.get('required').value; + const isEditable: string = this.updateMultipleAttributesKeySettingsForm.get('isEditable').value; + const useCustomIcon: boolean = this.updateMultipleAttributesKeySettingsForm.get('useCustomIcon').value; + const useGetValueFunction: boolean = this.updateMultipleAttributesKeySettingsForm.get('useGetValueFunction').value; + const useSetValueFunction: boolean = this.updateMultipleAttributesKeySettingsForm.get('useSetValueFunction').value; + + this.updateMultipleAttributesKeySettingsForm.disable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').enable({emitEvent: false}); + + if (!dataKeyHidden) { + this.updateMultipleAttributesKeySettingsForm.get('dataKeyType').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('required').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('isEditable').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('useCustomIcon').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('useGetValueFunction').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('useSetValueFunction').enable({emitEvent: false}); + + if (isEditable !== 'disabled') { + this.updateMultipleAttributesKeySettingsForm.get('disabledOnDataKey').enable({emitEvent: false}); + } + + if (dataKeyValueType === 'booleanSwitch') { + this.updateMultipleAttributesKeySettingsForm.get('slideToggleLabelPosition').enable({emitEvent: false}); + } else if (dataKeyValueType === 'select') { + this.updateMultipleAttributesKeySettingsForm.get('selectOptions').enable({emitEvent: false}); + } else if (dataKeyValueType === 'integer' || dataKeyValueType === 'double') { + this.updateMultipleAttributesKeySettingsForm.get('step').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('minValue').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('maxValue').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('minValueErrorMessage').enable({emitEvent: false}); + this.updateMultipleAttributesKeySettingsForm.get('maxValueErrorMessage').enable({emitEvent: false}); + } else if (dataKeyValueType === 'dateTime' || dataKeyValueType === 'date' || dataKeyValueType === 'time') { + this.updateMultipleAttributesKeySettingsForm.get('invalidDateErrorMessage').enable({emitEvent: false}); + } + if (required) { + this.updateMultipleAttributesKeySettingsForm.get('requiredErrorMessage').enable({emitEvent: false}); + } + if (useCustomIcon) { + this.updateMultipleAttributesKeySettingsForm.get('customIcon').enable({emitEvent: false}); + } else { + this.updateMultipleAttributesKeySettingsForm.get('icon').enable({emitEvent: false}); + } + if (useGetValueFunction) { + this.updateMultipleAttributesKeySettingsForm.get('getValueFunctionBody').enable({emitEvent: false}); + } + if (useSetValueFunction) { + this.updateMultipleAttributesKeySettingsForm.get('setValueFunctionBody').enable({emitEvent: false}); + } + } + this.updateMultipleAttributesKeySettingsForm.updateValueAndValidity({emitEvent: false}); + } + + protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) { + settingsForm.setControl('selectOptions', this.prepareSelectOptionsFormArray(settings.selectOptions), {emitEvent: false}); + } + + private prepareSelectOptionsFormArray(selectOptions: DataKeySelectOption[] | undefined): FormArray { + const selectOptionsControls: Array = []; + if (selectOptions) { + selectOptions.forEach((selectOption) => { + selectOptionsControls.push(this.fb.control(selectOption, [dataKeySelectOptionValidator])); + }); + } + return this.fb.array(selectOptionsControls, [(control: AbstractControl) => { + const selectOptionItems = control.value; + if (!selectOptionItems || !selectOptionItems.length) { + return { + selectOptionItems: true + }; + } + return null; + }]); + } + + selectOptionsFormArray(): FormArray { + return this.updateMultipleAttributesKeySettingsForm.get('selectOptions') as FormArray; + } + + public trackBySelectOption(index: number, selectOptionControl: AbstractControl): any { + return selectOptionControl; + } + + public removeSelectOption(index: number) { + (this.updateMultipleAttributesKeySettingsForm.get('selectOptions') as FormArray).removeAt(index); + } + + public addSelectOption() { + const selectOption: DataKeySelectOption = { + value: null, + label: null + }; + const selectOptionsArray = this.updateMultipleAttributesKeySettingsForm.get('selectOptions') as FormArray; + const selectOptionControl = this.fb.control(selectOption, [dataKeySelectOptionValidator]); + (selectOptionControl as any).new = true; + selectOptionsArray.push(selectOptionControl); + this.updateMultipleAttributesKeySettingsForm.updateValueAndValidity(); + if (!this.updateMultipleAttributesKeySettingsForm.valid) { + this.onSettingsChanged(this.updateMultipleAttributesKeySettingsForm.value); + } + } + + selectOptionDrop(event: CdkDragDrop) { + const selectOptionsArray = this.updateMultipleAttributesKeySettingsForm.get('selectOptions') as FormArray; + const selectOption = selectOptionsArray.at(event.previousIndex); + selectOptionsArray.removeAt(event.previousIndex); + selectOptionsArray.insert(event.currentIndex, selectOption); + } + + displayErrorMessagesSection(): boolean { + const dataKeyHidden: boolean = this.updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value; + const required: boolean = this.updateMultipleAttributesKeySettingsForm.get('required').value; + const dataKeyValueType: string = this.updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value; + return !dataKeyHidden && (required || (['integer', 'double', 'dateTime', 'date', 'time'].includes(dataKeyValueType))); + } +} + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.html new file mode 100644 index 0000000000..327879ed03 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.html @@ -0,0 +1,94 @@ + +
+
+ widgets.input-widgets.general-settings + + widgets.input-widgets.widget-title + + + + {{ 'widgets.input-widgets.show-result-message' | translate }} + +
+
+ widgets.input-widgets.action-buttons + + + + + {{ 'widgets.input-widgets.show-action-buttons' | translate }} + + + + widget-config.advanced-settings + + + +
+ + {{ 'widgets.input-widgets.update-all-values' | translate }} + +
+ + widgets.input-widgets.save-button-label + + + + widgets.input-widgets.reset-button-label + + +
+
+
+
+
+
+ widgets.input-widgets.group-settings +
+ + {{ 'widgets.input-widgets.show-group-title' | translate }} + + + widgets.input-widgets.group-title + + +
+
+
+ widgets.input-widgets.fields-alignment +
+ + widgets.input-widgets.fields-alignment + + + {{ 'widgets.input-widgets.fields-alignment-row' | translate }} + + + {{ 'widgets.input-widgets.fields-alignment-column' | translate }} + + + + + widgets.input-widgets.fields-in-row + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.ts new file mode 100644 index 0000000000..167b6c5f49 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component.ts @@ -0,0 +1,117 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; + +@Component({ + selector: 'tb-update-multiple-attributes-widget-settings', + templateUrl: './update-multiple-attributes-widget-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class UpdateMultipleAttributesWidgetSettingsComponent extends WidgetSettingsComponent { + + updateMultipleAttributesWidgetSettingsForm: FormGroup; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + } + + protected settingsForm(): FormGroup { + return this.updateMultipleAttributesWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return { + widgetTitle: '', + showResultMessage: true, + showActionButtons: true, + updateAllValues: false, + saveButtonLabel: '', + resetButtonLabel: '', + showGroupTitle: false, + groupTitle: '', + fieldsAlignment: 'row', + fieldsInRow: 2 + }; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.updateMultipleAttributesWidgetSettingsForm = this.fb.group({ + + // General settings + + widgetTitle: [settings.widgetTitle, []], + showResultMessage: [settings.showResultMessage, []], + + // Action button settings + + showActionButtons: [settings.showActionButtons, []], + updateAllValues: [settings.updateAllValues, []], + saveButtonLabel: [settings.saveButtonLabel, []], + resetButtonLabel: [settings.resetButtonLabel, []], + + // Group settings + + showGroupTitle: [settings.showGroupTitle, []], + groupTitle: [settings.groupTitle, []], + + // Fields alignment + + fieldsAlignment: [settings.fieldsAlignment, []], + fieldsInRow: [settings.fieldsInRow, [Validators.min(1)]], + }); + } + + protected validatorTriggers(): string[] { + return ['showActionButtons', 'showGroupTitle', 'fieldsAlignment']; + } + + protected updateValidators(emitEvent: boolean) { + const showActionButtons: boolean = this.updateMultipleAttributesWidgetSettingsForm.get('showActionButtons').value; + const showGroupTitle: boolean = this.updateMultipleAttributesWidgetSettingsForm.get('showGroupTitle').value; + const fieldsAlignment: string = this.updateMultipleAttributesWidgetSettingsForm.get('fieldsAlignment').value; + + if (showActionButtons) { + this.updateMultipleAttributesWidgetSettingsForm.get('updateAllValues').enable(); + this.updateMultipleAttributesWidgetSettingsForm.get('saveButtonLabel').enable(); + this.updateMultipleAttributesWidgetSettingsForm.get('resetButtonLabel').enable(); + } else { + this.updateMultipleAttributesWidgetSettingsForm.get('updateAllValues').disable(); + this.updateMultipleAttributesWidgetSettingsForm.get('saveButtonLabel').disable(); + this.updateMultipleAttributesWidgetSettingsForm.get('resetButtonLabel').disable(); + } + if (showGroupTitle) { + this.updateMultipleAttributesWidgetSettingsForm.get('groupTitle').enable(); + } else { + this.updateMultipleAttributesWidgetSettingsForm.get('groupTitle').disable(); + } + if (fieldsAlignment === 'row') { + this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').enable(); + } else { + this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').disable(); + } + this.updateMultipleAttributesWidgetSettingsForm.get('updateAllValues').updateValueAndValidity({emitEvent}); + this.updateMultipleAttributesWidgetSettingsForm.get('saveButtonLabel').updateValueAndValidity({emitEvent}); + this.updateMultipleAttributesWidgetSettingsForm.get('resetButtonLabel').updateValueAndValidity({emitEvent}); + this.updateMultipleAttributesWidgetSettingsForm.get('groupTitle').updateValueAndValidity({emitEvent}); + this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').updateValueAndValidity({emitEvent}); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index bbf7e52a10..d982dba872 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -202,6 +202,15 @@ import { import { PhotoCameraInputWidgetSettingsComponent } from '@home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component'; +import { + UpdateMultipleAttributesWidgetSettingsComponent +} from '@home/components/widget/lib/settings/input/update-multiple-attributes-widget-settings.component'; +import { + DataKeySelectOptionComponent +} from '@home/components/widget/lib/settings/input/datakey-select-option.component'; +import { + UpdateMultipleAttributesKeySettingsComponent +} from '@home/components/widget/lib/settings/input/update-multiple-attributes-key-settings.component'; @NgModule({ declarations: [ @@ -275,7 +284,10 @@ import { UpdateDateAttributeWidgetSettingsComponent, UpdateLocationAttributeWidgetSettingsComponent, UpdateJsonAttributeWidgetSettingsComponent, - PhotoCameraInputWidgetSettingsComponent + PhotoCameraInputWidgetSettingsComponent, + UpdateMultipleAttributesWidgetSettingsComponent, + DataKeySelectOptionComponent, + UpdateMultipleAttributesKeySettingsComponent ], imports: [ CommonModule, @@ -353,7 +365,10 @@ import { UpdateDateAttributeWidgetSettingsComponent, UpdateLocationAttributeWidgetSettingsComponent, UpdateJsonAttributeWidgetSettingsComponent, - PhotoCameraInputWidgetSettingsComponent + PhotoCameraInputWidgetSettingsComponent, + UpdateMultipleAttributesWidgetSettingsComponent, + DataKeySelectOptionComponent, + UpdateMultipleAttributesKeySettingsComponent ] }) export class WidgetSettingsModule { @@ -415,5 +430,7 @@ export const widgetSettingsComponentsMap: {[key: string]: Type {{materialIconFormGroup.get('icon').value}} - icon.icon + {{ label }}