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": "{}",
"dataKeySettingsSchema": "{}\n",
"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,
"tags": [

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

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"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,
"tags": [

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

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"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,
"tags": [

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

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"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,
"tags": [

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

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"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
}

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

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"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
}

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

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"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
}

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

@ -15,7 +15,9 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"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
}

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>
<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>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="donutStartAngle" type="number" min="0" max="360" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<ng-template #minMaxValue>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showMinMax">
{{ 'widgets.gauge.min-and-max-value' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.gauge.min-value-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="minValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.gauge.max-value-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="maxValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="minMaxFont"
disabledLineHeight
[previewText]="previewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="minMaxColor">
</tb-color-input>
</div>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showMinMax">
{{ 'widgets.gauge.min-and-max-value' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.gauge.min-value-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="minValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.gauge.max-value-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="maxValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="minMaxFont"
disabledLineHeight
[previewText]="previewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="minMaxColor">
</tb-color-input>
</div>
</ng-template>
</div>
<div class="tb-form-row column-xs">
<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>
<tb-color-settings formControlName="barColor"
rangeAdvancedMode
[minValue]="simpleGaugeWidgetConfigForm.get('minValue').value"
[maxValue]="simpleGaugeWidgetConfigForm.get('maxValue').value"
[aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.value' | translate }}">
</tb-color-settings>
</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 { WidgetConfigComponentData } from '@home/models/widget-component.models';
import {
Datasource,
datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation,
WidgetConfig,
@ -29,18 +30,18 @@ import {
getTimewindowConfig,
setTimewindowConfig
} 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 {
colorLevel,
backwardCompatibilityFixedLevelColors,
defaultDigitalSimpleGaugeOptions,
digitalGaugeLayoutImages,
digitalGaugeLayouts,
digitalGaugeLayoutTranslations,
DigitalGaugeSettings,
DigitalGaugeType, FixedLevelColors
DigitalGaugeType
} 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({
selector: 'tb-digital-simple-gauge-basic-config',
@ -60,6 +61,15 @@ export class DigitalSimpleGaugeBasicConfigComponent extends BasicWidgetConfigCom
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;
digitalGaugeLayouts = digitalGaugeLayouts;
@ -89,12 +99,12 @@ export class DigitalSimpleGaugeBasicConfigComponent extends BasicWidgetConfigCom
const settings: DigitalGaugeSettings = {...defaultDigitalSimpleGaugeOptions, ...(configData.config.settings || {})};
if (!settings.barColor) {
settings.barColor = constantColor(null);
settings.barColor = constantColor(settings.gaugeColor);
if (settings.fixedLevelColors?.length) {
settings.barColor.rangeList = {
advancedMode: settings.useFixedLevelColor,
range: null,
rangeAdvanced: deepClone(settings.fixedLevelColors) as AdvancedColorRange[]
rangeAdvanced: backwardCompatibilityFixedLevelColors(settings.fixedLevelColors)
};
}
if (settings.levelColors?.length) {
@ -205,8 +215,8 @@ export class DigitalSimpleGaugeBasicConfigComponent extends BasicWidgetConfigCom
if (isDonut) {
this.simpleGaugeWidgetConfigForm.get('donutStartAngle').enable();
this.simpleGaugeWidgetConfigForm.get('showMinMax').disable({emitEvent: false});
this.simpleGaugeWidgetConfigForm.get('minValue').disable();
this.simpleGaugeWidgetConfigForm.get('maxValue').disable();
this.simpleGaugeWidgetConfigForm.get('minValue').enable();
this.simpleGaugeWidgetConfigForm.get('maxValue').enable();
this.simpleGaugeWidgetConfigForm.get('minMaxFont').disable();
this.simpleGaugeWidgetConfigForm.get('minMaxColor').disable();
} else {

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

@ -103,6 +103,8 @@
rangeAdvancedMode
gradientAdvancedMode
[aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.value' | translate }}">
</tb-color-settings>
</div>
@ -115,6 +117,8 @@
minValue="0"
maxValue="100"
[aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.battery-level-color' | translate }}">
</tb-color-settings>
</div>
@ -126,6 +130,8 @@
minValue="0"
maxValue="100"
[aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.battery-shape-color' | translate }}">
</tb-color-settings>
</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 {
DataKey,
Datasource,
datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation,
WidgetConfig,
@ -74,6 +75,15 @@ export class BatteryLevelBasicConfigComponent extends BasicWidgetConfigComponent
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>,
protected widgetConfigComponent: WidgetConfigComponent,
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 { AnimationRule } from '@home/components/widget/lib/analogue-gauge.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 {
valueSource: string;
@ -27,8 +34,8 @@ export interface AttributeSourceProperty {
}
export interface FixedLevelColors {
from?: AttributeSourceProperty | number;
to?: AttributeSourceProperty | number;
from?: ValueSourceWithDataKey | number;
to?: ValueSourceWithDataKey | number;
color: string;
}
@ -105,7 +112,7 @@ export interface DigitalGaugeSettings {
hideValue?: boolean;
hideMinMax?: boolean;
showTicks?: boolean;
ticksValue?: AttributeSourceProperty[];
ticksValue?: ValueSourceWithDataKey[];
ticks?: number[];
colorTicks?: string;
tickWidth?: number;
@ -115,3 +122,41 @@ export const defaultDigitalSimpleGaugeOptions: DigitalGaugeSettings = {
gaugeType: DigitalGaugeType.donut,
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 {
attributesGaugeType,
AttributeSourceProperty,
ColorLevelSetting,
DigitalGaugeSettings,
FixedLevelColors
@ -32,7 +31,12 @@ import { DataKey, Datasource, DatasourceData, DatasourceType, widgetType } from
import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
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;
// @dynamic
@ -212,9 +216,9 @@ export class TbCanvasDigitalGauge {
throw new Error('Not valid entity aliase name ' + entityAlias);
}
const datasource = datasources.find((datasourceIteration) => {
return datasourceIteration.entityAliasId === entityAliasId;
});
const datasource = datasources.find((datasourceIteration) =>
datasourceIteration.entityAliasId === entityAliasId
);
const dataKey: DataKey = {
type: DataKeyType.attribute,
@ -225,9 +229,9 @@ export class TbCanvasDigitalGauge {
};
if (datasource) {
const findDataKey = datasource.dataKeys.find((dataKeyIteration) => {
return dataKeyIteration.name === attribute;
});
const findDataKey = datasource.dataKeys.find((dataKeyIteration) =>
dataKeyIteration.name === attribute
);
if (findDataKey) {
findDataKey.settings.push(settings);
@ -278,15 +282,16 @@ export class TbCanvasDigitalGauge {
});
function setLevelColor(levelSetting, color: string) {
if (levelSetting.valueSource === 'predefinedValue' && isFinite(levelSetting.value)) {
if (levelSetting.type === ValueSourceDataKeyType.constant && isFinite(levelSetting.value)) {
predefineLevelColors.push({
value: levelSetting.value,
color
});
} else if (levelSetting.entityAlias && levelSetting.attribute) {
} else if (levelSetting.type === ValueSourceDataKeyType.latestKey || levelSetting.type === ValueSourceDataKeyType.entity) {
try {
levelColorsDatasource = TbCanvasDigitalGauge.generateDatasource(this.ctx, levelColorsDatasource,
levelSetting.entityAlias, levelSetting.attribute, {color, index: predefineLevelColors.length});
levelColorsDatasource = ColorProcessor.generateDatasource(
this.ctx, levelColorsDatasource, levelSetting, {color, index: predefineLevelColors.length}
);
} catch (e) {
return;
}
@ -315,17 +320,18 @@ export class TbCanvasDigitalGauge {
return predefineLevelColors;
}
settingTicksSubscribe(options: AttributeSourceProperty[]): number[] {
settingTicksSubscribe(options: ValueSourceWithDataKey[]): number[] {
let ticksDatasource: Datasource[] = [];
const predefineTicks: number[] = [];
for (const tick of options) {
if (tick.valueSource === 'predefinedValue' && isFinite(tick.value)) {
if (tick.type === ValueSourceDataKeyType.constant && isFinite(tick.value)) {
predefineTicks.push(tick.value);
} else if (tick.entityAlias && tick.attribute) {
} else {
try {
ticksDatasource = TbCanvasDigitalGauge
.generateDatasource(this.ctx, ticksDatasource, tick.entityAlias, tick.attribute, predefineTicks.length);
ticksDatasource = ColorProcessor.generateDatasource(
this.ctx, ticksDatasource, tick, {index: predefineTicks.length}
);
} catch (e) {
continue;
}
@ -364,7 +370,7 @@ export class TbCanvasDigitalGauge {
if (keyData && keyData.data && keyData.data[0]) {
const attrValue = keyData.data[0][1];
if (isFinite(attrValue)) {
for (const setting of keyData.dataKey.settings) {
for (const setting of keyData.dataKey.settings.dataKeySettings) {
switch (typeAttributes) {
case 'levelColors':
this.localSettings.levelColors[setting.index] = {
@ -373,7 +379,7 @@ export class TbCanvasDigitalGauge {
};
break;
case 'ticks':
this.localSettings.ticks[setting] = attrValue;
this.localSettings.ticks[setting.index] = attrValue;
break;
}
}

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

@ -28,7 +28,7 @@ import {
ViewEncapsulation
} from '@angular/core';
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 {
backgroundStyle,
@ -46,9 +46,15 @@ import {
BatteryLevelWidgetSettings
} from '@home/components/widget/lib/indicator/battery-level-widget.models';
import { ResizeObserver } from '@juggle/resize-observer';
import { Observable } from 'rxjs';
import { forkJoin, Observable } from 'rxjs';
import { ImagePipe } from '@shared/pipe/image.pipe';
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 = {
shapeAspectRatio: 64 / 113,
@ -201,6 +207,10 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView
this.overlayStyle = overlayStyle(this.settings.background.overlay);
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() {
@ -218,6 +228,11 @@ export class BatteryLevelWidgetComponent implements OnInit, OnDestroy, AfterView
if (this.batteryBoxResize$) {
this.batteryBoxResize$.disconnect();
}
this.valueColor.colorUpdated.unsubscribe();
this.batteryLevelColor.colorUpdated.unsubscribe();
this.batteryShapeColor.colorUpdated.unsubscribe();
this.batteryLevelColor.destroy();
this.valueColor.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 fxFlex fxLayout="row" fxLayoutAlign="start center">
<div class="range-text" translate>widgets.color.from</div>
<tb-value-source fxFlex
formControlName="from"
entityAliasPlaceholder="widgets.color.entity-alias"
entityAttributePlaceholder="widgets.color.entity-attribute"
[aliasController]="aliasController">
</tb-value-source>
<tb-value-source-data-key fxFlex
formControlName="from"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource">
</tb-value-source-data-key>
</div>
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<div class="range-text" translate>widgets.color.to</div>
<tb-value-source fxFlex
formControlName="to"
entityAliasPlaceholder="widgets.color.entity-alias"
entityAttributePlaceholder="widgets.color.entity-attribute"
[aliasController]="aliasController">
</tb-value-source>
<tb-value-source-data-key fxFlex
formControlName="to"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource">
</tb-value-source-data-key>
</div>
</div>
<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 { IAliasController } from '@core/api/widget-api.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({
selector: 'tb-advanced-range',
@ -48,6 +50,12 @@ export class AdvancedRangeComponent extends PageComponent implements OnInit, Con
@Input()
aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Output()
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>
</ng-template>
<div class="tb-color-ranges" [ngSwitch]="colorRangeListFormGroup.get('advancedMode').value">
<ng-template [ngSwitchCase]="false">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="rangeDrop($event, 'range')">
<div cdkDrag class="tb-form-row no-padding no-border tb-draggable"
[formGroup]="rangeFormGroup"
*ngFor="let rangeFormGroup of rangeListFormGroups; trackBy: trackByRange; let $index = index; last as isLast;"
[ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<div class="range">
<div class="range-container">
<div class="tb-value-range-text" translate>widgets.color.from</div>
<mat-form-field fxFlex appearance="outline" class="center number" subscriptSizing="dynamic">
<input matInput type="number" formControlName="from" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<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">
<input matInput type="number" formControlName="to" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
<div class="tb-color-ranges" [ngSwitch]="colorRangeListFormGroup.get('advancedMode').value">
<ng-template [ngSwitchCase]="false">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="rangeDrop($event, 'range')">
<div cdkDrag class="tb-form-row no-padding no-border tb-draggable"
[formGroup]="rangeFormGroup"
*ngFor="let rangeFormGroup of rangeListFormGroups; trackBy: trackByRange; let $index = index; last as isLast;"
[ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<div class="range">
<div class="range-container">
<div class="tb-value-range-text" translate>widgets.color.from</div>
<mat-form-field fxFlex appearance="outline" class="center number" subscriptSizing="dynamic">
<input matInput type="number" formControlName="from" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<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">
<input matInput type="number" formControlName="to" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-color-input asBoxInput
formControlName="color">
</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>
<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>
</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"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
(removeAdvancedRange)="removeAdvancedRange($index)">
</tb-advanced-range>
<button class="tb-box-button"
mat-icon-button
type="button"
@ -74,36 +99,13 @@
</button>
</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>
<button class="tb-add-color-range"
mat-stroked-button
(click)="addRange()">
<mat-icon>add</mat-icon>
</button>
</div>
<button class="tb-add-color-range"
mat-stroked-button
(click)="addRange()">
<mat-icon>add</mat-icon>
</button>
</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,
ValidationErrors
} 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 { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -34,6 +39,8 @@ import { deepClone, isDefinedAndNotNull, isUndefined } from '@core/utils';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { IAliasController } from '@core/api/widget-api.models';
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 {
const range: AdvancedColorRange = control.value;
@ -71,6 +78,12 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On
@Input()
aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Input()
@coerceBoolean()
advancedMode = false;
@ -184,10 +197,10 @@ export class ColorRangeListComponent implements OnInit, ControlValueAccessor, On
public addAdvancedRange() {
const advancedRange: AdvancedColorRange = {
from: {
valueSource: 'predefinedValue'
type: ValueSourceDataKeyType.constant
},
to: {
valueSource: 'predefinedValue'
type: ValueSourceDataKeyType.constant
},
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"
[advancedMode]="gradientAdvancedMode"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
[popover]="popover">
</tb-gradient>
</div>
@ -49,6 +51,8 @@
panelTitle="{{ 'widgets.color.value-range' | translate }}"
[advancedMode]="rangeAdvancedMode"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
[popover]="popover">
</tb-color-range-list>
</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 { 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 { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
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 { IAliasController } from '@core/api/widget-api.models';
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({
selector: 'tb-color-settings-panel',
@ -51,6 +58,12 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit
@Input()
aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Input()
@coerceBoolean()
rangeAdvancedMode = false;
@ -86,7 +99,7 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit
{
type: [this.colorSettings?.type || ColorType.constant, []],
color: [this.colorSettings?.color, []],
gradient: [this.colorSettings?.gradient, []],
gradient: [this.colorSettings?.gradient || defaultGradient(), []],
rangeList: [this.colorSettings?.rangeList, []],
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 { deepClone, isDefinedAndNotNull } from '@core/utils';
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()
export class ColorSettingsComponentService {
@ -85,6 +87,12 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD
@Input()
aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Input()
@coerceBoolean()
rangeAdvancedMode = false;
@ -155,6 +163,8 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD
colorSettings: this.modelValue,
settingsComponents: this.colorSettingsComponentService.getOtherColorSettingsComponents(this),
aliasController: this.aliasController,
dataKeyCallbacks: this.dataKeyCallbacks,
datasource: this.datasource,
rangeAdvancedMode: this.rangeAdvancedMode,
gradientAdvancedMode: this.gradientAdvancedMode,
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;
.mat-mdc-form-field-infix {
padding-top: 0;
padding-bottom: 6px;
padding-top: 0 !important;
padding-bottom: 6px !important;
.mdc-evolution-chip-set .mdc-evolution-chip {
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 translate>widgets.color.gradient-start</div>
<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>
<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 }}">
@ -98,7 +98,7 @@
<div class="gradient-container gradient-container-end">
<div translate>widgets.color.gradient-end</div>
<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>
<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 }}">
@ -118,13 +118,13 @@
<span fxHide.xs translate>widgets.color.gradient-start</span>
<span fxHide fxShow.xs translate>widgets.color.gradient-start-min</span>
</div>
<div class="start-gradient-container start-gradient-container-advanced">
<tb-value-source fxFlex
formControlName="source"
entityAliasPlaceholder="widgets.color.entity-alias"
entityAttributePlaceholder="widgets.color.entity-attribute"
[aliasController]="aliasController">
</tb-value-source>
<div class="tb-form-row no-border no-padding start-gradient-container start-gradient-container-advanced">
<tb-value-source-data-key fxFlex
formControlName="source"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource">
</tb-value-source-data-key>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
@ -146,12 +146,12 @@
<span fxHide fxShow.xs translate>widgets.color.gradient-color-min</span>
</div>
<div class="list-gradient-container list-gradient-container-advanced">
<tb-value-source fxFlex
formControlName="source"
entityAliasPlaceholder="widgets.color.entity-alias"
entityAttributePlaceholder="widgets.color.entity-attribute"
[aliasController]="aliasController">
</tb-value-source>
<tb-value-source-data-key fxFlex
formControlName="source"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource">
</tb-value-source-data-key>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
@ -190,13 +190,13 @@
<span fxHide.xs translate>widgets.color.gradient-end</span>
<span fxHide fxShow.xs translate>widgets.color.gradient-end-min</span>
</div>
<div class="end-gradient-container end-gradient-container-advanced">
<tb-value-source fxFlex
formControlName="source"
entityAliasPlaceholder="widgets.color.entity-alias"
entityAttributePlaceholder="widgets.color.entity-attribute"
[aliasController]="aliasController">
</tb-value-source>
<div class="tb-form-row no-border no-padding end-gradient-container end-gradient-container-advanced">
<tb-value-source-data-key fxFlex
formControlName="source"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource">
</tb-value-source-data-key>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>

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

@ -24,7 +24,7 @@ import {
UntypedFormBuilder,
UntypedFormGroup
} 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 { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -33,6 +33,8 @@ import { IAliasController } from '@core/api/widget-api.models';
import { DomSanitizer } from '@angular/platform-browser';
import { coerceBoolean } from '@shared/decorators/coercion';
import { isDefinedAndNotNull } from '@core/utils';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { Datasource } from '@shared/models/widget.models';
@Component({
selector: 'tb-gradient',
@ -62,6 +64,12 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro
@Input()
aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Input()
minValue: string;
@ -84,6 +92,7 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro
private sanitizer: DomSanitizer) {}
ngOnInit(): void {
console.log(this.minValue, this.maxValue);
this.gradientFormGroup = this.fb.group({
advancedMode: [false],
gradient: this.fb.group({
@ -93,12 +102,12 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro
}),
gradientAdvanced: this.fb.group({
start: this.fb.group({
source: [],
source: [{type: ValueSourceDataKeyType.constant}],
color: ['rgba(0, 255, 0, 1)']
}),
gradientList: this.fb.array([]),
end: this.fb.group({
source: [],
source: [{type: ValueSourceDataKeyType.constant}],
color: ['rgba(255, 0, 0, 1)']
})
})
@ -147,26 +156,27 @@ export class GradientComponent implements OnInit, ControlValueAccessor, OnDestro
get generatePointers() {
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>` +
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>`;
} 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 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>` +
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>`;
}
}
pointer(value?: number, advanced = false) {
pointer(shift: number, index?: number, value?: number, advanced = false) {
if (advanced) {
return `<div class="pointer" style="left: ${value}%"></div>`;
return `<div class="pointer" style="left: ${shift}%"></div>`;
} else {
return `<div class="pointer" style="left: ${value}%">` +
`<div class="pointer-value"><span class="pointer-value-text">${Math.floor((+this.maxValue*value)/100)}</span></div></div>`;
return `<div class="pointer" style="left: ${shift}%">` +
`<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 { AdvancedRangeComponent } from '@home/components/widget/lib/settings/common/advanced-range.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({
declarations: [
@ -170,6 +173,7 @@ import { GradientComponent } from '@home/components/widget/lib/settings/common/g
BackgroundSettingsComponent,
BackgroundSettingsPanelComponent,
ValueSourceComponent,
ValueSourceDataKeyComponent,
LegendConfigComponent,
WidgetFontComponent,
CountWidgetSettingsComponent,
@ -233,6 +237,7 @@ import { GradientComponent } from '@home/components/widget/lib/settings/common/g
BackgroundSettingsComponent,
BackgroundSettingsPanelComponent,
ValueSourceComponent,
ValueSourceDataKeyComponent,
LegendConfigComponent,
WidgetFontComponent,
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>
<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>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="donutStartAngle" type="number" min="0" max="360" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<ng-template #minMaxValue>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showMinMax">
{{ 'widgets.gauge.min-and-max-value' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.gauge.min-value-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="minValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.gauge.max-value-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="maxValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="minMaxFont"
disabledLineHeight
[previewText]="previewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="minMaxColor">
</tb-color-input>
</div>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showMinMax">
{{ 'widgets.gauge.min-and-max-value' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div class="tb-small-label" translate>widgets.gauge.min-value-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="minValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.gauge.max-value-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="maxValue" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="minMaxFont"
disabledLineHeight
[previewText]="previewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="minMaxColor">
</tb-color-input>
</div>
</ng-template>
</div>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showValue">
@ -185,6 +183,8 @@
[minValue]="digitalGaugeWidgetSettingsForm.get('minValue').value"
[maxValue]="digitalGaugeWidgetSettingsForm.get('maxValue').value"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.value' | translate }}">
</tb-color-settings>
</div>
@ -225,6 +225,8 @@
let $index = index; last as isLast;" fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-tick-value [formControl]="tickValueControl"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
(removeTickValue)="removeTickValue($index)">
</tb-tick-value>
</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.
///
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { Datasource, WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { Component } from '@angular/core';
import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { GaugeType } from '@home/components/widget/lib/canvas-digital-gauge';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { ValueSourceProperty } from '@home/components/widget/lib/settings/common/value-source.component';
import {
backwardCompatibilityFixedLevelColors,
backwardCompatibilityTicks,
digitalGaugeLayoutImages,
digitalGaugeLayouts,
digitalGaugeLayoutTranslations,
DigitalGaugeType
} from '@home/components/widget/lib/digital-gauge.models';
import { deepClone, formatValue } from '@core/utils';
import { ColorSettings, ColorType, constantColor, simpleDateFormat } from '@shared/models/widget-settings.models';
import { formatValue } from '@core/utils';
import {
ColorSettings,
ColorType,
constantColor,
simpleDateFormat,
ValueSourceDataKeyType,
ValueSourceWithDataKey
} from '@shared/models/widget-settings.models';
@Component({
selector: 'tb-digital-gauge-widget-settings',
@ -49,6 +57,15 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
valuePreviewFn = this._valuePreviewFn.bind(this, true);
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>,
protected fb: UntypedFormBuilder) {
super(store);
@ -120,16 +137,14 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
}
protected onSettingsSet(settings: WidgetSettings) {
if (!settings.barColor) {
settings.barColor = constantColor(null);
settings.barColor = constantColor(settings.gaugeColor);
if (settings.fixedLevelColors.length) {
settings.barColor.rangeList = {
advancedMode: settings.useFixedLevelColor,
range: null,
rangeAdvanced: deepClone(settings.fixedLevelColors)
rangeAdvanced: backwardCompatibilityFixedLevelColors(settings.fixedLevelColors)
};
}
if (settings.levelColors.length) {
@ -184,7 +199,7 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
showTicks: [settings.showTicks, []],
tickWidth: [settings.tickWidth, [Validators.min(0)]],
colorTicks: [settings.colorTicks, []],
ticksValue: this.prepareTicksValueFormArray(settings.ticksValue),
ticksValue: this.prepareTicksValueFormArray(backwardCompatibilityTicks(settings.ticksValue)),
animation: [settings.animation, []],
animationDuration: [settings.animationDuration, [Validators.min(0)]],
@ -229,11 +244,32 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
const showTicks: boolean = this.digitalGaugeWidgetSettingsForm.get('showTicks').value;
const animation: boolean = this.digitalGaugeWidgetSettingsForm.get('animation').value;
if (gaugeType === 'donut') {
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 {
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) {
this.digitalGaugeWidgetSettingsForm.get('title').enable();
this.digitalGaugeWidgetSettingsForm.get('titleFont').enable();
@ -267,17 +303,6 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
this.digitalGaugeWidgetSettingsForm.get('valueFont').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) {
this.digitalGaugeWidgetSettingsForm.get('tickWidth').enable();
this.digitalGaugeWidgetSettingsForm.get('colorTicks').enable();
@ -323,7 +348,7 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
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> = [];
if (ticksValue) {
ticksValue.forEach((tickValue) => {
@ -346,8 +371,8 @@ export class DigitalGaugeWidgetSettingsComponent extends WidgetSettingsComponent
}
public addTickValue() {
const tickValue: ValueSourceProperty = {
valueSource: 'predefinedValue'
const tickValue: ValueSourceWithDataKey = {
type: ValueSourceDataKeyType.constant
};
const tickValuesArray = this.digitalGaugeWidgetSettingsForm.get('ticksValue') as UntypedFormArray;
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">
<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
class="tb-box-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 { TranslateService } from '@ngx-translate/core';
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({
selector: 'tb-tick-value',
@ -43,6 +45,12 @@ export class TickValueComponent extends PageComponent implements OnInit, Control
@Input()
aliasController: IAliasController;
@Input()
dataKeyCallbacks: DataKeysCallbacks;
@Input()
datasource: Datasource;
@Output()
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
gradientAdvancedMode
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.value' | translate }}">
</tb-color-settings>
</div>
@ -64,6 +66,8 @@
minValue="0"
maxValue="100"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.battery-level-color' | translate }}">
</tb-color-settings>
</div>
@ -75,6 +79,8 @@
minValue="0"
maxValue="100"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
settingsKey="{{'widgets.battery-level.battery-shape-color' | translate }}">
</tb-color-settings>
</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 { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { Datasource, WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@ -48,6 +48,15 @@ export class BatteryLevelWidgetSettingsComponent extends WidgetSettingsComponent
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>,
private $injector: Injector,
private fb: UntypedFormBuilder) {

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

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

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

@ -7019,8 +7019,14 @@
"predefined-value": "Constant",
"entity-attribute": "Entity attribute",
"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-attribute": "Source entity attribute"
"source-entity-attribute": "Source entity attribute",
"type-constant": "Constant",
"type-latest-key": "Key",
"type-entity": "Entity"
},
"rpc-state": {
"initial-state": "Initial state",

Loading…
Cancel
Save