Browse Source

UI: Add timeseries to color settings and update settings for digital neon and lcd gauge

pull/10557/head
Artem Dzhereleiko 2 years ago
parent
commit
4d9ef2d3b2
  1. 4
      application/src/main/data/json/system/widget_types/digital_horizontal_bar.json
  2. 4
      application/src/main/data/json/system/widget_types/digital_speedometer.json
  3. 4
      application/src/main/data/json/system/widget_types/digital_thermometer.json
  4. 4
      application/src/main/data/json/system/widget_types/digital_vertical_bar.json
  5. 4
      application/src/main/data/json/system/widget_types/lcd_bar_gauge.json
  6. 4
      application/src/main/data/json/system/widget_types/lcd_gauge.json
  7. 4
      application/src/main/data/json/system/widget_types/neon_gauge.json
  8. 4
      application/src/main/data/json/system/widget_types/simple_neon_gauge.json
  9. 52
      ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.html
  10. 26
      ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.ts
  11. 6
      ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.html
  12. 10
      ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.ts
  13. 53
      ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts
  14. 44
      ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts
  15. 19
      ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts
  16. 24
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.html
  17. 8
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.ts
  18. 120
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html
  19. 19
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts
  20. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html
  21. 17
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts
  22. 10
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts
  23. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss
  24. 44
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.html
  25. 32
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts
  26. 77
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.html
  27. 40
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.scss
  28. 191
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.ts
  29. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  30. 52
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html
  31. 71
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts
  32. 7
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.html
  33. 8
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts
  34. 6
      ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.html
  35. 11
      ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.ts
  36. 195
      ui-ngx/src/app/shared/models/widget-settings.models.ts
  37. 8
      ui-ngx/src/assets/locale/locale.constant-en_US.json

4
application/src/main/data/json/system/widget_types/digital_horizontal_bar.json

@ -15,7 +15,9 @@
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings", "settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 80) {\\n\\tvalue = 80;\\n} else if (value > 160) {\\n\\tvalue = 160;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"horizontalBar\",\"showTitle\":false,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital horizontal bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" "hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 80) {\\n\\tvalue = 80;\\n} else if (value > 160) {\\n\\tvalue = 160;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"horizontalBar\",\"showTitle\":false,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital horizontal bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
}, },
"externalId": null, "externalId": null,
"tags": [ "tags": [

4
application/src/main/data/json/system/widget_types/digital_speedometer.json

@ -15,7 +15,9 @@
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings", "settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 45) {\\n\\tvalue = 45;\\n} else if (value > 130) {\\n\\tvalue = 130;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital speedometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" "hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 45) {\\n\\tvalue = 45;\\n} else if (value > 130) {\\n\\tvalue = 130;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#008000\",\"#fbc02d\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#ffffff\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital speedometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
}, },
"externalId": null, "externalId": null,
"tags": [ "tags": [

4
application/src/main/data/json/system/widget_types/digital_thermometer.json

@ -15,7 +15,9 @@
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings", "settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < -60) {\\n\\tvalue = 60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[\"#304ffe\",\"#7e57c2\",\"#ff4081\",\"#d32f2f\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"dashThickness\":1.5,\"minValue\":-60,\"gaugeColor\":\"#333333\",\"neonGlowBrightness\":35,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital thermometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" "hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < -60) {\\n\\tvalue = 60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[\"#304ffe\",\"#7e57c2\",\"#ff4081\",\"#d32f2f\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":18},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"dashThickness\":1.5,\"minValue\":-60,\"gaugeColor\":\"#333333\",\"neonGlowBrightness\":35,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital thermometer\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
}, },
"externalId": null, "externalId": null,
"tags": [ "tags": [

4
application/src/main/data/json/system/widget_types/digital_vertical_bar.json

@ -15,7 +15,9 @@
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings", "settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#3d5afe\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":14},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":8,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#cccccc\"},\"neonGlowBrightness\":20,\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"verticalBar\",\"showTitle\":false,\"minValue\":-60,\"dashThickness\":1.2,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital vertical bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" "hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":60,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[\"#3d5afe\",\"#f44336\"],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":14},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":8,\"style\":\"normal\",\"weight\":\"normal\",\"color\":\"#cccccc\"},\"neonGlowBrightness\":20,\"showUnitTitle\":true,\"gaugeColor\":\"#171a1c\",\"gaugeType\":\"verticalBar\",\"showTitle\":false,\"minValue\":-60,\"dashThickness\":1.2,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Digital vertical bar\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
}, },
"externalId": null, "externalId": null,
"tags": [ "tags": [

4
application/src/main/data/json/system/widget_types/lcd_bar_gauge.json

@ -15,7 +15,9 @@
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings", "settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"400\",\"size\":16},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"verticalBar\",\"units\":\"%\"},\"title\":\"LCD bar gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" "hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Roboto\",\"style\":\"normal\",\"weight\":\"400\",\"size\":16},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"verticalBar\",\"units\":\"%\"},\"title\":\"LCD bar gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
}, },
"externalId": null "externalId": null
} }

4
application/src/main/data/json/system/widget_types/lcd_gauge.json

@ -15,7 +15,9 @@
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings", "settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 180) {\\n\\tvalue = 180;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"arc\"},\"title\":\"LCD gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" "hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 180) {\\n\\tvalue = 180;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#babab2\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":180,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"refreshAnimationType\":\"linear\",\"refreshAnimationTime\":700,\"startAnimationType\":\"linear\",\"startAnimationTime\":700,\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":0,\"dashThickness\":1.5,\"decimals\":0,\"unitTitle\":\"MPH\",\"showUnitTitle\":true,\"defaultColor\":\"#444444\",\"gaugeType\":\"arc\"},\"title\":\"LCD gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"configMode\":\"basic\"}"
}, },
"externalId": null "externalId": null
} }

4
application/src/main/data/json/system/widget_types/neon_gauge.json

@ -15,7 +15,9 @@
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings", "settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":70,\"dashThickness\":1,\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Neon gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" "hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":0.75,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":70,\"dashThickness\":1,\"gaugeType\":\"arc\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Neon gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
}, },
"externalId": null "externalId": null
} }

4
application/src/main/data/json/system/widget_types/simple_neon_gauge.json

@ -15,7 +15,9 @@
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-digital-gauge-widget-settings", "settingsDirective": "tb-digital-gauge-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#388e3c\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Simple neon gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" "hasBasicMode": true,
"basicModeDirective": "tb-digital-simple-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#388e3c\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#000000\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"maxValue\":100,\"minValue\":0,\"donutStartAngle\":90,\"showValue\":true,\"showMinMax\":true,\"gaugeWidthScale\":1,\"levelColors\":[],\"titleFont\":{\"family\":\"Roboto\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"labelFont\":{\"family\":\"Roboto\",\"size\":8,\"style\":\"normal\",\"weight\":\"500\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"style\":\"normal\",\"weight\":\"500\",\"size\":32},\"minMaxFont\":{\"family\":\"Segment7Standard\",\"size\":12,\"style\":\"normal\",\"weight\":\"500\"},\"neonGlowBrightness\":40,\"dashThickness\":1.5,\"gaugeType\":\"donut\",\"animation\":true,\"animationDuration\":500,\"animationRule\":\"linear\"},\"title\":\"Simple neon gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"configMode\":\"basic\"}"
}, },
"externalId": null "externalId": null
} }

52
ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.html

@ -44,38 +44,36 @@
</tb-image-cards-select-option> </tb-image-cards-select-option>
</tb-image-cards-select> </tb-image-cards-select>
<div class="tb-form-row space-between" *ngIf="simpleGaugeWidgetConfigForm.get('gaugeType').value === digitalGaugeType.donut; else minMaxValue"> <div class="tb-form-row space-between" *ngIf="simpleGaugeWidgetConfigForm.get('gaugeType').value === digitalGaugeType.donut">
<div class="fixed-title-width">{{ 'widgets.gauge.donut-start-angle' | translate }}</div> <div class="fixed-title-width">{{ 'widgets.gauge.donut-start-angle' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic"> <mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="donutStartAngle" type="number" min="0" max="360" step="1" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput formControlName="donutStartAngle" type="number" min="0" max="360" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field> </mat-form-field>
</div> </div>
<ng-template #minMaxValue> <div class="tb-form-row space-between column-xs">
<div class="tb-form-row space-between column-xs"> <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showMinMax">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showMinMax"> {{ 'widgets.gauge.min-and-max-value' | translate }}
{{ 'widgets.gauge.min-and-max-value' | translate }} </mat-slide-toggle>
</mat-slide-toggle> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div class="tb-small-label" translate>widgets.gauge.min-value-short</div>
<div class="tb-small-label" translate>widgets.gauge.min-value-short</div> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <input matInput formControlName="minValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<input matInput formControlName="minValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}"> </mat-form-field>
</mat-form-field> <div class="tb-small-label" translate>widgets.gauge.max-value-short</div>
<div class="tb-small-label" translate>widgets.gauge.max-value-short</div> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <input matInput formControlName="maxValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<input matInput formControlName="maxValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}"> </mat-form-field>
</mat-form-field> <tb-font-settings formControlName="minMaxFont"
<tb-font-settings formControlName="minMaxFont" disabledLineHeight
disabledLineHeight [previewText]="previewFn">
[previewText]="previewFn"> </tb-font-settings>
</tb-font-settings> <tb-color-input asBoxInput
<tb-color-input asBoxInput colorClearButton
colorClearButton formControlName="minMaxColor">
formControlName="minMaxColor"> </tb-color-input>
</tb-color-input>
</div>
</div> </div>
</ng-template> </div>
<div class="tb-form-row column-xs"> <div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showValue"> <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showValue">
@ -139,7 +137,11 @@
<div class="fixed-title-width">{{ 'widgets.gauge.bar-color' | translate }}</div> <div class="fixed-title-width">{{ 'widgets.gauge.bar-color' | translate }}</div>
<tb-color-settings formControlName="barColor" <tb-color-settings formControlName="barColor"
rangeAdvancedMode rangeAdvancedMode
[minValue]="simpleGaugeWidgetConfigForm.get('minValue').value"
[maxValue]="simpleGaugeWidgetConfigForm.get('maxValue').value"
[aliasController]="aliasController" [aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.value' | translate }}"> settingsKey="{{'widgets.battery-level.value' | translate }}">
</tb-color-settings> </tb-color-settings>
</div> </div>

26
ui-ngx/src/app/modules/home/components/widget/config/basic/gauge/digital-simple-gauge-basic-config.component.ts

@ -20,6 +20,7 @@ import { AppState } from '@core/core.state';
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { import {
Datasource,
datasourcesHasAggregation, datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation, datasourcesHasOnlyComparisonAggregation,
WidgetConfig, WidgetConfig,
@ -29,18 +30,18 @@ import {
getTimewindowConfig, getTimewindowConfig,
setTimewindowConfig setTimewindowConfig
} from '@home/components/widget/config/timewindow-config-panel.component'; } from '@home/components/widget/config/timewindow-config-panel.component';
import { deepClone, formatValue, isUndefined } from '@core/utils'; import { formatValue, isUndefined } from '@core/utils';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { import {
colorLevel, backwardCompatibilityFixedLevelColors,
defaultDigitalSimpleGaugeOptions, defaultDigitalSimpleGaugeOptions,
digitalGaugeLayoutImages, digitalGaugeLayoutImages,
digitalGaugeLayouts, digitalGaugeLayouts,
digitalGaugeLayoutTranslations, digitalGaugeLayoutTranslations,
DigitalGaugeSettings, DigitalGaugeSettings,
DigitalGaugeType, FixedLevelColors DigitalGaugeType
} from '@home/components/widget/lib/digital-gauge.models'; } from '@home/components/widget/lib/digital-gauge.models';
import { AdvancedColorRange, ColorSettings, ColorType, constantColor } from '@shared/models/widget-settings.models'; import { ColorSettings, ColorType, constantColor } from '@shared/models/widget-settings.models';
@Component({ @Component({
selector: 'tb-digital-simple-gauge-basic-config', selector: 'tb-digital-simple-gauge-basic-config',
@ -60,6 +61,15 @@ export class DigitalSimpleGaugeBasicConfigComponent extends BasicWidgetConfigCom
return datasourcesHasOnlyComparisonAggregation(datasources); return datasourcesHasOnlyComparisonAggregation(datasources);
} }
public get datasource(): Datasource {
const datasources: Datasource[] = this.widgetConfig.config.datasources;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
digitalGaugeType = DigitalGaugeType; digitalGaugeType = DigitalGaugeType;
digitalGaugeLayouts = digitalGaugeLayouts; digitalGaugeLayouts = digitalGaugeLayouts;
@ -89,12 +99,12 @@ export class DigitalSimpleGaugeBasicConfigComponent extends BasicWidgetConfigCom
const settings: DigitalGaugeSettings = {...defaultDigitalSimpleGaugeOptions, ...(configData.config.settings || {})}; const settings: DigitalGaugeSettings = {...defaultDigitalSimpleGaugeOptions, ...(configData.config.settings || {})};
if (!settings.barColor) { if (!settings.barColor) {
settings.barColor = constantColor(null); settings.barColor = constantColor(settings.gaugeColor);
if (settings.fixedLevelColors?.length) { if (settings.fixedLevelColors?.length) {
settings.barColor.rangeList = { settings.barColor.rangeList = {
advancedMode: settings.useFixedLevelColor, advancedMode: settings.useFixedLevelColor,
range: null, range: null,
rangeAdvanced: deepClone(settings.fixedLevelColors) as AdvancedColorRange[] rangeAdvanced: backwardCompatibilityFixedLevelColors(settings.fixedLevelColors)
}; };
} }
if (settings.levelColors?.length) { if (settings.levelColors?.length) {
@ -205,8 +215,8 @@ export class DigitalSimpleGaugeBasicConfigComponent extends BasicWidgetConfigCom
if (isDonut) { if (isDonut) {
this.simpleGaugeWidgetConfigForm.get('donutStartAngle').enable(); this.simpleGaugeWidgetConfigForm.get('donutStartAngle').enable();
this.simpleGaugeWidgetConfigForm.get('showMinMax').disable({emitEvent: false}); this.simpleGaugeWidgetConfigForm.get('showMinMax').disable({emitEvent: false});
this.simpleGaugeWidgetConfigForm.get('minValue').disable(); this.simpleGaugeWidgetConfigForm.get('minValue').enable();
this.simpleGaugeWidgetConfigForm.get('maxValue').disable(); this.simpleGaugeWidgetConfigForm.get('maxValue').enable();
this.simpleGaugeWidgetConfigForm.get('minMaxFont').disable(); this.simpleGaugeWidgetConfigForm.get('minMaxFont').disable();
this.simpleGaugeWidgetConfigForm.get('minMaxColor').disable(); this.simpleGaugeWidgetConfigForm.get('minMaxColor').disable();
} else { } else {

6
ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.html

@ -103,6 +103,8 @@
rangeAdvancedMode rangeAdvancedMode
gradientAdvancedMode gradientAdvancedMode
[aliasController]="aliasController" [aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.value' | translate }}"> settingsKey="{{'widgets.battery-level.value' | translate }}">
</tb-color-settings> </tb-color-settings>
</div> </div>
@ -115,6 +117,8 @@
minValue="0" minValue="0"
maxValue="100" maxValue="100"
[aliasController]="aliasController" [aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.battery-level-color' | translate }}"> settingsKey="{{'widgets.battery-level.battery-level-color' | translate }}">
</tb-color-settings> </tb-color-settings>
</div> </div>
@ -126,6 +130,8 @@
minValue="0" minValue="0"
maxValue="100" maxValue="100"
[aliasController]="aliasController" [aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.battery-shape-color' | translate }}"> settingsKey="{{'widgets.battery-level.battery-shape-color' | translate }}">
</tb-color-settings> </tb-color-settings>
</div> </div>

10
ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/battery-level-basic-config.component.ts

@ -22,6 +22,7 @@ import { BasicWidgetConfigComponent } from '@home/components/widget/config/widge
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { import {
DataKey, DataKey,
Datasource,
datasourcesHasAggregation, datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation, datasourcesHasOnlyComparisonAggregation,
WidgetConfig, WidgetConfig,
@ -74,6 +75,15 @@ export class BatteryLevelBasicConfigComponent extends BasicWidgetConfigComponent
return [BatteryLevelLayout.vertical_divided, BatteryLevelLayout.horizontal_divided].includes(layout); return [BatteryLevelLayout.vertical_divided, BatteryLevelLayout.horizontal_divided].includes(layout);
} }
public get datasource(): Datasource {
const datasources: Datasource[] = this.widgetConfig.config.datasources;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent, protected widgetConfigComponent: WidgetConfigComponent,
private fb: UntypedFormBuilder) { private fb: UntypedFormBuilder) {

53
ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.models.ts

@ -17,7 +17,14 @@
import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge'; import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge';
import { AnimationRule } from '@home/components/widget/lib/analogue-gauge.models'; import { AnimationRule } from '@home/components/widget/lib/analogue-gauge.models';
import { FontSettings } from '@home/components/widget/lib/settings.models'; import { FontSettings } from '@home/components/widget/lib/settings.models';
import { ColorSettings } from '@shared/models/widget-settings.models'; import {
AdvancedColorRange,
ColorSettings,
ValueSourceDataKeyType,
ValueSourceWithDataKey
} from '@shared/models/widget-settings.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { isDefinedAndNotNull } from '@core/utils';
export interface AttributeSourceProperty { export interface AttributeSourceProperty {
valueSource: string; valueSource: string;
@ -27,8 +34,8 @@ export interface AttributeSourceProperty {
} }
export interface FixedLevelColors { export interface FixedLevelColors {
from?: AttributeSourceProperty | number; from?: ValueSourceWithDataKey | number;
to?: AttributeSourceProperty | number; to?: ValueSourceWithDataKey | number;
color: string; color: string;
} }
@ -105,7 +112,7 @@ export interface DigitalGaugeSettings {
hideValue?: boolean; hideValue?: boolean;
hideMinMax?: boolean; hideMinMax?: boolean;
showTicks?: boolean; showTicks?: boolean;
ticksValue?: AttributeSourceProperty[]; ticksValue?: ValueSourceWithDataKey[];
ticks?: number[]; ticks?: number[];
colorTicks?: string; colorTicks?: string;
tickWidth?: number; tickWidth?: number;
@ -115,3 +122,41 @@ export const defaultDigitalSimpleGaugeOptions: DigitalGaugeSettings = {
gaugeType: DigitalGaugeType.donut, gaugeType: DigitalGaugeType.donut,
timestampFormat: 'yyyy-MM-dd HH:mm:ss', timestampFormat: 'yyyy-MM-dd HH:mm:ss',
}; };
export const backwardCompatibilityFixedLevelColors = (fixedLevelColors) => {
const valueSourceWithDataKey: AdvancedColorRange[] = [];
fixedLevelColors.forEach(fixedLevelColor => valueSourceWithDataKey.push({
from: {
type: fixedLevelColor?.from?.valueSource === 'predefinedValue' ? ValueSourceDataKeyType.constant : ValueSourceDataKeyType.entity,
value: fixedLevelColor?.from?.value || null,
entityAlias: fixedLevelColor?.from?.entityAlias || '',
entityKey: fixedLevelColor?.from?.attribute || '',
entityKeyType: DataKeyType.attribute
},
to: {
type: fixedLevelColor?.to?.valueSource === 'predefinedValue' ? ValueSourceDataKeyType.constant : ValueSourceDataKeyType.entity,
value: fixedLevelColor?.to?.value || null,
entityAlias: fixedLevelColor?.to?.entityAlias || '',
entityKey: fixedLevelColor?.to?.attribute || '',
entityKeyType: DataKeyType.attribute
},
color: fixedLevelColor.color
}) );
return valueSourceWithDataKey;
};
export const backwardCompatibilityTicks = (ticksValue) => {
const ticks: ValueSourceWithDataKey[] = [];
if (ticksValue?.length && isDefinedAndNotNull(ticksValue[0]?.valueSource)) {
ticksValue.forEach(tick => ticks.push({
type: tick?.valueSource === 'predefinedValue' ? ValueSourceDataKeyType.constant : ValueSourceDataKeyType.entity,
value: tick?.value || null,
entityAlias: tick?.entityAlias || '',
entityKey: tick?.attribute || '',
entityKeyType: DataKeyType.attribute
}) );
} else {
return ticksValue;
}
return ticks;
};

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

@ -18,7 +18,6 @@ import * as CanvasGauges from 'canvas-gauges';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { import {
attributesGaugeType, attributesGaugeType,
AttributeSourceProperty,
ColorLevelSetting, ColorLevelSetting,
DigitalGaugeSettings, DigitalGaugeSettings,
FixedLevelColors FixedLevelColors
@ -32,7 +31,12 @@ import { DataKey, Datasource, DatasourceData, DatasourceType, widgetType } from
import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { EMPTY, Observable } from 'rxjs'; import { EMPTY, Observable } from 'rxjs';
import { ColorType } from '@shared/models/widget-settings.models'; import {
ColorProcessor,
ColorType,
ValueSourceDataKeyType,
ValueSourceWithDataKey
} from '@shared/models/widget-settings.models';
import GenericOptions = CanvasGauges.GenericOptions; import GenericOptions = CanvasGauges.GenericOptions;
// @dynamic // @dynamic
@ -212,9 +216,9 @@ export class TbCanvasDigitalGauge {
throw new Error('Not valid entity aliase name ' + entityAlias); throw new Error('Not valid entity aliase name ' + entityAlias);
} }
const datasource = datasources.find((datasourceIteration) => { const datasource = datasources.find((datasourceIteration) =>
return datasourceIteration.entityAliasId === entityAliasId; datasourceIteration.entityAliasId === entityAliasId
}); );
const dataKey: DataKey = { const dataKey: DataKey = {
type: DataKeyType.attribute, type: DataKeyType.attribute,
@ -225,9 +229,9 @@ export class TbCanvasDigitalGauge {
}; };
if (datasource) { if (datasource) {
const findDataKey = datasource.dataKeys.find((dataKeyIteration) => { const findDataKey = datasource.dataKeys.find((dataKeyIteration) =>
return dataKeyIteration.name === attribute; dataKeyIteration.name === attribute
}); );
if (findDataKey) { if (findDataKey) {
findDataKey.settings.push(settings); findDataKey.settings.push(settings);
@ -278,15 +282,16 @@ export class TbCanvasDigitalGauge {
}); });
function setLevelColor(levelSetting, color: string) { function setLevelColor(levelSetting, color: string) {
if (levelSetting.valueSource === 'predefinedValue' && isFinite(levelSetting.value)) { if (levelSetting.type === ValueSourceDataKeyType.constant && isFinite(levelSetting.value)) {
predefineLevelColors.push({ predefineLevelColors.push({
value: levelSetting.value, value: levelSetting.value,
color color
}); });
} else if (levelSetting.entityAlias && levelSetting.attribute) { } else if (levelSetting.type === ValueSourceDataKeyType.latestKey || levelSetting.type === ValueSourceDataKeyType.entity) {
try { try {
levelColorsDatasource = TbCanvasDigitalGauge.generateDatasource(this.ctx, levelColorsDatasource, levelColorsDatasource = ColorProcessor.generateDatasource(
levelSetting.entityAlias, levelSetting.attribute, {color, index: predefineLevelColors.length}); this.ctx, levelColorsDatasource, levelSetting, {color, index: predefineLevelColors.length}
);
} catch (e) { } catch (e) {
return; return;
} }
@ -315,17 +320,18 @@ export class TbCanvasDigitalGauge {
return predefineLevelColors; return predefineLevelColors;
} }
settingTicksSubscribe(options: AttributeSourceProperty[]): number[] { settingTicksSubscribe(options: ValueSourceWithDataKey[]): number[] {
let ticksDatasource: Datasource[] = []; let ticksDatasource: Datasource[] = [];
const predefineTicks: number[] = []; const predefineTicks: number[] = [];
for (const tick of options) { for (const tick of options) {
if (tick.valueSource === 'predefinedValue' && isFinite(tick.value)) { if (tick.type === ValueSourceDataKeyType.constant && isFinite(tick.value)) {
predefineTicks.push(tick.value); predefineTicks.push(tick.value);
} else if (tick.entityAlias && tick.attribute) { } else {
try { try {
ticksDatasource = TbCanvasDigitalGauge ticksDatasource = ColorProcessor.generateDatasource(
.generateDatasource(this.ctx, ticksDatasource, tick.entityAlias, tick.attribute, predefineTicks.length); this.ctx, ticksDatasource, tick, {index: predefineTicks.length}
);
} catch (e) { } catch (e) {
continue; continue;
} }
@ -364,7 +370,7 @@ export class TbCanvasDigitalGauge {
if (keyData && keyData.data && keyData.data[0]) { if (keyData && keyData.data && keyData.data[0]) {
const attrValue = keyData.data[0][1]; const attrValue = keyData.data[0][1];
if (isFinite(attrValue)) { if (isFinite(attrValue)) {
for (const setting of keyData.dataKey.settings) { for (const setting of keyData.dataKey.settings.dataKeySettings) {
switch (typeAttributes) { switch (typeAttributes) {
case 'levelColors': case 'levelColors':
this.localSettings.levelColors[setting.index] = { this.localSettings.levelColors[setting.index] = {
@ -373,7 +379,7 @@ export class TbCanvasDigitalGauge {
}; };
break; break;
case 'ticks': case 'ticks':
this.localSettings.ticks[setting] = attrValue; this.localSettings.ticks[setting.index] = attrValue;
break; break;
} }
} }

19
ui-ngx/src/app/modules/home/components/widget/lib/indicator/battery-level-widget.component.ts

@ -28,7 +28,7 @@ import {
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils'; import { formattedDataFormDatasourceData, formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { import {
backgroundStyle, backgroundStyle,
@ -46,9 +46,15 @@ import {
BatteryLevelWidgetSettings BatteryLevelWidgetSettings
} from '@home/components/widget/lib/indicator/battery-level-widget.models'; } from '@home/components/widget/lib/indicator/battery-level-widget.models';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
import { Observable } from 'rxjs'; import { forkJoin, Observable } from 'rxjs';
import { ImagePipe } from '@shared/pipe/image.pipe'; import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { FormattedData } from '@shared/models/widget.models';
import {
parseThresholdData,
TimeSeriesChartThresholdType
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { map } from 'rxjs/operators';
const verticalBatteryDimensions = { const verticalBatteryDimensions = {
shapeAspectRatio: 64 / 113, shapeAspectRatio: 64 / 113,
@ -201,6 +207,10 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView
this.overlayStyle = overlayStyle(this.settings.background.overlay); this.overlayStyle = overlayStyle(this.settings.background.overlay);
this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0; this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0;
this.valueColor.colorUpdated.subscribe(() => this.cd.markForCheck());
this.batteryLevelColor.colorUpdated.subscribe(() => this.cd.markForCheck());
this.batteryShapeColor.colorUpdated.subscribe(() => this.cd.markForCheck());
} }
ngAfterViewInit() { ngAfterViewInit() {
@ -218,6 +228,11 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView
if (this.batteryBoxResize$) { if (this.batteryBoxResize$) {
this.batteryBoxResize$.disconnect(); this.batteryBoxResize$.disconnect();
} }
this.valueColor.colorUpdated.unsubscribe();
this.batteryLevelColor.colorUpdated.unsubscribe();
this.batteryShapeColor.colorUpdated.unsubscribe();
this.batteryLevelColor.destroy(); this.batteryLevelColor.destroy();
this.valueColor.destroy(); this.valueColor.destroy();
this.batteryShapeColor.destroy(); this.batteryShapeColor.destroy();

24
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.html

@ -20,21 +20,21 @@
<div class="range-input range-input-fields"> <div class="range-input range-input-fields">
<div fxFlex fxLayout="row" fxLayoutAlign="start center"> <div fxFlex fxLayout="row" fxLayoutAlign="start center">
<div class="range-text" translate>widgets.color.from</div> <div class="range-text" translate>widgets.color.from</div>
<tb-value-source fxFlex <tb-value-source-data-key fxFlex
formControlName="from" formControlName="from"
entityAliasPlaceholder="widgets.color.entity-alias" [aliasController]="aliasController"
entityAttributePlaceholder="widgets.color.entity-attribute" [dataKeyCallbacks]="dataKeyCallbacks"
[aliasController]="aliasController"> [datasource]="datasource">
</tb-value-source> </tb-value-source-data-key>
</div> </div>
<div fxFlex fxLayout="row" fxLayoutAlign="start center"> <div fxFlex fxLayout="row" fxLayoutAlign="start center">
<div class="range-text" translate>widgets.color.to</div> <div class="range-text" translate>widgets.color.to</div>
<tb-value-source fxFlex <tb-value-source-data-key fxFlex
formControlName="to" formControlName="to"
entityAliasPlaceholder="widgets.color.entity-alias" [aliasController]="aliasController"
entityAttributePlaceholder="widgets.color.entity-attribute" [dataKeyCallbacks]="dataKeyCallbacks"
[aliasController]="aliasController"> [datasource]="datasource">
</tb-value-source> </tb-value-source-data-key>
</div> </div>
</div> </div>
<tb-color-input asBoxInput <tb-color-input asBoxInput

8
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/advanced-range.component.ts

@ -27,6 +27,8 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
import { AdvancedColorRange } from '@shared/models/widget-settings.models'; import { AdvancedColorRange } from '@shared/models/widget-settings.models';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { Datasource } from '@shared/models/widget.models';
@Component({ @Component({
selector: 'tb-advanced-range', selector: 'tb-advanced-range',
@ -48,6 +50,12 @@ export class AdvancedRangeComponent extends PageComponent implements OnInit, Con
@Input() @Input()
aliasController: IAliasController; aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Output() @Output()
removeAdvancedRange = new EventEmitter(); removeAdvancedRange = new EventEmitter();

120
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html

@ -34,36 +34,61 @@
</div> </div>
</ng-template> </ng-template>
<div class="tb-color-ranges" [ngSwitch]="colorRangeListFormGroup.get('advancedMode').value"> <div class="tb-color-ranges" [ngSwitch]="colorRangeListFormGroup.get('advancedMode').value">
<ng-template [ngSwitchCase]="false"> <ng-template [ngSwitchCase]="false">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical" <div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="rangeDrop($event, 'range')"> (cdkDropListDropped)="rangeDrop($event, 'range')">
<div cdkDrag class="tb-form-row no-padding no-border tb-draggable" <div cdkDrag class="tb-form-row no-padding no-border tb-draggable"
[formGroup]="rangeFormGroup" [formGroup]="rangeFormGroup"
*ngFor="let rangeFormGroup of rangeListFormGroups; trackBy: trackByRange; let $index = index; last as isLast;" *ngFor="let rangeFormGroup of rangeListFormGroups; trackBy: trackByRange; let $index = index; last as isLast;"
[ngStyle]="!isLast ? {paddingBottom: '8px'} : {}"> [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<div class="range"> <div class="range">
<div class="range-container"> <div class="range-container">
<div class="tb-value-range-text" translate>widgets.color.from</div> <div class="tb-value-range-text" translate>widgets.color.from</div>
<mat-form-field fxFlex appearance="outline" class="center number" subscriptSizing="dynamic"> <mat-form-field fxFlex appearance="outline" class="center number" subscriptSizing="dynamic">
<input matInput type="number" formControlName="from" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput type="number" formControlName="from" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field> </mat-form-field>
<div class="tb-value-range-text tb-value-range-text-to" translate>widgets.color.to</div> <div class="tb-value-range-text tb-value-range-text-to" translate>widgets.color.to</div>
<mat-form-field fxFlex appearance="outline" class="center number" subscriptSizing="dynamic"> <mat-form-field fxFlex appearance="outline" class="center number" subscriptSizing="dynamic">
<input matInput type="number" formControlName="to" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput type="number" formControlName="to" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field> </mat-form-field>
<tb-color-input asBoxInput <tb-color-input asBoxInput
formControlName="color"> formControlName="color">
</tb-color-input> </tb-color-input>
</div>
<button type="button"
mat-icon-button
class="tb-box-button"
(click)="removeRange($index)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
<button class="tb-box-button"
mat-icon-button
type="button"
cdkDragHandle
matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above">
<mat-icon>drag_indicator</mat-icon>
</button>
</div> </div>
<button type="button" </div>
mat-icon-button </div>
class="tb-box-button" </ng-template>
(click)="removeRange($index)" <ng-template [ngSwitchCase]="true">
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above"> <div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
<mat-icon>delete</mat-icon> (cdkDropListDropped)="rangeDrop($event, 'rangeAdvanced')">
</button> <div cdkDrag class="tb-draggable tb-advanced-range-drag"
*ngFor="let advancedRangeControl of advancedRangeControls; trackBy: trackByAdvancedRange;let $index = index; last as isLast;"
[ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-advanced-range [formControl]="advancedRangeControl"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
(removeAdvancedRange)="removeAdvancedRange($index)">
</tb-advanced-range>
<button class="tb-box-button" <button class="tb-box-button"
mat-icon-button mat-icon-button
type="button" type="button"
@ -74,36 +99,13 @@
</button> </button>
</div> </div>
</div> </div>
</div>
</ng-template>
<ng-template [ngSwitchCase]="true">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="rangeDrop($event, 'rangeAdvanced')">
<div cdkDrag class="tb-draggable tb-advanced-range-drag"
*ngFor="let advancedRangeControl of advancedRangeControls; trackBy: trackByAdvancedRange;let $index = index; last as isLast;"
[ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-advanced-range [formControl]="advancedRangeControl"
[aliasController]="aliasController"
(removeAdvancedRange)="removeAdvancedRange($index)">
</tb-advanced-range>
<button class="tb-box-button"
mat-icon-button
type="button"
cdkDragHandle
matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above">
<mat-icon>drag_indicator</mat-icon>
</button>
</div>
</div>
</ng-template> </ng-template>
</div> </div>
<button class="tb-add-color-range" <button class="tb-add-color-range"
mat-stroked-button mat-stroked-button
(click)="addRange()"> (click)="addRange()">
<mat-icon>add</mat-icon> <mat-icon>add</mat-icon>
</button> </button>
</ng-container> </ng-container>

19
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts

@ -26,7 +26,12 @@ import {
UntypedFormGroup, UntypedFormGroup,
ValidationErrors ValidationErrors
} from '@angular/forms'; } from '@angular/forms';
import { AdvancedColorRange, ColorRange, ColorRangeSettings } from '@shared/models/widget-settings.models'; import {
AdvancedColorRange,
ColorRange,
ColorRangeSettings,
ValueSourceDataKeyType
} from '@shared/models/widget-settings.models';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -34,6 +39,8 @@ import { deepClone, isDefinedAndNotNull, isUndefined } from '@core/utils';
import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { Datasource } from '@shared/models/widget.models';
export function advancedRangeValidator(control: AbstractControl): ValidationErrors | null { export function advancedRangeValidator(control: AbstractControl): ValidationErrors | null {
const range: AdvancedColorRange = control.value; const range: AdvancedColorRange = control.value;
@ -71,6 +78,12 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On
@Input() @Input()
aliasController: IAliasController; aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Input() @Input()
@coerceBoolean() @coerceBoolean()
advancedMode = false; advancedMode = false;
@ -184,10 +197,10 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On
public addAdvancedRange() { public addAdvancedRange() {
const advancedRange: AdvancedColorRange = { const advancedRange: AdvancedColorRange = {
from: { from: {
valueSource: 'predefinedValue' type: ValueSourceDataKeyType.constant
}, },
to: { to: {
valueSource: 'predefinedValue' type: ValueSourceDataKeyType.constant
}, },
color: null color: null
}; };

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html

@ -40,6 +40,8 @@
[maxValue]="maxValue" [maxValue]="maxValue"
[advancedMode]="gradientAdvancedMode" [advancedMode]="gradientAdvancedMode"
[aliasController]="aliasController" [aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
[popover]="popover"> [popover]="popover">
</tb-gradient> </tb-gradient>
</div> </div>
@ -49,6 +51,8 @@
panelTitle="{{ 'widgets.color.value-range' | translate }}" panelTitle="{{ 'widgets.color.value-range' | translate }}"
[advancedMode]="rangeAdvancedMode" [advancedMode]="rangeAdvancedMode"
[aliasController]="aliasController" [aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
[popover]="popover"> [popover]="popover">
</tb-color-range-list> </tb-color-range-list>
</div> </div>

17
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts

@ -16,7 +16,12 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { ColorSettings, ColorType, colorTypeTranslations } from '@shared/models/widget-settings.models'; import {
ColorSettings,
ColorType,
colorTypeTranslations,
defaultGradient
} from '@shared/models/widget-settings.models';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@ -26,6 +31,8 @@ import { WidgetService } from '@core/http/widget.service';
import { ColorSettingsComponent } from '@home/components/widget/lib/settings/common/color-settings.component'; import { ColorSettingsComponent } from '@home/components/widget/lib/settings/common/color-settings.component';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { Datasource } from '@shared/models/widget.models';
@Component({ @Component({
selector: 'tb-color-settings-panel', selector: 'tb-color-settings-panel',
@ -51,6 +58,12 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit
@Input() @Input()
aliasController: IAliasController; aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Input() @Input()
@coerceBoolean() @coerceBoolean()
rangeAdvancedMode = false; rangeAdvancedMode = false;
@ -86,7 +99,7 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit
{ {
type: [this.colorSettings?.type || ColorType.constant, []], type: [this.colorSettings?.type || ColorType.constant, []],
color: [this.colorSettings?.color, []], color: [this.colorSettings?.color, []],
gradient: [this.colorSettings?.gradient, []], gradient: [this.colorSettings?.gradient || defaultGradient(), []],
rangeList: [this.colorSettings?.rangeList, []], rangeList: [this.colorSettings?.rangeList, []],
colorFunction: [this.colorSettings?.colorFunction, []] colorFunction: [this.colorSettings?.colorFunction, []]
} }

10
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts

@ -34,6 +34,8 @@ import {
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
import { deepClone, isDefinedAndNotNull } from '@core/utils'; import { deepClone, isDefinedAndNotNull } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { Datasource } from '@shared/models/widget.models';
@Injectable() @Injectable()
export class ColorSettingsComponentService { export class ColorSettingsComponentService {
@ -85,6 +87,12 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD
@Input() @Input()
aliasController: IAliasController; aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Input() @Input()
@coerceBoolean() @coerceBoolean()
rangeAdvancedMode = false; rangeAdvancedMode = false;
@ -155,6 +163,8 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD
colorSettings: this.modelValue, colorSettings: this.modelValue,
settingsComponents: this.colorSettingsComponentService.getOtherColorSettingsComponents(this), settingsComponents: this.colorSettingsComponentService.getOtherColorSettingsComponents(this),
aliasController: this.aliasController, aliasController: this.aliasController,
dataKeyCallbacks: this.dataKeyCallbacks,
datasource: this.datasource,
rangeAdvancedMode: this.rangeAdvancedMode, rangeAdvancedMode: this.rangeAdvancedMode,
gradientAdvancedMode: this.gradientAdvancedMode, gradientAdvancedMode: this.gradientAdvancedMode,
minValue: this.minValue, minValue: this.minValue,

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss

@ -28,8 +28,8 @@
padding-right: 0; padding-right: 0;
.mat-mdc-form-field-infix { .mat-mdc-form-field-infix {
padding-top: 0; padding-top: 0 !important;
padding-bottom: 6px; padding-bottom: 6px !important;
.mdc-evolution-chip-set .mdc-evolution-chip { .mdc-evolution-chip-set .mdc-evolution-chip {
margin: 0; margin: 0;

44
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.html

@ -40,7 +40,7 @@
<div class="gradient-container gradient-container-start"> <div class="gradient-container gradient-container-start">
<div translate>widgets.color.gradient-start</div> <div translate>widgets.color.gradient-start</div>
<div class="start-gradient-container"> <div class="start-gradient-container">
<div class="tb-form-row no-border no-padding" fxHide.xs [fxHide]="!minValue?.length"> <div class="tb-form-row no-border no-padding" fxHide.xs [fxHide]="!minValue?.toString()?.length">
<div class="gradient-text" translate>widgets.color.start-value</div> <div class="gradient-text" translate>widgets.color.start-value</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="0" disabled [(ngModel)]="minValue" [ngModelOptions]="{standalone: true}" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput type="number" min="0" disabled [(ngModel)]="minValue" [ngModelOptions]="{standalone: true}" placeholder="{{ 'widget-config.set' | translate }}">
@ -98,7 +98,7 @@
<div class="gradient-container gradient-container-end"> <div class="gradient-container gradient-container-end">
<div translate>widgets.color.gradient-end</div> <div translate>widgets.color.gradient-end</div>
<div class="end-gradient-container"> <div class="end-gradient-container">
<div class="tb-form-row no-border no-padding" fxHide.xs [fxHide]="!maxValue?.length"> <div class="tb-form-row no-border no-padding" fxHide.xs [fxHide]="!maxValue?.toString()?.length">
<div class="gradient-text" translate>widgets.color.end-value</div> <div class="gradient-text" translate>widgets.color.end-value</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="0" disabled [(ngModel)]="maxValue" [ngModelOptions]="{standalone: true}" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput type="number" min="0" disabled [(ngModel)]="maxValue" [ngModelOptions]="{standalone: true}" placeholder="{{ 'widget-config.set' | translate }}">
@ -118,13 +118,13 @@
<span fxHide.xs translate>widgets.color.gradient-start</span> <span fxHide.xs translate>widgets.color.gradient-start</span>
<span fxHide fxShow.xs translate>widgets.color.gradient-start-min</span> <span fxHide fxShow.xs translate>widgets.color.gradient-start-min</span>
</div> </div>
<div class="start-gradient-container start-gradient-container-advanced"> <div class="tb-form-row no-border no-padding start-gradient-container start-gradient-container-advanced">
<tb-value-source fxFlex <tb-value-source-data-key fxFlex
formControlName="source" formControlName="source"
entityAliasPlaceholder="widgets.color.entity-alias" [aliasController]="aliasController"
entityAttributePlaceholder="widgets.color.entity-attribute" [dataKeyCallbacks]="dataKeyCallbacks"
[aliasController]="aliasController"> [datasource]="datasource">
</tb-value-source> </tb-value-source-data-key>
<tb-color-input asBoxInput <tb-color-input asBoxInput
formControlName="color"> formControlName="color">
</tb-color-input> </tb-color-input>
@ -146,12 +146,12 @@
<span fxHide fxShow.xs translate>widgets.color.gradient-color-min</span> <span fxHide fxShow.xs translate>widgets.color.gradient-color-min</span>
</div> </div>
<div class="list-gradient-container list-gradient-container-advanced"> <div class="list-gradient-container list-gradient-container-advanced">
<tb-value-source fxFlex <tb-value-source-data-key fxFlex
formControlName="source" formControlName="source"
entityAliasPlaceholder="widgets.color.entity-alias" [aliasController]="aliasController"
entityAttributePlaceholder="widgets.color.entity-attribute" [dataKeyCallbacks]="dataKeyCallbacks"
[aliasController]="aliasController"> [datasource]="datasource">
</tb-value-source> </tb-value-source-data-key>
<tb-color-input asBoxInput <tb-color-input asBoxInput
formControlName="color"> formControlName="color">
</tb-color-input> </tb-color-input>
@ -190,13 +190,13 @@
<span fxHide.xs translate>widgets.color.gradient-end</span> <span fxHide.xs translate>widgets.color.gradient-end</span>
<span fxHide fxShow.xs translate>widgets.color.gradient-end-min</span> <span fxHide fxShow.xs translate>widgets.color.gradient-end-min</span>
</div> </div>
<div class="end-gradient-container end-gradient-container-advanced"> <div class="tb-form-row no-border no-padding end-gradient-container end-gradient-container-advanced">
<tb-value-source fxFlex <tb-value-source-data-key fxFlex
formControlName="source" formControlName="source"
entityAliasPlaceholder="widgets.color.entity-alias" [aliasController]="aliasController"
entityAttributePlaceholder="widgets.color.entity-attribute" [dataKeyCallbacks]="dataKeyCallbacks"
[aliasController]="aliasController"> [datasource]="datasource">
</tb-value-source> </tb-value-source-data-key>
<tb-color-input asBoxInput <tb-color-input asBoxInput
formControlName="color"> formControlName="color">
</tb-color-input> </tb-color-input>

32
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/gradient.component.ts

@ -24,7 +24,7 @@ import {
UntypedFormBuilder, UntypedFormBuilder,
UntypedFormGroup UntypedFormGroup
} from '@angular/forms'; } from '@angular/forms';
import { AdvancedGradient, ColorGradientSettings } from '@shared/models/widget-settings.models'; import { AdvancedGradient, ColorGradientSettings, ValueSourceDataKeyType } from '@shared/models/widget-settings.models';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -33,6 +33,8 @@ import { IAliasController } from '@core/api/widget-api.models';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull } from '@core/utils';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { Datasource } from '@shared/models/widget.models';
@Component({ @Component({
selector: 'tb-gradient', selector: 'tb-gradient',
@ -62,6 +64,12 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro
@Input() @Input()
aliasController: IAliasController; aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Input() @Input()
minValue: string; minValue: string;
@ -84,6 +92,7 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro
private sanitizer: DomSanitizer) {} private sanitizer: DomSanitizer) {}
ngOnInit(): void { ngOnInit(): void {
console.log(this.minValue, this.maxValue);
this.gradientFormGroup = this.fb.group({ this.gradientFormGroup = this.fb.group({
advancedMode: [false], advancedMode: [false],
gradient: this.fb.group({ gradient: this.fb.group({
@ -93,12 +102,12 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro
}), }),
gradientAdvanced: this.fb.group({ gradientAdvanced: this.fb.group({
start: this.fb.group({ start: this.fb.group({
source: [], source: [{type: ValueSourceDataKeyType.constant}],
color: ['rgba(0, 255, 0, 1)'] color: ['rgba(0, 255, 0, 1)']
}), }),
gradientList: this.fb.array([]), gradientList: this.fb.array([]),
end: this.fb.group({ end: this.fb.group({
source: [], source: [{type: ValueSourceDataKeyType.constant}],
color: ['rgba(255, 0, 0, 1)'] color: ['rgba(255, 0, 0, 1)']
}) })
}) })
@ -147,26 +156,27 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro
get generatePointers() { get generatePointers() {
if (this.gradientFormGroup.get('advancedMode').value) { if (this.gradientFormGroup.get('advancedMode').value) {
const point = 100 / (this.advancedGradientListFormArray.value.length + 1); const shift = 100 / (this.advancedGradientListFormArray.value.length + 1);
return `<div class="pointer start"></div>` + return `<div class="pointer start"></div>` +
this.advancedGradientListFormArray.value.map((v, i) => this.pointer(point * (i + 1), true)).join('') + this.advancedGradientListFormArray.value.map((v, i) => this.pointer(shift * (i + 1), i+1, null, true)).join('') +
`<div class="pointer end"></div>`; `<div class="pointer end"></div>`;
} else { } else {
const point = 100 / (this.gradientListFormArray.value.length + 1); const point = (+this.maxValue - +this.minValue) / (this.gradientListFormArray.value.length + 1);
const shift = 100 / (this.gradientListFormArray.value.length + 1);
const min = isDefinedAndNotNull(this.minValue) ? this.minValue : 0; const min = isDefinedAndNotNull(this.minValue) ? this.minValue : 0;
const max = isDefinedAndNotNull(this.maxValue) ? this.maxValue : 100; const max = isDefinedAndNotNull(this.maxValue) ? this.maxValue : 100;
return `<div class="pointer start"><div class="pointer-value"><span class="pointer-value-text">${min}</span></div></div>` + return `<div class="pointer start"><div class="pointer-value"><span class="pointer-value-text">${min}</span></div></div>` +
this.gradientListFormArray.value.map((v, i) => this.pointer(point * (i + 1))).join('') + this.gradientListFormArray.value.map((v, i) => this.pointer(shift * (i + 1), i+1, point)).join('') +
`<div class="pointer end"><div class="pointer-value"><span class="pointer-value-text">${max}</span></div></div>`; `<div class="pointer end"><div class="pointer-value"><span class="pointer-value-text">${max}</span></div></div>`;
} }
} }
pointer(value?: number, advanced = false) { pointer(shift: number, index?: number, value?: number, advanced = false) {
if (advanced) { if (advanced) {
return `<div class="pointer" style="left: ${value}%"></div>`; return `<div class="pointer" style="left: ${shift}%"></div>`;
} else { } else {
return `<div class="pointer" style="left: ${value}%">` + return `<div class="pointer" style="left: ${shift}%">` +
`<div class="pointer-value"><span class="pointer-value-text">${Math.floor((+this.maxValue*value)/100)}</span></div></div>`; `<div class="pointer-value"><span class="pointer-value-text">${Math.floor(+this.minValue + (value * index))}</span></div></div>`;
} }
} }

77
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.html

@ -0,0 +1,77 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div [formGroup]="valueSourceFormGroup" class="tb-value-source tb-form-row no-padding no-border">
<div class="tb-source-field">
<mat-form-field class="tb-type-field" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type">
<mat-option *ngFor="let type of valueSourceDataKeyTypes" [value]="type">
{{ valueSourceDataKeyTypeTranslation.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<tb-entity-alias-input
class="tb-entity-alias-field"
*ngIf="valueSourceFormGroup.get('type').value === valueSourceDataKeyType.entity"
[aliasController]="aliasController"
required
formControlName="entityAlias">
</tb-entity-alias-input>
</div>
<div class="tb-key-value-field">
<mat-form-field *ngIf="valueSourceFormGroup.get('type').value === valueSourceDataKeyType.constant"
appearance="outline" class="tb-constant-field number" subscriptSizing="dynamic"
[class.tb-suffix-absolute]="!valueSourceFormGroup.get('value').value">
<input required matInput formControlName="value" type="number" placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="'widgets.value-source.value-required' | translate"
*ngIf="valueSourceFormGroup.get('value').hasError('required')
&& valueSourceFormGroup.get('value').touched"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
<tb-data-key-input
*ngIf="valueSourceFormGroup.get('type').value === valueSourceDataKeyType.latestKey"
required
requiredText="widgets.value-source.key-required"
[datasourceType]="datasource?.type"
[entityAliasId]="datasource?.entityAliasId"
[deviceId]="datasource?.deviceId"
[aliasController]="aliasController"
[dataKeyType]="datasource?.type === datasourceType.function ? dataKeyType.function : null"
[dataKeyTypes]="[dataKeyType.attribute, dataKeyType.timeseries]"
[callbacks]="dataKeyCallbacks"
[editable]="false"
[formControl]="latestKeyFormControl">
</tb-data-key-input>
<tb-data-key-input
*ngIf="valueSourceFormGroup.get('type').value === valueSourceDataKeyType.entity"
required
requiredText="widgets.value-source.entity-key-required"
[datasourceType]="datasourceType.entity"
[aliasController]="aliasController"
[entityAlias]="valueSourceFormGroup.get('entityAlias').value"
[dataKeyTypes]="[dataKeyType.attribute, dataKeyType.timeseries]"
[callbacks]="dataKeyCallbacks"
[editable]="false"
[formControl]="entityKeyFormControl">
</tb-data-key-input>
</div>
</div>

40
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.scss

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
.tb-value-source {
display: flex;
gap: 12px;
}
.tb-source-field {
display: flex;
flex: 1;
gap: 12px;
.tb-type-field, .tb-entity-alias-field {
flex: 1;
}
}
.tb-key-value-field, .tb-constant-field {
display: flex;
flex: 1;
}
::ng-deep {
.tb-data-key-input {
flex: 1;
}
}
}

191
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.ts

@ -0,0 +1,191 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
Validators
} from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { IAliasController } from '@core/api/widget-api.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { DataKey, Datasource, DatasourceType } from '@app/shared/models/widget.models';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import {
ValueSourceDataKeyType,
ValueSourceDataKeyTypes,
ValueSourceDataKeyTypeTranslation
} from '@shared/models/widget-settings.models';
@Component({
selector: 'tb-value-source-data-key',
templateUrl: './value-source-data-key.component.html',
styleUrls: ['value-source-data-key.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ValueSourceDataKeyComponent),
multi: true
}
]
})
export class ValueSourceDataKeyComponent extends PageComponent implements OnInit, ControlValueAccessor {
dataKeyType = DataKeyType;
datasourceType = DatasourceType;
valueSourceDataKeyType = ValueSourceDataKeyType;
valueSourceDataKeyTypes = ValueSourceDataKeyTypes;
valueSourceDataKeyTypeTranslation = ValueSourceDataKeyTypeTranslation;
@Input()
disabled: boolean;
@Input()
aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
valueSourceFormGroup: UntypedFormGroup;
modelValue;
latestKeyFormControl: UntypedFormControl;
entityKeyFormControl: UntypedFormControl;
private propagateChange = (_val: any) => {};
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder,
private cd: ChangeDetectorRef) {
super(store);
}
ngOnInit(): void {
this.valueSourceFormGroup = this.fb.group({
type: [ValueSourceDataKeyType.constant, []],
value: [null, [Validators.required]],
entityAlias: [null, [Validators.required]]
});
this.latestKeyFormControl = this.fb.control(null, [Validators.required]);
this.entityKeyFormControl = this.fb.control(null, [Validators.required]);
this.valueSourceFormGroup.valueChanges.subscribe(
() => this.updateModel()
);
this.latestKeyFormControl.valueChanges.subscribe(
() => this.updateModel()
);
this.entityKeyFormControl.valueChanges.subscribe(
() => this.updateModel()
);
this.valueSourceFormGroup.get('type').valueChanges.subscribe(() => {
this.updateValidators();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.valueSourceFormGroup.disable({emitEvent: false});
this.latestKeyFormControl.disable({emitEvent: false});
this.entityKeyFormControl.disable({emitEvent: false});
} else {
this.valueSourceFormGroup.enable({emitEvent: false});
this.updateValidators();
}
}
writeValue(value): void {
this.modelValue = value;
this.valueSourceFormGroup.patchValue(
{
type: value.type,
value: value.value,
entityAlias: value.entityAlias
}, {emitEvent: false}
);
if (value.type === ValueSourceDataKeyType.latestKey) {
this.latestKeyFormControl.patchValue({
type: value.latestKeyType,
name: value.latestKey
}, {emitEvent: false});
} else if (value.type === ValueSourceDataKeyType.entity) {
this.entityKeyFormControl.patchValue({
type: value.entityKeyType,
name: value.entityKey
}, {emitEvent: false});
}
this.updateValidators();
this.cd.markForCheck();
}
private updateModel() {
const value = this.valueSourceFormGroup.value;
this.modelValue.type = value.type;
this.modelValue.value = value.value;
this.modelValue.entityAlias = value.entityAlias;
if (value.type === ValueSourceDataKeyType.latestKey) {
const latestKey: DataKey = this.latestKeyFormControl.value;
this.modelValue.latestKey = latestKey?.name;
this.modelValue.latestKeyType = (latestKey?.type as any);
} else if (value.type === ValueSourceDataKeyType.entity) {
const entityKey: DataKey = this.entityKeyFormControl.value;
this.modelValue.entityKey = entityKey?.name;
this.modelValue.entityKeyType = (entityKey?.type as any);
}
this.propagateChange(this.modelValue);
}
private updateValidators(): void {
const type: ValueSourceDataKeyType = this.valueSourceFormGroup.get('type').value;
if (type === ValueSourceDataKeyType.constant) {
this.valueSourceFormGroup.get('value').enable({emitEvent: false});
this.valueSourceFormGroup.get('entityAlias').disable({emitEvent: false});
this.latestKeyFormControl.disable({emitEvent: false});
this.entityKeyFormControl.disable({emitEvent: false});
} else if (type === ValueSourceDataKeyType.latestKey) {
this.valueSourceFormGroup.get('value').disable({emitEvent: false});
this.valueSourceFormGroup.get('entityAlias').disable({emitEvent: false});
this.latestKeyFormControl.enable({emitEvent: false});
this.entityKeyFormControl.disable({emitEvent: false});
} else if (type === ValueSourceDataKeyType.entity) {
this.valueSourceFormGroup.get('value').disable({emitEvent: false});
this.valueSourceFormGroup.get('entityAlias').enable({emitEvent: false});
this.latestKeyFormControl.disable({emitEvent: false});
this.entityKeyFormControl.enable({emitEvent: false});
}
}
}

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

@ -152,6 +152,9 @@ import {
import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/common/chart/chart-bar-settings.component'; import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/common/chart/chart-bar-settings.component';
import { AdvancedRangeComponent } from '@home/components/widget/lib/settings/common/advanced-range.component'; import { AdvancedRangeComponent } from '@home/components/widget/lib/settings/common/advanced-range.component';
import { GradientComponent } from '@home/components/widget/lib/settings/common/gradient.component'; import { GradientComponent } from '@home/components/widget/lib/settings/common/gradient.component';
import {
ValueSourceDataKeyComponent
} from '@home/components/widget/lib/settings/common/value-source-data-key.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -170,6 +173,7 @@ import { GradientComponent } from '@home/components/widget/lib/settings/common/g
BackgroundSettingsComponent, BackgroundSettingsComponent,
BackgroundSettingsPanelComponent, BackgroundSettingsPanelComponent,
ValueSourceComponent, ValueSourceComponent,
ValueSourceDataKeyComponent,
LegendConfigComponent, LegendConfigComponent,
WidgetFontComponent, WidgetFontComponent,
CountWidgetSettingsComponent, CountWidgetSettingsComponent,
@ -233,6 +237,7 @@ import { GradientComponent } from '@home/components/widget/lib/settings/common/g
BackgroundSettingsComponent, BackgroundSettingsComponent,
BackgroundSettingsPanelComponent, BackgroundSettingsPanelComponent,
ValueSourceComponent, ValueSourceComponent,
ValueSourceDataKeyComponent,
LegendConfigComponent, LegendConfigComponent,
WidgetFontComponent, WidgetFontComponent,
CountWidgetSettingsComponent, CountWidgetSettingsComponent,

52
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.html

@ -32,38 +32,36 @@
</tb-image-cards-select-option> </tb-image-cards-select-option>
</tb-image-cards-select> </tb-image-cards-select>
<div class="tb-form-row space-between" *ngIf="digitalGaugeWidgetSettingsForm.get('gaugeType').value === digitalGaugeType.donut; else minMaxValue"> <div class="tb-form-row space-between" *ngIf="digitalGaugeWidgetSettingsForm.get('gaugeType').value === digitalGaugeType.donut">
<div class="fixed-title-width">{{ 'widgets.gauge.donut-start-angle' | translate }}</div> <div class="fixed-title-width">{{ 'widgets.gauge.donut-start-angle' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic"> <mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="donutStartAngle" type="number" min="0" max="360" step="1" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput formControlName="donutStartAngle" type="number" min="0" max="360" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field> </mat-form-field>
</div> </div>
<ng-template #minMaxValue> <div class="tb-form-row space-between column-xs">
<div class="tb-form-row space-between column-xs"> <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showMinMax">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showMinMax"> {{ 'widgets.gauge.min-and-max-value' | translate }}
{{ 'widgets.gauge.min-and-max-value' | translate }} </mat-slide-toggle>
</mat-slide-toggle> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div class="tb-small-label" translate>widgets.gauge.min-value-short</div>
<div class="tb-small-label" translate>widgets.gauge.min-value-short</div> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <input matInput formControlName="minValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<input matInput formControlName="minValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}"> </mat-form-field>
</mat-form-field> <div class="tb-small-label" translate>widgets.gauge.max-value-short</div>
<div class="tb-small-label" translate>widgets.gauge.max-value-short</div> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <input matInput formControlName="maxValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<input matInput formControlName="maxValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}"> </mat-form-field>
</mat-form-field> <tb-font-settings formControlName="minMaxFont"
<tb-font-settings formControlName="minMaxFont" disabledLineHeight
disabledLineHeight [previewText]="previewFn">
[previewText]="previewFn"> </tb-font-settings>
</tb-font-settings> <tb-color-input asBoxInput
<tb-color-input asBoxInput colorClearButton
colorClearButton formControlName="minMaxColor">
formControlName="minMaxColor"> </tb-color-input>
</tb-color-input>
</div>
</div> </div>
</ng-template> </div>
<div class="tb-form-row space-between column-xs"> <div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showValue"> <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showValue">
@ -185,6 +183,8 @@
[minValue]="digitalGaugeWidgetSettingsForm.get('minValue').value" [minValue]="digitalGaugeWidgetSettingsForm.get('minValue').value"
[maxValue]="digitalGaugeWidgetSettingsForm.get('maxValue').value" [maxValue]="digitalGaugeWidgetSettingsForm.get('maxValue').value"
[aliasController]="aliasController" [aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.value' | translate }}"> settingsKey="{{'widgets.battery-level.value' | translate }}">
</tb-color-settings> </tb-color-settings>
</div> </div>
@ -225,6 +225,8 @@
let $index = index; last as isLast;" fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}"> let $index = index; last as isLast;" fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-tick-value [formControl]="tickValueControl" <tb-tick-value [formControl]="tickValueControl"
[aliasController]="aliasController" [aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
(removeTickValue)="removeTickValue($index)"> (removeTickValue)="removeTickValue($index)">
</tb-tick-value> </tb-tick-value>
</div> </div>

71
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/digital-gauge-widget-settings.component.ts

@ -14,22 +14,30 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; import { Datasource, WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge'; import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge';
import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { ValueSourceProperty } from '@home/components/widget/lib/settings/common/value-source.component';
import { import {
backwardCompatibilityFixedLevelColors,
backwardCompatibilityTicks,
digitalGaugeLayoutImages, digitalGaugeLayoutImages,
digitalGaugeLayouts, digitalGaugeLayouts,
digitalGaugeLayoutTranslations, digitalGaugeLayoutTranslations,
DigitalGaugeType DigitalGaugeType
} from '@home/components/widget/lib/digital-gauge.models'; } from '@home/components/widget/lib/digital-gauge.models';
import { deepClone, formatValue } from '@core/utils'; import { formatValue } from '@core/utils';
import { ColorSettings, ColorType, constantColor, simpleDateFormat } from '@shared/models/widget-settings.models'; import {
ColorSettings,
ColorType,
constantColor,
simpleDateFormat,
ValueSourceDataKeyType,
ValueSourceWithDataKey
} from '@shared/models/widget-settings.models';
@Component({ @Component({
selector: 'tb-digital-gauge-widget-settings', selector: 'tb-digital-gauge-widget-settings',
@ -49,6 +57,15 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
valuePreviewFn = this._valuePreviewFn.bind(this, true); valuePreviewFn = this._valuePreviewFn.bind(this, true);
previewFn = this._valuePreviewFn.bind(this, false); previewFn = this._valuePreviewFn.bind(this, false);
public get datasource(): Datasource {
const datasources: Datasource[] = this.widgetConfig.config.datasources;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected fb: UntypedFormBuilder) { protected fb: UntypedFormBuilder) {
super(store); super(store);
@ -120,16 +137,14 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
} }
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {
if (!settings.barColor) { if (!settings.barColor) {
settings.barColor = constantColor(null); settings.barColor = constantColor(settings.gaugeColor);
if (settings.fixedLevelColors.length) { if (settings.fixedLevelColors.length) {
settings.barColor.rangeList = { settings.barColor.rangeList = {
advancedMode: settings.useFixedLevelColor, advancedMode: settings.useFixedLevelColor,
range: null, range: null,
rangeAdvanced: deepClone(settings.fixedLevelColors) rangeAdvanced: backwardCompatibilityFixedLevelColors(settings.fixedLevelColors)
}; };
} }
if (settings.levelColors.length) { if (settings.levelColors.length) {
@ -184,7 +199,7 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
showTicks: [settings.showTicks, []], showTicks: [settings.showTicks, []],
tickWidth: [settings.tickWidth, [Validators.min(0)]], tickWidth: [settings.tickWidth, [Validators.min(0)]],
colorTicks: [settings.colorTicks, []], colorTicks: [settings.colorTicks, []],
ticksValue: this.prepareTicksValueFormArray(settings.ticksValue), ticksValue: this.prepareTicksValueFormArray(backwardCompatibilityTicks(settings.ticksValue)),
animation: [settings.animation, []], animation: [settings.animation, []],
animationDuration: [settings.animationDuration, [Validators.min(0)]], animationDuration: [settings.animationDuration, [Validators.min(0)]],
@ -229,11 +244,32 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
const showTicks: boolean = this.digitalGaugeWidgetSettingsForm.get('showTicks').value; const showTicks: boolean = this.digitalGaugeWidgetSettingsForm.get('showTicks').value;
const animation: boolean = this.digitalGaugeWidgetSettingsForm.get('animation').value; const animation: boolean = this.digitalGaugeWidgetSettingsForm.get('animation').value;
if (gaugeType === 'donut') { if (gaugeType === 'donut') {
this.digitalGaugeWidgetSettingsForm.get('donutStartAngle').enable(); this.digitalGaugeWidgetSettingsForm.get('donutStartAngle').enable();
this.digitalGaugeWidgetSettingsForm.get('showMinMax').disable({emitEvent: false});
this.digitalGaugeWidgetSettingsForm.get('minValue').enable({emitEvent: false});
this.digitalGaugeWidgetSettingsForm.get('maxValue').enable({emitEvent: false});
this.digitalGaugeWidgetSettingsForm.get('minMaxFont').disable({emitEvent: false});
this.digitalGaugeWidgetSettingsForm.get('minMaxColor').disable({emitEvent: false});
} else { } else {
this.digitalGaugeWidgetSettingsForm.get('donutStartAngle').disable(); this.digitalGaugeWidgetSettingsForm.get('donutStartAngle').disable();
this.digitalGaugeWidgetSettingsForm.get('showMinMax').enable({emitEvent: false});
if (showMinMax) {
this.digitalGaugeWidgetSettingsForm.get('minValue').enable();
this.digitalGaugeWidgetSettingsForm.get('maxValue').enable();
this.digitalGaugeWidgetSettingsForm.get('minMaxFont').enable();
this.digitalGaugeWidgetSettingsForm.get('minMaxColor').enable();
} else {
this.digitalGaugeWidgetSettingsForm.get('minValue').disable();
this.digitalGaugeWidgetSettingsForm.get('maxValue').disable();
this.digitalGaugeWidgetSettingsForm.get('minMaxFont').disable();
this.digitalGaugeWidgetSettingsForm.get('minMaxColor').disable();
}
} }
if (showTitle) { if (showTitle) {
this.digitalGaugeWidgetSettingsForm.get('title').enable(); this.digitalGaugeWidgetSettingsForm.get('title').enable();
this.digitalGaugeWidgetSettingsForm.get('titleFont').enable(); this.digitalGaugeWidgetSettingsForm.get('titleFont').enable();
@ -267,17 +303,6 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
this.digitalGaugeWidgetSettingsForm.get('valueFont').disable(); this.digitalGaugeWidgetSettingsForm.get('valueFont').disable();
this.digitalGaugeWidgetSettingsForm.get('valueColor').disable(); this.digitalGaugeWidgetSettingsForm.get('valueColor').disable();
} }
if (showMinMax) {
this.digitalGaugeWidgetSettingsForm.get('minValue').enable();
this.digitalGaugeWidgetSettingsForm.get('maxValue').enable();
this.digitalGaugeWidgetSettingsForm.get('minMaxFont').enable();
this.digitalGaugeWidgetSettingsForm.get('minMaxColor').enable();
} else {
this.digitalGaugeWidgetSettingsForm.get('minValue').disable();
this.digitalGaugeWidgetSettingsForm.get('maxValue').disable();
this.digitalGaugeWidgetSettingsForm.get('minMaxFont').disable();
this.digitalGaugeWidgetSettingsForm.get('minMaxColor').disable();
}
if (showTicks) { if (showTicks) {
this.digitalGaugeWidgetSettingsForm.get('tickWidth').enable(); this.digitalGaugeWidgetSettingsForm.get('tickWidth').enable();
this.digitalGaugeWidgetSettingsForm.get('colorTicks').enable(); this.digitalGaugeWidgetSettingsForm.get('colorTicks').enable();
@ -323,7 +348,7 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
settingsForm.setControl('ticksValue', this.prepareTicksValueFormArray(settings.ticksValue), {emitEvent: false}); settingsForm.setControl('ticksValue', this.prepareTicksValueFormArray(settings.ticksValue), {emitEvent: false});
} }
private prepareTicksValueFormArray(ticksValue: ValueSourceProperty[] | undefined): UntypedFormArray { private prepareTicksValueFormArray(ticksValue: ValueSourceWithDataKey[] | undefined): UntypedFormArray {
const ticksValueControls: Array<AbstractControl> = []; const ticksValueControls: Array<AbstractControl> = [];
if (ticksValue) { if (ticksValue) {
ticksValue.forEach((tickValue) => { ticksValue.forEach((tickValue) => {
@ -346,8 +371,8 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
} }
public addTickValue() { public addTickValue() {
const tickValue: ValueSourceProperty = { const tickValue: ValueSourceWithDataKey = {
valueSource: 'predefinedValue' type: ValueSourceDataKeyType.constant
}; };
const tickValuesArray = this.digitalGaugeWidgetSettingsForm.get('ticksValue') as UntypedFormArray; const tickValuesArray = this.digitalGaugeWidgetSettingsForm.get('ticksValue') as UntypedFormArray;
const tickValueControl = this.fb.control(tickValue, []); const tickValueControl = this.fb.control(tickValue, []);

7
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.html

@ -17,7 +17,12 @@
--> -->
<div fxLayout="row" [formGroup]="tickValueFormGroup"> <div fxLayout="row" [formGroup]="tickValueFormGroup">
<tb-value-source fxFlex formControlName="tickValue" [aliasController]="aliasController"></tb-value-source> <tb-value-source-data-key fxFlex
formControlName="tickValue"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource">
</tb-value-source-data-key>
<button *ngIf="!disabled" mat-icon-button <button *ngIf="!disabled" mat-icon-button
class="tb-box-button" class="tb-box-button"
type="button" type="button"

8
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts

@ -22,6 +22,8 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { Datasource } from '@shared/models/widget.models';
@Component({ @Component({
selector: 'tb-tick-value', selector: 'tb-tick-value',
@ -43,6 +45,12 @@ export class TickValueComponent extends PageComponent implements OnInit, Control
@Input() @Input()
aliasController: IAliasController; aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Output() @Output()
removeTickValue = new EventEmitter(); removeTickValue = new EventEmitter();

6
ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.html

@ -52,6 +52,8 @@
rangeAdvancedMode rangeAdvancedMode
gradientAdvancedMode gradientAdvancedMode
[aliasController]="aliasController" [aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.value' | translate }}"> settingsKey="{{'widgets.battery-level.value' | translate }}">
</tb-color-settings> </tb-color-settings>
</div> </div>
@ -64,6 +66,8 @@
minValue="0" minValue="0"
maxValue="100" maxValue="100"
[aliasController]="aliasController" [aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.battery-level-color' | translate }}"> settingsKey="{{'widgets.battery-level.battery-level-color' | translate }}">
</tb-color-settings> </tb-color-settings>
</div> </div>
@ -75,6 +79,8 @@
minValue="0" minValue="0"
maxValue="100" maxValue="100"
[aliasController]="aliasController" [aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.battery-shape-color' | translate }}"> settingsKey="{{'widgets.battery-level.battery-shape-color' | translate }}">
</tb-color-settings> </tb-color-settings>
</div> </div>

11
ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/battery-level-widget-settings.component.ts

@ -15,7 +15,7 @@
/// ///
import { Component, Injector } from '@angular/core'; import { Component, Injector } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; import { Datasource, WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
@ -48,6 +48,15 @@ export class BatteryLevelWidgetSettingsComponent extends WidgetSettingsComponent
return [BatteryLevelLayout.vertical_divided, BatteryLevelLayout.horizontal_divided].includes(layout); return [BatteryLevelLayout.vertical_divided, BatteryLevelLayout.horizontal_divided].includes(layout);
} }
public get datasource(): Datasource {
const datasources: Datasource[] = this.widgetConfig.config.datasources;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private $injector: Injector, private $injector: Injector,
private fb: UntypedFormBuilder) { private fb: UntypedFormBuilder) {

195
ui-ngx/src/app/shared/models/widget-settings.models.ts

@ -14,15 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { import { isDefinedAndNotNull, isNumber, isNumeric, isUndefinedOrNull, mergeDeep, parseFunction } from '@core/utils';
isDefinedAndNotNull,
isNumber,
isNumeric,
isString,
isUndefinedOrNull,
mergeDeep,
parseFunction
} from '@core/utils';
import { import {
DataEntry, DataEntry,
DataKey, DataKey,
@ -30,9 +22,10 @@ import {
DatasourceData, DatasourceData,
DatasourceType, DatasourceType,
TargetDevice, TargetDevice,
TargetDeviceType, widgetType TargetDeviceType,
widgetType
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
import { ChangeDetectorRef, Injector } from '@angular/core'; import { ChangeDetectorRef, EventEmitter, Injector } from '@angular/core';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; import { DateAgoPipe } from '@shared/pipe/date-ago.pipe';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -44,17 +37,10 @@ import { map } from 'rxjs/operators';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { AVG_MONTH, DAY, HOUR, Interval, IntervalMath, MINUTE, SECOND, YEAR } from '@shared/models/time/time.models'; import { AVG_MONTH, DAY, HOUR, Interval, IntervalMath, MINUTE, SECOND, YEAR } from '@shared/models/time/time.models';
import moment from 'moment'; import moment from 'moment';
import {
attributesGaugeType,
ColorLevelSetting,
FixedLevelColors
} from '@home/components/widget/lib/digital-gauge.models';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { DigitalGaugeColorRange } from '@home/components/widget/lib/canvas-digital-gauge';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { ValueSourceProperty } from '@home/components/widget/lib/settings/common/value-source.component';
export type ComponentStyle = {[klass: string]: any}; export type ComponentStyle = {[klass: string]: any};
@ -120,9 +106,35 @@ export interface ColorRange {
color: string; color: string;
} }
export enum ValueSourceDataKeyType {
constant = 'constant',
latestKey = 'latestKey',
entity = 'entity'
}
export const ValueSourceDataKeyTypes = Object.keys(ValueSourceDataKeyType) as ValueSourceDataKeyType[];
export const ValueSourceDataKeyTypeTranslation = new Map<ValueSourceDataKeyType, string>(
[
[ValueSourceDataKeyType.constant, 'widgets.value-source.type-constant'],
[ValueSourceDataKeyType.latestKey, 'widgets.value-source.type-latest-key'],
[ValueSourceDataKeyType.entity, 'widgets.value-source.type-entity']
]
);
export interface ValueSourceWithDataKey {
type: ValueSourceDataKeyType;
value?: number;
latestKeyType?: DataKeyType.attribute | DataKeyType.timeseries;
latestKey?: string;
entityKeyType?: DataKeyType.attribute | DataKeyType.timeseries;
entityAlias?: string;
entityKey?: string;
}
export interface AdvancedColorRange { export interface AdvancedColorRange {
from?: ValueSourceProperty; from?: ValueSourceWithDataKey;
to?: ValueSourceProperty; to?: ValueSourceWithDataKey;
color: string; color: string;
} }
@ -214,7 +226,7 @@ export interface ColorRangeSettings {
} }
export interface AdvancedGradient { export interface AdvancedGradient {
source: ValueSourceProperty; source: ValueSourceWithDataKey;
color: string; color: string;
} }
@ -224,6 +236,11 @@ export interface ColorGradientSettings {
gradientAdvanced?: Array<AdvancedGradient>; gradientAdvanced?: Array<AdvancedGradient>;
} }
export interface DataKeySettings {
color?: string;
index: number;
}
export interface TimewindowStyle { export interface TimewindowStyle {
showIcon: boolean; showIcon: boolean;
icon: string; icon: string;
@ -248,6 +265,21 @@ export const constantColor = (color: string): ColorSettings => ({
colorFunction: defaultColorFunction colorFunction: defaultColorFunction
}); });
export const defaultGradient = (): ColorGradientSettings => ({
advancedMode: false,
gradient: ['rgba(0, 255, 0, 1)', 'rgba(255, 0, 0, 1)'],
gradientAdvanced: [
{
source: {type: ValueSourceDataKeyType.constant},
color: 'rgba(0, 255, 0, 1)'
},
{
source: {type: ValueSourceDataKeyType.constant},
color: 'rgba(255, 0, 0, 1)'
}
]
});
export const defaultColorFunction = 'var temperature = value;\n' + export const defaultColorFunction = 'var temperature = value;\n' +
'if (typeof temperature !== undefined) {\n' + 'if (typeof temperature !== undefined) {\n' +
' var percent = (temperature + 60)/120 * 100;\n' + ' var percent = (temperature + 60)/120 * 100;\n' +
@ -293,7 +325,7 @@ type ValueColorFunction = (value: any) => string;
export abstract class ColorProcessor { export abstract class ColorProcessor {
static fromSettings(color: ColorSettings, ctx?: WidgetContext, cd?: ChangeDetectorRef): ColorProcessor { static fromSettings(color: ColorSettings, ctx?: WidgetContext, cd?: ChangeDetectorRef, maxValue = 100): ColorProcessor {
const settings = color || constantColor(null); const settings = color || constantColor(null);
switch (settings.type) { switch (settings.type) {
case ColorType.constant: case ColorType.constant:
@ -303,7 +335,7 @@ export abstract class ColorProcessor {
case ColorType.function: case ColorType.function:
return new FunctionColorProcessor(settings); return new FunctionColorProcessor(settings);
case ColorType.gradient: case ColorType.gradient:
return new GradientColorProcessor(settings, ctx, cd); return new GradientColorProcessor(settings, ctx, cd, maxValue);
default: default:
return new ConstantColorProcessor(settings); return new ConstantColorProcessor(settings);
} }
@ -311,6 +343,8 @@ export abstract class ColorProcessor {
color: string; color: string;
colorUpdated: EventEmitter<void> = new EventEmitter<void>();
protected constructor(protected settings: ColorSettings) { protected constructor(protected settings: ColorSettings) {
this.color = settings.color; this.color = settings.color;
} }
@ -318,42 +352,70 @@ export abstract class ColorProcessor {
abstract update(value: any): void; abstract update(value: any): void;
abstract destroy(): void; abstract destroy(): void;
generateDatasource(ctx: WidgetContext, datasources: Datasource[], entityAlias: string, static generateDatasource(
attribute: string, settings: any): Datasource[]{ ctx: WidgetContext,
const entityAliasId = ctx.aliasController.getEntityAliasId(entityAlias); datasources: Datasource[],
if (!entityAliasId) { valueSource: ValueSourceWithDataKey,
throw new Error('Not valid entity aliase name ' + entityAlias); settings: DataKeySettings): Datasource[]{
if (valueSource.type === ValueSourceDataKeyType.latestKey) {
if (ctx.datasources.length) {
if (isDefinedAndNotNull(ctx.datasources[0].aliasName)) {
datasources = this.parseDatasource(ctx, valueSource, ctx.datasources[0].aliasName, settings, datasources, true);
} else {
datasources = this.parseDatasource(ctx, valueSource, '', settings, datasources, true);
}
}
} else if (valueSource.type === ValueSourceDataKeyType.entity) {
datasources = this.parseDatasource(ctx, valueSource, valueSource.entityAlias, settings, datasources, false);
} }
return datasources;
}
const datasource = datasources.find((datasourceIteration) => datasourceIteration.entityAliasId === entityAliasId); static parseDatasource(ctx: WidgetContext,
valueSource: ValueSourceWithDataKey,
const dataKey: DataKey = { entityAlias: string, settings: DataKeySettings,
type: DataKeyType.attribute, datasources: Datasource[],
name: attribute, isLatest: boolean) {
label: attribute, let dataKey: DataKey = null;
settings: [settings], const dataKeySettings = [settings];
_hash: Math.random() const entityAliasId = ctx.aliasController.getEntityAliasId(entityAlias);
let datasource = datasources.find(d =>
entityAlias ? (d.entityAliasId === entityAliasId) : (d.deviceId === ctx.datasources[0].deviceId)
);
dataKey = {
type: isLatest ? valueSource.latestKeyType : valueSource.entityKeyType,
name: isLatest ? valueSource.latestKey : valueSource.entityKey,
label: isLatest ? valueSource.latestKey : valueSource.entityKey,
settings: {dataKeySettings}
}; };
if (datasource) { if (datasource) {
const findDataKey = datasource.dataKeys.find((dataKeyIteration) => dataKeyIteration.name === attribute); const findDataKey = datasource.dataKeys.find(dataKeyIteration =>
dataKeyIteration.name === (isLatest ? valueSource.latestKey : valueSource.entityKey));
if (findDataKey) { if (findDataKey) {
findDataKey.settings.push(settings); findDataKey.settings.dataKeySettings.push(settings);
} else { } else {
datasource.dataKeys.push(dataKey); datasource.dataKeys.push(dataKey);
} }
} else { } else {
const datasourceAttribute: Datasource = { if (entityAlias) {
type: DatasourceType.entity, datasource = {
name: entityAlias, type: DatasourceType.entity,
aliasName: entityAlias, name: entityAlias,
entityAliasId, aliasName: entityAlias,
dataKeys: [dataKey] entityAliasId,
}; dataKeys: [ dataKey ]
datasources.push(datasourceAttribute); };
} else {
datasource = {
type: DatasourceType.device,
name: ctx.datasources[0].name,
deviceName: ctx.datasources[0].name,
deviceId: ctx.datasources[0].deviceId,
dataKeys: [ dataKey ]
};
}
datasources.push(datasource);
} }
return datasources; return datasources;
} }
@ -384,13 +446,13 @@ class RangeColorProcessor extends ColorProcessor {
let advancedRangeDatasource: Datasource[] = []; let advancedRangeDatasource: Datasource[] = [];
let index = 0; let index = 0;
function setRangeColor(rangeSetting, color: string) { function setRangeColor(rangeSetting: ValueSourceWithDataKey, color: string) {
if (rangeSetting.valueSource === 'predefinedValue' && isFinite(rangeSetting.value)) { if (rangeSetting.type === ValueSourceDataKeyType.constant && isFinite(rangeSetting.value)) {
index++; index++;
} else if (rangeSetting.entityAlias && rangeSetting.attribute) { } else {
try { try {
advancedRangeDatasource = this.generateDatasource(this.ctx, advancedRangeDatasource, advancedRangeDatasource = this.generateDatasource(this.ctx, advancedRangeDatasource,
rangeSetting.entityAlias, rangeSetting.attribute, {color, index}); rangeSetting, {color, index});
} catch (e) { } catch (e) {
return; return;
} }
@ -413,7 +475,7 @@ class RangeColorProcessor extends ColorProcessor {
}); });
} else { } else {
this.color = this.computeFromRange(this.progress); this.color = this.computeFromRange(this.progress);
this.cd.markForCheck(); this.colorUpdated.emit();
} }
} }
@ -430,7 +492,7 @@ class RangeColorProcessor extends ColorProcessor {
onDataUpdated: (subscription) => { onDataUpdated: (subscription) => {
this.updateAttribute(subscription.data); this.updateAttribute(subscription.data);
this.color = this.computeFromRange(this.progress); this.color = this.computeFromRange(this.progress);
this.cd.markForCheck(); this.colorUpdated.emit();
} }
} }
}; };
@ -443,7 +505,7 @@ class RangeColorProcessor extends ColorProcessor {
if (keyData && keyData.data && keyData.data[0]) { if (keyData && keyData.data && keyData.data[0]) {
const attrValue = keyData.data[0][1]; const attrValue = keyData.data[0][1];
if (isFinite(attrValue)) { if (isFinite(attrValue)) {
for (const setting of keyData.dataKey.settings) { for (const setting of keyData.dataKey.settings.dataKeySettings) {
const index = Math.floor(setting.index/2); const index = Math.floor(setting.index/2);
if (index === setting.index/2) { if (index === setting.index/2) {
this.settings.rangeList.rangeAdvanced[index].from.value = attrValue; this.settings.rangeList.rangeAdvanced[index].from.value = attrValue;
@ -535,12 +597,13 @@ class GradientColorProcessor extends ColorProcessor {
constructor(protected settings: ColorSettings, constructor(protected settings: ColorSettings,
protected ctx: WidgetContext, protected ctx: WidgetContext,
protected cd: ChangeDetectorRef) { protected cd: ChangeDetectorRef,
protected maxValue: number) {
super(settings); super(settings);
} }
update(value: any): void { update(value: any): void {
this.progress = value / 100; this.progress = value / this.maxValue;
if(isDefinedAndNotNull(this.settings.gradient.advancedMode) && this.settings.gradient.advancedMode) { if(isDefinedAndNotNull(this.settings.gradient.advancedMode) && this.settings.gradient.advancedMode) {
this.advancedRangeSubscribe(this.settings.gradient.gradientAdvanced); this.advancedRangeSubscribe(this.settings.gradient.gradientAdvanced);
} else { } else {
@ -563,7 +626,7 @@ class GradientColorProcessor extends ColorProcessor {
if (levelColor !== null) { if (levelColor !== null) {
const tColor = tinycolor(levelColor); const tColor = tinycolor(levelColor);
colorsRange.push({ colorsRange.push({
pct: this.settings.gradient.advancedMode ? levelColors[i].source.value / 100 : inc * i, pct: this.settings.gradient.advancedMode ? levelColors[i].source.value / this.maxValue : inc * i,
color: tColor.toRgb(), color: tColor.toRgb(),
alpha: tColor.getAlpha(), alpha: tColor.getAlpha(),
rgbString: tColor.toRgbString() rgbString: tColor.toRgbString()
@ -599,13 +662,13 @@ class GradientColorProcessor extends ColorProcessor {
let advancedGradientDatasource: Datasource[] = []; let advancedGradientDatasource: Datasource[] = [];
let index = 0; let index = 0;
function setGradientColor(gradientSetting: ValueSourceProperty) { function setGradientColor(gradientSetting: ValueSourceWithDataKey) {
if (gradientSetting.valueSource === 'predefinedValue' && isFinite(gradientSetting.value)) { if (gradientSetting.type === ValueSourceDataKeyType.constant && isFinite(gradientSetting.value)) {
index++; index++;
} else if (gradientSetting.entityAlias && gradientSetting.attribute) { } else {
try { try {
advancedGradientDatasource = this.generateDatasource(this.ctx, advancedGradientDatasource, advancedGradientDatasource = this.generateDatasource(this.ctx, advancedGradientDatasource,
gradientSetting.entityAlias, gradientSetting.attribute, {index}); gradientSetting, {index});
} catch (e) { } catch (e) {
return; return;
} }
@ -623,7 +686,7 @@ class GradientColorProcessor extends ColorProcessor {
}); });
} else { } else {
this.color = this.getProgressColor(this.progress, this.settings.gradient.gradientAdvanced); this.color = this.getProgressColor(this.progress, this.settings.gradient.gradientAdvanced);
this.cd.markForCheck(); this.colorUpdated.emit();
} }
} }
@ -640,7 +703,7 @@ class GradientColorProcessor extends ColorProcessor {
onDataUpdated: (subscription) => { onDataUpdated: (subscription) => {
this.updateAttribute(subscription.data); this.updateAttribute(subscription.data);
this.color = this.getProgressColor(this.progress, this.settings.gradient.gradientAdvanced); this.color = this.getProgressColor(this.progress, this.settings.gradient.gradientAdvanced);
this.cd.markForCheck(); this.colorUpdated.emit();
} }
} }
}; };
@ -653,7 +716,7 @@ class GradientColorProcessor extends ColorProcessor {
if (keyData && keyData.data && keyData.data[0]) { if (keyData && keyData.data && keyData.data[0]) {
const attrValue = keyData.data[0][1]; const attrValue = keyData.data[0][1];
if (isFinite(attrValue)) { if (isFinite(attrValue)) {
for (const setting of keyData.dataKey.settings) { for (const setting of keyData.dataKey.settings.dataKeySettings) {
this.settings.gradient.gradientAdvanced[setting.index].source.value = attrValue; this.settings.gradient.gradientAdvanced[setting.index].source.value = attrValue;
} }
} }

8
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -7019,8 +7019,14 @@
"predefined-value": "Constant", "predefined-value": "Constant",
"entity-attribute": "Entity attribute", "entity-attribute": "Entity attribute",
"value": "Value", "value": "Value",
"value-required": "Value is required.",
"key-required": "Key is required.",
"entity-key-required": "Entity key is required.",
"source-entity-alias": "Source entity alias", "source-entity-alias": "Source entity alias",
"source-entity-attribute": "Source entity attribute" "source-entity-attribute": "Source entity attribute",
"type-constant": "Constant",
"type-latest-key": "Key",
"type-entity": "Entity"
}, },
"rpc-state": { "rpc-state": {
"initial-state": "Initial state", "initial-state": "Initial state",

Loading…
Cancel
Save