Browse Source

Implement control widgets.

pull/2490/head
Igor Kulikov 7 years ago
parent
commit
9e1fe1e1c3
  1. 16
      application/src/main/data/json/system/widget_bundles/control_widgets.json
  2. 2
      ui-ngx/src/app/core/api/widget-api.models.ts
  3. 6
      ui-ngx/src/app/modules/home/components/widget/lib/canvas-digital-gauge.ts
  4. 45
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.html
  5. 168
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.scss
  6. 435
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts
  7. 32
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.html
  8. 74
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.scss
  9. 342
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts
  10. 38
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.html
  11. 198
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.scss
  12. 338
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts
  13. 44
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/rpc-widgets.module.ts
  14. 36
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/knob.svg
  15. 43
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-bar-checked.svg
  16. 42
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-bar.svg
  17. 40
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-checked.svg
  18. 37
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb.svg
  19. 39
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.html
  20. 147
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.scss
  21. 354
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts
  22. 5
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

16
application/src/main/data/json/system/widget_bundles/control_widgets.json

@ -45,9 +45,9 @@
"sizeX": 5, "sizeX": 5,
"sizeY": 4.5, "sizeY": 4.5,
"resources": [], "resources": [],
"templateHtml": "<tb-knob ctx='ctx'></tb-knob>", "templateHtml": "<tb-knob [ctx]='ctx'></tb-knob>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n", "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 \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n \"title\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"requestTimeout\"\n ]\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n \"title\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"requestTimeout\"\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\",\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\",\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
@ -61,9 +61,9 @@
"sizeX": 4, "sizeX": 4,
"sizeY": 2.5, "sizeY": 2.5,
"resources": [], "resources": [],
"templateHtml": "<tb-switch ctx='ctx'></tb-switch>", "templateHtml": "<tb-switch [ctx]='ctx'></tb-switch>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n", "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 },\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 },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\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 },\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 },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"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}" "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"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}"
@ -77,9 +77,9 @@
"sizeX": 2.5, "sizeX": 2.5,
"sizeY": 2, "sizeY": 2,
"resources": [], "resources": [],
"templateHtml": "<tb-round-switch ctx='ctx'></tb-round-switch>", "templateHtml": "<tb-round-switch [ctx]='ctx'></tb-round-switch>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n", "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 },\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 },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\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 },\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 },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"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}" "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"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}"
@ -93,9 +93,9 @@
"sizeX": 2.5, "sizeX": 2.5,
"sizeY": 2.5, "sizeY": 2.5,
"resources": [], "resources": [],
"templateHtml": "<tb-led-indicator ctx='ctx'></tb-led-indicator>", "templateHtml": "<tb-led-indicator [ctx]='ctx'></tb-led-indicator>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n", "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\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\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\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true,\"checkStatusMethod\":\"checkStatus\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true,\"checkStatusMethod\":\"checkStatus\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"

2
ui-ngx/src/app/core/api/widget-api.models.ts

@ -185,7 +185,7 @@ export interface WidgetSubscriptionCallbacks {
} }
export interface WidgetSubscriptionOptions { export interface WidgetSubscriptionOptions {
type: widgetType; type?: widgetType;
stateData?: boolean; stateData?: boolean;
alarmSource?: Datasource; alarmSource?: Datasource;
alarmSearchStatus?: AlarmSearchStatus; alarmSearchStatus?: AlarmSearchStatus;

6
ui-ngx/src/app/modules/home/components/widget/lib/canvas-digital-gauge.ts

@ -395,10 +395,12 @@ export class CanvasDigitalGauge extends BaseGauge {
let color = this.contextProgressClone.currentColor; let color = this.contextProgressClone.currentColor;
const options = this.options as CanvasDigitalGaugeOptions; const options = this.options as CanvasDigitalGaugeOptions;
if (!color) { if (!color) {
const progress = (CanvasGauges.drawings.normalizedValue(options).normal - options.minValue) /
(options.maxValue - options.minValue);
if (options.neonGlowBrightness) { if (options.neonGlowBrightness) {
color = getProgressColor(0, options.neonColorsRange); color = getProgressColor(progress, options.neonColorsRange);
} else { } else {
color = getProgressColor(0, options.colorsRange); color = getProgressColor(progress, options.colorsRange);
} }
} }
return color; return color;

45
ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.html

@ -0,0 +1,45 @@
<!--
Copyright © 2016-2019 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.
-->
<div class="tb-knob" fxLayout="column" [ngStyle]="{'pointerEvents': ctx.isEdit ? 'none' : 'all'}">
<div #knobContainer id="knob-container" fxFlex fxLayout="column" fxLayoutAlign="center center">
<div #knob class="knob">
<div #knobValueContainer class="value-container" fxLayout="row" fxLayoutAlign="center center">
<span #knobValue class="knob-value">{{ value }}</span>
</div>
<div class="canvas-background"></div>
<canvas #canvasBar id="canvasBar"></canvas>
<div #knobTopPointerContainer class="top-pointer-container">
<div #knobTopPointer class="top-pointer"></div>
</div>
<div class="top"></div>
<div #knobErrorContainer class="error-container" [ngStyle]="{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}"
fxLayout="row" fxLayoutAlign="center center">
<span #knobError class="knob-error">{{ error }}</span>
</div>
<div #knobTitleContainer class="title-container" fxLayout="row" fxLayoutAlign="center center">
<span #knobTitle class="knob-title">{{ title }}</span>
</div>
<div #knobMinmaxContainer class="minmax-container" fxLayout="row" fxLayoutAlign="start center">
<span class="minmax-label">min</span>
<span fxFlex></span>
<span class="minmax-label">max</span>
</div>
<div #textMeasure id="text-measure"></div>
</div>
</div>
</div>

168
ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.scss

@ -0,0 +1,168 @@
/**
* Copyright © 2016-2019 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.
*/
$knob-img: url("./svg/knob.svg") !default;
$bars-margin-pct: percentage(.033) !default;
$background-margin-pct: percentage(.05) !default;
$value-container-margin-pct: percentage(.35) !default;
$error-height: percentage(.05) !default;
$title-height: percentage(.066) !default;
$title-container-margin-pct: percentage(.2) !default;
$title-container-margin-bottom-pct: percentage(.05) !default;
$minmax-height: percentage(.04) !default;
$minmax-container-margin-pct: percentage(.18) !default;
$minmax-container-margin-bottom-pct: percentage(.12) !default;
$background-color: #e6e7e8 !default;
:host {
.tb-knob {
width: 100%;
height: 100%;
background: $background-color;
.knob {
position: relative;
&[draggable] {
user-select: none;
}
#canvasBar {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2;
}
.canvas-background {
position: absolute;
top: $background-margin-pct;
right: $background-margin-pct;
bottom: $background-margin-pct;
left: $background-margin-pct;
z-index: 2;
background: #3f4346;
border-radius: 50%;
}
.value-container {
position: absolute;
top: $value-container-margin-pct;
right: $value-container-margin-pct;
bottom: $value-container-margin-pct;
left: $value-container-margin-pct;
z-index: 4;
.knob-value {
font-weight: 500;
color: #fff;
white-space: nowrap;
}
}
.error-container {
position: absolute;
top: 1%;
right: 0;
left: 0;
z-index: 4;
height: $error-height;
.knob-error {
color: #ff3315;
white-space: nowrap;
}
}
.title-container {
position: absolute;
right: $title-container-margin-pct;
bottom: $title-container-margin-bottom-pct;
left: $title-container-margin-pct;
z-index: 4;
height: $title-height;
.knob-title {
font-weight: 500;
color: #757575;
white-space: nowrap;
}
}
.minmax-container {
position: absolute;
right: $minmax-container-margin-pct;
bottom: $minmax-container-margin-bottom-pct;
left: $minmax-container-margin-pct;
z-index: 4;
height: $minmax-height;
.minmax-label {
font-weight: 500;
color: #757575;
white-space: nowrap;
}
}
.top-pointer-container {
position: absolute;
top: $bars-margin-pct;
right: $bars-margin-pct;
bottom: $bars-margin-pct;
left: $bars-margin-pct;
z-index: 3;
cursor: pointer !important;
.top-pointer {
position: absolute;
top: 50%;
left: 22%;
width: 5%;
height: 5%;
margin-top: -2.5%;
cursor: pointer !important;
content: "";
background-color: #b5b5b5;
border-radius: 50%;
box-shadow: 1px 0 2px #040404;
}
}
.top {
position: absolute;
top: $bars-margin-pct;
right: $bars-margin-pct;
bottom: $bars-margin-pct;
left: $bars-margin-pct;
z-index: 2;
cursor: pointer !important;
background: $knob-img no-repeat;
background-size: contain;
}
#text-measure {
position: absolute;
width: auto;
height: auto;
white-space: nowrap;
visibility: hidden;
}
}
}
}

435
ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts

@ -0,0 +1,435 @@
///
/// Copyright © 2016-2019 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, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { WidgetContext } from '@home/models/widget-component.models';
import { UtilsService } from '@core/services/utils.service';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { isDefined, isNumber } from '@core/utils';
import { CanvasDigitalGauge, CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge';
import GenericOptions = CanvasGauges.GenericOptions;
import * as tinycolor_ from 'tinycolor2';
const tinycolor = tinycolor_;
interface KnobSettings {
minValue: number;
maxValue: number;
initialValue: number;
title: string;
getValueMethod: string;
setValueMethod: string;
requestTimeout: number;
}
@Component({
selector: 'tb-knob',
templateUrl: './knob.component.html',
styleUrls: ['./knob.component.scss']
})
export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
@ViewChild('knob', {static: true}) knobRef: ElementRef<HTMLElement>;
@ViewChild('knobContainer', {static: true}) knobContainerRef: ElementRef<HTMLElement>;
@ViewChild('knobTopPointerContainer', {static: true}) knobTopPointerContainerRef: ElementRef<HTMLElement>;
@ViewChild('knobTopPointer', {static: true}) knobTopPointerRef: ElementRef<HTMLElement>;
@ViewChild('knobValueContainer', {static: true}) knobValueContainerRef: ElementRef<HTMLElement>;
@ViewChild('knobValue', {static: true}) knobValueRef: ElementRef<HTMLElement>;
@ViewChild('knobErrorContainer', {static: true}) knobErrorContainerRef: ElementRef<HTMLElement>;
@ViewChild('knobError', {static: true}) knobErrorRef: ElementRef<HTMLElement>;
@ViewChild('knobTitleContainer', {static: true}) knobTitleContainerRef: ElementRef<HTMLElement>;
@ViewChild('knobTitle', {static: true}) knobTitleRef: ElementRef<HTMLElement>;
@ViewChild('knobMinmaxContainer', {static: true}) knobMinmaxContainerRef: ElementRef<HTMLElement>;
@ViewChild('textMeasure', {static: true}) textMeasureRef: ElementRef<HTMLElement>;
@ViewChild('canvasBar', {static: true}) canvasBarElementRef: ElementRef<HTMLElement>;
@Input()
ctx: WidgetContext;
value = '0';
error = '';
title = '';
minValue: number;
maxValue: number;
private startDeg = -1;
private currentDeg = 0;
private rotation = 0;
private lastDeg = 0;
private moving = false;
private minDeg = -45;
private maxDeg = 225;
private isSimulated: boolean;
private requestTimeout: number;
private getValueMethod: string;
private setValueMethod: string;
private executingUpdateValue: boolean;
private scheduledValue: number;
private rpcValue: number;
private knob: JQuery<HTMLElement>;
private knobContainer: JQuery<HTMLElement>;
private knobTopPointerContainer: JQuery<HTMLElement>;
private knobTopPointer: JQuery<HTMLElement>;
private knobValueContainer: JQuery<HTMLElement>;
private knobValue: JQuery<HTMLElement>;
private knobTitleContainer: JQuery<HTMLElement>;
private knobTitle: JQuery<HTMLElement>;
private knobErrorContainer: JQuery<HTMLElement>;
private knobError: JQuery<HTMLElement>;
private knobMinmaxContainer: JQuery<HTMLElement>;
private minmaxLabel: JQuery<HTMLElement>;
private textMeasure: JQuery<HTMLElement>;
private canvasBarElement: HTMLElement;
private canvasBar: CanvasDigitalGauge;
private knobResizeListener: any;
constructor(private utils: UtilsService,
protected store: Store<AppState>) {
super(store);
}
ngOnInit(): void {
this.knob = $(this.knobRef.nativeElement);
this.knobContainer = $(this.knobContainerRef.nativeElement);
this.knobTopPointerContainer = $(this.knobTopPointerContainerRef.nativeElement);
this.knobTopPointer = $(this.knobTopPointerRef.nativeElement);
this.knobValueContainer = $(this.knobValueContainerRef.nativeElement);
this.knobValue = $(this.knobValueRef.nativeElement);
this.knobTitleContainer = $(this.knobTitleContainerRef.nativeElement);
this.knobTitle = $(this.knobTitleRef.nativeElement);
this.knobErrorContainer = $(this.knobErrorContainerRef.nativeElement);
this.knobError = $(this.knobErrorRef.nativeElement);
this.knobMinmaxContainer = $(this.knobMinmaxContainerRef.nativeElement);
this.minmaxLabel = this.knobMinmaxContainer.find('.minmax-label');
this.textMeasure = $(this.textMeasureRef.nativeElement);
this.canvasBarElement = this.canvasBarElementRef.nativeElement;
this.knobResizeListener = this.resize.bind(this);
// @ts-ignore
addResizeListener(this.knobContainerRef.nativeElement, this.knobResizeListener);
this.init();
}
ngOnDestroy(): void {
if (this.knobResizeListener) {
// @ts-ignore
removeResizeListener(this.knobContainerRef.nativeElement, this.knobResizeListener);
}
}
private init() {
const settings: KnobSettings = this.ctx.settings;
this.minValue = isDefined(settings.minValue) ? settings.minValue : 0;
this.maxValue = isDefined(settings.maxValue) ? settings.maxValue : 100;
this.title = isDefined(settings.title) ? settings.title : '';
const levelColors = ['#19ff4b', '#ffff19', '#ff3232'];
const canvasBarData: CanvasDigitalGaugeOptions = {
renderTo: this.canvasBarElement,
hideValue: true,
neonGlowBrightness: 0,
gaugeWidthScale: 0.4,
gaugeColor: 'rgba(0, 0, 0, 0)',
levelColors,
minValue: this.minValue,
maxValue: this.maxValue,
gaugeType: 'donut',
dashThickness: 2,
donutStartAngle: 3/4*Math.PI,
donutEndAngle: 9/4*Math.PI,
animation: false
};
this.canvasBar = new CanvasDigitalGauge(canvasBarData).draw();
this.knob.on('click', (e) => {
if (this.moving) {
this.moving = false;
return false;
}
e.preventDefault();
const offset = this.knob.offset();
const center = {
y : offset.top + this.knob.height()/2,
x: offset.left + this.knob.width()/2
};
const rad2deg = 180/Math.PI;
const t: Touch = ((e.originalEvent as any).touches) ? (e.originalEvent as any).touches[0] : e;
const a = center.y - t.pageY;
const b = center.x - t.pageX;
let deg = Math.atan2(a,b)*rad2deg;
if(deg < 0){
deg = 360 + deg;
}
if (deg > this.maxDeg) {
if (deg - 360 > this.minDeg) {
deg = deg - 360;
} else {
return false;
}
}
this.currentDeg = deg;
this.lastDeg = deg;
this.knobTopPointerContainer.css('transform','rotate('+(this.currentDeg)+'deg)');
this.turn(this.degreeToRatio(this.currentDeg));
this.rotation = this.currentDeg;
this.startDeg = -1;
});
this.knob.on('mousedown touchstart', (e) => {
e.preventDefault();
const offset = this.knob.offset();
const center = {
y : offset.top + this.knob.height()/2,
x: offset.left + this.knob.width()/2
};
const rad2deg = 180/Math.PI;
this.knob.on('mousemove.rem touchmove.rem', (ev) => {
this.moving = true;
const t: Touch = ((ev.originalEvent as any).touches) ? (ev.originalEvent as any).touches[0] : ev;
const a = center.y - t.pageY;
const b = center.x - t.pageX;
let deg = Math.atan2(a,b)*rad2deg;
if(deg < 0){
deg = 360 + deg;
}
if(this.startDeg === -1){
this.startDeg = deg;
}
let tmp = Math.floor((deg-this.startDeg) + this.rotation);
if(tmp < 0){
tmp = 360 + tmp;
}
else if(tmp > 359){
tmp = tmp % 360;
}
if (tmp > this.maxDeg) {
if (tmp - 360 > this.minDeg) {
tmp = tmp - 360;
} else {
const deltaMax = Math.abs(this.maxDeg - this.lastDeg);
const deltaMin = Math.abs(this.minDeg - this.lastDeg);
if (deltaMax < deltaMin) {
tmp = this.maxDeg;
} else {
tmp = this.minDeg;
}
}
}
if(Math.abs(tmp - this.lastDeg) > 180){
this.startDeg = deg;
this.rotation = this.currentDeg;
return false;
}
this.currentDeg = tmp;
this.lastDeg = tmp;
this.knobTopPointerContainer.css('transform','rotate('+(this.currentDeg)+'deg)');
this.turn(this.degreeToRatio(this.currentDeg));
});
$(document).on('mouseup.rem touchend.rem',() => {
this.knob.off('.rem');
$(document).off('.rem');
this.rotation = this.currentDeg;
this.startDeg = -1;
});
});
const initialValue = isDefined(settings.initialValue) ? settings.initialValue : this.minValue;
this.setValue(initialValue);
const subscription = this.ctx.defaultSubscription;
const rpcEnabled = subscription.rpcEnabled;
this.isSimulated = this.utils.widgetEditMode;
this.requestTimeout = 500;
if (settings.requestTimeout) {
this.requestTimeout = settings.requestTimeout;
}
this.getValueMethod = 'getValue';
if (settings.getValueMethod && settings.getValueMethod.length) {
this.getValueMethod = settings.getValueMethod;
}
this.setValueMethod = 'setValue';
if (settings.setValueMethod && settings.setValueMethod.length) {
this.setValueMethod = settings.setValueMethod;
}
if (!rpcEnabled) {
this.onError('Target device is not set!');
} else {
if (!this.isSimulated) {
this.rpcRequestValue();
}
}
}
private degreeToRatio(degree: number): number {
return (degree-this.minDeg)/(this.maxDeg-this.minDeg);
}
private ratioToDegree(ratio: number): number {
return this.minDeg + ratio*(this.maxDeg-this.minDeg);
}
private turn(ratio: number) {
const value = Number((this.minValue + (this.maxValue - this.minValue)*ratio).toFixed(this.ctx.decimals));
if (this.canvasBar.value !== value) {
this.canvasBar.value = value;
}
this.updateColor(this.canvasBar.getValueColor());
this.onValue(value);
}
private resize() {
const width = this.knobContainer.width();
const height = this.knobContainer.height();
const size = Math.min(width, height);
this.knob.css({width: size, height: size});
this.canvasBar.update({width: size, height: size} as GenericOptions);
this.setFontSize(this.knobTitle, this.title, this.knobTitleContainer.height(), this.knobTitleContainer.width());
this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width());
const minmaxHeight = this.knobMinmaxContainer.height();
this.minmaxLabel.css({fontSize: minmaxHeight+'px', lineHeight: minmaxHeight+'px'});
this.checkValueSize();
}
private checkValueSize() {
const fontSize = this.knobValueContainer.height()/3.3;
const containerWidth = this.knobValueContainer.width();
this.setFontSize(this.knobValue, this.value+'', fontSize, containerWidth);
}
private setFontSize(element: JQuery<HTMLElement>, text: string, fontSize: number, maxWidth: number) {
let textWidth = this.measureTextWidth(text, fontSize);
while (textWidth > maxWidth) {
fontSize--;
textWidth = this.measureTextWidth(text, fontSize);
}
element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
}
private measureTextWidth(text: string, fontSize: number): number {
this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
this.textMeasure.text(text);
return this.textMeasure.width();
}
private setValue(value: number) {
const ratio = (value-this.minValue) / (this.maxValue - this.minValue);
this.rotation = this.lastDeg = this.currentDeg = this.ratioToDegree(ratio);
this.knobTopPointerContainer.css('transform','rotate('+(this.currentDeg)+'deg)');
if (this.canvasBar.value !== value) {
this.canvasBar.value = value;
}
this.updateColor(this.canvasBar.getValueColor());
this.value = this.formatValue(value);
this.checkValueSize();
}
private updateColor(color: string) {
const glowColor = tinycolor(color).brighten(30).toHexString();
this.knobValue.css({color: glowColor});
const textShadow = `${color} 1px 1px 10px, ${glowColor} 1px 1px 10px`;
this.knobValue.css({textShadow});
this.knobTopPointer.css({backgroundColor: glowColor});
const boxShadow = `inset 1px 0 2px #040404, 1px 1px 8px 2px ${glowColor}`;
this.knobTopPointer.css({boxShadow});
}
private onValue(value: number) {
this.value = this.formatValue(value);
this.checkValueSize();
this.rpcUpdateValue(value);
this.ctx.detectChanges();
}
private formatValue(value: any): string {
return this.ctx.utils.formatValue(value, this.ctx.decimals, this.ctx.units, true);
}
private rpcRequestValue() {
this.error = '';
this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe(
(responseBody) => {
if (isNumber(responseBody)) {
const numValue = Number(Number(responseBody).toFixed(this.ctx.decimals));
this.setValue(numValue);
} else {
const errorText = `Unable to parse response: ${responseBody}`;
this.onError(errorText);
}
},
() => {
const errorText = this.ctx.defaultSubscription.rpcErrorText;
this.onError(errorText);
}
);
}
private rpcUpdateValue(value: number) {
if (this.executingUpdateValue) {
this.scheduledValue = value;
return;
} else {
this.scheduledValue = null;
this.rpcValue = value;
this.executingUpdateValue = true;
}
this.error = '';
this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, value, this.requestTimeout).subscribe(
() => {
this.executingUpdateValue = false;
if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) {
this.rpcUpdateValue(this.scheduledValue);
}
},
() => {
this.executingUpdateValue = false;
const errorText = this.ctx.defaultSubscription.rpcErrorText;
this.onError(errorText);
}
);
}
private onError(error: string) {
this.error = error;
this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width());
this.ctx.detectChanges();
}
}

32
ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.html

@ -0,0 +1,32 @@
<!--
Copyright © 2016-2019 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.
-->
<div class="tb-led-indicator" fxLayout="column">
<div fxFlex="20" #ledTitleContainer class="title-container" fxLayout="row" fxLayoutAlign="center center" [fxShow]="showTitle">
<span #ledTitle class="led-title">{{title}}</span>
</div>
<div fxFlex="{{showTitle ? 80 : 100}}"
[ngStyle]="{paddingTop: showTitle ? '5px': '10px'}" #ledContainer id="led-container" fxLayout="column" fxLayoutAlign="center center">
<div #led class="led">
</div>
</div>
<div #ledErrorContainer class="error-container" [ngStyle]="{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}"
fxLayout="row" fxLayoutAlign="center center">
<span #ledError class="led-error">{{ error }}</span>
</div>
<div #textMeasure id="text-measure"></div>
</div>

74
ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.scss

@ -0,0 +1,74 @@
/**
* Copyright © 2016-2019 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 "~compass-sass-mixins/lib/compass";
$error-height: 14px !default;
$background-color: #e6e7e8 !default;
:host {
.tb-led-indicator {
width: 100%;
height: 100%;
background: $background-color;
.title-container {
.led-title {
font-weight: 500;
color: #757575;
white-space: nowrap;
}
}
.error-container {
position: absolute;
top: 1%;
right: 0;
left: 0;
z-index: 4;
height: $error-height;
.led-error {
color: #ff3315;
white-space: nowrap;
}
}
#text-measure {
position: absolute;
width: auto;
height: auto;
white-space: nowrap;
visibility: hidden;
}
#led-container {
padding: 10px;
.led {
position: relative;
cursor: pointer;
background-image: radial-gradient(circle closest-corner at 50% 50%, transparent, rgba(0, 0, 0, .25));
border-radius: 50%;
transition: background-color .5s, box-shadow .5s;
&.disabled {
background-image: radial-gradient(circle closest-corner at 50% 50%, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1));
}
}
}
}
}

342
ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts

@ -0,0 +1,342 @@
///
/// Copyright © 2016-2019 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, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { WidgetContext } from '@home/models/widget-component.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { isDefined } from '@core/utils';
import * as tinycolor_ from 'tinycolor2';
import { UtilsService } from '@core/services/utils.service';
import { IWidgetSubscription, SubscriptionInfo, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { DatasourceType, widgetType } from '@shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models';
import Timeout = NodeJS.Timeout;
const tinycolor = tinycolor_;
const checkStatusPollingInterval = 10000;
type RetrieveValueMethod = 'attribute' | 'timeseries';
interface LedIndicatorSettings {
initialValue: boolean;
title: string;
ledColor: string;
performCheckStatus: boolean;
checkStatusMethod: string;
retrieveValueMethod: RetrieveValueMethod;
valueAttribute: string;
parseValueFunction: string;
requestTimeout: number;
}
@Component({
selector: 'tb-led-indicator',
templateUrl: './led-indicator.component.html',
styleUrls: ['./led-indicator.component.scss']
})
export class LedIndicatorComponent extends PageComponent implements OnInit, OnDestroy {
@ViewChild('led', {static: true}) ledRef: ElementRef<HTMLElement>;
@ViewChild('ledContainer', {static: true}) ledContainerRef: ElementRef<HTMLElement>;
@ViewChild('textMeasure', {static: true}) textMeasureRef: ElementRef<HTMLElement>;
@ViewChild('ledTitleContainer', {static: true}) ledTitleContainerRef: ElementRef<HTMLElement>;
@ViewChild('ledTitle', {static: true}) ledTitleRef: ElementRef<HTMLElement>;
@ViewChild('ledErrorContainer', {static: true}) ledErrorContainerRef: ElementRef<HTMLElement>;
@ViewChild('ledError', {static: true}) ledErrorRef: ElementRef<HTMLElement>;
@Input()
ctx: WidgetContext;
showTitle = false;
value = false;
error = '';
title = '';
private valueAttribute: string;
private ledColor: string;
private ledMiddleColor: string;
private disabledColor: string;
private disabledMiddleColor: string;
private isSimulated: boolean;
private requestTimeout: number;
private retrieveValueMethod: RetrieveValueMethod;
private parseValueFunction: (data: any) => boolean;
private performCheckStatus: boolean;
private checkStatusMethod: string;
private destroyed = false;
private checkStatusTimeoutHandle: Timeout;
private subscription: IWidgetSubscription;
private led: JQuery<HTMLElement>;
private ledContainer: JQuery<HTMLElement>;
private textMeasure: JQuery<HTMLElement>;
private ledTitleContainer: JQuery<HTMLElement>;
private ledTitle: JQuery<HTMLElement>;
private ledErrorContainer: JQuery<HTMLElement>;
private ledError: JQuery<HTMLElement>;
private ledResizeListener: any;
private subscriptionOptions: WidgetSubscriptionOptions = {
callbacks: {
onDataUpdated: this.onDataUpdated.bind(this),
onDataUpdateError: this.onDataUpdateError.bind(this),
dataLoading: () => {}
}
};
constructor(private utils: UtilsService,
protected store: Store<AppState>) {
super(store);
}
ngOnInit(): void {
this.led = $(this.ledRef.nativeElement);
this.ledContainer = $(this.ledContainerRef.nativeElement);
this.textMeasure = $(this.textMeasureRef.nativeElement);
this.ledTitleContainer = $(this.ledTitleContainerRef.nativeElement);
this.ledTitle = $(this.ledTitleRef.nativeElement);
this.ledErrorContainer = $(this.ledErrorContainerRef.nativeElement);
this.ledError = $(this.ledErrorRef.nativeElement);
this.ledResizeListener = this.resize.bind(this);
// @ts-ignore
addResizeListener(this.ledContainerRef.nativeElement, this.ledResizeListener);
this.init();
}
ngOnDestroy(): void {
this.destroyed = true;
if (this.checkStatusTimeoutHandle) {
clearTimeout(this.checkStatusTimeoutHandle);
}
if (this.subscription) {
this.ctx.subscriptionApi.removeSubscription(this.subscription.id);
}
if (this.ledResizeListener) {
// @ts-ignore
removeResizeListener(this.ledContainerRef.nativeElement, this.ledResizeListener);
}
}
private init() {
const settings: LedIndicatorSettings = this.ctx.settings;
this.title = isDefined(settings.title) ? settings.title : '';
this.showTitle = !!(this.title && this.title.length);
const origColor = isDefined(settings.ledColor) ? settings.ledColor : 'green';
this.valueAttribute = isDefined(settings.valueAttribute) ? settings.valueAttribute : 'value';
this.ledColor = tinycolor(origColor).brighten(30).toHexString();
this.ledMiddleColor = tinycolor(origColor).toHexString();
this.disabledColor = tinycolor(origColor).darken(40).toHexString();
this.disabledMiddleColor = tinycolor(origColor).darken(60).toHexString();
const initialValue = isDefined(settings.initialValue) ? settings.initialValue : false;
this.setValue(initialValue, true);
const subscription = this.ctx.defaultSubscription;
const rpcEnabled = subscription.rpcEnabled;
this.isSimulated = this.utils.widgetEditMode;
this.requestTimeout = 500;
if (settings.requestTimeout) {
this.requestTimeout = settings.requestTimeout;
}
this.retrieveValueMethod = 'attribute';
if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) {
this.retrieveValueMethod = settings.retrieveValueMethod;
}
this.parseValueFunction = (data) => !!data;
if (settings.parseValueFunction && settings.parseValueFunction.length) {
try {
this.parseValueFunction = new Function('data', settings.parseValueFunction) as (data: any) => boolean;
} catch (e) {
this.parseValueFunction = (data) => !!data;
}
}
this.performCheckStatus = settings.performCheckStatus !== false;
if (this.performCheckStatus) {
this.checkStatusMethod = 'checkStatus';
if (settings.checkStatusMethod && settings.checkStatusMethod.length) {
this.checkStatusMethod = settings.checkStatusMethod;
}
}
if (!rpcEnabled) {
this.onError('Target device is not set!');
} else {
if (!this.isSimulated) {
if (this.performCheckStatus) {
this.rpcCheckStatus();
} else {
this.subscribeForValue();
}
}
}
}
private resize() {
const width = this.ledContainer.width();
const height = this.ledContainer.height();
const size = Math.min(width, height);
this.led.css({width: size, height: size});
if (this.showTitle) {
this.setFontSize(this.ledTitle, this.title, this.ledTitleContainer.height() * 2 / 3, this.ledTitleContainer.width());
}
this.setFontSize(this.ledError, this.error, this.ledErrorContainer.height(), this.ledErrorContainer.width());
}
private setFontSize(element: JQuery<HTMLElement>, text: string, fontSize: number, maxWidth: number) {
let textWidth = this.measureTextWidth(text, fontSize);
while (textWidth > maxWidth) {
fontSize--;
textWidth = this.measureTextWidth(text, fontSize);
}
element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
}
private measureTextWidth(text: string, fontSize: number): number {
this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
this.textMeasure.text(text);
return this.textMeasure.width();
}
private onError(error: string) {
this.error = error;
this.setFontSize(this.ledError, this.error, this.ledErrorContainer.height(), this.ledErrorContainer.width());
this.ctx.detectChanges();
}
private setValue(value: boolean, forceUpdate?: boolean) {
if (this.value !== value || forceUpdate) {
this.value = value;
this.updateColor();
}
}
private updateColor() {
const color = this.value ? this.ledColor : this.disabledColor;
const middleColor = this.value ? this.ledMiddleColor : this.disabledMiddleColor;
const boxShadow = `#000 0 -1px 6px 1px, inset ${middleColor} 0 -1px 8px, ${color} 0 3px 11px`;
this.led.css({backgroundColor: color});
this.led.css({boxShadow});
if (this.value) {
this.led.removeClass( 'disabled' );
} else {
this.led.addClass( 'disabled' );
}
}
private rpcCheckStatus() {
if (this.destroyed) {
return;
}
this.error = '';
this.ctx.controlApi.sendTwoWayCommand(this.checkStatusMethod, null, this.requestTimeout).subscribe(
(responseBody) => {
const status = !!responseBody;
if (status) {
if (this.checkStatusTimeoutHandle) {
clearTimeout(this.checkStatusTimeoutHandle);
this.checkStatusTimeoutHandle = null;
}
this.subscribeForValue();
} else {
const errorText = 'Unknown device status!';
this.onError(errorText);
if (this.checkStatusTimeoutHandle) {
clearTimeout(this.checkStatusTimeoutHandle);
}
this.checkStatusTimeoutHandle = setTimeout(this.rpcCheckStatus.bind(this), checkStatusPollingInterval);
}
},
() => {
const errorText = this.ctx.defaultSubscription.rpcErrorText;
this.onError(errorText);
if (this.checkStatusTimeoutHandle) {
clearTimeout(this.checkStatusTimeoutHandle);
}
this.checkStatusTimeoutHandle = setTimeout(this.rpcCheckStatus.bind(this), checkStatusPollingInterval);
}
);
}
private subscribeForValue() {
const subscriptionsInfo: SubscriptionInfo[] = [{
type: DatasourceType.entity,
entityType: EntityType.DEVICE,
entityId: this.ctx.defaultSubscription.targetDeviceId
}];
if (this.retrieveValueMethod === 'attribute') {
subscriptionsInfo[0].attributes = [
{name: this.valueAttribute}
];
} else {
subscriptionsInfo[0].timeseries = [
{name: this.valueAttribute}
];
}
this.ctx.subscriptionApi.createSubscriptionFromInfo (
widgetType.latest, subscriptionsInfo, this.subscriptionOptions, false, true).subscribe(
(subscription) => {
this.subscription = subscription;
}
);
}
private onDataUpdated (subscription: IWidgetSubscription, detectChanges: boolean) {
let value = false;
const data = subscription.data;
if (data.length) {
const keyData = data[0];
if (keyData && keyData.data && keyData.data[0]) {
const attrValue = keyData.data[0][1];
if (attrValue) {
let parsed = null;
try {
parsed = this.parseValueFunction(JSON.parse(attrValue));
} catch (e){/**/}
value = !!parsed;
}
}
}
this.setValue(value);
if (detectChanges) {
this.ctx.detectChanges();
}
}
private onDataUpdateError(subscription: IWidgetSubscription, e: any) {
const exceptionData = this.utils.parseException(e);
let errorText = exceptionData.name;
if (exceptionData.message) {
errorText += ': ' + exceptionData.message;
}
this.onError(errorText);
}
}

38
ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.html

@ -0,0 +1,38 @@
<!--
Copyright © 2016-2019 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.
-->
<div class="tb-round-switch" fxLayout="column" [ngStyle]="{'pointerEvents': ctx.isEdit ? 'none' : 'all'}">
<div #switchTitleContainer fxFlex="20" class="title-container" fxLayout="row" fxLayoutAlign="center center" [fxShow]="showTitle">
<span #switchTitle class="switch-title">{{title}}</span>
</div>
<div #switchContainer fxFlex="{{showTitle ? 80 : 100}}" [ngStyle]="{paddingTop: showTitle ? '5px': '10px'}" id="switch-container" fxLayout="column" fxLayoutAlign="center center">
<div #switch class="switch">
<input #onoff type="checkbox" id="{{checkboxId}}" name="onoff" />
<div class="back">
<label class="but" for="{{checkboxId}}">
<span class="on">I</span>
<span class="off">0</span>
</label>
</div>
</div>
</div>
<div #switchErrorContainer class="error-container" [ngStyle]="{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}"
fxLayout="row" fxLayoutAlign="center center">
<span #switchError class="switch-error">{{ error }}</span>
</div>
<div #textMeasure id="text-measure"></div>
</div>

198
ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.scss

@ -0,0 +1,198 @@
/**
* Copyright © 2016-2019 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 "~compass-sass-mixins/lib/compass";
$error-height: 14px !default;
$background-color: #e6e7e8 !default;
:host {
.tb-round-switch {
width: 100%;
height: 100%;
background: $background-color;
.title-container {
.switch-title {
font-weight: 500;
color: #757575;
white-space: nowrap;
}
}
.error-container {
position: absolute;
top: 1%;
right: 0;
left: 0;
z-index: 4;
height: $error-height;
.switch-error {
color: #ff3315;
white-space: nowrap;
}
}
#text-measure {
position: absolute;
width: auto;
height: auto;
white-space: nowrap;
visibility: hidden;
}
#switch-container {
padding: 10px;
.switch {
position: relative;
box-sizing: border-box;
width: 260px;
min-width: 260px;
height: 260px;
min-height: 260px;
padding: 25px;
font-family: sans-serif;
font-size: 48px;
color: #424242;
cursor: pointer;
background: #ddd;
background: linear-gradient(180deg, #bbb, #ddd);
border-radius: 130px;
box-shadow: 0 0 0 8px rgba(0, 0, 0, .1),
0 0 3px 1px rgba(0, 0, 0, .1),
inset 0 8px 3px -8px rgba(255, 255, 255, .4);
input {
display: none;
}
.on,
.off {
position: absolute;
width: 100%;
text-align: center;
text-shadow: 1px 1px 4px #4a4a4a;
}
.on {
top: 10px;
font-family: sans-serif;
color: #444;
transition: all .1s;
}
.off {
bottom: 5px;
transition: all .1s;
transform: scaleY(.85);
}
.but {
position: relative;
display: block;
width: 200px;
height: 178px;
font-size: 48px;
cursor: pointer;
background-color: #d8d8d8;
border-bottom-width: 0;
border-radius: 400px 400px 400px 400px / 400px 400px 300px 300px;
box-shadow: inset 8px 6px 5px -7px #a2a2a2,
inset -8px 6px 5px -7px #a2a2a2,
inset 0 -3px 2px -2px rgba(200, 200, 200, .5),
0 3px 3px -2px #fff,
inset 0 -230px 60px -200px rgba(255, 255, 255, .2),
inset 0 220px 40px -200px rgba(0, 0, 0, .3);
transition: all .2s;
}
.back {
box-sizing: border-box;
width: 210px;
height: 210px;
padding: 4px 4px;
cursor: pointer;
background-color: #888787;
background-image: linear-gradient(-90deg, transparent 30%, transparent 70%), linear-gradient(0deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%);
border-radius: 105px;
box-shadow: 30px 30px 30px -20px rgba(58, 58, 58, .3),
-30px 30px 30px -20px rgba(58, 58, 58, .3),
0 30px 30px 0 rgba(16, 16, 16, .3),
inset 0 -1px 0 0 #484848;
transition: all .2s;
}
input:checked + .back .on,
input:checked + .back .off {
text-shadow: 1px 1px 4px #4a4a4a;
}
input:checked + .back .on {
top: 10px;
color: #4c4c4c;
transform: scaleY(.85);
}
input:checked + .back .off {
bottom: 5px;
color: #444;
transform: scaleY(1);
}
input:checked + .back .but {
margin-top: 20px;
background: #dcdcdc radial-gradient(circle closest-corner at 50% 15%, rgba(0, 0, 0, .3), transparent);
border-radius: 400px 400px 400px 400px / 300px 300px 400px 400px;
box-shadow: inset 8px -4px 5px -7px #a9a9a9,
inset -8px -4px 5px -7px #808080,
0 -3px 8px -4px rgba(50, 50, 50, .4),
inset 0 3px 4px -2px #9c9c9c,
inset 0 280px 40px -200px rgba(0, 0, 0, .2),
inset 0 -200px 40px -200px rgba(180, 180, 180, .2);
}
input:checked + .back {
padding: 2px 4px;
background-image: linear-gradient(0deg, #868686 30%, transparent 70%), linear-gradient(90deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%);
box-shadow: 30px 30px 30px -20px rgba(49, 49, 49, .1),
-30px 30px 30px -20px rgba(111, 111, 111, .1),
0 30px 30px 0 rgba(0, 0, 0, .2),
inset 0 1px 2px 0 rgba(167, 167, 167, .6);
}
}
}
}
}

338
ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts

@ -0,0 +1,338 @@
///
/// Copyright © 2016-2019 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 { AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { WidgetContext } from '@home/models/widget-component.models';
import { UtilsService } from '@core/services/utils.service';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { isDefined } from '@core/utils';
import { IWidgetSubscription, SubscriptionInfo, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { DatasourceType, widgetType } from '@shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models';
type RetrieveValueMethod = 'rpc' | 'attribute' | 'timeseries';
interface RoundSwitchSettings {
initialValue: boolean;
title: string;
retrieveValueMethod: RetrieveValueMethod;
valueKey: string;
getValueMethod: string;
setValueMethod: string;
parseValueFunction: string;
convertValueFunction: string;
requestTimeout: number;
}
@Component({
selector: 'tb-round-switch',
templateUrl: './round-switch.component.html',
styleUrls: ['./round-switch.component.scss']
})
export class RoundSwitchComponent extends PageComponent implements OnInit, OnDestroy {
@ViewChild('switch', {static: true}) switchElementRef: ElementRef<HTMLElement>;
@ViewChild('switchContainer', {static: true}) switchContainerRef: ElementRef<HTMLElement>;
@ViewChild('onoff', {static: true}) onoffRef: ElementRef<HTMLElement>;
@ViewChild('textMeasure', {static: true}) textMeasureRef: ElementRef<HTMLElement>;
@ViewChild('switchTitleContainer', {static: true}) switchTitleContainerRef: ElementRef<HTMLElement>;
@ViewChild('switchTitle', {static: true}) switchTitleRef: ElementRef<HTMLElement>;
@ViewChild('switchErrorContainer', {static: true}) switchErrorContainerRef: ElementRef<HTMLElement>;
@ViewChild('switchError', {static: true}) switchErrorRef: ElementRef<HTMLElement>;
@Input()
ctx: WidgetContext;
showTitle = false;
value = false;
error = '';
title = '';
checkboxId = 'onoff-' + this.utils.guid();
private isSimulated: boolean;
private requestTimeout: number;
private retrieveValueMethod: RetrieveValueMethod;
private valueKey: string;
private parseValueFunction: (data: any) => boolean;
private convertValueFunction: (value: any) => any;
private getValueMethod: string;
private setValueMethod: string;
private valueSubscription: IWidgetSubscription;
private executingUpdateValue: boolean;
private scheduledValue: boolean;
private rpcValue: boolean;
private switchElement: JQuery<HTMLElement>;
private switchContainer: JQuery<HTMLElement>;
private onoff: JQuery<HTMLElement>;
private textMeasure: JQuery<HTMLElement>;
private switchTitleContainer: JQuery<HTMLElement>;
private switchTitle: JQuery<HTMLElement>;
private switchErrorContainer: JQuery<HTMLElement>;
private switchError: JQuery<HTMLElement>;
private switchResizeListener: any;
constructor(private utils: UtilsService,
protected store: Store<AppState>) {
super(store);
}
ngOnInit(): void {
this.switchElement = $(this.switchElementRef.nativeElement);
this.switchContainer = $(this.switchContainerRef.nativeElement);
this.onoff = $(this.onoffRef.nativeElement);
this.textMeasure = $(this.textMeasureRef.nativeElement);
this.switchTitleContainer = $(this.switchTitleContainerRef.nativeElement);
this.switchTitle = $(this.switchTitleRef.nativeElement);
this.switchErrorContainer = $(this.switchErrorContainerRef.nativeElement);
this.switchError = $(this.switchErrorRef.nativeElement);
this.onoff.on('change', () => {
this.value = this.onoff.prop('checked') === false;
this.onValue();
});
this.switchResizeListener = this.resize.bind(this);
// @ts-ignore
addResizeListener(this.switchContainerRef.nativeElement, this.switchResizeListener);
this.init();
// this.ctx.resize = this.resize.bind(this);
}
ngOnDestroy(): void {
if (this.valueSubscription) {
this.ctx.subscriptionApi.removeSubscription(this.valueSubscription.id);
}
if (this.switchResizeListener) {
// @ts-ignore
removeResizeListener(this.switchContainerRef.nativeElement, this.switchResizeListener);
}
}
private init() {
const settings: RoundSwitchSettings = this.ctx.settings;
this.title = isDefined(settings.title) ? settings.title : '';
this.showTitle = !!(this.title && this.title.length);
const initialValue = isDefined(settings.initialValue) ? settings.initialValue : false;
this.setValue(initialValue);
const subscription = this.ctx.defaultSubscription;
const rpcEnabled = subscription.rpcEnabled;
this.isSimulated = this.utils.widgetEditMode;
this.requestTimeout = 500;
if (settings.requestTimeout) {
this.requestTimeout = settings.requestTimeout;
}
this.retrieveValueMethod = 'rpc';
if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) {
this.retrieveValueMethod = settings.retrieveValueMethod;
}
this.valueKey = 'value';
if (settings.valueKey && settings.valueKey.length) {
this.valueKey = settings.valueKey;
}
this.parseValueFunction = (data) => !!data;
if (settings.parseValueFunction && settings.parseValueFunction.length) {
try {
this.parseValueFunction = new Function('data', settings.parseValueFunction) as (data: any) => boolean;
} catch (e) {
this.parseValueFunction = (data) => !!data;
}
}
this.convertValueFunction = (value) => value;
if (settings.convertValueFunction && settings.convertValueFunction.length) {
try {
this.convertValueFunction = new Function('value', settings.convertValueFunction) as (value: any) => any;
} catch (e) {
this.convertValueFunction = (value) => value;
}
}
this.getValueMethod = 'getValue';
if (settings.getValueMethod && settings.getValueMethod.length) {
this.getValueMethod = settings.getValueMethod;
}
this.setValueMethod = 'setValue';
if (settings.setValueMethod && settings.setValueMethod.length) {
this.setValueMethod = settings.setValueMethod;
}
if (!rpcEnabled) {
this.onError('Target device is not set!');
} else {
if (!this.isSimulated) {
if (this.retrieveValueMethod === 'rpc') {
this.rpcRequestValue();
} else if (this.retrieveValueMethod === 'attribute' || this.retrieveValueMethod === 'timeseries') {
this.subscribeForValue();
}
}
}
}
private resize() {
const width = this.switchContainer.width();
const height = this.switchContainer.height();
const size = Math.min(width, height);
const scale = size/260;
this.switchElement.css({
'-webkit-transform': `scale(${scale})`,
'-moz-transform': `scale(${scale})`,
'-ms-transform': `scale(${scale})`,
'-o-transform': `scale(${scale})`,
transform: `scale(${scale})`
});
if (this.showTitle) {
this.setFontSize(this.switchTitle, this.title, this.switchTitleContainer.height() * 2 / 3, this.switchTitleContainer.width());
}
this.setFontSize(this.switchError, this.error, this.switchErrorContainer.height(), this.switchErrorContainer.width());
}
private setFontSize(element: JQuery<HTMLElement>, text: string, fontSize: number, maxWidth: number) {
let textWidth = this.measureTextWidth(text, fontSize);
while (textWidth > maxWidth) {
fontSize--;
textWidth = this.measureTextWidth(text, fontSize);
}
element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
}
private measureTextWidth(text: string, fontSize: number): number {
this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
this.textMeasure.text(text);
return this.textMeasure.width();
}
private onError(error: string) {
this.error = error;
this.setFontSize(this.switchError, this.error, this.switchErrorContainer.height(), this.switchErrorContainer.width());
this.ctx.detectChanges();
}
private setValue(value: boolean) {
this.value = value ? true : false;
this.onoff.prop('checked', !this.value);
}
private onValue() {
this.rpcUpdateValue(this.value);
}
private rpcRequestValue() {
this.error = '';
this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe(
(responseBody) => {
this.setValue(this.parseValueFunction(responseBody));
},
() => {
const errorText = this.ctx.defaultSubscription.rpcErrorText;
this.onError(errorText);
}
);
}
private rpcUpdateValue(value) {
if (this.executingUpdateValue) {
this.scheduledValue = value;
return;
} else {
this.scheduledValue = null;
this.rpcValue = value;
this.executingUpdateValue = true;
}
this.error = '';
this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout).subscribe(
() => {
this.executingUpdateValue = false;
if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) {
this.rpcUpdateValue(this.scheduledValue);
}
},
() => {
this.executingUpdateValue = false;
const errorText = this.ctx.defaultSubscription.rpcErrorText;
this.onError(errorText);
}
);
}
private subscribeForValue() {
const valueSubscriptionInfo: SubscriptionInfo[] = [{
type: DatasourceType.entity,
entityType: EntityType.DEVICE,
entityId: this.ctx.defaultSubscription.targetDeviceId
}];
if (this.retrieveValueMethod === 'attribute') {
valueSubscriptionInfo[0].attributes = [
{name: this.valueKey}
];
} else {
valueSubscriptionInfo[0].timeseries = [
{name: this.valueKey}
];
}
const subscriptionOptions: WidgetSubscriptionOptions = {
callbacks: {
onDataUpdated: this.onDataUpdated.bind(this),
onDataUpdateError: this.onDataUpdateError.bind(this)
}
};
this.ctx.subscriptionApi.createSubscriptionFromInfo (
widgetType.latest, valueSubscriptionInfo, subscriptionOptions, false, true).subscribe(
(subscription) => {
this.valueSubscription = subscription;
}
);
}
private onDataUpdated(subscription: IWidgetSubscription, detectChanges: boolean) {
let value = false;
const data = subscription.data;
if (data.length) {
const keyData = data[0];
if (keyData && keyData.data && keyData.data[0]) {
const attrValue = keyData.data[0][1];
if (attrValue) {
let parsed = null;
try {
parsed = this.parseValueFunction(JSON.parse(attrValue));
} catch (e){/**/}
value = !!parsed;
}
}
}
this.setValue(value);
if (detectChanges) {
this.ctx.detectChanges();
}
}
private onDataUpdateError(subscription: IWidgetSubscription, e: any) {
const exceptionData = this.utils.parseException(e);
let errorText = exceptionData.name;
if (exceptionData.message) {
errorText += ': ' + exceptionData.message;
}
this.onError(errorText);
}
}

44
ui-ngx/src/app/modules/home/components/widget/lib/rpc/rpc-widgets.module.ts

@ -0,0 +1,44 @@
///
/// Copyright © 2016-2019 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 { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@app/shared/shared.module';
import { LedIndicatorComponent } from '@home/components/widget/lib/rpc/led-indicator.component';
import { RoundSwitchComponent } from '@home/components/widget/lib/rpc/round-switch.component';
import { SwitchComponent } from '@home/components/widget/lib/rpc/switch.component';
import { KnobComponent } from '@home/components/widget/lib/rpc/knob.component';
@NgModule({
declarations:
[
LedIndicatorComponent,
RoundSwitchComponent,
SwitchComponent,
KnobComponent
],
imports: [
CommonModule,
SharedModule
],
exports: [
LedIndicatorComponent,
RoundSwitchComponent,
SwitchComponent,
KnobComponent
]
})
export class RpcWidgetsModule { }

36
ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/knob.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

43
ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-bar-checked.svg

@ -0,0 +1,43 @@
<svg id="svg5466" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="8.4667mm" width="23.616mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 83.679981 29.999998" xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs id="defs5468">
<linearGradient id="linearGradient6001-5" y2="-327.83" gradientUnits="userSpaceOnUse" x2="895.4" gradientTransform="translate(-31.905 97.852)" y1="-357.17" x1="895.4">
<stop id="stop5439" stop-color="#bdbdc1" offset="0"/>
<stop id="stop5441" stop-color="#f4f4f4" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient6003-6" y2="-392.51" gradientUnits="userSpaceOnUse" x2="892.44" gradientTransform="matrix(1 0 0 .78494 -34.597 55.388)" y1="-366.35" x1="892.44">
<stop id="stop5486" stop-color="#838383" offset="0"/>
<stop id="stop5492" stop-color="#979797" offset=".53774"/>
<stop id="stop5488" stop-color="#a7a7a7" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient6436" y2="-334.29" xlink:href="#linearGradient5528" gradientUnits="userSpaceOnUse" x2="913.4" gradientTransform="matrix(.78723 0 0 .78723 192.99 -72.172)" y1="-349.49" x1="913.38"/>
<linearGradient id="linearGradient5528">
<stop id="stop5530" stop-color="#3d3d3d" stop-opacity=".86275" offset="0"/>
<stop id="stop5532" stop-color="#fff" stop-opacity="0" offset="1"/>
</linearGradient>
<radialGradient id="radialGradient5502-17-9" xlink:href="#linearGradient5496" gradientUnits="userSpaceOnUse" cy="-341.87" cx="916.16" gradientTransform="matrix(4.5735 -.17574 .13786 3.5876 -3230.9 1043.7)" r="7.5588"/>
<linearGradient id="linearGradient5496">
<stop id="stop5498" stop-color="#fff" stop-opacity="0" offset="0"/>
<stop id="stop5550" stop-color="#fff" stop-opacity=".10435" offset=".17494"/>
<stop id="stop5552" stop-color="#fff" stop-opacity=".92174" offset=".39279"/>
<stop id="stop5500" stop-color="#fff" offset="1"/>
</linearGradient>
</defs>
<g id="layer1" transform="translate(-126.73 -454.51)">
<g id="g6480" transform="translate(-692.33 714.68)">
<g>
<path id="rect5195-5-0-1-9-6" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m826.67-260.18c-4.201 0.00074-7.606 3.8877-7.6055 8.6819v12.639c0.00065 4.7933 3.4054 8.6788 7.6055 8.6795h68.469c4.2001-0.00073 7.6049-3.8862 7.6055-8.6795v-12.639c0.00053-4.7942-3.4046-8.6812-7.6055-8.6819z" fill="url(#linearGradient6001-5)"/>
<path id="rect5195-5-0-1-4-0-6" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m826.67-257.4c-4.201 0.00062-7.606 3.2632-7.6055 7.2872v10.608c0.00065 4.0232 3.4054 7.2845 7.6055 7.2851h68.469c4.2001-0.00062 7.6049-3.2619 7.6055-7.2851v-10.608c0.00053-4.024-3.4046-7.2866-7.6055-7.2872z" fill="#585859"/>
<path id="rect5195-5-0-1-4-3-0-8" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m826.67-253.64c-4.201 0.00052-7.606 2.7592-7.6055 6.1617v8.9697c0.00065 3.4018 3.4054 6.1594 7.6055 6.16h68.469c4.2001-0.00052 7.6049-2.7581 7.6055-6.16v-8.9697c0.00053-3.4025-3.4046-6.1612-7.6055-6.1617z" fill="url(#linearGradient6003-6)"/>
</g>
<g id="g5554-2-1" transform="translate(-29.817 95.669)">
<circle id="path5494-3-7-8" cx="911.68" cy="-340.85" r="6.0124" fill="url(#linearGradient6436)"/>
<circle id="path5494-1-9" cx="911.68" cy="-340.85" r="5.3939" fill="url(#radialGradient5502-17-9)"/>
</g>
<g id="g5554-8-6-2" transform="translate(-71.652 95.669)">
<circle id="path5494-3-9-2-9" cx="911.68" cy="-340.85" r="6.0124" fill="url(#linearGradient6436)"/>
<circle id="path5494-7-0-5" cx="911.68" cy="-340.85" r="5.3939" fill="url(#radialGradient5502-17-9)"/>
</g>
<path id="rect5195-5-0-1-4-0-4-4" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m826.67-257.4c-4.201 0.00062-7.606 3.2632-7.6055 7.2872v10.608c0.00065 4.0232 3.4054 7.2845 7.6055 7.2851h68.469c4.2001-0.00062 7.6049-3.2619 7.6055-7.2851v-10.608c0.00053-4.024-3.4046-7.2866-7.6055-7.2872z" fill-opacity=".39080" fill="#ff6e4a"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

42
ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-bar.svg

@ -0,0 +1,42 @@
<svg id="svg4749" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="8.4667mm" width="23.616mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 83.679981 29.999998" xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs id="defs4751">
<linearGradient id="linearGradient5443" y2="-327.83" gradientUnits="userSpaceOnUse" x2="895.4" y1="-357.17" x1="895.4">
<stop id="stop5439" stop-color="#bdbdc1" offset="0"/>
<stop id="stop5441" stop-color="#f4f4f4" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient5490" y2="-392.51" gradientUnits="userSpaceOnUse" x2="892.44" gradientTransform="matrix(1 0 0 .78494 -2.6922 -42.464)" y1="-366.35" x1="892.44">
<stop id="stop5486" stop-color="#838383" offset="0"/>
<stop id="stop5492" stop-color="#979797" offset=".53774"/>
<stop id="stop5488" stop-color="#a7a7a7" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient5534" y2="-334.29" xlink:href="#linearGradient5528" gradientUnits="userSpaceOnUse" x2="913.4" gradientTransform="matrix(.78723 0 0 .78723 192.99 -72.172)" y1="-349.49" x1="913.38"/>
<linearGradient id="linearGradient5528">
<stop id="stop5530" stop-color="#3d3d3d" stop-opacity=".86275" offset="0"/>
<stop id="stop5532" stop-color="#fff" stop-opacity="0" offset="1"/>
</linearGradient>
<radialGradient id="radialGradient5502" xlink:href="#linearGradient5496" gradientUnits="userSpaceOnUse" cy="-341.87" cx="916.16" gradientTransform="matrix(4.5735 -.17574 .13786 3.5876 -3230.9 1043.7)" r="7.5588"/>
<linearGradient id="linearGradient5496">
<stop id="stop5498" stop-color="#fff" stop-opacity="0" offset="0"/>
<stop id="stop5550" stop-color="#fff" stop-opacity=".10435" offset=".17494"/>
<stop id="stop5552" stop-color="#fff" stop-opacity=".92174" offset=".39279"/>
<stop id="stop5500" stop-color="#fff" offset="1"/>
</linearGradient>
</defs>
<g id="layer1" transform="translate(-312.45 -714.51)">
<g id="g5878" transform="translate(-538.52 1072.5)">
<g>
<path id="rect5195-5-0-1" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m858.57-358.03c-4.201 0.00074-7.606 3.8877-7.6055 8.6819v12.639c0.00065 4.7933 3.4054 8.6788 7.6055 8.6795h68.469c4.2001-0.00073 7.6049-3.8862 7.6055-8.6795v-12.639c0.00053-4.7942-3.4046-8.6812-7.6055-8.6819z" fill="url(#linearGradient5443)"/>
<path id="rect5195-5-0-1-4" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m858.57-355.25c-4.201 0.00062-7.606 3.2632-7.6055 7.2872v10.608c0.00065 4.0232 3.4054 7.2845 7.6055 7.2851h68.469c4.2001-0.00062 7.6049-3.2619 7.6055-7.2851v-10.608c0.00053-4.024-3.4046-7.2866-7.6055-7.2872z" fill="#585859"/>
<path id="rect5195-5-0-1-4-3" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m858.57-351.49c-4.201 0.00052-7.606 2.7592-7.6055 6.1617v8.9697c0.00065 3.4018 3.4054 6.1594 7.6055 6.16h68.469c4.2001-0.00052 7.6049-2.7581 7.6055-6.16v-8.9697c0.00053-3.4025-3.4046-6.1612-7.6055-6.1617z" fill="url(#linearGradient5490)"/>
</g>
<g id="g5554" transform="translate(2.0884 -2.1835)">
<circle id="path5494-3" cx="911.68" cy="-340.85" r="6.0124" fill="url(#linearGradient5534)"/>
<circle id="path5494" cx="911.68" cy="-340.85" r="5.3939" fill="url(#radialGradient5502)"/>
</g>
<g id="g5554-8" transform="translate(-39.746 -2.1835)">
<circle id="path5494-3-9" cx="911.68" cy="-340.85" r="6.0124" fill="url(#linearGradient5534)"/>
<circle id="path5494-7" cx="911.68" cy="-340.85" r="5.3939" fill="url(#radialGradient5502)"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

40
ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-checked.svg

@ -0,0 +1,40 @@
<svg id="svg7523" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="7.6161mm" width="11.594mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 41.08074 26.986356" xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs id="defs7525">
<linearGradient id="linearGradient6257" y2="-412.79" gradientUnits="userSpaceOnUse" x2="821.92" gradientTransform="matrix(1 0 0 1.0219 -4.8132 52.955)" y1="-386.98" x1="822.46">
<stop id="stop5264" stop-color="#3d3d3d" offset="0"/>
<stop id="stop5266" stop-color="#fbfbfb" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient6259" y2="-350.07" gradientUnits="userSpaceOnUse" x2="821.71" gradientTransform="translate(-3.8933 5.8759)" y1="-372.35" x1="821.89">
<stop id="stop5239" stop-color="#d1d1d1" offset="0"/>
<stop id="stop5243" stop-color="#e2e3e2" offset=".36037"/>
<stop id="stop5245" stop-color="#ededed" offset=".67781"/>
<stop id="stop5241" stop-color="#fcfcfc" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient6261" y2="-333.89" gradientUnits="userSpaceOnUse" x2="913.16" gradientTransform="matrix(.78723 0 0 .78723 105.52 -77.537)" y1="-348.85" x1="912.9">
<stop id="stop5650" stop-color="#939393" offset="0"/>
<stop id="stop5652" stop-color="#fff" offset="1"/>
</linearGradient>
<radialGradient id="radialGradient5502-1-0-6" gradientUnits="userSpaceOnUse" cy="-342.2" cx="916.19" gradientTransform="matrix(1.9993 -.036196 .028926 1.5978 -997.39 230.16)" r="7.5588">
<stop id="stop5656" stop-color="#fff" stop-opacity="0" offset="0"/>
<stop id="stop5658" stop-color="#fff" stop-opacity=".18551" offset=".20750"/>
<stop id="stop5660" stop-color="#fff" stop-opacity=".32464" offset=".31814"/>
<stop id="stop5662" stop-color="#fff" offset="1"/>
</radialGradient>
</defs>
<g id="layer1" transform="translate(-259.46 -610.3)">
<g id="g6501" transform="translate(-602.12 869.32)">
<g id="g5664-5" transform="translate(57.91 100.68)">
<g id="g5329-3-4" transform="translate(13.694 -2.3675)">
<path id="rect5195-5-0-8-35" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m797.58-357.34a7.6067 7.8088 0 0 0 -7.6055 7.8098v11.369a7.6067 7.8088 0 0 0 7.6055 7.8076h25.87a7.6067 7.8088 0 0 0 7.6055 -7.8076v-11.369a7.6067 7.8088 0 0 0 -7.6055 -7.8098h-25.87z" fill="#1b1b1b"/>
<g id="g5300-6-7" transform="translate(-6.6863 11.667)">
<path id="rect5195-5-03-5" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m804.57-368.63a7.429 7.5918 0 0 0 -7.4279 7.5927v11.053a7.429 7.5918 0 0 0 7.4279 7.5906h25.266a7.429 7.5918 0 0 0 7.4279 -7.5906v-11.053a7.429 7.5918 0 0 0 -7.4279 -7.5927h-25.266z" fill="url(#linearGradient6257)"/>
<path id="rect5195-3-6" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m805.23-367.67a7.0362 7.0362 0 0 0 -7.0352 7.0371v10.244a7.0362 7.0362 0 0 0 7.0352 7.0352h23.93a7.0362 7.0362 0 0 0 7.0352 -7.0352v-10.244a7.0362 7.0362 0 0 0 -7.0352 -7.0371h-23.93z" fill="url(#linearGradient6259)"/>
</g>
</g>
<circle id="path5494-3-9-7-5" cx="824.21" cy="-346.21" r="6.0124" fill="url(#linearGradient6261)"/>
<circle id="path5494-7-8-95" cx="824.21" cy="-346.21" r="5.3939" fill="url(#radialGradient5502-1-0-6)"/>
</g>
<path id="rect5195-5-0-8-3-8" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m869.18-259.03a7.6067 7.8088 0 0 0 -7.6055 7.8098v11.369a7.6067 7.8088 0 0 0 7.6055 7.8076h25.87a7.6067 7.8088 0 0 0 7.6055 -7.8076v-11.369a7.6067 7.8088 0 0 0 -7.6055 -7.8098h-25.87z" fill-opacity=".78448" fill="#ff6e40"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

37
ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb.svg

@ -0,0 +1,37 @@
<svg id="svg6201" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="7.6161mm" width="11.594mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 41.08074 26.986349" xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs id="defs6203">
<linearGradient id="linearGradient5382" y2="-412.79" gradientUnits="userSpaceOnUse" x2="821.92" gradientTransform="matrix(1 0 0 1.0219 -4.8132 52.955)" y1="-386.98" x1="822.46">
<stop id="stop5264" stop-color="#3d3d3d" offset="0"/>
<stop id="stop5266" stop-color="#fbfbfb" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient5384" y2="-350.07" gradientUnits="userSpaceOnUse" x2="821.71" gradientTransform="translate(-3.8933 5.8759)" y1="-372.35" x1="821.89">
<stop id="stop5239" stop-color="#d1d1d1" offset="0"/>
<stop id="stop5243" stop-color="#e2e3e2" offset=".36037"/>
<stop id="stop5245" stop-color="#ededed" offset=".67781"/>
<stop id="stop5241" stop-color="#fcfcfc" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient5644" y2="-333.89" gradientUnits="userSpaceOnUse" x2="913.16" gradientTransform="matrix(.78723 0 0 .78723 105.52 -77.537)" y1="-348.85" x1="912.9">
<stop id="stop5650" stop-color="#939393" offset="0"/>
<stop id="stop5652" stop-color="#fff" offset="1"/>
</linearGradient>
<radialGradient id="radialGradient5502-1-0" gradientUnits="userSpaceOnUse" cy="-342.2" cx="916.19" gradientTransform="matrix(1.9993 -.036196 .028926 1.5978 -997.39 230.16)" r="7.5588">
<stop id="stop5656" stop-color="#fff" stop-opacity="0" offset="0"/>
<stop id="stop5658" stop-color="#fff" stop-opacity=".18551" offset=".20750"/>
<stop id="stop5660" stop-color="#fff" stop-opacity=".32464" offset=".31814"/>
<stop id="stop5662" stop-color="#fff" offset="1"/>
</radialGradient>
</defs>
<g id="layer1" transform="translate(-288.03 -544.58)">
<g id="g5664" transform="translate(-515.63 904.29)">
<g id="g5329-3" transform="translate(13.694 -2.3675)">
<path id="rect5195-5-0-8" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m797.58-357.34a7.6067 7.8088 0 0 0 -7.6055 7.8098v11.369a7.6067 7.8088 0 0 0 7.6055 7.8076h25.87a7.6067 7.8088 0 0 0 7.6055 -7.8076v-11.369a7.6067 7.8088 0 0 0 -7.6055 -7.8098h-25.87z" fill="#1b1b1b"/>
<g id="g5300-6" transform="translate(-6.6863 11.667)">
<path id="rect5195-5-03" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m804.57-368.63a7.429 7.5918 0 0 0 -7.4279 7.5927v11.053a7.429 7.5918 0 0 0 7.4279 7.5906h25.266a7.429 7.5918 0 0 0 7.4279 -7.5906v-11.053a7.429 7.5918 0 0 0 -7.4279 -7.5927h-25.266z" fill="url(#linearGradient5382)"/>
<path id="rect5195-3" style="color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;text-decoration-style:solid;image-rendering:auto;white-space:normal;text-indent:0;text-transform:none" d="m805.23-367.67a7.0362 7.0362 0 0 0 -7.0352 7.0371v10.244a7.0362 7.0362 0 0 0 7.0352 7.0352h23.93a7.0362 7.0362 0 0 0 7.0352 -7.0352v-10.244a7.0362 7.0362 0 0 0 -7.0352 -7.0371h-23.93z" fill="url(#linearGradient5384)"/>
</g>
</g>
<circle id="path5494-3-9-7" cy="-346.21" cx="824.21" r="6.0124" fill="url(#linearGradient5644)"/>
<circle id="path5494-7-8" cy="-346.21" cx="824.21" r="5.3939" fill="url(#radialGradient5502-1-0)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

39
ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.html

@ -0,0 +1,39 @@
<!--
Copyright © 2016-2019 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.
-->
<div class="tb-switch" fxLayout="column" [ngStyle]="{'pointerEvents': ctx.isEdit ? 'none' : 'all'}">
<div #switchErrorContainer class="error-container" [ngStyle]="{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}"
fxLayout="row" fxLayoutAlign="center center">
<span #switchError class="switch-error">{{ error }}</span>
</div>
<div #switchTitleContainer fxFlex="30" class="title-container" fxLayout="row" fxLayoutAlign="center center" [fxShow]="showTitle">
<span #switchTitle class="switch-title">{{title}}</span>
</div>
<div #switchContainer fxFlex="{{showTitle ? 70 : 100}}" id="switch-container" fxLayout="column" fxLayoutAlign="center center">
<div #switch class="switch">
<mat-slide-toggle #matSlideToggle [(ngModel)]="value" (change)="onValue()">
</mat-slide-toggle>
</div>
<div #onoffContainer class="onoff-container" fxLayout="row" fxLayoutAlign="center start" [fxShow]="showOnOffLabels">
<span #offLabel fxFlex class="off-label" [fxShow]="!value" style="text-align: center;">OFF</span>
<span fxFlex [fxShow]="value"></span>
<span #onLabel fxFlex class="on-label" [fxShow]="value" style="text-align: center;">ON</span>
<span fxFlex [fxShow]="!value"></span>
</div>
</div>
<div #textMeasure id="text-measure"></div>
</div>

147
ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.scss

@ -0,0 +1,147 @@
/**
* Copyright © 2016-2019 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.
*/
$thumb-img: url("./svg/thumb.svg") !default;
$thumb-checked-img: url("./svg/thumb-checked.svg") !default;
$thumb-bar-img: url("./svg/thumb-bar.svg") !default;
$thumb-bar-checked-img: url("./svg/thumb-bar-checked.svg") !default;
$background-color: #e6e7e8 !default;
$error-height: 14px !default;
:host {
.tb-switch {
width: 100%;
height: 100%;
background: $background-color;
.error-container {
position: absolute;
top: 1%;
right: 0;
left: 0;
z-index: 4;
height: $error-height;
.switch-error {
color: #ff3315;
white-space: nowrap;
}
}
.onoff-container {
height: 100%;
font-weight: 500;
color: #757575;
white-space: nowrap;
.off-label {
color: #b7b5b5;
}
.on-label {
color: #ff7e57;
text-shadow: #ff6e4a 1px 1px 10px, #ffd1c3 1px 1px 10px;
}
}
.title-container {
.switch-title {
font-weight: 500;
color: #757575;
white-space: nowrap;
}
}
#switch-container {
padding-right: 10px;
padding-left: 10px;
}
.switch {
position: relative;
}
#text-measure {
position: absolute;
width: auto;
height: auto;
white-space: nowrap;
visibility: hidden;
}
}
}
:host ::ng-deep {
.tb-switch {
.switch {
mat-slide-toggle {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: 0;
.mat-slide-toggle-bar {
top: 0;
left: 0;
width: 100%;
height: 100%;
background: $thumb-bar-img no-repeat;
background-size: contain;
border-radius: 0;
}
.mat-slide-toggle-thumb-container {
top: 5%;
left: .25%;
width: 50%;
height: 90%;
}
.mat-slide-toggle-thumb {
top: 0;
left: 0;
width: 100%;
height: 100%;
background: $thumb-img no-repeat;
background-size: contain;
border-radius: 0;
box-shadow: none;
}
&.mat-checked {
.mat-slide-toggle-thumb-container {
transform: translate3d(100%,0,0);
}
.mat-slide-toggle-bar {
background: $thumb-bar-checked-img no-repeat;
background-size: contain;
}
.mat-slide-toggle-thumb {
background: $thumb-checked-img no-repeat;
background-size: contain;
}
}
}
}
}
}

354
ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts

@ -0,0 +1,354 @@
///
/// Copyright © 2016-2019 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, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { WidgetContext } from '@home/models/widget-component.models';
import { UtilsService } from '@core/services/utils.service';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { isDefined } from '@core/utils';
import { IWidgetSubscription, SubscriptionInfo, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { DatasourceType, widgetType } from '@shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models';
import { MatSlideToggle } from '@angular/material/slide-toggle';
const switchAspectRation = 2.7893;
type RetrieveValueMethod = 'rpc' | 'attribute' | 'timeseries';
interface SwitchSettings {
initialValue: boolean;
title: string;
showOnOffLabels: boolean;
retrieveValueMethod: RetrieveValueMethod;
valueKey: string;
getValueMethod: string;
setValueMethod: string;
parseValueFunction: string;
convertValueFunction: string;
requestTimeout: number;
}
@Component({
selector: 'tb-switch',
templateUrl: './switch.component.html',
styleUrls: ['./switch.component.scss']
})
export class SwitchComponent extends PageComponent implements OnInit, OnDestroy {
@ViewChild('switch', {static: true}) switchElementRef: ElementRef<HTMLElement>;
@ViewChild('switchContainer', {static: true}) switchContainerRef: ElementRef<HTMLElement>;
@ViewChild('matSlideToggle', {static: true}) matSlideToggleRef: MatSlideToggle;
@ViewChild('onoffContainer', {static: true}) onoffContainerRef: ElementRef<HTMLElement>;
@ViewChild('onLabel', {static: true}) onLabelRef: ElementRef<HTMLElement>;
@ViewChild('offLabel', {static: true}) offLabelRef: ElementRef<HTMLElement>;
@ViewChild('switchTitleContainer', {static: true}) switchTitleContainerRef: ElementRef<HTMLElement>;
@ViewChild('switchTitle', {static: true}) switchTitleRef: ElementRef<HTMLElement>;
@ViewChild('textMeasure', {static: true}) textMeasureRef: ElementRef<HTMLElement>;
@ViewChild('switchErrorContainer', {static: true}) switchErrorContainerRef: ElementRef<HTMLElement>;
@ViewChild('switchError', {static: true}) switchErrorRef: ElementRef<HTMLElement>;
@Input()
ctx: WidgetContext;
showTitle = false;
value = false;
error = '';
title = '';
showOnOffLabels = false;
private isSimulated: boolean;
private requestTimeout: number;
private retrieveValueMethod: RetrieveValueMethod;
private valueKey: string;
private parseValueFunction: (data: any) => boolean;
private convertValueFunction: (value: any) => any;
private getValueMethod: string;
private setValueMethod: string;
private valueSubscription: IWidgetSubscription;
private executingUpdateValue: boolean;
private scheduledValue: boolean;
private rpcValue: boolean;
private switchElement: JQuery<HTMLElement>;
private switchContainer: JQuery<HTMLElement>;
private matSlideToggle: JQuery<HTMLElement>;
private onoffContainer: JQuery<HTMLElement>;
private onLabel: JQuery<HTMLElement>;
private offLabel: JQuery<HTMLElement>;
private switchTitleContainer: JQuery<HTMLElement>;
private switchTitle: JQuery<HTMLElement>;
private textMeasure: JQuery<HTMLElement>;
private switchErrorContainer: JQuery<HTMLElement>;
private switchError: JQuery<HTMLElement>;
private switchResizeListener: any;
constructor(private utils: UtilsService,
protected store: Store<AppState>) {
super(store);
}
ngOnInit(): void {
this.switchElement = $(this.switchElementRef.nativeElement);
this.switchContainer = $(this.switchContainerRef.nativeElement);
this.matSlideToggle = $(this.matSlideToggleRef._elementRef.nativeElement);
this.onoffContainer = $(this.onoffContainerRef.nativeElement);
this.onLabel = $(this.onLabelRef.nativeElement);
this.offLabel = $(this.offLabelRef.nativeElement);
this.switchTitleContainer = $(this.switchTitleContainerRef.nativeElement);
this.switchTitle = $(this.switchTitleRef.nativeElement);
this.textMeasure = $(this.textMeasureRef.nativeElement);
this.switchErrorContainer = $(this.switchErrorContainerRef.nativeElement);
this.switchError = $(this.switchErrorRef.nativeElement);
this.switchResizeListener = this.resize.bind(this);
// @ts-ignore
addResizeListener(this.switchContainerRef.nativeElement, this.switchResizeListener);
this.init();
}
ngOnDestroy(): void {
if (this.valueSubscription) {
this.ctx.subscriptionApi.removeSubscription(this.valueSubscription.id);
}
if (this.switchResizeListener) {
// @ts-ignore
removeResizeListener(this.switchContainerRef.nativeElement, this.switchResizeListener);
}
}
private init() {
const settings: SwitchSettings = this.ctx.settings;
this.title = isDefined(settings.title) ? settings.title : '';
this.showTitle = !!(this.title && this.title.length);
this.showOnOffLabels = isDefined(settings.showOnOffLabels) ? settings.showOnOffLabels : true;
const initialValue = isDefined(settings.initialValue) ? settings.initialValue : false;
this.setValue(initialValue);
const subscription = this.ctx.defaultSubscription;
const rpcEnabled = subscription.rpcEnabled;
this.isSimulated = this.utils.widgetEditMode;
this.requestTimeout = 500;
if (settings.requestTimeout) {
this.requestTimeout = settings.requestTimeout;
}
this.retrieveValueMethod = 'rpc';
if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) {
this.retrieveValueMethod = settings.retrieveValueMethod;
}
this.valueKey = 'value';
if (settings.valueKey && settings.valueKey.length) {
this.valueKey = settings.valueKey;
}
this.parseValueFunction = (data) => !!data;
if (settings.parseValueFunction && settings.parseValueFunction.length) {
try {
this.parseValueFunction = new Function('data', settings.parseValueFunction) as (data: any) => boolean;
} catch (e) {
this.parseValueFunction = (data) => !!data;
}
}
this.convertValueFunction = (value) => value;
if (settings.convertValueFunction && settings.convertValueFunction.length) {
try {
this.convertValueFunction = new Function('value', settings.convertValueFunction) as (value: any) => any;
} catch (e) {
this.convertValueFunction = (value) => value;
}
}
this.getValueMethod = 'getValue';
if (settings.getValueMethod && settings.getValueMethod.length) {
this.getValueMethod = settings.getValueMethod;
}
this.setValueMethod = 'setValue';
if (settings.setValueMethod && settings.setValueMethod.length) {
this.setValueMethod = settings.setValueMethod;
}
if (!rpcEnabled) {
this.onError('Target device is not set!');
} else {
if (!this.isSimulated) {
if (this.retrieveValueMethod === 'rpc') {
this.rpcRequestValue();
} else if (this.retrieveValueMethod === 'attribute' || this.retrieveValueMethod === 'timeseries') {
this.subscribeForValue();
}
}
}
}
private resize() {
let width = this.switchContainer.width();
let height = this.switchContainer.height();
if (this.showOnOffLabels) {
height = height*2/3;
}
const ratio = width/height;
if (ratio > switchAspectRation) {
width = height*switchAspectRation;
} else {
height = width/switchAspectRation;
}
this.switchElement.css({width, height});
this.matSlideToggle.css({width, height, minWidth: width});
if (this.showTitle) {
this.setFontSize(this.switchTitle, this.title, this.switchTitleContainer.height() * 2 / 3, this.switchTitleContainer.width());
}
if (this.showOnOffLabels) {
this.onoffContainer.css({width, height: this.switchContainer.height() / 3});
this.setFontSize(this.onLabel, 'OFF', this.onoffContainer.height(), this.onoffContainer.width() / 2);
this.setFontSize(this.offLabel, 'OFF', this.onoffContainer.height(), this.onoffContainer.width() / 2);
}
this.setFontSize(this.switchError, this.error, this.switchErrorContainer.height(), this.switchErrorContainer.width());
}
private setFontSize(element: JQuery<HTMLElement>, text: string, fontSize: number, maxWidth: number) {
let textWidth = this.measureTextWidth(text, fontSize);
while (textWidth > maxWidth) {
fontSize--;
textWidth = this.measureTextWidth(text, fontSize);
}
element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
}
private measureTextWidth(text: string, fontSize: number): number {
this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
this.textMeasure.text(text);
return this.textMeasure.width();
}
private onError(error: string) {
this.error = error;
this.setFontSize(this.switchError, this.error, this.switchErrorContainer.height(), this.switchErrorContainer.width());
this.ctx.detectChanges();
}
private setValue(value: boolean) {
this.value = value ? true : false;
}
public onValue() {
this.rpcUpdateValue(this.value);
}
private rpcRequestValue() {
this.error = '';
this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe(
(responseBody) => {
this.setValue(this.parseValueFunction(responseBody));
},
() => {
const errorText = this.ctx.defaultSubscription.rpcErrorText;
this.onError(errorText);
}
);
}
private rpcUpdateValue(value) {
if (this.executingUpdateValue) {
this.scheduledValue = value;
return;
} else {
this.scheduledValue = null;
this.rpcValue = value;
this.executingUpdateValue = true;
}
this.error = '';
this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout).subscribe(
() => {
this.executingUpdateValue = false;
if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) {
this.rpcUpdateValue(this.scheduledValue);
}
},
() => {
this.executingUpdateValue = false;
const errorText = this.ctx.defaultSubscription.rpcErrorText;
this.onError(errorText);
}
);
}
private subscribeForValue() {
const valueSubscriptionInfo: SubscriptionInfo[] = [{
type: DatasourceType.entity,
entityType: EntityType.DEVICE,
entityId: this.ctx.defaultSubscription.targetDeviceId
}];
if (this.retrieveValueMethod === 'attribute') {
valueSubscriptionInfo[0].attributes = [
{name: this.valueKey}
];
} else {
valueSubscriptionInfo[0].timeseries = [
{name: this.valueKey}
];
}
const subscriptionOptions: WidgetSubscriptionOptions = {
callbacks: {
onDataUpdated: this.onDataUpdated.bind(this),
onDataUpdateError: this.onDataUpdateError.bind(this)
}
};
this.ctx.subscriptionApi.createSubscriptionFromInfo (
widgetType.latest, valueSubscriptionInfo, subscriptionOptions, false, true).subscribe(
(subscription) => {
this.valueSubscription = subscription;
}
);
}
private onDataUpdated(subscription: IWidgetSubscription, detectChanges: boolean) {
let value = false;
const data = subscription.data;
if (data.length) {
const keyData = data[0];
if (keyData && keyData.data && keyData.data[0]) {
const attrValue = keyData.data[0][1];
if (attrValue) {
let parsed = null;
try {
parsed = this.parseValueFunction(JSON.parse(attrValue));
} catch (e){/**/}
value = !!parsed;
}
}
}
this.setValue(value);
if (detectChanges) {
this.ctx.detectChanges();
}
}
private onDataUpdateError(subscription: IWidgetSubscription, e: any) {
const exceptionData = this.utils.parseException(e);
let errorText = exceptionData.name;
if (exceptionData.message) {
errorText += ': ' + exceptionData.message;
}
this.onError(errorText);
}
}

5
ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

@ -25,6 +25,7 @@ import { SharedHomeComponentsModule } from '@home/components/shared-home-compone
import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/timeseries-table-widget.component'; import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/timeseries-table-widget.component';
import { EntitiesHierarchyWidgetComponent } from '@home/components/widget/lib/entities-hierarchy-widget.component'; import { EntitiesHierarchyWidgetComponent } from '@home/components/widget/lib/entities-hierarchy-widget.component';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
import { RpcWidgetsModule } from '@home/components/widget/lib/rpc/rpc-widgets.module';
@NgModule({ @NgModule({
declarations: declarations:
@ -39,13 +40,15 @@ import { CustomDialogService } from '@home/components/widget/dialog/custom-dialo
imports: [ imports: [
CommonModule, CommonModule,
SharedModule, SharedModule,
RpcWidgetsModule,
SharedHomeComponentsModule SharedHomeComponentsModule
], ],
exports: [ exports: [
EntitiesTableWidgetComponent, EntitiesTableWidgetComponent,
AlarmsTableWidgetComponent, AlarmsTableWidgetComponent,
TimeseriesTableWidgetComponent, TimeseriesTableWidgetComponent,
EntitiesHierarchyWidgetComponent EntitiesHierarchyWidgetComponent,
RpcWidgetsModule
], ],
providers: [ providers: [
CustomDialogService CustomDialogService

Loading…
Cancel
Save