From 54d256279afb28ce1ab2dea83948b1542deaa423 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 18 Apr 2022 18:20:02 +0300 Subject: [PATCH] UI: Add settings forms for 'Slide toggle'/'Round switch'/'Switch control' control widgets. --- .../widget_bundles/control_widgets.json | 9 +- .../chart/flot-bar-key-settings.component.ts | 8 +- .../flot-bar-widget-settings.component.ts | 8 +- .../chart/flot-line-key-settings.component.ts | 8 +- .../flot-line-widget-settings.component.ts | 9 +- ...ound-switch-widget-settings.component.html | 31 ++ .../round-switch-widget-settings.component.ts | 79 +++++ ...lide-toggle-widget-settings.component.html | 58 ++++ .../slide-toggle-widget-settings.component.ts | 87 +++++ ...tch-control-widget-settings.component.html | 34 ++ ...witch-control-widget-settings.component.ts | 83 +++++ .../switch-rpc-settings.component.html | 123 +++++++ .../control/switch-rpc-settings.component.ts | 308 ++++++++++++++++++ .../lib/settings/widget-settings.module.ts | 27 +- .../assets/locale/locale.constant-en_US.json | 33 ++ 15 files changed, 895 insertions(+), 10 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-rpc-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-rpc-settings.component.ts diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json index f36ee29c06..6722827622 100644 --- a/application/src/main/data/json/system/widget_bundles/control_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json @@ -73,8 +73,9 @@ "templateHtml": "", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/parse_value_fn\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/convert_value_fn\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", + "settingsSchema": "", "dataKeySettingsSchema": "{}\n", + "settingsDirective": "tb-switch-control-widget-settings", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"showOnOffLabels\":true,\"title\":\"Switch control\"},\"title\":\"Switch Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" } }, @@ -91,8 +92,9 @@ "templateHtml": "", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/parse_value_fn\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/convert_value_fn\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", + "settingsSchema": "", "dataKeySettingsSchema": "{}\n", + "settingsDirective": "tb-round-switch-widget-settings", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"title\":\"Round switch\",\"retrieveValueMethod\":\"rpc\",\"valueKey\":\"value\",\"parseValueFunction\":\"return data ? true : false;\",\"convertValueFunction\":\"return value;\"},\"title\":\"Round switch\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" } }, @@ -181,8 +183,9 @@ "templateHtml": "", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Slide toggle label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"labelPosition\": {\n \"title\": \"Label position\",\n \"type\": \"string\",\n \"default\": \"after\"\n },\n \"sliderColor\": {\n \"title\": \"Slider color\",\n \"type\": \"string\",\n \"default\": \"accent\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n {\n \"key\": \"labelPosition\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"before\",\n \"label\": \"Before\"\n },\n {\n \"value\": \"after\",\n \"label\": \"After\"\n }\n ]\n },\n {\n \"key\": \"sliderColor\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"primary\",\n \"label\": \"Primary\"\n },\n {\n \"value\": \"accent\",\n \"label\": \"Accent\"\n },\n {\n \"value\": \"warn\",\n \"label\": \"Warn\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/parse_value_fn\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/rpc/convert_value_fn\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", + "settingsSchema": "", "dataKeySettingsSchema": "{}\n", + "settingsDirective": "tb-slide-toggle-widget-settings", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#ffffff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"title\":\"Slide toggle control\",\"retrieveValueMethod\":\"rpc\",\"valueKey\":\"value\",\"parseValueFunction\":\"return data ? true : false;\",\"convertValueFunction\":\"return value;\",\"requestPersistent\":false,\"labelPosition\":\"after\",\"sliderColor\":\"accent\"},\"title\":\"Slide Toggle Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2,\"widgetCss\":\"\",\"noDataDisplayMessage\":\"\"}" } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-key-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-key-settings.component.ts index 1dd9c73dd0..5882707eea 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-key-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-key-settings.component.ts @@ -45,10 +45,16 @@ export class FlotBarKeySettingsComponent extends WidgetSettingsComponent { protected onSettingsSet(settings: WidgetSettings) { this.flotBarKeySettingsForm = this.fb.group({ - flotKeySettings: [settings, []] + flotKeySettings: [settings.flotKeySettings, []] }); } + protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { + return { + flotKeySettings: settings + }; + } + protected prepareOutputSettings(settings: any): WidgetSettings { return settings.flotKeySettings; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-widget-settings.component.ts index 305780dc5c..285c339edf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-widget-settings.component.ts @@ -45,10 +45,16 @@ export class FlotBarWidgetSettingsComponent extends WidgetSettingsComponent { protected onSettingsSet(settings: WidgetSettings) { this.flotBarWidgetSettingsForm = this.fb.group({ - flotSettings: [settings, []] + flotSettings: [settings.flotSettings, []] }); } + protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { + return { + flotSettings: settings + }; + } + protected prepareOutputSettings(settings: any): WidgetSettings { return settings.flotSettings; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-key-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-key-settings.component.ts index 46b83715fe..1ae2c4fc43 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-key-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-key-settings.component.ts @@ -45,10 +45,16 @@ export class FlotLineKeySettingsComponent extends WidgetSettingsComponent { protected onSettingsSet(settings: WidgetSettings) { this.flotLineKeySettingsForm = this.fb.group({ - flotKeySettings: [settings, []] + flotKeySettings: [settings.flotKeySettings, []] }); } + protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { + return { + flotKeySettings: settings + }; + } + protected prepareOutputSettings(settings: any): WidgetSettings { return settings.flotKeySettings; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-widget-settings.component.ts index 0ce8dbe2a1..d2b0e76ca6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-widget-settings.component.ts @@ -20,6 +20,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { flotDefaultSettings } from '@home/components/widget/lib/settings/chart/flot-widget-settings.component'; +import { deepClone } from '@core/utils'; @Component({ selector: 'tb-flot-line-widget-settings', @@ -45,10 +46,16 @@ export class FlotLineWidgetSettingsComponent extends WidgetSettingsComponent { protected onSettingsSet(settings: WidgetSettings) { this.flotLineWidgetSettingsForm = this.fb.group({ - flotSettings: [settings, []] + flotSettings: [settings.flotSettings, []] }); } + protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { + return { + flotSettings: settings + }; + } + protected prepareOutputSettings(settings: any): WidgetSettings { return settings.flotSettings; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.html new file mode 100644 index 0000000000..30da5695d1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.html @@ -0,0 +1,31 @@ + + +
+
+ widgets.rpc.common-settings + + widgets.rpc.switch-title + + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts new file mode 100644 index 0000000000..cfbdd757f7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts @@ -0,0 +1,79 @@ +/// +/// 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 } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { switchRpcDefaultSettings } from '@home/components/widget/lib/settings/control/switch-rpc-settings.component'; +import { deepClone } from '@core/utils'; + +@Component({ + selector: 'tb-round-switch-widget-settings', + templateUrl: './round-switch-widget-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class RoundSwitchWidgetSettingsComponent extends WidgetSettingsComponent { + + roundSwitchWidgetSettingsForm: FormGroup; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + } + + get targetDeviceAliasId(): string { + const aliasIds = this.widget.config.targetDeviceAliasIds; + if (aliasIds && aliasIds.length) { + return aliasIds[0]; + } + return null; + } + + protected settingsForm(): FormGroup { + return this.roundSwitchWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return { + title: '', + ...switchRpcDefaultSettings() + }; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.roundSwitchWidgetSettingsForm = this.fb.group({ + title: [settings.title, []], + switchRpcSettings: [settings.switchRpcSettings, []] + }); + } + + protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { + const switchRpcSettings = deepClone(settings, ['title']); + return { + title: settings.title, + switchRpcSettings + }; + } + + protected prepareOutputSettings(settings: any): WidgetSettings { + return { + title: settings.title, + ...settings.switchRpcSettings + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.html new file mode 100644 index 0000000000..7e78ec91a1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.html @@ -0,0 +1,58 @@ + + +
+
+ widgets.rpc.common-settings + + widgets.rpc.slide-toggle-label + + +
+ + widgets.rpc.label-position + + + {{ 'widgets.rpc.label-position-before' | translate }} + + + {{ 'widgets.rpc.label-position-after' | translate }} + + + + + widgets.rpc.slider-color + + + {{ 'widgets.rpc.slider-color-primary' | translate }} + + + {{ 'widgets.rpc.slider-color-accent' | translate }} + + + {{ 'widgets.rpc.slider-color-warn' | translate }} + + + +
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts new file mode 100644 index 0000000000..47cf13da53 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts @@ -0,0 +1,87 @@ +/// +/// 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 } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { switchRpcDefaultSettings } from '@home/components/widget/lib/settings/control/switch-rpc-settings.component'; +import { deepClone } from '@core/utils'; + +@Component({ + selector: 'tb-slide-toggle-widget-settings', + templateUrl: './slide-toggle-widget-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class SlideToggleWidgetSettingsComponent extends WidgetSettingsComponent { + + slideToggleWidgetSettingsForm: FormGroup; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + } + + get targetDeviceAliasId(): string { + const aliasIds = this.widget.config.targetDeviceAliasIds; + if (aliasIds && aliasIds.length) { + return aliasIds[0]; + } + return null; + } + + protected settingsForm(): FormGroup { + return this.slideToggleWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return { + title: '', + labelPosition: 'after', + sliderColor: 'accent', + ...switchRpcDefaultSettings() + }; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.slideToggleWidgetSettingsForm = this.fb.group({ + title: [settings.title, []], + labelPosition: [settings.labelPosition, []], + sliderColor: [settings.sliderColor, []], + switchRpcSettings: [settings.switchRpcSettings, []] + }); + } + + protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { + const switchRpcSettings = deepClone(settings, ['title', 'labelPosition', 'sliderColor']); + return { + title: settings.title, + labelPosition: settings.labelPosition, + sliderColor: settings.sliderColor, + switchRpcSettings + }; + } + + protected prepareOutputSettings(settings: any): WidgetSettings { + return { + title: settings.title, + labelPosition: settings.labelPosition, + sliderColor: settings.sliderColor, + ...settings.switchRpcSettings + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.html new file mode 100644 index 0000000000..da0b4237db --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.html @@ -0,0 +1,34 @@ + + +
+
+ widgets.rpc.common-settings + + widgets.rpc.switch-title + + + + {{ 'widgets.rpc.show-on-off-labels' | translate }} + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts new file mode 100644 index 0000000000..33e928f2c5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts @@ -0,0 +1,83 @@ +/// +/// 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 } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { switchRpcDefaultSettings } from '@home/components/widget/lib/settings/control/switch-rpc-settings.component'; +import { deepClone } from '@core/utils'; + +@Component({ + selector: 'tb-switch-control-widget-settings', + templateUrl: './switch-control-widget-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class SwitchControlWidgetSettingsComponent extends WidgetSettingsComponent { + + switchControlWidgetSettingsForm: FormGroup; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + } + + get targetDeviceAliasId(): string { + const aliasIds = this.widget.config.targetDeviceAliasIds; + if (aliasIds && aliasIds.length) { + return aliasIds[0]; + } + return null; + } + + protected settingsForm(): FormGroup { + return this.switchControlWidgetSettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return { + title: '', + showOnOffLabels: true, + ...switchRpcDefaultSettings() + }; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.switchControlWidgetSettingsForm = this.fb.group({ + title: [settings.title, []], + showOnOffLabels: [settings.showOnOffLabels, []], + switchRpcSettings: [settings.switchRpcSettings, []] + }); + } + + protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { + const switchRpcSettings = deepClone(settings, ['title', 'showOnOffLabels']); + return { + title: settings.title, + showOnOffLabels: settings.showOnOffLabels, + switchRpcSettings + }; + } + + protected prepareOutputSettings(settings: any): WidgetSettings { + return { + title: settings.title, + showOnOffLabels: settings.showOnOffLabels, + ...settings.switchRpcSettings + }; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-rpc-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-rpc-settings.component.html new file mode 100644 index 0000000000..3bb4e01573 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-rpc-settings.component.html @@ -0,0 +1,123 @@ + +
+
+ widgets.rpc.value-settings + + {{ 'widgets.rpc.initial-value' | translate }} + +
+ widgets.rpc.retrieve-value-settings + + widgets.rpc.retrieve-value-method + + + {{ 'widgets.rpc.retrieve-value-method-none' | translate }} + + + {{ 'widgets.rpc.retrieve-value-method-rpc' | translate }} + + + {{ 'widgets.rpc.retrieve-value-method-attribute' | translate }} + + + {{ 'widgets.rpc.retrieve-value-method-timeseries' | translate }} + + + + + {{ (switchRpcSettingsFormGroup.get('retrieveValueMethod').value === 'attribute' + ? 'widgets.rpc.attribute-value-key' + : 'widgets.rpc.timeseries-value-key') | translate }} + + + + + + + + + + widgets.rpc.get-value-method + + + + +
+
+ widgets.rpc.update-value-settings + + widgets.rpc.set-value-method + + + + +
+
+
+ widgets.rpc.rpc-settings + + widgets.rpc.request-timeout + + +
+ widgets.rpc.persistent-rpc-settings + + + + + {{ 'widgets.rpc.request-persistent' | translate }} + + + + widget-config.advanced-settings + + + + + widgets.rpc.persistent-polling-interval + + + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-rpc-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-rpc-settings.component.ts new file mode 100644 index 0000000000..6be84b4241 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-rpc-settings.component.ts @@ -0,0 +1,308 @@ +/// +/// 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, ElementRef, forwardRef, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator, + 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 { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { WidgetService } from '@core/http/widget.service'; +import { Observable, of } from 'rxjs'; +import { IAliasController } from '@core/api/widget-api.models'; +import { catchError, map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators'; +import { DataKey } from '@shared/models/widget.models'; +import { EntityService } from '@core/http/entity.service'; + +export declare type RpcRetrieveValueMethod = 'none' | 'rpc' | 'attribute' | 'timeseries'; + +export interface SwitchRpcSettings { + initialValue: boolean; + retrieveValueMethod: RpcRetrieveValueMethod; + valueKey: string; + getValueMethod: string; + setValueMethod: string; + parseValueFunction: string; + convertValueFunction: string; + requestTimeout: number; + requestPersistent: boolean; + persistentPollingInterval: number; +} + +export function switchRpcDefaultSettings(): SwitchRpcSettings { + return { + initialValue: false, + retrieveValueMethod: 'rpc', + valueKey: 'value', + getValueMethod: 'getValue', + parseValueFunction: 'return data ? true : false;', + setValueMethod: 'setValue', + convertValueFunction: 'return value;', + requestTimeout: 500, + requestPersistent: false, + persistentPollingInterval: 5000 + }; +} + +@Component({ + selector: 'tb-switch-rpc-settings', + templateUrl: './switch-rpc-settings.component.html', + styleUrls: ['./../widget-settings.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SwitchRpcSettingsComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => SwitchRpcSettingsComponent), + multi: true + } + ] +}) +export class SwitchRpcSettingsComponent extends PageComponent implements OnInit, ControlValueAccessor, Validator, OnChanges { + + @ViewChild('keyInput') keyInput: ElementRef; + + @Input() + disabled: boolean; + + @Input() + aliasController: IAliasController; + + @Input() + targetDeviceAliasId: string; + + functionScopeVariables = this.widgetService.getWidgetScopeVariables(); + + private modelValue: SwitchRpcSettings; + + private propagateChange = null; + + public switchRpcSettingsFormGroup: FormGroup; + + filteredKeys: Observable>; + keySearchText = ''; + + private latestKeySearchResult: Array = null; + private keysFetchObservable$: Observable> = null; + + constructor(protected store: Store, + private translate: TranslateService, + private widgetService: WidgetService, + private entityService: EntityService, + private fb: FormBuilder) { + super(store); + } + + ngOnInit(): void { + this.switchRpcSettingsFormGroup = this.fb.group({ + + // Value settings + + initialValue: [false, []], + + // --> Retrieve value settings + + retrieveValueMethod: ['rpc', []], + valueKey: ['value', [Validators.required]], + getValueMethod: ['getValue', [Validators.required]], + parseValueFunction: ['return data ? true : false;', []], + + // --> Update value settings + + setValueMethod: ['setValue', [Validators.required]], + convertValueFunction: ['return value;', []], + + // RPC settings + + requestTimeout: [500, [Validators.min(0)]], + + // Persistent RPC settings + + requestPersistent: [false, []], + persistentPollingInterval: [5000, [Validators.min(1000)]], + }); + this.switchRpcSettingsFormGroup.get('retrieveValueMethod').valueChanges.subscribe(() => { + this.clearKeysCache(); + this.updateValidators(true); + }); + this.switchRpcSettingsFormGroup.get('requestPersistent').valueChanges.subscribe(() => { + this.updateValidators(true); + }); + this.switchRpcSettingsFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + this.updateValidators(false); + + this.filteredKeys = this.switchRpcSettingsFormGroup.get('valueKey').valueChanges + .pipe( + map(value => value ? value : ''), + mergeMap(name => this.fetchKeys(name) ) + ); + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (propName === 'targetDeviceAliasId') { + this.clearKeysCache(); + } + } + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.switchRpcSettingsFormGroup.disable({emitEvent: false}); + } else { + this.switchRpcSettingsFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: SwitchRpcSettings): void { + this.modelValue = value; + this.switchRpcSettingsFormGroup.patchValue( + value, {emitEvent: false} + ); + this.updateValidators(false); + } + + clearKey() { + this.switchRpcSettingsFormGroup.get('valueKey').patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.keyInput.nativeElement.blur(); + this.keyInput.nativeElement.focus(); + }, 0); + } + + public validate(c: FormControl) { + return this.switchRpcSettingsFormGroup.valid ? null : { + switchRpcSettings: { + valid: false, + }, + }; + } + + private updateModel() { + const value: SwitchRpcSettings = this.switchRpcSettingsFormGroup.value; + this.modelValue = value; + this.propagateChange(this.modelValue); + } + + private updateValidators(emitEvent?: boolean): void { + const retrieveValueMethod: RpcRetrieveValueMethod = this.switchRpcSettingsFormGroup.get('retrieveValueMethod').value; + const requestPersistent: boolean = this.switchRpcSettingsFormGroup.get('requestPersistent').value; + if (retrieveValueMethod === 'none') { + this.switchRpcSettingsFormGroup.get('valueKey').disable({emitEvent}); + this.switchRpcSettingsFormGroup.get('getValueMethod').disable({emitEvent}); + this.switchRpcSettingsFormGroup.get('parseValueFunction').disable({emitEvent}); + } else if (retrieveValueMethod === 'rpc') { + this.switchRpcSettingsFormGroup.get('valueKey').disable({emitEvent}); + this.switchRpcSettingsFormGroup.get('getValueMethod').enable({emitEvent}); + this.switchRpcSettingsFormGroup.get('parseValueFunction').enable({emitEvent}); + } else { + this.switchRpcSettingsFormGroup.get('valueKey').enable({emitEvent}); + this.switchRpcSettingsFormGroup.get('getValueMethod').disable({emitEvent}); + this.switchRpcSettingsFormGroup.get('parseValueFunction').enable({emitEvent}); + } + if (requestPersistent) { + this.switchRpcSettingsFormGroup.get('persistentPollingInterval').enable({emitEvent}); + } else { + this.switchRpcSettingsFormGroup.get('persistentPollingInterval').disable({emitEvent}); + } + this.switchRpcSettingsFormGroup.get('valueKey').updateValueAndValidity({emitEvent: false}); + this.switchRpcSettingsFormGroup.get('getValueMethod').updateValueAndValidity({emitEvent: false}); + this.switchRpcSettingsFormGroup.get('parseValueFunction').updateValueAndValidity({emitEvent: false}); + this.switchRpcSettingsFormGroup.get('persistentPollingInterval').updateValueAndValidity({emitEvent: false}); + } + + private clearKeysCache(): void { + this.latestKeySearchResult = null; + this.keysFetchObservable$ = null; + } + + private fetchKeys(searchText?: string): Observable> { + if (this.keySearchText !== searchText || this.latestKeySearchResult === null) { + this.keySearchText = searchText; + const dataKeyFilter = this.createKeyFilter(this.keySearchText); + return this.getKeys().pipe( + map(name => name.filter(dataKeyFilter)), + tap(res => this.latestKeySearchResult = res) + ); + } + return of(this.latestKeySearchResult); + } + + private getKeys() { + if (this.keysFetchObservable$ === null) { + let fetchObservable: Observable>; + if (this.targetDeviceAliasId) { + const retrieveValueMethod: RpcRetrieveValueMethod = this.switchRpcSettingsFormGroup.get('retrieveValueMethod').value; + const dataKeyTypes = retrieveValueMethod === 'attribute' ? [DataKeyType.attribute] : [DataKeyType.timeseries]; + fetchObservable = this.fetchEntityKeys(this.targetDeviceAliasId, dataKeyTypes); + } else { + fetchObservable = of([]); + } + this.keysFetchObservable$ = fetchObservable.pipe( + map((dataKeys) => dataKeys.map((dataKey) => dataKey.name)), + publishReplay(1), + refCount() + ); + } + return this.keysFetchObservable$; + } + + private fetchEntityKeys(entityAliasId: string, dataKeyTypes: Array): Observable> { + return this.aliasController.getAliasInfo(entityAliasId).pipe( + mergeMap((aliasInfo) => { + return this.entityService.getEntityKeysByEntityFilter( + aliasInfo.entityFilter, + dataKeyTypes, + {ignoreLoading: true, ignoreErrors: true} + ).pipe( + catchError(() => of([])) + ); + }), + catchError(() => of([] as Array)) + ); + } + + private createKeyFilter(query: string): (key: string) => boolean { + const lowercaseQuery = query.toLowerCase(); + return key => key.toLowerCase().startsWith(lowercaseQuery); + } +} 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 3f81632178..c5591106ad 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 @@ -106,6 +106,16 @@ import { import { DoughnutChartWidgetSettingsComponent } from '@home/components/widget/lib/settings/chart/doughnut-chart-widget-settings.component'; +import { SwitchRpcSettingsComponent } from '@home/components/widget/lib/settings/control/switch-rpc-settings.component'; +import { + RoundSwitchWidgetSettingsComponent +} from '@home/components/widget/lib/settings/control/round-switch-widget-settings.component'; +import { + SwitchControlWidgetSettingsComponent +} from '@home/components/widget/lib/settings/control/switch-control-widget-settings.component'; +import { + SlideToggleWidgetSettingsComponent +} from '@home/components/widget/lib/settings/control/slide-toggle-widget-settings.component'; @NgModule({ declarations: [ @@ -145,7 +155,11 @@ import { FlotPieWidgetSettingsComponent, FlotPieKeySettingsComponent, ChartWidgetSettingsComponent, - DoughnutChartWidgetSettingsComponent + DoughnutChartWidgetSettingsComponent, + SwitchRpcSettingsComponent, + RoundSwitchWidgetSettingsComponent, + SwitchControlWidgetSettingsComponent, + SlideToggleWidgetSettingsComponent ], imports: [ CommonModule, @@ -189,7 +203,11 @@ import { FlotPieWidgetSettingsComponent, FlotPieKeySettingsComponent, ChartWidgetSettingsComponent, - DoughnutChartWidgetSettingsComponent + DoughnutChartWidgetSettingsComponent, + SwitchRpcSettingsComponent, + RoundSwitchWidgetSettingsComponent, + SwitchControlWidgetSettingsComponent, + SlideToggleWidgetSettingsComponent ] }) export class WidgetSettingsModule { @@ -222,5 +240,8 @@ export const widgetSettingsComponentsMap: {[key: string]: Type