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 @@
+
+
+
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 @@
+
+
+
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 @@
+
+
+
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 @@
+
+
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