Browse Source

Merge branch 'rc' of github.com:thingsboard/thingsboard into rc

pull/13063/head
Igor Kulikov 2 years ago
parent
commit
4be2f3b72d
  1. 2
      application/src/main/data/json/system/scada_symbols/wind-turbine-cluster-hp.svg
  2. 4
      application/src/main/data/json/system/widget_types/bar_chart.json
  3. 4
      application/src/main/data/json/system/widget_types/bar_chart_with_labels.json
  4. 4
      application/src/main/data/json/system/widget_types/bars.json
  5. 4
      application/src/main/data/json/system/widget_types/doughnut.json
  6. 4
      application/src/main/data/json/system/widget_types/horizontal_doughnut.json
  7. 4
      application/src/main/data/json/system/widget_types/image_map.json
  8. 4
      application/src/main/data/json/system/widget_types/line_chart.json
  9. 4
      application/src/main/data/json/system/widget_types/map.json
  10. 4
      application/src/main/data/json/system/widget_types/pie.json
  11. 4
      application/src/main/data/json/system/widget_types/point_chart.json
  12. 4
      application/src/main/data/json/system/widget_types/polar_area.json
  13. 4
      application/src/main/data/json/system/widget_types/radar.json
  14. 4
      application/src/main/data/json/system/widget_types/range_chart.json
  15. 4
      application/src/main/data/json/system/widget_types/route_map.json
  16. 4
      application/src/main/data/json/system/widget_types/state_chart.json
  17. 4
      application/src/main/data/json/system/widget_types/time_series_chart.json
  18. 4
      application/src/main/data/json/system/widget_types/trip_map.json
  19. 2
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java
  20. 2
      ui-ngx/src/app/core/api/widget-api.models.ts
  21. 2
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts
  22. 6
      ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html
  23. 91
      ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts
  24. 1
      ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.html
  25. 25
      ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.ts
  26. 1
      ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings-button.component.ts
  27. 2
      ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings-panel.component.html
  28. 1
      ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.html
  29. 1
      ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.ts
  30. 3
      ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts
  31. 6
      ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts
  32. 1
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/widget-actions-panel.component.ts
  33. 9
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html
  34. 7
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss
  35. 7
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts
  36. 1
      ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart-widget.component.html
  37. 9
      ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.html
  38. 7
      ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss
  39. 7
      ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.ts
  40. 9
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.html
  41. 6
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.scss
  42. 7
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts
  43. 11
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html
  44. 6
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss
  45. 3
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts
  46. 11
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.html
  47. 6
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.scss
  48. 4
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.ts
  49. 19
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts
  50. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component.html
  51. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component.html
  52. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html
  53. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.html
  54. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.scss
  55. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html
  56. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-clustering-settings.component.html
  57. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings-panel.component.scss
  58. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/trip-timeline-settings.component.html
  59. 3
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  60. 1
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  61. 63
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.html
  62. 7
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts
  63. 6
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  64. 1
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  65. 1
      ui-ngx/src/app/shared/models/calculated-field.models.ts
  66. 1
      ui-ngx/src/app/shared/models/widget.models.ts
  67. 6
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
application/src/main/data/json/system/scada_symbols/wind-turbine-cluster-hp.svg

@ -349,7 +349,7 @@
<path d="m410.21 378.76c0.18845-0.0291 0.36793-0.0562 0.53942-0.0832-0.10768 0.13636-0.22135 0.27974-0.341 0.42913-1.2354 1.548-3.043 3.7849-5.3283 6.5371-4.5726 5.5054-11.055 13.07-18.696 21.308-15.303 16.496-35.181 35.608-53.635 46.322-18.456 10.715-44.854 18.469-66.713 23.547-10.915 2.5356-20.671 4.3985-27.699 5.6267-3.5127 0.61461-6.3434 1.0708-8.2946 1.3726-0.18745 0.0291-0.36793 0.0561-0.53941 0.0832 0.10868-0.13635 0.22234-0.27973 0.34199-0.42912 1.2354-1.548 3.0431-3.7849 5.3284-6.5371 4.5716-5.5054 11.055-13.07 18.696-21.307 15.303-16.497 35.18-35.608 53.635-46.323 18.456-10.715 44.854-18.469 66.712-23.547 10.915-2.5356 20.671-4.3985 27.699-5.6267 3.5137-0.6146 6.3444-1.0708 8.2946-1.3726z"/> <path d="m410.21 378.76c0.18845-0.0291 0.36793-0.0562 0.53942-0.0832-0.10768 0.13636-0.22135 0.27974-0.341 0.42913-1.2354 1.548-3.043 3.7849-5.3283 6.5371-4.5726 5.5054-11.055 13.07-18.696 21.308-15.303 16.496-35.181 35.608-53.635 46.322-18.456 10.715-44.854 18.469-66.713 23.547-10.915 2.5356-20.671 4.3985-27.699 5.6267-3.5127 0.61461-6.3434 1.0708-8.2946 1.3726-0.18745 0.0291-0.36793 0.0561-0.53941 0.0832 0.10868-0.13635 0.22234-0.27973 0.34199-0.42912 1.2354-1.548 3.0431-3.7849 5.3284-6.5371 4.5716-5.5054 11.055-13.07 18.696-21.307 15.303-16.497 35.18-35.608 53.635-46.323 18.456-10.715 44.854-18.469 66.712-23.547 10.915-2.5356 20.671-4.3985 27.699-5.6267 3.5137-0.6146 6.3444-1.0708 8.2946-1.3726z"/>
<path d="m183.22 378.76c0.18844-0.0291 0.36792-0.0561 0.53942-0.0832-0.10768 0.13634-0.22135 0.27971-0.34101 0.4291-1.2354 1.548-3.0431 3.7849-5.3285 6.537-4.5727 5.5054-11.055 13.07-18.696 21.308-15.303 16.496-35.181 35.608-53.637 46.322-18.456 10.715-44.855 18.469-66.714 23.547-10.915 2.5356-20.672 4.3985-27.699 5.6267-3.5134 0.6146-6.3438 1.0708-8.2948 1.3726-0.18807 0.0291-0.36798 0.0561-0.53953 0.0832 0.10838-0.13634 0.22218-0.27971 0.34129-0.42911 1.2356-1.548 3.0432-3.7849 5.329-6.537 4.5719-5.5054 11.055-13.07 18.696-21.306 15.303-16.497 35.181-35.608 53.637-46.323 18.456-10.715 44.855-18.469 66.713-23.547 10.915-2.5356 20.672-4.3985 27.699-5.6267 3.5138-0.6146 6.3445-1.0708 8.2948-1.3726z"/> <path d="m183.22 378.76c0.18844-0.0291 0.36792-0.0561 0.53942-0.0832-0.10768 0.13634-0.22135 0.27971-0.34101 0.4291-1.2354 1.548-3.0431 3.7849-5.3285 6.537-4.5727 5.5054-11.055 13.07-18.696 21.308-15.303 16.496-35.181 35.608-53.637 46.322-18.456 10.715-44.855 18.469-66.714 23.547-10.915 2.5356-20.672 4.3985-27.699 5.6267-3.5134 0.6146-6.3438 1.0708-8.2948 1.3726-0.18807 0.0291-0.36798 0.0561-0.53953 0.0832 0.10838-0.13634 0.22218-0.27971 0.34129-0.42911 1.2356-1.548 3.0432-3.7849 5.329-6.537 4.5719-5.5054 11.055-13.07 18.696-21.306 15.303-16.497 35.181-35.608 53.637-46.323 18.456-10.715 44.855-18.469 66.713-23.547 10.915-2.5356 20.672-4.3985 27.699-5.6267 3.5138-0.6146 6.3445-1.0708 8.2948-1.3726z"/>
<circle cx="414" cy="378" r="27"/> <circle cx="414" cy="378" r="27"/>
<circle cx="187" cy="378"/> <circle cx="187" cy="378" r="27"/>
<path d="m299.84 3.9752c0.178-0.45587 0.34-0.87084 0.486-1.2433 0.147 0.37248 0.309 0.78745 0.486 1.2433 0.9 2.3145 2.187 5.6829 3.732 9.8942 3.089 8.4232 7.207 20.216 11.326 33.692 8.245 26.98 16.456 60.603 16.456 87.437s-8.211 60.457-16.456 87.437c-4.119 13.476-8.237 25.269-11.326 33.692-1.545 4.2111-2.832 7.5794-3.732 9.8939-0.177 0.4559-0.339 0.87081-0.486 1.2437-0.146-0.37292-0.308-0.78783-0.486-1.2437-0.9-2.3145-2.187-5.6828-3.731-9.8939-3.089-8.4232-7.208-20.216-11.326-33.692-8.246-26.98-16.457-60.603-16.457-87.437s8.211-60.457 16.457-87.437c4.118-13.476 8.237-25.269 11.326-33.692 1.544-4.2114 2.831-7.5797 3.731-9.8942z"/> <path d="m299.84 3.9752c0.178-0.45587 0.34-0.87084 0.486-1.2433 0.147 0.37248 0.309 0.78745 0.486 1.2433 0.9 2.3145 2.187 5.6829 3.732 9.8942 3.089 8.4232 7.207 20.216 11.326 33.692 8.245 26.98 16.456 60.603 16.456 87.437s-8.211 60.457-16.456 87.437c-4.119 13.476-8.237 25.269-11.326 33.692-1.545 4.2111-2.832 7.5794-3.732 9.8939-0.177 0.4559-0.339 0.87081-0.486 1.2437-0.146-0.37292-0.308-0.78783-0.486-1.2437-0.9-2.3145-2.187-5.6828-3.731-9.8939-3.089-8.4232-7.208-20.216-11.326-33.692-8.246-26.98-16.457-60.603-16.457-87.437s8.211-60.457 16.457-87.437c4.118-13.476 8.237-25.269 11.326-33.692 1.544-4.2114 2.831-7.5797 3.731-9.8942z"/>
<path d="m261.38 799 25.902-528h26.096l25.902 528z"/> <path d="m261.38 799 25.902-528h26.096l25.902 528z"/>
<path d="m303.5 272.39c-0.30616-0.38092-0.58532-0.72884-0.83445-1.0418 0.39622 0.059 0.83645 0.12598 1.3207 0.20096 2.4564 0.37792 6.0193 0.94779 10.442 1.7156 8.8458 1.5376 21.127 3.8671 34.866 7.0394 27.508 6.3506 60.756 16.052 84.014 29.469 23.257 13.418 48.289 37.339 67.548 57.967 9.6182 10.304 17.779 19.767 23.534 26.653 2.8766 3.4422 5.1528 6.2406 6.7077 8.1772 0.30717 0.38191 0.58533 0.72984 0.83445 1.0428-0.39521-0.06-0.83645-0.12696-1.3197-0.20095-2.4563-0.37791-6.0203-0.94779-10.443-1.7166-8.8448-1.5367-21.127-3.8671-34.866-7.0384-27.508-6.3516-60.756-16.053-84.013-29.47-23.258-13.417-48.29-37.338-67.548-57.967-9.6192-10.304-17.78-19.766-23.535-26.652-2.8766-3.4432-5.1528-6.2406-6.7077-8.1782z"/> <path d="m303.5 272.39c-0.30616-0.38092-0.58532-0.72884-0.83445-1.0418 0.39622 0.059 0.83645 0.12598 1.3207 0.20096 2.4564 0.37792 6.0193 0.94779 10.442 1.7156 8.8458 1.5376 21.127 3.8671 34.866 7.0394 27.508 6.3506 60.756 16.052 84.014 29.469 23.257 13.418 48.289 37.339 67.548 57.967 9.6182 10.304 17.779 19.767 23.534 26.653 2.8766 3.4422 5.1528 6.2406 6.7077 8.1772 0.30717 0.38191 0.58533 0.72984 0.83445 1.0428-0.39521-0.06-0.83645-0.12696-1.3197-0.20095-2.4563-0.37791-6.0203-0.94779-10.443-1.7166-8.8448-1.5367-21.127-3.8671-34.866-7.0384-27.508-6.3516-60.756-16.053-84.013-29.47-23.258-13.417-48.29-37.338-67.548-57.967-9.6192-10.304-17.78-19.766-23.535-26.652-2.8766-3.4432-5.1528-6.2406-6.7077-8.1782z"/>

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

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

@ -9,9 +9,9 @@
"sizeX": 8, "sizeX": 8,
"sizeY": 5, "sizeY": 5,
"resources": [], "resources": [],
"templateHtml": "<tb-time-series-chart-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-time-series-chart-widget>", "templateHtml": "<tb-time-series-chart-widget \n [ctx]=\"ctx\">\n</tb-time-series-chart-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n chartType: 'bar',\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings('bar'),\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n chartType: 'bar',\n previewWidth: '80%',\n embedTitlePanel: true,\n embedActionsPanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings('bar'),\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n",
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}", "dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}", "latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 8, "sizeX": 8,
"sizeY": 5, "sizeY": 5,
"resources": [], "resources": [],
"templateHtml": "<tb-bar-chart-with-labels-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-bar-chart-with-labels-widget>", "templateHtml": "<tb-bar-chart-with-labels-widget \n [ctx]=\"ctx\">\n</tb-bar-chart-with-labels-widget>",
"templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.barChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.barChartWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: false,\n defaultDataKeysFunction: function() {\n return [{ name: 'humidity', label: 'Humidity', type: 'timeseries' }];\n }\n };\n}\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.barChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.barChartWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '80%',\n embedTitlePanel: true,\n embedActionsPanel: true,\n hasAdditionalLatestDataKeys: false,\n defaultDataKeysFunction: function() {\n return [{ name: 'humidity', label: 'Humidity', type: 'timeseries' }];\n }\n };\n}\n",
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}", "dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}", "latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 5, "sizeX": 5,
"sizeY": 4, "sizeY": 4,
"resources": [], "resources": [],
"templateHtml": "<tb-bar-chart-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-bar-chart-widget>", "templateHtml": "<tb-bar-chart-widget \n [ctx]=\"ctx\">\n</tb-bar-chart-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.barChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.barChartWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '380px',\n embedTitlePanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar', type: 'timeseries', color: '#FF4D5A' },\n { name: 'hydroelectricPower', label: 'Hydroelectric', type: 'timeseries', color: '#FFDE30' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.barChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.barChartWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '380px',\n embedTitlePanel: true,\n embedActionsPanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar', type: 'timeseries', color: '#FF4D5A' },\n { name: 'hydroelectricPower', label: 'Hydroelectric', type: 'timeseries', color: '#FFDE30' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n\n",
"settingsSchema": "", "settingsSchema": "",
"dataKeySettingsSchema": "", "dataKeySettingsSchema": "",
"settingsDirective": "tb-bar-chart-widget-settings", "settingsDirective": "tb-bar-chart-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 4, "sizeX": 4,
"sizeY": 3, "sizeY": 3,
"resources": [], "resources": [],
"templateHtml": "<tb-doughnut-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-doughnut-widget>", "templateHtml": "<tb-doughnut-widget \n [ctx]=\"ctx\">\n</tb-doughnut-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.doughnutWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.doughnutWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '380px',\n previewHeight: '300px',\n embedTitlePanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind power', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar power', type: 'timeseries', color: '#FF4D5A' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.doughnutWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.doughnutWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '380px',\n previewHeight: '300px',\n embedTitlePanel: true,\n embedActionsPanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind power', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar power', type: 'timeseries', color: '#FF4D5A' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n",
"settingsSchema": "", "settingsSchema": "",
"dataKeySettingsSchema": "", "dataKeySettingsSchema": "",
"settingsDirective": "tb-doughnut-widget-settings", "settingsDirective": "tb-doughnut-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 4, "sizeX": 4,
"sizeY": 2.5, "sizeY": 2.5,
"resources": [], "resources": [],
"templateHtml": "<tb-doughnut-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-doughnut-widget>", "templateHtml": "<tb-doughnut-widget \n [ctx]=\"ctx\">\n</tb-doughnut-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.doughnutWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.doughnutWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '380px',\n previewHeight: '220px',\n embedTitlePanel: true,\n horizontal: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind power', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar power', type: 'timeseries', color: '#FF4D5A' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.doughnutWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.doughnutWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '380px',\n previewHeight: '220px',\n embedTitlePanel: true,\n embedActionsPanel: true,\n horizontal: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind power', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar power', type: 'timeseries', color: '#FF4D5A' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n",
"settingsSchema": "", "settingsSchema": "",
"dataKeySettingsSchema": "", "dataKeySettingsSchema": "",
"settingsDirective": "tb-doughnut-widget-settings", "settingsDirective": "tb-doughnut-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 8.5, "sizeX": 8.5,
"sizeY": 6, "sizeY": 6,
"resources": [], "resources": [],
"templateHtml": "<tb-map-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-map-widget>", "templateHtml": "<tb-map-widget \n [ctx]=\"ctx\">\n</tb-map-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n embedActionsPanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}\n",
"settingsForm": [], "settingsForm": [],
"dataKeySettingsForm": [], "dataKeySettingsForm": [],
"settingsDirective": "tb-map-widget-settings", "settingsDirective": "tb-map-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 8, "sizeX": 8,
"sizeY": 5, "sizeY": 5,
"resources": [], "resources": [],
"templateHtml": "<tb-time-series-chart-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-time-series-chart-widget>", "templateHtml": "<tb-time-series-chart-widget \n [ctx]=\"ctx\">\n</tb-time-series-chart-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n chartType: 'line',\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings('line'),\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n chartType: 'line',\n previewWidth: '80%',\n embedTitlePanel: true,\n embedActionsPanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings('line'),\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n",
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}", "dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}", "latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 8.5, "sizeX": 8.5,
"sizeY": 6, "sizeY": 6,
"resources": [], "resources": [],
"templateHtml": "<tb-map-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-map-widget>", "templateHtml": "<tb-map-widget \n [ctx]=\"ctx\">\n</tb-map-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n embedActionsPanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}\n",
"settingsForm": [], "settingsForm": [],
"dataKeySettingsForm": [], "dataKeySettingsForm": [],
"settingsDirective": "tb-map-widget-settings", "settingsDirective": "tb-map-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 5, "sizeX": 5,
"sizeY": 4, "sizeY": 4,
"resources": [], "resources": [],
"templateHtml": "<tb-pie-chart-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-pie-chart-widget>", "templateHtml": "<tb-pie-chart-widget \n [ctx]=\"ctx\">\n</tb-pie-chart-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.pieChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.pieChartWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '380px',\n embedTitlePanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar', type: 'timeseries', color: '#FF4D5A' },\n { name: 'hydroelectricPower', label: 'Hydroelectric', type: 'timeseries', color: '#FFDE30' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n\nself.actionSources = function() {\n return {\n 'sliceClick': {\n name: 'widget-action.pie-slice-click',\n multiple: false\n }\n };\n}\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.pieChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.pieChartWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '380px',\n embedTitlePanel: true,\n embedActionsPanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar', type: 'timeseries', color: '#FF4D5A' },\n { name: 'hydroelectricPower', label: 'Hydroelectric', type: 'timeseries', color: '#FFDE30' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n\nself.actionSources = function() {\n return {\n 'sliceClick': {\n name: 'widget-action.pie-slice-click',\n multiple: false\n }\n };\n}\n",
"settingsSchema": "", "settingsSchema": "",
"dataKeySettingsSchema": "", "dataKeySettingsSchema": "",
"settingsDirective": "tb-pie-chart-widget-settings", "settingsDirective": "tb-pie-chart-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 8, "sizeX": 8,
"sizeY": 5, "sizeY": 5,
"resources": [], "resources": [],
"templateHtml": "<tb-time-series-chart-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-time-series-chart-widget>", "templateHtml": "<tb-time-series-chart-widget \n [ctx]=\"ctx\">\n</tb-time-series-chart-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n chartType: 'point',\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings('point'),\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n chartType: 'point',\n previewWidth: '80%',\n embedTitlePanel: true,\n embedActionsPanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings('point'),\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n",
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}", "dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}", "latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 5, "sizeX": 5,
"sizeY": 4, "sizeY": 4,
"resources": [], "resources": [],
"templateHtml": "<tb-polar-area-chart-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-polar-area-chart-widget>", "templateHtml": "<tb-polar-area-chart-widget \n [ctx]=\"ctx\">\n</tb-polar-area-chart-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.polarAreaChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.polarAreaChartWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '380px',\n embedTitlePanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar', type: 'timeseries', color: '#FF4D5A' },\n { name: 'hydroelectricPower', label: 'Hydroelectric', type: 'timeseries', color: '#FFDE30' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.polarAreaChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.polarAreaChartWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '380px',\n embedTitlePanel: true,\n embedActionsPanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar', type: 'timeseries', color: '#FF4D5A' },\n { name: 'hydroelectricPower', label: 'Hydroelectric', type: 'timeseries', color: '#FFDE30' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n\n",
"settingsSchema": "", "settingsSchema": "",
"dataKeySettingsSchema": "", "dataKeySettingsSchema": "",
"settingsDirective": "tb-polar-area-chart-widget-settings", "settingsDirective": "tb-polar-area-chart-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 5, "sizeX": 5,
"sizeY": 4, "sizeY": 4,
"resources": [], "resources": [],
"templateHtml": "<tb-radar-chart-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-radar-chart-widget>", "templateHtml": "<tb-radar-chart-widget \n [ctx]=\"ctx\">\n</tb-radar-chart-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.radarChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.radarChartWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '380px',\n embedTitlePanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar', type: 'timeseries', color: '#FF4D5A' },\n { name: 'hydroelectricPower', label: 'Hydroelectric', type: 'timeseries', color: '#FFDE30' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.radarChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.radarChartWidget.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n previewWidth: '500px',\n previewHeight: '380px',\n embedTitlePanel: true,\n embedActionsPanel: true,\n defaultDataKeysFunction: function() {\n return [{ name: 'windPower', label: 'Wind', type: 'timeseries', color: '#08872B' },\n { name: 'solarPower', label: 'Solar', type: 'timeseries', color: '#FF4D5A' },\n { name: 'hydroelectricPower', label: 'Hydroelectric', type: 'timeseries', color: '#FFDE30' }];\n }\n };\n};\n\nself.onDestroy = function() {\n};\n\n",
"settingsSchema": "", "settingsSchema": "",
"dataKeySettingsSchema": "", "dataKeySettingsSchema": "",
"settingsDirective": "tb-radar-chart-widget-settings", "settingsDirective": "tb-radar-chart-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 8, "sizeX": 8,
"sizeY": 5, "sizeY": 5,
"resources": [], "resources": [],
"templateHtml": "<tb-range-chart-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-range-chart-widget>", "templateHtml": "<tb-range-chart-widget \n [ctx]=\"ctx\">\n</tb-range-chart-widget>",
"templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.rangeChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.rangeChartWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: false,\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries' }];\n }\n };\n}\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.rangeChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.rangeChartWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n embedActionsPanel: true,\n hasAdditionalLatestDataKeys: false,\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries' }];\n }\n };\n}\n",
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}", "dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}", "latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 8.5, "sizeX": 8.5,
"sizeY": 6, "sizeY": 6,
"resources": [], "resources": [],
"templateHtml": "<tb-map-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-map-widget>", "templateHtml": "<tb-map-widget \n [ctx]=\"ctx\">\n</tb-map-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n trip: true,\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n trip: true,\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n embedActionsPanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}",
"settingsForm": [], "settingsForm": [],
"dataKeySettingsForm": [], "dataKeySettingsForm": [],
"latestDataKeySettingsForm": [], "latestDataKeySettingsForm": [],

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

@ -9,9 +9,9 @@
"sizeX": 8, "sizeX": 8,
"sizeY": 5, "sizeY": 5,
"resources": [], "resources": [],
"templateHtml": "<tb-time-series-chart-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-time-series-chart-widget>", "templateHtml": "<tb-time-series-chart-widget \n [ctx]=\"ctx\">\n</tb-time-series-chart-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true,\n chartType: 'state',\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings('state'),\n defaultDataKeysFunction: function() {\n return [{ name: 'state', label: 'State', type: 'timeseries', units: '', decimals: 0 }];\n }\n };\n}\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true,\n chartType: 'state',\n previewWidth: '80%',\n embedTitlePanel: true,\n embedActionsPanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings('state'),\n defaultDataKeysFunction: function() {\n return [{ name: 'state', label: 'State', type: 'timeseries', units: '', decimals: 0 }];\n }\n };\n}\n",
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}", "dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}", "latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 8, "sizeX": 8,
"sizeY": 5, "sizeY": 5,
"resources": [], "resources": [],
"templateHtml": "<tb-time-series-chart-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-time-series-chart-widget>", "templateHtml": "<tb-time-series-chart-widget \n [ctx]=\"ctx\">\n</tb-time-series-chart-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '80%',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings(),\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.timeSeriesChartWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeSeriesChartWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '80%',\n embedTitlePanel: true,\n embedActionsPanel: true,\n hasAdditionalLatestDataKeys: true,\n dataKeySettingsFunction: TbTimeSeriesChart.dataKeySettings(),\n defaultDataKeysFunction: function() {\n return [{ name: 'temperature', label: 'Temperature', type: 'timeseries', units: '°C', decimals: 0 }];\n }\n };\n}\n",
"settingsSchema": "{}", "settingsSchema": "{}",
"dataKeySettingsSchema": "{}", "dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}", "latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 8.5, "sizeX": 8.5,
"sizeY": 6, "sizeY": 6,
"resources": [], "resources": [],
"templateHtml": "<tb-map-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-map-widget>", "templateHtml": "<tb-map-widget \n [ctx]=\"ctx\">\n</tb-map-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n trip: true,\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n trip: true,\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n embedActionsPanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}",
"settingsForm": [], "settingsForm": [],
"dataKeySettingsForm": [], "dataKeySettingsForm": [],
"latestDataKeySettingsForm": [], "latestDataKeySettingsForm": [],

2
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java

@ -422,7 +422,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
private Map<String, ArgumentEntry> mapToArgumentsWithFetchedValue(CalculatedFieldCtx ctx, List<String> removedTelemetryKeys) { private Map<String, ArgumentEntry> mapToArgumentsWithFetchedValue(CalculatedFieldCtx ctx, List<String> removedTelemetryKeys) {
Map<String, Argument> deletedArguments = ctx.getArguments().entrySet().stream() Map<String, Argument> deletedArguments = ctx.getArguments().entrySet().stream()
.filter(entry -> removedTelemetryKeys.contains(entry.getKey())) .filter(entry -> removedTelemetryKeys.contains(entry.getValue().getRefEntityKey().getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Map<String, ArgumentEntry> fetchedArgs = cfService.fetchArgsFromDb(tenantId, entityId, deletedArguments); Map<String, ArgumentEntry> fetchedArgs = cfService.fetchArgsFromDb(tenantId, entityId, deletedArguments);

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

@ -91,7 +91,7 @@ export interface IWidgetUtils {
} }
export interface PlaceMapItemActionData { export interface PlaceMapItemActionData {
action: WidgetAction; action: WidgetAction | WidgetActionDescriptor;
additionalParams?: any; additionalParams?: any;
afterPlaceItemCallback: ($event: Event, descriptor: WidgetAction, entityId?: EntityId, entityName?: string, afterPlaceItemCallback: ($event: Event, descriptor: WidgetAction, entityId?: EntityId, entityName?: string,
additionalParams?: any, entityLabel?: string) => void; additionalParams?: any, entityLabel?: string) => void;

2
ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts

@ -160,7 +160,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
private getExpressionLabel(entity: CalculatedField): string { private getExpressionLabel(entity: CalculatedField): string {
if (entity.type === CalculatedFieldType.SCRIPT) { if (entity.type === CalculatedFieldType.SCRIPT) {
return 'function calculate(' + Object.keys(entity.configuration.arguments).join(', ') + ')'; return 'function calculate(ctx, ' + Object.keys(entity.configuration.arguments).join(', ') + ')';
} else { } else {
return entity.configuration.expression; return entity.configuration.expression;
} }

6
ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html

@ -87,17 +87,17 @@
</ng-container> </ng-container>
<ng-container matColumnDef="actions" stickyEnd> <ng-container matColumnDef="actions" stickyEnd>
<mat-header-cell *matHeaderCellDef class="w-20 min-w-20"/> <mat-header-cell *matHeaderCellDef class="w-20 min-w-20"/>
<mat-cell *matCellDef="let argument; let $index = index"> <mat-cell *matCellDef="let argument;">
<div class="tb-form-table-row-cell-buttons flex w-20 min-w-20"> <div class="tb-form-table-row-cell-buttons flex w-20 min-w-20">
<button type="button" <button type="button"
mat-icon-button mat-icon-button
#button #button
(click)="manageArgument($event, button, argument, $index)" (click)="manageArgument($event, button, argument)"
[matTooltip]="'action.edit' | translate" [matTooltip]="'action.edit' | translate"
matTooltipPosition="above"> matTooltipPosition="above">
<mat-icon <mat-icon
[matBadgeHidden]="!(argument.refEntityKey.type === ArgumentType.Rolling [matBadgeHidden]="!(argument.refEntityKey.type === ArgumentType.Rolling
&& calculatedFieldType === CalculatedFieldType.SIMPLE) && !entityNameErrorSet.has(argument.refEntityId?.id)" && calculatedFieldType === CalculatedFieldType.SIMPLE) && argument.refEntityId?.id !== NULL_UUID"
matBadgeColor="warn" matBadgeColor="warn"
matBadgeSize="small" matBadgeSize="small"
matBadge="*" matBadge="*"

91
ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts

@ -28,7 +28,6 @@ import {
ViewContainerRef, ViewContainerRef,
} from '@angular/core'; } from '@angular/core';
import { import {
AbstractControl,
ControlValueAccessor, ControlValueAccessor,
FormBuilder, FormBuilder,
NG_VALIDATORS, NG_VALIDATORS,
@ -50,7 +49,7 @@ import { TbPopoverService } from '@shared/components/popover.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models';
import { getEntityDetailsPageURL, isDefined, isDefinedAndNotNull, isEqual } from '@core/utils'; import { getEntityDetailsPageURL, isEqual } from '@core/utils';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract'; import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
@ -58,8 +57,9 @@ import { MatSort } from '@angular/material/sort';
import { getCurrentAuthState } from '@core/auth/auth.selectors'; import { getCurrentAuthState } from '@core/auth/auth.selectors';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { catchError } from 'rxjs/operators'; import { forkJoin, Observable } from 'rxjs';
import { NEVER } from 'rxjs'; import { NULL_UUID } from '@shared/models/id/has-uuid';
import { BaseData } from '@shared/models/base-data';
@Component({ @Component({
selector: 'tb-calculated-field-arguments-table', selector: 'tb-calculated-field-arguments-table',
@ -88,9 +88,8 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
@ViewChild(MatSort, { static: true }) sort: MatSort; @ViewChild(MatSort, { static: true }) sort: MatSort;
errorText = ''; errorText = '';
argumentsFormArray = this.fb.array<AbstractControl>([]); argumentsFormArray = this.fb.array<CalculatedFieldArgumentValue>([]);
entityNameMap = new Map<string, string>(); entityNameMap = new Map<string, string>();
entityNameErrorSet = new Set<string>();
sortOrder = { direction: 'asc', property: '' }; sortOrder = { direction: 'asc', property: '' };
dataSource = new CalculatedFieldArgumentDatasource(); dataSource = new CalculatedFieldArgumentDatasource();
@ -100,6 +99,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
readonly ArgumentType = ArgumentType; readonly ArgumentType = ArgumentType;
readonly CalculatedFieldType = CalculatedFieldType; readonly CalculatedFieldType = CalculatedFieldType;
readonly maxArgumentsPerCF = getCurrentAuthState(this.store).maxArgumentsPerCF; readonly maxArgumentsPerCF = getCurrentAuthState(this.store).maxArgumentsPerCF;
readonly NULL_UUID = NULL_UUID;
private popoverComponent: TbPopoverComponent<CalculatedFieldArgumentPanelComponent>; private popoverComponent: TbPopoverComponent<CalculatedFieldArgumentPanelComponent>;
private propagateChange: (argumentsObj: Record<string, CalculatedFieldArgument>) => void = () => {}; private propagateChange: (argumentsObj: Record<string, CalculatedFieldArgument>) => void = () => {};
@ -115,7 +115,6 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
private store: Store<AppState> private store: Store<AppState>
) { ) {
this.argumentsFormArray.valueChanges.pipe(takeUntilDestroyed()).subscribe(value => { this.argumentsFormArray.valueChanges.pipe(takeUntilDestroyed()).subscribe(value => {
this.updateEntityNameMap(value);
this.updateDataSource(value); this.updateDataSource(value);
this.propagateChange(this.getArgumentsObject(value)); this.propagateChange(this.getArgumentsObject(value));
}); });
@ -154,7 +153,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
this.argumentsFormArray.markAsDirty(); this.argumentsFormArray.markAsDirty();
} }
manageArgument($event: Event, matButton: MatButton, argument = {} as CalculatedFieldArgumentValue, index?: number): void { manageArgument($event: Event, matButton: MatButton, argument = {} as CalculatedFieldArgumentValue): void {
$event?.stopPropagation(); $event?.stopPropagation();
if (this.popoverComponent && !this.popoverComponent.tbHidden) { if (this.popoverComponent && !this.popoverComponent.tbHidden) {
this.popoverComponent.hide(); this.popoverComponent.hide();
@ -163,15 +162,16 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
if (this.popoverService.hasPopover(trigger)) { if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger); this.popoverService.hidePopover(trigger);
} else { } else {
const index = this.argumentsFormArray.controls.findIndex(control => isEqual(control.value, argument));
const isExists = index !== -1;
const ctx = { const ctx = {
index, index,
argument, argument,
entityId: this.entityId, entityId: this.entityId,
calculatedFieldType: this.calculatedFieldType, calculatedFieldType: this.calculatedFieldType,
buttonTitle: this.argumentsFormArray.at(index)?.value ? 'action.apply' : 'action.add', buttonTitle: isExists ? 'action.apply' : 'action.add',
tenantId: this.tenantId, tenantId: this.tenantId,
entityName: this.entityName, entityName: this.entityName,
entityHasError: this.entityNameErrorSet.has(argument.refEntityId?.id),
usedArgumentNames: this.argumentsFormArray.value.map(({ argumentName }) => argumentName).filter(name => name !== argument.argumentName), usedArgumentNames: this.argumentsFormArray.value.map(({ argumentName }) => argumentName).filter(name => name !== argument.argumentName),
}; };
this.popoverComponent = this.popoverService.displayPopover({ this.popoverComponent = this.popoverService.displayPopover({
@ -179,19 +179,20 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
renderer: this.renderer, renderer: this.renderer,
componentType: CalculatedFieldArgumentPanelComponent, componentType: CalculatedFieldArgumentPanelComponent,
hostView: this.viewContainerRef, hostView: this.viewContainerRef,
preferredPlacement: isDefined(index) ? 'left' : 'right', preferredPlacement: isExists ? 'left' : 'right',
context: ctx, context: ctx,
isModal: true isModal: true
}); });
this.popoverComponent.tbComponentRef.instance.argumentsDataApplied.subscribe(({ value, index }) => { this.popoverComponent.tbComponentRef.instance.argumentsDataApplied.subscribe(({ entityName, ...value }) => {
this.popoverComponent.hide(); this.popoverComponent.hide();
const formGroup = this.fb.group(value); if (entityName) {
if (isDefinedAndNotNull(index)) { this.entityNameMap.set(value.refEntityId.id, entityName);
this.argumentsFormArray.setControl(index, formGroup); }
if (isExists) {
this.argumentsFormArray.at(index).setValue(value);
} else { } else {
this.argumentsFormArray.push(formGroup); this.argumentsFormArray.push(this.fb.control(value));
} }
formGroup.markAsDirty();
this.cd.markForCheck(); this.cd.markForCheck();
}); });
} }
@ -206,7 +207,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
if (this.calculatedFieldType === CalculatedFieldType.SIMPLE if (this.calculatedFieldType === CalculatedFieldType.SIMPLE
&& this.argumentsFormArray.controls.some(control => control.value.refEntityKey.type === ArgumentType.Rolling)) { && this.argumentsFormArray.controls.some(control => control.value.refEntityKey.type === ArgumentType.Rolling)) {
this.errorText = 'calculated-fields.hint.arguments-simple-with-rolling'; this.errorText = 'calculated-fields.hint.arguments-simple-with-rolling';
} else if (this.entityNameErrorSet.size) { } else if (this.argumentsFormArray.controls.some(control => control.value.refEntityId?.id === NULL_UUID)) {
this.errorText = 'calculated-fields.hint.arguments-entity-not-found'; this.errorText = 'calculated-fields.hint.arguments-entity-not-found';
} else if (!this.argumentsFormArray.controls.length) { } else if (!this.argumentsFormArray.controls.length) {
this.errorText = 'calculated-fields.hint.arguments-empty'; this.errorText = 'calculated-fields.hint.arguments-empty';
@ -225,7 +226,8 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
writeValue(argumentsObj: Record<string, CalculatedFieldArgument>): void { writeValue(argumentsObj: Record<string, CalculatedFieldArgument>): void {
this.argumentsFormArray.clear(); this.argumentsFormArray.clear();
this.populateArgumentsFormArray(argumentsObj) this.populateArgumentsFormArray(argumentsObj);
this.updateEntityNameMap(this.argumentsFormArray.value);
} }
getEntityDetailsPageURL(id: string, type: EntityType): string { getEntityDetailsPageURL(id: string, type: EntityType): string {
@ -238,27 +240,48 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
...argumentsObj[key], ...argumentsObj[key],
argumentName: key argumentName: key
}; };
this.argumentsFormArray.push(this.fb.group(value), { emitEvent: false }); this.argumentsFormArray.push(this.fb.control(value), { emitEvent: false });
}); });
this.argumentsFormArray.updateValueAndValidity(); this.argumentsFormArray.updateValueAndValidity();
} }
private updateEntityNameMap(value: CalculatedFieldArgumentValue[]): void { private updateEntityNameMap(values: CalculatedFieldArgumentValue[]): void {
this.entityNameErrorSet.clear(); const entitiesByType = values.reduce((acc, { refEntityId = {}}) => {
value.forEach(({ refEntityId = {}}) => { if (refEntityId.id && refEntityId.entityType !== ArgumentEntityType.Tenant) {
if (refEntityId.id && !this.entityNameMap.has(refEntityId.id)) {
const { id, entityType } = refEntityId as EntityId; const { id, entityType } = refEntityId as EntityId;
this.entityService.getEntity(entityType as EntityType, id, { ignoreLoading: true, ignoreErrors: true }) acc[entityType] = acc[entityType] ?? [];
.pipe( acc[entityType].push(id);
catchError(() => {
this.entityNameErrorSet.add(id);
return NEVER;
}),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(entity => this.entityNameMap.set(id, entity.name));
} }
}); return acc;
}, {} as Record<EntityType, string[]>);
const tasks = Object.entries(entitiesByType).map(([entityType, ids]) =>
this.entityService.getEntities(entityType as EntityType, ids)
);
if (!tasks.length) {
return;
}
this.fetchEntityNames(tasks, values);
}
private fetchEntityNames(tasks: Observable<BaseData<EntityId>[]>[], values: CalculatedFieldArgumentValue[]): void {
forkJoin(tasks as Observable<BaseData<EntityId>[]>[])
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((result: Array<BaseData<EntityId>>[]) => {
result.forEach((entities: BaseData<EntityId>[]) => entities.forEach((entity: BaseData<EntityId>) => this.entityNameMap.set(entity.id.id, entity.name)));
let updateTable = false;
values.forEach(({ refEntityId }) => {
if (refEntityId?.id && !this.entityNameMap.has(refEntityId.id) && refEntityId.entityType !== ArgumentEntityType.Tenant) {
updateTable = true;
const control = this.argumentsFormArray.controls.find(control => control.value.refEntityId?.id === refEntityId.id);
const value = control.value;
value.refEntityId.id = NULL_UUID;
control.setValue(value, { emitEvent: false });
}
});
if (updateTable) {
this.argumentsFormArray.updateValueAndValidity();
}
});
} }
private getSortValue(argument: CalculatedFieldArgumentValue, column: string): string { private getSortValue(argument: CalculatedFieldArgumentValue, column: string): string {

1
ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.html

@ -88,6 +88,7 @@
[placeholder]="'action.set' | translate" [placeholder]="'action.set' | translate"
[required]="true" [required]="true"
[entityType]="ArgumentEntityTypeParamsMap.get(entityType).entityType" [entityType]="ArgumentEntityTypeParamsMap.get(entityType).entityType"
(entityChanged)="entityNameSubject.next($event?.name)"
/> />
</div> </div>
} }

25
ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.ts

@ -36,12 +36,13 @@ import { EntityId } from '@shared/models/id/entity-id';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { EntityFilter } from '@shared/models/query/query.models'; import { EntityFilter } from '@shared/models/query/query.models';
import { AliasFilterType } from '@shared/models/alias.models'; import { AliasFilterType } from '@shared/models/alias.models';
import { merge } from 'rxjs'; import { BehaviorSubject, merge } from 'rxjs';
import { MINUTE } from '@shared/models/time/time.models'; import { MINUTE } from '@shared/models/time/time.models';
import { getCurrentAuthState } from '@core/auth/auth.selectors'; import { getCurrentAuthState } from '@core/auth/auth.selectors';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { EntityAutocompleteComponent } from '@shared/components/entity/entity-autocomplete.component'; import { EntityAutocompleteComponent } from '@shared/components/entity/entity-autocomplete.component';
import { NULL_UUID } from '@shared/models/id/has-uuid';
@Component({ @Component({
selector: 'tb-calculated-field-argument-panel', selector: 'tb-calculated-field-argument-panel',
@ -51,18 +52,16 @@ import { EntityAutocompleteComponent } from '@shared/components/entity/entity-au
export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewInit { export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewInit {
@Input() buttonTitle: string; @Input() buttonTitle: string;
@Input() index: number;
@Input() argument: CalculatedFieldArgumentValue; @Input() argument: CalculatedFieldArgumentValue;
@Input() entityId: EntityId; @Input() entityId: EntityId;
@Input() tenantId: string; @Input() tenantId: string;
@Input() entityName: string; @Input() entityName: string;
@Input() entityHasError: boolean;
@Input() calculatedFieldType: CalculatedFieldType; @Input() calculatedFieldType: CalculatedFieldType;
@Input() usedArgumentNames: string[]; @Input() usedArgumentNames: string[];
@ViewChild('entityAutocomplete') entityAutocomplete: EntityAutocompleteComponent; @ViewChild('entityAutocomplete') entityAutocomplete: EntityAutocompleteComponent;
argumentsDataApplied = output<{ value: CalculatedFieldArgumentValue, index: number }>(); argumentsDataApplied = output<CalculatedFieldArgumentValue>();
readonly maxDataPointsPerRollingArg = getCurrentAuthState(this.store).maxDataPointsPerRollingArg; readonly maxDataPointsPerRollingArg = getCurrentAuthState(this.store).maxDataPointsPerRollingArg;
readonly defaultLimit = Math.floor(this.maxDataPointsPerRollingArg / 10); readonly defaultLimit = Math.floor(this.maxDataPointsPerRollingArg / 10);
@ -85,6 +84,7 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI
argumentTypes: ArgumentType[]; argumentTypes: ArgumentType[];
entityFilter: EntityFilter; entityFilter: EntityFilter;
entityNameSubject = new BehaviorSubject<string>(null);
readonly argumentEntityTypes = Object.values(ArgumentEntityType) as ArgumentEntityType[]; readonly argumentEntityTypes = Object.values(ArgumentEntityType) as ArgumentEntityType[];
readonly ArgumentEntityTypeTranslations = ArgumentEntityTypeTranslations; readonly ArgumentEntityTypeTranslations = ArgumentEntityTypeTranslations;
@ -106,7 +106,7 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI
private store: Store<AppState> private store: Store<AppState>
) { ) {
this.observeEntityFilterChanges(); this.observeEntityFilterChanges();
this.observeEntityTypeChanges() this.observeEntityTypeChanges();
this.observeEntityKeyChanges(); this.observeEntityKeyChanges();
this.observeUpdatePosition(); this.observeUpdatePosition();
} }
@ -141,7 +141,7 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI
} }
ngAfterViewInit(): void { ngAfterViewInit(): void {
if (this.entityHasError) { if (this.argument.refEntityId?.id === NULL_UUID) {
this.entityAutocomplete.selectEntityFormGroup.get('entity').markAsTouched(); this.entityAutocomplete.selectEntityFormGroup.get('entity').markAsTouched();
} }
} }
@ -152,11 +152,14 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI
if (refEntityId.entityType === ArgumentEntityType.Tenant) { if (refEntityId.entityType === ArgumentEntityType.Tenant) {
refEntityId.id = this.tenantId; refEntityId.id = this.tenantId;
} }
if (refEntityId.entityType !== ArgumentEntityType.Current && refEntityId.entityType !== ArgumentEntityType.Tenant) {
value.entityName = this.entityNameSubject.value;
}
if (value.defaultValue) { if (value.defaultValue) {
value.defaultValue = value.defaultValue.trim(); value.defaultValue = value.defaultValue.trim();
} }
value.refEntityKey.key = value.refEntityKey.key.trim(); value.refEntityKey.key = value.refEntityKey.key.trim();
this.argumentsDataApplied.emit({ value, index: this.index }); this.argumentsDataApplied.emit(value);
} }
cancel(): void { cancel(): void {
@ -212,12 +215,16 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewI
} }
private observeEntityTypeChanges(): void { private observeEntityTypeChanges(): void {
this.argumentFormGroup.get('refEntityId').get('entityType').valueChanges this.refEntityIdFormGroup.get('entityType').valueChanges
.pipe(distinctUntilChanged(), takeUntilDestroyed()) .pipe(distinctUntilChanged(), takeUntilDestroyed())
.subscribe(type => { .subscribe(type => {
this.argumentFormGroup.get('refEntityId').get('id').setValue(''); this.argumentFormGroup.get('refEntityId').get('id').setValue('');
const isEntityWithId = type !== ArgumentEntityType.Tenant && type !== ArgumentEntityType.Current;
this.argumentFormGroup.get('refEntityId') this.argumentFormGroup.get('refEntityId')
.get('id')[type === ArgumentEntityType.Tenant || type === ArgumentEntityType.Current ? 'disable' : 'enable'](); .get('id')[isEntityWithId ? 'enable' : 'disable']();
if (!isEntityWithId) {
this.entityNameSubject.next(null);
}
if (!this.enableAttributeScopeSelection) { if (!this.enableAttributeScopeSelection) {
this.refEntityKeyFormGroup.get('scope').setValue(AttributeScope.SERVER_SCOPE); this.refEntityKeyFormGroup.get('scope').setValue(AttributeScope.SERVER_SCOPE);
} }

1
ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings-button.component.ts

@ -153,5 +153,6 @@ export class EntityDebugSettingsButtonComponent implements ControlValueAccessor
} else { } else {
this.debugSettingsFormGroup.enable({emitEvent: false}); this.debugSettingsFormGroup.enable({emitEvent: false});
} }
this.cd.markForCheck();
} }
} }

2
ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings-panel.component.html

@ -53,7 +53,7 @@
color="primary" color="primary"
type="button" type="button"
(click)="onCancel(); additionalActionConfig.action()"> (click)="onCancel(); additionalActionConfig.action()">
{{ additionalActionConfig.title | translate }} {{ additionalActionConfig.title }}
</button> </button>
} }
</div> </div>

1
ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.html

@ -33,6 +33,7 @@
[callbacks]="data.callbacks" [callbacks]="data.callbacks"
[widgetType] = "data.widgetType" [widgetType] = "data.widgetType"
[actionSources]="actionSources" [actionSources]="actionSources"
[defaultIconColor]="data.defaultIconColor"
[additionalWidgetActionTypes]="data.additionalWidgetActionTypes" [additionalWidgetActionTypes]="data.additionalWidgetActionTypes"
formControlName="actions"> formControlName="actions">
</tb-manage-widget-actions> </tb-manage-widget-actions>

1
ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.ts

@ -32,6 +32,7 @@ export interface ManageWidgetActionsDialogData {
actionsData: WidgetActionsData; actionsData: WidgetActionsData;
callbacks: WidgetActionCallbacks; callbacks: WidgetActionCallbacks;
widgetType: widgetType; widgetType: widgetType;
defaultIconColor?: string;
additionalWidgetActionTypes?: WidgetActionType[]; additionalWidgetActionTypes?: WidgetActionType[];
} }

3
ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts

@ -72,6 +72,8 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
@Input() widgetType: widgetType; @Input() widgetType: widgetType;
@Input() defaultIconColor: string;
@Input() callbacks: WidgetActionCallbacks; @Input() callbacks: WidgetActionCallbacks;
@Input() actionSources: {[actionSourceId: string]: WidgetActionSource}; @Input() actionSources: {[actionSourceId: string]: WidgetActionSource};
@ -233,6 +235,7 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
actionsData, actionsData,
action: deepClone(action), action: deepClone(action),
widgetType: this.widgetType, widgetType: this.widgetType,
defaultIconColor: this.defaultIconColor,
additionalWidgetActionTypes: this.additionalWidgetActionTypes additionalWidgetActionTypes: this.additionalWidgetActionTypes
} }
}).afterClosed().subscribe( }).afterClosed().subscribe(

6
ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts

@ -61,6 +61,7 @@ export interface WidgetActionDialogData {
actionsData: WidgetActionsData; actionsData: WidgetActionsData;
action?: WidgetActionDescriptorInfo; action?: WidgetActionDescriptorInfo;
widgetType: widgetType; widgetType: widgetType;
defaultIconColor?: string;
additionalWidgetActionTypes?: WidgetActionType[]; additionalWidgetActionTypes?: WidgetActionType[];
} }
@ -78,6 +79,8 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
isAdd: boolean; isAdd: boolean;
action: WidgetActionDescriptorInfo; action: WidgetActionDescriptorInfo;
defaultIconColor: string;
customActionEditorCompleter = CustomActionEditorCompleter; customActionEditorCompleter = CustomActionEditorCompleter;
submitted = false; submitted = false;
@ -106,6 +109,7 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
private destroyRef: DestroyRef) { private destroyRef: DestroyRef) {
super(store, router, dialogRef); super(store, router, dialogRef);
this.isAdd = data.isAdd; this.isAdd = data.isAdd;
this.defaultIconColor = data.defaultIconColor;
if (this.isAdd) { if (this.isAdd) {
this.action = { this.action = {
id: this.utils.guid(), id: this.utils.guid(),
@ -130,7 +134,7 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
buttonType: [{ value: this.action.buttonType ?? WidgetHeaderActionButtonType.icon, disabled: true}, []], buttonType: [{ value: this.action.buttonType ?? WidgetHeaderActionButtonType.icon, disabled: true}, []],
showIcon: [{ value: this.action.showIcon ?? true, disabled: true}, []], showIcon: [{ value: this.action.showIcon ?? true, disabled: true}, []],
icon: [this.action.icon, Validators.required], icon: [this.action.icon, Validators.required],
buttonColor: [{ value: this.action.buttonColor ?? 'rgba(0, 0, 0, 0.87)', disabled: true}, []], buttonColor: [{ value: this.action.buttonColor ?? this.defaultIconColor, disabled: true}, []],
buttonFillColor: [{ value: this.action.buttonFillColor ?? '#3F52DD', disabled: true}, []], buttonFillColor: [{ value: this.action.buttonFillColor ?? '#3F52DD', disabled: true}, []],
buttonBorderColor: [{ value: this.action.buttonBorderColor ?? '#3F52DD', disabled: true}, []], buttonBorderColor: [{ value: this.action.buttonBorderColor ?? '#3F52DD', disabled: true}, []],
customButtonStyle: [{ value: this.action.customButtonStyle ?? {}, disabled: true}, []], customButtonStyle: [{ value: this.action.customButtonStyle ?? {}, disabled: true}, []],

1
ui-ngx/src/app/modules/home/components/widget/config/basic/common/widget-actions-panel.component.ts

@ -123,6 +123,7 @@ export class WidgetActionsPanelComponent implements ControlValueAccessor, OnInit
callbacks: this.widgetConfigComponent.widgetConfigCallbacks, callbacks: this.widgetConfigComponent.widgetConfigCallbacks,
actionsData, actionsData,
widgetType: this.widgetConfigComponent.widgetType, widgetType: this.widgetConfigComponent.widgetType,
defaultIconColor: this.widgetConfigComponent.widgetSettings.get('color').value,
additionalWidgetActionTypes: this.widgetConfigComponent.modelValue.typeParameters.additionalWidgetActionTypes additionalWidgetActionTypes: this.widgetConfigComponent.modelValue.typeParameters.additionalWidgetActionTypes
} }
}).afterClosed().subscribe( }).afterClosed().subscribe(

9
ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html

@ -17,7 +17,14 @@
--> -->
<div class="tb-bar-chart-panel" [style.padding]="padding" [style]="backgroundStyle$ | async"> <div class="tb-bar-chart-panel" [style.padding]="padding" [style]="backgroundStyle$ | async">
<div class="tb-bar-chart-overlay" [style]="overlayStyle"></div> <div class="tb-bar-chart-overlay" [style]="overlayStyle"></div>
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> @if (widgetComponent.dashboardWidget.showWidgetTitlePanel) {
<div class="tb-widget-title-row flex justify-between">
<ng-container *ngTemplateOutlet="widgetComponent.widgetTitlePanel"></ng-container>
<ng-container *ngTemplateOutlet="widgetComponent.widgetHeaderActionsPanel"></ng-container>
</div>
} @else {
<ng-container class="tb-widget-actions-absolute" *ngTemplateOutlet="widgetComponent.widgetHeaderActionsPanel; context: {absolute: true}"></ng-container>
}
<div class="tb-bar-chart-content" [class]="legendClass"> <div class="tb-bar-chart-content" [class]="legendClass">
<div #chartShape class="tb-bar-chart-shape"> <div #chartShape class="tb-bar-chart-shape">
</div> </div>

7
ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss

@ -104,4 +104,11 @@
} }
} }
} }
.tb-widget-title-row .tb-widget-actions {
margin: 0;
}
.tb-widget-actions.tb-widget-actions-absolute {
z-index: 2;
}
} }

7
ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts

@ -40,6 +40,7 @@ import {
} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models'; } from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models';
import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart'; import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
import { DataKey } from '@shared/models/widget.models'; import { DataKey } from '@shared/models/widget.models';
import { WidgetComponent } from '@home/components/widget/widget.component';
@Component({ @Component({
selector: 'tb-bar-chart-with-labels-widget', selector: 'tb-bar-chart-with-labels-widget',
@ -57,9 +58,6 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
@Input() @Input()
ctx: WidgetContext; ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
showLegend: boolean; showLegend: boolean;
legendClass: string; legendClass: string;
@ -73,7 +71,8 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
private timeSeriesChart: TbTimeSeriesChart; private timeSeriesChart: TbTimeSeriesChart;
constructor(private imagePipe: ImagePipe, constructor(public widgetComponent: WidgetComponent,
private imagePipe: ImagePipe,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
private renderer: Renderer2, private renderer: Renderer2,
private cd: ChangeDetectorRef) { private cd: ChangeDetectorRef) {

1
ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart-widget.component.html

@ -19,6 +19,5 @@
#latestChart #latestChart
[ctx]="ctx" [ctx]="ctx"
[settings]="settings" [settings]="settings"
[widgetTitlePanel]="widgetTitlePanel"
[callbacks]="callbacks" [callbacks]="callbacks"
></tb-latest-chart> ></tb-latest-chart>

9
ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.html

@ -17,7 +17,14 @@
--> -->
<div class="tb-latest-chart-panel" [style.padding]="padding" [style]="backgroundStyle$ | async"> <div class="tb-latest-chart-panel" [style.padding]="padding" [style]="backgroundStyle$ | async">
<div class="tb-latest-chart-overlay" [style]="overlayStyle"></div> <div class="tb-latest-chart-overlay" [style]="overlayStyle"></div>
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> @if (widgetComponent.dashboardWidget.showWidgetTitlePanel) {
<div class="tb-widget-title-row flex justify-between">
<ng-container *ngTemplateOutlet="widgetComponent.widgetTitlePanel"></ng-container>
<ng-container *ngTemplateOutlet="widgetComponent.widgetHeaderActionsPanel"></ng-container>
</div>
} @else {
<ng-container class="tb-widget-actions-absolute" *ngTemplateOutlet="widgetComponent.widgetHeaderActionsPanel; context: {absolute: true}"></ng-container>
}
<div #chartContent class="tb-latest-chart-content" [class]="legendClass"> <div #chartContent class="tb-latest-chart-content" [class]="legendClass">
<div #chartShape class="tb-latest-chart-shape"> <div #chartShape class="tb-latest-chart-shape">
</div> </div>

7
ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss

@ -106,4 +106,11 @@
} }
} }
} }
.tb-widget-title-row .tb-widget-actions {
margin: 0;
}
.tb-widget-actions.tb-widget-actions-absolute {
z-index: 2;
}
} }

7
ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.ts

@ -68,9 +68,6 @@ export class LatestChartComponent implements OnInit, OnDestroy, AfterViewInit {
@Input() @Input()
ctx: WidgetContext; ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
@Input() @Input()
callbacks: LatestChartComponentCallbacks; callbacks: LatestChartComponentCallbacks;
@ -98,9 +95,9 @@ export class LatestChartComponent implements OnInit, OnDestroy, AfterViewInit {
private latestChart: TbLatestChart<LatestChartSettings>; private latestChart: TbLatestChart<LatestChartSettings>;
constructor(private imagePipe: ImagePipe, constructor(public widgetComponent: WidgetComponent,
private imagePipe: ImagePipe,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
private widgetComponent: WidgetComponent,
private renderer: Renderer2, private renderer: Renderer2,
private translate: TranslateService, private translate: TranslateService,
private cd: ChangeDetectorRef) { private cd: ChangeDetectorRef) {

9
ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.html

@ -17,7 +17,14 @@
--> -->
<div class="tb-range-chart-panel" [style.padding]="padding" [style]="backgroundStyle$ | async"> <div class="tb-range-chart-panel" [style.padding]="padding" [style]="backgroundStyle$ | async">
<div class="tb-range-chart-overlay" [style]="overlayStyle"></div> <div class="tb-range-chart-overlay" [style]="overlayStyle"></div>
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> @if (widgetComponent.dashboardWidget.showWidgetTitlePanel) {
<div class="tb-widget-title-row flex justify-between">
<ng-container *ngTemplateOutlet="widgetComponent.widgetTitlePanel"></ng-container>
<ng-container *ngTemplateOutlet="widgetComponent.widgetHeaderActionsPanel"></ng-container>
</div>
} @else {
<ng-container class="tb-widget-actions-absolute" *ngTemplateOutlet="widgetComponent.widgetHeaderActionsPanel; context: {absolute: true}"></ng-container>
}
<div class="tb-range-chart-content" [class]="legendClass"> <div class="tb-range-chart-content" [class]="legendClass">
<div #chartShape class="tb-range-chart-shape"> <div #chartShape class="tb-range-chart-shape">
</div> </div>

6
ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.scss

@ -104,4 +104,10 @@
} }
} }
} }
.tb-widget-title-row .tb-widget-actions {
margin: 0;
}
.tb-widget-actions.tb-widget-actions-absolute {
z-index: 2;
}
} }

7
ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts

@ -48,6 +48,7 @@ import { Observable } from 'rxjs';
import { ImagePipe } from '@shared/pipe/image.pipe'; import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart'; import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
import { WidgetComponent } from '@home/components/widget/widget.component';
@Component({ @Component({
selector: 'tb-range-chart-widget', selector: 'tb-range-chart-widget',
@ -65,9 +66,6 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
@Input() @Input()
ctx: WidgetContext; ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
showLegend: boolean; showLegend: boolean;
legendClass: string; legendClass: string;
@ -86,7 +84,8 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
private timeSeriesChart: TbTimeSeriesChart; private timeSeriesChart: TbTimeSeriesChart;
constructor(private imagePipe: ImagePipe, constructor(public widgetComponent: WidgetComponent,
private imagePipe: ImagePipe,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
private renderer: Renderer2, private renderer: Renderer2,
private cd: ChangeDetectorRef) { private cd: ChangeDetectorRef) {

11
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html

@ -17,9 +17,14 @@
--> -->
<div class="tb-time-series-chart-panel" [style.padding]="padding" [style]="backgroundStyle$ | async"> <div class="tb-time-series-chart-panel" [style.padding]="padding" [style]="backgroundStyle$ | async">
<div class="tb-time-series-chart-overlay" [style]="overlayStyle"></div> <div class="tb-time-series-chart-overlay" [style]="overlayStyle"></div>
<ng-container *ngIf="widgetComponent.dashboardWidget.showWidgetTitlePanel"> @if (widgetComponent.dashboardWidget.showWidgetTitlePanel) {
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> <div class="tb-widget-title-row flex justify-between">
</ng-container> <ng-container *ngTemplateOutlet="widgetComponent.widgetTitlePanel"></ng-container>
<ng-container *ngTemplateOutlet="widgetComponent.widgetHeaderActionsPanel"></ng-container>
</div>
} @else {
<ng-container class="tb-widget-actions-absolute" *ngTemplateOutlet="widgetComponent.widgetHeaderActionsPanel; context: {absolute: true}"></ng-container>
}
<div class="tb-time-series-chart-content" [class]="legendClass"> <div class="tb-time-series-chart-content" [class]="legendClass">
<div #chartShape class="tb-time-series-chart-shape"> <div #chartShape class="tb-time-series-chart-shape">
</div> </div>

6
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss

@ -156,4 +156,10 @@ $maxLegendHeight: 35%;
} }
} }
} }
.tb-widget-title-row .tb-widget-actions {
margin: 0;
}
.tb-widget-actions.tb-widget-actions-absolute {
z-index: 2;
}
} }

3
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts

@ -61,9 +61,6 @@ export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterV
@Input() @Input()
ctx: WidgetContext; ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
horizontalLegendPosition = false; horizontalLegendPosition = false;
showLegend: boolean; showLegend: boolean;

11
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.html

@ -17,9 +17,14 @@
--> -->
<div class="tb-map-panel" [style.padding]="padding" [style]="backgroundStyle$ | async"> <div class="tb-map-panel" [style.padding]="padding" [style]="backgroundStyle$ | async">
<div class="tb-map-overlay" [style]="overlayStyle"></div> <div class="tb-map-overlay" [style]="overlayStyle"></div>
<ng-container *ngIf="widgetComponent.dashboardWidget.showWidgetTitlePanel"> @if (widgetComponent.dashboardWidget.showWidgetTitlePanel) {
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> <div class="tb-widget-title-row flex justify-between">
</ng-container> <ng-container *ngTemplateOutlet="widgetComponent.widgetTitlePanel"></ng-container>
<ng-container *ngTemplateOutlet="widgetComponent.widgetHeaderActionsPanel"></ng-container>
</div>
} @else {
<ng-container class="tb-widget-actions-absolute" *ngTemplateOutlet="widgetComponent.widgetHeaderActionsPanel; context: {absolute: true}"></ng-container>
}
<div #mapElement> <div #mapElement>
</div> </div>
</div> </div>

6
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.scss

@ -34,4 +34,10 @@
div.tb-widget-title { div.tb-widget-title {
padding: 0; padding: 0;
} }
.tb-widget-title-row .tb-widget-actions {
margin: 0;
}
.tb-widget-actions.tb-widget-actions-absolute {
z-index: 2;
}
} }

4
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.ts

@ -21,7 +21,6 @@ import {
Input, Input,
OnDestroy, OnDestroy,
OnInit, OnInit,
TemplateRef,
ViewChild, ViewChild,
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
@ -55,9 +54,6 @@ export class MapWidgetComponent implements OnInit, OnDestroy {
@Input() @Input()
ctx: WidgetContext; ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
backgroundStyle$: Observable<ComponentStyle>; backgroundStyle$: Observable<ComponentStyle>;
overlayStyle: ComponentStyle = {}; overlayStyle: ComponentStyle = {};
padding: string; padding: string;

19
ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts

@ -133,6 +133,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
private currentEditButton: L.TB.ToolbarButton; private currentEditButton: L.TB.ToolbarButton;
private dragMode = true; private dragMode = true;
private createMapItemActionId: string;
private get isPlacingItem(): boolean { private get isPlacingItem(): boolean {
return !!this.currentEditButton; return !!this.currentEditButton;
@ -686,9 +687,14 @@ export abstract class TbMap<S extends BaseMapSettings> {
} }
private createItem(actionData: PlaceMapItemActionData, prepareDrawMode: () => void) { private createItem(actionData: PlaceMapItemActionData, prepareDrawMode: () => void) {
if (this.isPlacingItem) { const actionId = 'id' in actionData.action ? actionData.action.id : 'map-button';
if (this.createMapItemActionId === actionId) {
this.finishCreatedItem();
return; return;
} }
if (isDefined(this.createMapItemActionId)) {
this.finishCreatedItem();
}
this.updatePlaceItemState(actionData.additionalParams?.button, true); this.updatePlaceItemState(actionData.additionalParams?.button, true);
this.map.once('pm:create', (e) => { this.map.once('pm:create', (e) => {
@ -701,7 +707,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
// @ts-ignore // @ts-ignore
e.layer._pmTempLayer = true; e.layer._pmTempLayer = true;
e.layer.remove(); e.layer.remove();
this.finishAdd(); this.finishCreatedItem();
}); });
prepareDrawMode(); prepareDrawMode();
@ -714,10 +720,12 @@ export abstract class TbMap<S extends BaseMapSettings> {
iconClass: 'tb-close', iconClass: 'tb-close',
title: this.ctx.translate.instant('action.cancel'), title: this.ctx.translate.instant('action.cancel'),
showText: true, showText: true,
click: this.finishAdd click: this.finishCreatedItem
} }
], false); ], false);
this.createMapItemActionId = actionId;
const convertLayerToCoordinates = (type: MapItemType, layer: L.Layer): {x: number; y: number} | TbPolygonRawCoordinates | TbCircleData => { const convertLayerToCoordinates = (type: MapItemType, layer: L.Layer): {x: number; y: number} | TbPolygonRawCoordinates | TbCircleData => {
switch (type) { switch (type) {
case MapItemType.marker: case MapItemType.marker:
@ -749,6 +757,11 @@ export abstract class TbMap<S extends BaseMapSettings> {
} }
} }
private finishCreatedItem = () => {
delete this.createMapItemActionId;
this.finishAdd();
}
private finishAdd = () => { private finishAdd = () => {
if (this.currentPopover) { if (this.currentPopover) {
this.currentPopover.hide(); this.currentPopover.hide();

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component.html

@ -60,7 +60,8 @@
[validationArgs]="[]" [validationArgs]="[]"
[editorCompleter]="customPrettyActionEditorCompleter" [editorCompleter]="customPrettyActionEditorCompleter"
functionTitle="{{ 'widget-action.custom-pretty-function' | translate }}" functionTitle="{{ 'widget-action.custom-pretty-function' | translate }}"
[helpId]="helpId"> [helpId]="helpId"
[helpPopupStyle]="{width: '900px'}">
</tb-js-func> </tb-js-func>
</div> </div>
</div> </div>

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component.html

@ -101,7 +101,8 @@
[validationArgs]="[]" [validationArgs]="[]"
[editorCompleter]="customPrettyActionEditorCompleter" [editorCompleter]="customPrettyActionEditorCompleter"
functionTitle="{{ 'widget-action.custom-pretty-function' | translate }}" functionTitle="{{ 'widget-action.custom-pretty-function' | translate }}"
[helpId]="helpId"> [helpId]="helpId"
[helpPopupStyle]="{width: '900px'}">
</tb-js-func> </tb-js-func>
</mat-tab> </mat-tab>
</mat-tab-group> </mat-tab-group>

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

@ -256,6 +256,7 @@
[editorCompleter]="customActionEditorCompleter" [editorCompleter]="customActionEditorCompleter"
withModules withModules
helpId="widget/action/custom_action_fn" helpId="widget/action/custom_action_fn"
[helpPopupStyle]="{width: '900px'}"
></tb-js-func> ></tb-js-func>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="widgetActionType.placeMapItem"> <ng-template [ngSwitchCase]="widgetActionType.placeMapItem">

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.html

@ -95,7 +95,8 @@
[functionArgs]="['data', 'dsData']" [functionArgs]="['data', 'dsData']"
[globalVariables]="functionScopeVariables" [globalVariables]="functionScopeVariables"
functionTitle="{{ 'widgets.maps.data-layer.color-function' | translate }}" functionTitle="{{ 'widgets.maps.data-layer.color-function' | translate }}"
[helpId]="helpId"> [helpId]="helpId"
[helpPopupStyle]="{width: '900px'}">
</tb-js-func> </tb-js-func>
</div> </div>
</ng-template> </ng-template>

1
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.scss

@ -43,6 +43,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; min-height: 0;
overflow: auto;
} }
.tb-data-layer-color-settings-panel-buttons { .tb-data-layer-color-settings-panel-buttons {
height: 40px; height: 40px;

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html

@ -46,7 +46,8 @@
[globalVariables]="functionScopeVariables" [globalVariables]="functionScopeVariables"
[functionArgs]="['data', 'dsData']" [functionArgs]="['data', 'dsData']"
functionTitle="{{ (patternType === 'label' ? 'widgets.maps.data-layer.label-function' : 'widgets.maps.data-layer.tooltip-function') | translate }}" functionTitle="{{ (patternType === 'label' ? 'widgets.maps.data-layer.label-function' : 'widgets.maps.data-layer.tooltip-function') | translate }}"
[helpId]="helpId"> [helpId]="helpId"
[helpPopupStyle]="{width: '900px'}">
</tb-js-func> </tb-js-func>
<ng-container *ngIf="patternType === 'tooltip'"> <ng-container *ngIf="patternType === 'tooltip'">
<div class="tb-form-row space-between column-xs"> <div class="tb-form-row space-between column-xs">

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-clustering-settings.component.html

@ -88,7 +88,8 @@
[globalVariables]="functionScopeVariables" [globalVariables]="functionScopeVariables"
[functionArgs]="['data', 'childCount']" [functionArgs]="['data', 'childCount']"
functionTitle="{{ 'widgets.maps.data-layer.marker.clustering.marker-color-function' | translate }}" functionTitle="{{ 'widgets.maps.data-layer.marker.clustering.marker-color-function' | translate }}"
helpId="widget/lib/map/clustering_color_fn"> helpId="widget/lib/map/clustering_color_fn"
[helpPopupStyle]="{width: '900px'}">
</tb-js-func> </tb-js-func>
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>

1
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings-panel.component.scss

@ -41,6 +41,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; min-height: 0;
overflow: auto;
} }
.tb-marker-image-settings-panel-buttons { .tb-marker-image-settings-panel-buttons {
height: 40px; height: 40px;

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/trip-timeline-settings.component.html

@ -70,7 +70,8 @@
[globalVariables]="functionScopeVariables" [globalVariables]="functionScopeVariables"
[functionArgs]="['data', 'dsData']" [functionArgs]="['data', 'dsData']"
functionTitle="{{ 'widgets.maps.timeline.location-snap-filter-function' | translate }}" functionTitle="{{ 'widgets.maps.timeline.location-snap-filter-function' | translate }}"
helpId="widget/lib/map/trip_point_as_anchor_fn"> helpId="widget/lib/map/trip_point_as_anchor_fn"
[helpPopupStyle]="{width: '900px'}">
</tb-js-func> </tb-js-func>
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>

3
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -631,6 +631,9 @@ export class WidgetComponentService {
if (isUndefined(result.typeParameters.embedTitlePanel)) { if (isUndefined(result.typeParameters.embedTitlePanel)) {
result.typeParameters.embedTitlePanel = false; result.typeParameters.embedTitlePanel = false;
} }
if (isUndefined(result.typeParameters.embedActionsPanel)) {
result.typeParameters.embedActionsPanel = false;
}
if (isUndefined(result.typeParameters.overflowVisible)) { if (isUndefined(result.typeParameters.overflowVisible)) {
result.typeParameters.overflowVisible = false; result.typeParameters.overflowVisible = false;
} }

1
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -163,6 +163,7 @@
<tb-manage-widget-actions <tb-manage-widget-actions
[callbacks]="widgetConfigCallbacks" [callbacks]="widgetConfigCallbacks"
[widgetType] = "modelValue.widgetType" [widgetType] = "modelValue.widgetType"
[defaultIconColor]="widgetSettings.get('color').value"
[actionSources]="modelValue.actionSources" [actionSources]="modelValue.actionSources"
[additionalWidgetActionTypes]="modelValue.typeParameters.additionalWidgetActionTypes" [additionalWidgetActionTypes]="modelValue.typeParameters.additionalWidgetActionTypes"
formControlName="actions"> formControlName="actions">

63
ui-ngx/src/app/modules/home/components/widget/widget-container.component.html

@ -33,33 +33,9 @@
<ng-container *ngIf="!widgetComponent.widgetContext?.embedTitlePanel"> <ng-container *ngIf="!widgetComponent.widgetContext?.embedTitlePanel">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> <ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
</ng-container> </ng-container>
<div *ngIf="widget.showWidgetActions" <ng-container *ngIf="!widgetComponent.widgetContext?.embedActionsPanel">
class="tb-widget-actions" <ng-container *ngTemplateOutlet="widgetHeaderActionsPanel"></ng-container>
[class]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel && !widgetComponent.widgetContext?.embedTitlePanel && (widget.showTitle||widget.hasAggregation))}" </ng-container>
(mousedown)="$event.stopPropagation()">
<div class="flex items-center">
@for (action of widget.customHeaderActions; track action.name; let last = $last) {
<ng-container *ngTemplateOutlet="widgetHeaderActionButton; context:{ action, last }"></ng-container>
}
</div>
<button mat-icon-button *ngFor="let action of widget.widgetActions"
type="button"
[class.!hidden]="isEdit || !action.show"
(click)="action.onAction($event)"
matTooltip="{{ action.name | translate }}"
matTooltipPosition="above">
<tb-icon>{{ action.icon }}</tb-icon>
</button>
<button mat-icon-button
type="button"
[class.!hidden]="isEdit || !widget.enableFullscreen"
(click)="$event.stopPropagation(); widget.isFullscreen = !widget.isFullscreen; updateEditWidgetActionsTooltipState()"
matTooltip="{{(widget.isFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
matTooltipPosition="above">
<tb-icon>{{ widget.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</tb-icon>
</button>
</div>
</div> </div>
<div class="tb-widget-content" [class.tb-no-interaction]="disableWidgetInteraction"> <div class="tb-widget-content" [class.tb-no-interaction]="disableWidgetInteraction">
<tb-widget #widgetComponent <tb-widget #widgetComponent
@ -68,7 +44,8 @@
[isEdit]="isEdit" [isEdit]="isEdit"
[isPreview]="isPreview" [isPreview]="isPreview"
[isMobile]="isMobile" [isMobile]="isMobile"
[widgetTitlePanel]="widgetTitlePanel"> [widgetTitlePanel]="widgetTitlePanel"
[widgetHeaderActionsPanel]="widgetHeaderActionsPanel">
</tb-widget> </tb-widget>
</div> </div>
</div> </div>
@ -102,6 +79,36 @@
</div> </div>
</ng-template> </ng-template>
<ng-template #widgetHeaderActionsPanel let-absolute="absolute">
<div *ngIf="widget.showWidgetActions"
class="tb-widget-actions"
[class.tb-widget-actions-absolute]="widgetActionAbsolute(widgetComponent, absolute)"
(mousedown)="$event.stopPropagation()">
<div class="flex items-center">
@for (action of widget.customHeaderActions; track action.name; let last = $last) {
<ng-container *ngTemplateOutlet="widgetHeaderActionButton; context:{ action, last }"></ng-container>
}
</div>
<button mat-icon-button *ngFor="let action of widget.widgetActions"
type="button"
[class.!hidden]="isEdit || !action.show"
(click)="action.onAction($event)"
matTooltip="{{ action.name | translate }}"
matTooltipPosition="above">
<tb-icon>{{ action.icon }}</tb-icon>
</button>
<button mat-icon-button
type="button"
[class.!hidden]="isEdit || !widget.enableFullscreen"
(click)="$event.stopPropagation(); widget.isFullscreen = !widget.isFullscreen; updateEditWidgetActionsTooltipState()"
matTooltip="{{(widget.isFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
matTooltipPosition="above">
<tb-icon>{{ widget.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</tb-icon>
</button>
</div>
</ng-template>
<ng-template #widgetHeaderActionButton let-action="action" let-last="last"> <ng-template #widgetHeaderActionButton let-action="action" let-last="last">
@switch (action.buttonType) { @switch (action.buttonType) {
@case (widgetHeaderActionButtonType.miniFab) { @case (widgetHeaderActionButtonType.miniFab) {

7
ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts

@ -48,6 +48,7 @@ import { TbContextMenuEvent } from '@shared/models/jquery-event.models';
import { WidgetHeaderActionButtonType } from '@shared/models/widget.models'; import { WidgetHeaderActionButtonType } from '@shared/models/widget.models';
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
import ITooltipsterGeoHelper = JQueryTooltipster.ITooltipsterGeoHelper; import ITooltipsterGeoHelper = JQueryTooltipster.ITooltipsterGeoHelper;
import { WidgetComponent } from '@home/components/widget/widget.component';
export enum WidgetComponentActionType { export enum WidgetComponentActionType {
MOUSE_DOWN, MOUSE_DOWN,
@ -216,6 +217,12 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
}); });
} }
widgetActionAbsolute(widgetComponent: WidgetComponent, absolute = false) {
return absolute ? true :
!(this.widget.showWidgetTitlePanel && !widgetComponent.widgetContext?.embedTitlePanel &&
(this.widget.showTitle||this.widget.hasAggregation)) && !widgetComponent.widgetContext?.embedActionsPanel;
}
onClicked(event: MouseEvent): void { onClicked(event: MouseEvent): void {
if (event && this.isEdit) { if (event && this.isEdit) {
event.stopPropagation(); event.stopPropagation();

6
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -139,6 +139,9 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
@Input() @Input()
widgetTitlePanel: TemplateRef<any>; widgetTitlePanel: TemplateRef<any>;
@Input()
widgetHeaderActionsPanel: TemplateRef<any>;
@Input() @Input()
isEdit: boolean; isEdit: boolean;
@ -365,7 +368,7 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
headerButtonStyle(buttonType: WidgetHeaderActionButtonType = WidgetHeaderActionButtonType.icon, headerButtonStyle(buttonType: WidgetHeaderActionButtonType = WidgetHeaderActionButtonType.icon,
customButtonStyle:{[key: string]: string}, customButtonStyle:{[key: string]: string},
buttonColor: string = 'rgba(0,0,0,0.87)', buttonColor: string = this.widget.config.color,
backgroundColor: string, backgroundColor: string,
borderColor: string) { borderColor: string) {
const buttonStyle = {}; const buttonStyle = {};
@ -483,6 +486,7 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
this.widgetType = this.widgetInfo.widgetTypeFunction; this.widgetType = this.widgetInfo.widgetTypeFunction;
this.typeParameters = this.widgetInfo.typeParameters; this.typeParameters = this.widgetInfo.typeParameters;
this.widgetContext.embedTitlePanel = this.typeParameters.embedTitlePanel; this.widgetContext.embedTitlePanel = this.typeParameters.embedTitlePanel;
this.widgetContext.embedActionsPanel = this.typeParameters.embedActionsPanel;
this.widgetContext.overflowVisible = this.typeParameters.overflowVisible; this.widgetContext.overflowVisible = this.typeParameters.overflowVisible;
if (!this.widgetType) { if (!this.widgetType) {

1
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -312,6 +312,7 @@ export class WidgetContext {
timeWindow?: WidgetTimewindow; timeWindow?: WidgetTimewindow;
embedTitlePanel?: boolean; embedTitlePanel?: boolean;
embedActionsPanel?: boolean;
overflowVisible?: boolean; overflowVisible?: boolean;
hideTitlePanel = false; hideTitlePanel = false;

1
ui-ngx/src/app/shared/models/calculated-field.models.ts

@ -144,6 +144,7 @@ export interface RefEntityId {
export interface CalculatedFieldArgumentValue extends CalculatedFieldArgument { export interface CalculatedFieldArgumentValue extends CalculatedFieldArgument {
argumentName: string; argumentName: string;
entityName?: string;
} }
export type CalculatedFieldTestScriptFn = (calculatedField: CalculatedField, argumentsObj?: Record<string, unknown>, closeAllOnSave?: boolean) => Observable<string>; export type CalculatedFieldTestScriptFn = (calculatedField: CalculatedField, argumentsObj?: Record<string, unknown>, closeAllOnSave?: boolean) => Observable<string>;

1
ui-ngx/src/app/shared/models/widget.models.ts

@ -185,6 +185,7 @@ export interface WidgetTypeParameters {
previewWidth?: string; previewWidth?: string;
previewHeight?: string; previewHeight?: string;
embedTitlePanel?: boolean; embedTitlePanel?: boolean;
embedActionsPanel?: boolean;
overflowVisible?: boolean; overflowVisible?: boolean;
hideDataTab?: boolean; hideDataTab?: boolean;
hideDataSettings?: boolean; hideDataSettings?: boolean;

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

@ -1000,8 +1000,8 @@
"calculated-field": "calculated field", "calculated-field": "calculated field",
"hint": { "hint": {
"main-limited": "No more than {{msg}} {{entity}} debug messages per {{time}} will be recorded.", "main-limited": "No more than {{msg}} {{entity}} debug messages per {{time}} will be recorded.",
"on-failure": "Log all debug messages.", "on-failure": "Log error messages only.",
"all-messages": "Log error messages only. " "all-messages": "Log all debug messages."
} }
}, },
"calculated-fields": { "calculated-fields": {
@ -8013,7 +8013,7 @@
"pattern-type-function": "Function", "pattern-type-function": "Function",
"label-pattern": "Label (pattern examples: '${entityName}', '${entityName}: (Text ${keyName} units.)' )", "label-pattern": "Label (pattern examples: '${entityName}', '${entityName}: (Text ${keyName} units.)' )",
"label-function": "Label function", "label-function": "Label function",
"tooltip-pattern": "Tooltip (for ex. 'Text ${keyName} units.' or <link-act name='my-action'>Link text</link-act>')", "tooltip-pattern": "Tooltip (for ex. 'Text ${keyName} units.' or <link-act name='my-action'>Link text</link-act>)",
"tooltip-function": "Tooltip function", "tooltip-function": "Tooltip function",
"tooltip-trigger": "Tooltip trigger", "tooltip-trigger": "Tooltip trigger",
"tooltip-trigger-click": "Show tooltip on click", "tooltip-trigger-click": "Show tooltip on click",

Loading…
Cancel
Save