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="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="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="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"/>

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,
"sizeY": 5,
"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": "",
"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": "{}",
"dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 8,
"sizeY": 5,
"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",
"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": "{}",
"dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 5,
"sizeY": 4,
"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": "",
"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": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-bar-chart-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 4,
"sizeY": 3,
"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": "",
"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": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-doughnut-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 4,
"sizeY": 2.5,
"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": "",
"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": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-doughnut-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 8.5,
"sizeY": 6,
"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": "",
"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": [],
"dataKeySettingsForm": [],
"settingsDirective": "tb-map-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 8,
"sizeY": 5,
"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": "",
"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": "{}",
"dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 8.5,
"sizeY": 6,
"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": "",
"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": [],
"dataKeySettingsForm": [],
"settingsDirective": "tb-map-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 5,
"sizeY": 4,
"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": "",
"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": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-pie-chart-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 8,
"sizeY": 5,
"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": "",
"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": "{}",
"dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 5,
"sizeY": 4,
"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": "",
"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": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-polar-area-chart-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 5,
"sizeY": 4,
"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": "",
"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": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-radar-chart-widget-settings",

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

@ -9,9 +9,9 @@
"sizeX": 8,
"sizeY": 5,
"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",
"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": "{}",
"dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 8.5,
"sizeY": 6,
"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": "",
"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": [],
"dataKeySettingsForm": [],
"latestDataKeySettingsForm": [],

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

@ -9,9 +9,9 @@
"sizeX": 8,
"sizeY": 5,
"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": "",
"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": "{}",
"dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 8,
"sizeY": 5,
"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": "",
"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": "{}",
"dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}",

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

@ -9,9 +9,9 @@
"sizeX": 8.5,
"sizeY": 6,
"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": "",
"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": [],
"dataKeySettingsForm": [],
"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) {
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));
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 {
action: WidgetAction;
action: WidgetAction | WidgetActionDescriptor;
additionalParams?: any;
afterPlaceItemCallback: ($event: Event, descriptor: WidgetAction, entityId?: EntityId, entityName?: string,
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 {
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 {
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 matColumnDef="actions" stickyEnd>
<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">
<button type="button"
mat-icon-button
#button
(click)="manageArgument($event, button, argument, $index)"
(click)="manageArgument($event, button, argument)"
[matTooltip]="'action.edit' | translate"
matTooltipPosition="above">
<mat-icon
[matBadgeHidden]="!(argument.refEntityKey.type === ArgumentType.Rolling
&& calculatedFieldType === CalculatedFieldType.SIMPLE) && !entityNameErrorSet.has(argument.refEntityId?.id)"
&& calculatedFieldType === CalculatedFieldType.SIMPLE) && argument.refEntityId?.id !== NULL_UUID"
matBadgeColor="warn"
matBadgeSize="small"
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,
} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormBuilder,
NG_VALIDATORS,
@ -50,7 +49,7 @@ import { TbPopoverService } from '@shared/components/popover.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { EntityId } from '@shared/models/id/entity-id';
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 { TbTableDatasource } from '@shared/components/table/table-datasource.abstract';
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 { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { catchError } from 'rxjs/operators';
import { NEVER } from 'rxjs';
import { forkJoin, Observable } from 'rxjs';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { BaseData } from '@shared/models/base-data';
@Component({
selector: 'tb-calculated-field-arguments-table',
@ -88,9 +88,8 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
@ViewChild(MatSort, { static: true }) sort: MatSort;
errorText = '';
argumentsFormArray = this.fb.array<AbstractControl>([]);
argumentsFormArray = this.fb.array<CalculatedFieldArgumentValue>([]);
entityNameMap = new Map<string, string>();
entityNameErrorSet = new Set<string>();
sortOrder = { direction: 'asc', property: '' };
dataSource = new CalculatedFieldArgumentDatasource();
@ -100,6 +99,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
readonly ArgumentType = ArgumentType;
readonly CalculatedFieldType = CalculatedFieldType;
readonly maxArgumentsPerCF = getCurrentAuthState(this.store).maxArgumentsPerCF;
readonly NULL_UUID = NULL_UUID;
private popoverComponent: TbPopoverComponent<CalculatedFieldArgumentPanelComponent>;
private propagateChange: (argumentsObj: Record<string, CalculatedFieldArgument>) => void = () => {};
@ -115,7 +115,6 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
private store: Store<AppState>
) {
this.argumentsFormArray.valueChanges.pipe(takeUntilDestroyed()).subscribe(value => {
this.updateEntityNameMap(value);
this.updateDataSource(value);
this.propagateChange(this.getArgumentsObject(value));
});
@ -154,7 +153,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
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();
if (this.popoverComponent && !this.popoverComponent.tbHidden) {
this.popoverComponent.hide();
@ -163,15 +162,16 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
const index = this.argumentsFormArray.controls.findIndex(control => isEqual(control.value, argument));
const isExists = index !== -1;
const ctx = {
index,
argument,
entityId: this.entityId,
calculatedFieldType: this.calculatedFieldType,
buttonTitle: this.argumentsFormArray.at(index)?.value ? 'action.apply' : 'action.add',
buttonTitle: isExists ? 'action.apply' : 'action.add',
tenantId: this.tenantId,
entityName: this.entityName,
entityHasError: this.entityNameErrorSet.has(argument.refEntityId?.id),
usedArgumentNames: this.argumentsFormArray.value.map(({ argumentName }) => argumentName).filter(name => name !== argument.argumentName),
};
this.popoverComponent = this.popoverService.displayPopover({
@ -179,19 +179,20 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
renderer: this.renderer,
componentType: CalculatedFieldArgumentPanelComponent,
hostView: this.viewContainerRef,
preferredPlacement: isDefined(index) ? 'left' : 'right',
preferredPlacement: isExists ? 'left' : 'right',
context: ctx,
isModal: true
});
this.popoverComponent.tbComponentRef.instance.argumentsDataApplied.subscribe(({ value, index }) => {
this.popoverComponent.tbComponentRef.instance.argumentsDataApplied.subscribe(({ entityName, ...value }) => {
this.popoverComponent.hide();
const formGroup = this.fb.group(value);
if (isDefinedAndNotNull(index)) {
this.argumentsFormArray.setControl(index, formGroup);
if (entityName) {
this.entityNameMap.set(value.refEntityId.id, entityName);
}
if (isExists) {
this.argumentsFormArray.at(index).setValue(value);
} else {
this.argumentsFormArray.push(formGroup);
this.argumentsFormArray.push(this.fb.control(value));
}
formGroup.markAsDirty();
this.cd.markForCheck();
});
}
@ -206,7 +207,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
if (this.calculatedFieldType === CalculatedFieldType.SIMPLE
&& this.argumentsFormArray.controls.some(control => control.value.refEntityKey.type === ArgumentType.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';
} else if (!this.argumentsFormArray.controls.length) {
this.errorText = 'calculated-fields.hint.arguments-empty';
@ -225,7 +226,8 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
writeValue(argumentsObj: Record<string, CalculatedFieldArgument>): void {
this.argumentsFormArray.clear();
this.populateArgumentsFormArray(argumentsObj)
this.populateArgumentsFormArray(argumentsObj);
this.updateEntityNameMap(this.argumentsFormArray.value);
}
getEntityDetailsPageURL(id: string, type: EntityType): string {
@ -238,27 +240,48 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
...argumentsObj[key],
argumentName: key
};
this.argumentsFormArray.push(this.fb.group(value), { emitEvent: false });
this.argumentsFormArray.push(this.fb.control(value), { emitEvent: false });
});
this.argumentsFormArray.updateValueAndValidity();
}
private updateEntityNameMap(value: CalculatedFieldArgumentValue[]): void {
this.entityNameErrorSet.clear();
value.forEach(({ refEntityId = {}}) => {
if (refEntityId.id && !this.entityNameMap.has(refEntityId.id)) {
private updateEntityNameMap(values: CalculatedFieldArgumentValue[]): void {
const entitiesByType = values.reduce((acc, { refEntityId = {}}) => {
if (refEntityId.id && refEntityId.entityType !== ArgumentEntityType.Tenant) {
const { id, entityType } = refEntityId as EntityId;
this.entityService.getEntity(entityType as EntityType, id, { ignoreLoading: true, ignoreErrors: true })
.pipe(
catchError(() => {
this.entityNameErrorSet.add(id);
return NEVER;
}),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(entity => this.entityNameMap.set(id, entity.name));
acc[entityType] = acc[entityType] ?? [];
acc[entityType].push(id);
}
});
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 {

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

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

@ -33,6 +33,7 @@
[callbacks]="data.callbacks"
[widgetType] = "data.widgetType"
[actionSources]="actionSources"
[defaultIconColor]="data.defaultIconColor"
[additionalWidgetActionTypes]="data.additionalWidgetActionTypes"
formControlName="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;
callbacks: WidgetActionCallbacks;
widgetType: widgetType;
defaultIconColor?: string;
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() defaultIconColor: string;
@Input() callbacks: WidgetActionCallbacks;
@Input() actionSources: {[actionSourceId: string]: WidgetActionSource};
@ -233,6 +235,7 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
actionsData,
action: deepClone(action),
widgetType: this.widgetType,
defaultIconColor: this.defaultIconColor,
additionalWidgetActionTypes: this.additionalWidgetActionTypes
}
}).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;
action?: WidgetActionDescriptorInfo;
widgetType: widgetType;
defaultIconColor?: string;
additionalWidgetActionTypes?: WidgetActionType[];
}
@ -78,6 +79,8 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
isAdd: boolean;
action: WidgetActionDescriptorInfo;
defaultIconColor: string;
customActionEditorCompleter = CustomActionEditorCompleter;
submitted = false;
@ -106,6 +109,7 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
private destroyRef: DestroyRef) {
super(store, router, dialogRef);
this.isAdd = data.isAdd;
this.defaultIconColor = data.defaultIconColor;
if (this.isAdd) {
this.action = {
id: this.utils.guid(),
@ -130,7 +134,7 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
buttonType: [{ value: this.action.buttonType ?? WidgetHeaderActionButtonType.icon, disabled: true}, []],
showIcon: [{ value: this.action.showIcon ?? true, disabled: true}, []],
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}, []],
buttonBorderColor: [{ value: this.action.buttonBorderColor ?? '#3F52DD', 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,
actionsData,
widgetType: this.widgetConfigComponent.widgetType,
defaultIconColor: this.widgetConfigComponent.widgetSettings.get('color').value,
additionalWidgetActionTypes: this.widgetConfigComponent.modelValue.typeParameters.additionalWidgetActionTypes
}
}).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-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 #chartShape class="tb-bar-chart-shape">
</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';
import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
import { DataKey } from '@shared/models/widget.models';
import { WidgetComponent } from '@home/components/widget/widget.component';
@Component({
selector: 'tb-bar-chart-with-labels-widget',
@ -57,9 +58,6 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
@Input()
ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
showLegend: boolean;
legendClass: string;
@ -73,7 +71,8 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
private timeSeriesChart: TbTimeSeriesChart;
constructor(private imagePipe: ImagePipe,
constructor(public widgetComponent: WidgetComponent,
private imagePipe: ImagePipe,
private sanitizer: DomSanitizer,
private renderer: Renderer2,
private cd: ChangeDetectorRef) {

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

@ -19,6 +19,5 @@
#latestChart
[ctx]="ctx"
[settings]="settings"
[widgetTitlePanel]="widgetTitlePanel"
[callbacks]="callbacks"
></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-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 #chartShape class="tb-latest-chart-shape">
</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()
ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
@Input()
callbacks: LatestChartComponentCallbacks;
@ -98,9 +95,9 @@ export class LatestChartComponent implements OnInit, OnDestroy, AfterViewInit {
private latestChart: TbLatestChart<LatestChartSettings>;
constructor(private imagePipe: ImagePipe,
constructor(public widgetComponent: WidgetComponent,
private imagePipe: ImagePipe,
private sanitizer: DomSanitizer,
private widgetComponent: WidgetComponent,
private renderer: Renderer2,
private translate: TranslateService,
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-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 #chartShape class="tb-range-chart-shape">
</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 { DomSanitizer } from '@angular/platform-browser';
import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
import { WidgetComponent } from '@home/components/widget/widget.component';
@Component({
selector: 'tb-range-chart-widget',
@ -65,9 +66,6 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
@Input()
ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
showLegend: boolean;
legendClass: string;
@ -86,7 +84,8 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
private timeSeriesChart: TbTimeSeriesChart;
constructor(private imagePipe: ImagePipe,
constructor(public widgetComponent: WidgetComponent,
private imagePipe: ImagePipe,
private sanitizer: DomSanitizer,
private renderer: Renderer2,
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-overlay" [style]="overlayStyle"></div>
<ng-container *ngIf="widgetComponent.dashboardWidget.showWidgetTitlePanel">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
</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-time-series-chart-content" [class]="legendClass">
<div #chartShape class="tb-time-series-chart-shape">
</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()
ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
horizontalLegendPosition = false;
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-overlay" [style]="overlayStyle"></div>
<ng-container *ngIf="widgetComponent.dashboardWidget.showWidgetTitlePanel">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
</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 #mapElement>
</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 {
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,
OnDestroy,
OnInit,
TemplateRef,
ViewChild,
ViewEncapsulation
} from '@angular/core';
@ -55,9 +54,6 @@ export class MapWidgetComponent implements OnInit, OnDestroy {
@Input()
ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
backgroundStyle$: Observable<ComponentStyle>;
overlayStyle: ComponentStyle = {};
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 dragMode = true;
private createMapItemActionId: string;
private get isPlacingItem(): boolean {
return !!this.currentEditButton;
@ -686,9 +687,14 @@ export abstract class TbMap<S extends BaseMapSettings> {
}
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;
}
if (isDefined(this.createMapItemActionId)) {
this.finishCreatedItem();
}
this.updatePlaceItemState(actionData.additionalParams?.button, true);
this.map.once('pm:create', (e) => {
@ -701,7 +707,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
// @ts-ignore
e.layer._pmTempLayer = true;
e.layer.remove();
this.finishAdd();
this.finishCreatedItem();
});
prepareDrawMode();
@ -714,10 +720,12 @@ export abstract class TbMap<S extends BaseMapSettings> {
iconClass: 'tb-close',
title: this.ctx.translate.instant('action.cancel'),
showText: true,
click: this.finishAdd
click: this.finishCreatedItem
}
], false);
this.createMapItemActionId = actionId;
const convertLayerToCoordinates = (type: MapItemType, layer: L.Layer): {x: number; y: number} | TbPolygonRawCoordinates | TbCircleData => {
switch (type) {
case MapItemType.marker:
@ -749,6 +757,11 @@ export abstract class TbMap<S extends BaseMapSettings> {
}
}
private finishCreatedItem = () => {
delete this.createMapItemActionId;
this.finishAdd();
}
private finishAdd = () => {
if (this.currentPopover) {
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]="[]"
[editorCompleter]="customPrettyActionEditorCompleter"
functionTitle="{{ 'widget-action.custom-pretty-function' | translate }}"
[helpId]="helpId">
[helpId]="helpId"
[helpPopupStyle]="{width: '900px'}">
</tb-js-func>
</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]="[]"
[editorCompleter]="customPrettyActionEditorCompleter"
functionTitle="{{ 'widget-action.custom-pretty-function' | translate }}"
[helpId]="helpId">
[helpId]="helpId"
[helpPopupStyle]="{width: '900px'}">
</tb-js-func>
</mat-tab>
</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"
withModules
helpId="widget/action/custom_action_fn"
[helpPopupStyle]="{width: '900px'}"
></tb-js-func>
</ng-template>
<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']"
[globalVariables]="functionScopeVariables"
functionTitle="{{ 'widgets.maps.data-layer.color-function' | translate }}"
[helpId]="helpId">
[helpId]="helpId"
[helpPopupStyle]="{width: '900px'}">
</tb-js-func>
</div>
</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;
flex-direction: column;
min-height: 0;
overflow: auto;
}
.tb-data-layer-color-settings-panel-buttons {
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"
[functionArgs]="['data', 'dsData']"
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>
<ng-container *ngIf="patternType === 'tooltip'">
<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"
[functionArgs]="['data', 'childCount']"
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>
</ng-template>
</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;
flex-direction: column;
min-height: 0;
overflow: auto;
}
.tb-marker-image-settings-panel-buttons {
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"
[functionArgs]="['data', 'dsData']"
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>
</ng-template>
</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)) {
result.typeParameters.embedTitlePanel = false;
}
if (isUndefined(result.typeParameters.embedActionsPanel)) {
result.typeParameters.embedActionsPanel = false;
}
if (isUndefined(result.typeParameters.overflowVisible)) {
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
[callbacks]="widgetConfigCallbacks"
[widgetType] = "modelValue.widgetType"
[defaultIconColor]="widgetSettings.get('color').value"
[actionSources]="modelValue.actionSources"
[additionalWidgetActionTypes]="modelValue.typeParameters.additionalWidgetActionTypes"
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 *ngTemplateOutlet="widgetTitlePanel"></ng-container>
</ng-container>
<div *ngIf="widget.showWidgetActions"
class="tb-widget-actions"
[class]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel && !widgetComponent.widgetContext?.embedTitlePanel && (widget.showTitle||widget.hasAggregation))}"
(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-container *ngIf="!widgetComponent.widgetContext?.embedActionsPanel">
<ng-container *ngTemplateOutlet="widgetHeaderActionsPanel"></ng-container>
</ng-container>
</div>
<div class="tb-widget-content" [class.tb-no-interaction]="disableWidgetInteraction">
<tb-widget #widgetComponent
@ -68,7 +44,8 @@
[isEdit]="isEdit"
[isPreview]="isPreview"
[isMobile]="isMobile"
[widgetTitlePanel]="widgetTitlePanel">
[widgetTitlePanel]="widgetTitlePanel"
[widgetHeaderActionsPanel]="widgetHeaderActionsPanel">
</tb-widget>
</div>
</div>
@ -102,6 +79,36 @@
</div>
</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">
@switch (action.buttonType) {
@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 ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
import ITooltipsterGeoHelper = JQueryTooltipster.ITooltipsterGeoHelper;
import { WidgetComponent } from '@home/components/widget/widget.component';
export enum WidgetComponentActionType {
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 {
if (event && this.isEdit) {
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()
widgetTitlePanel: TemplateRef<any>;
@Input()
widgetHeaderActionsPanel: TemplateRef<any>;
@Input()
isEdit: boolean;
@ -365,7 +368,7 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
headerButtonStyle(buttonType: WidgetHeaderActionButtonType = WidgetHeaderActionButtonType.icon,
customButtonStyle:{[key: string]: string},
buttonColor: string = 'rgba(0,0,0,0.87)',
buttonColor: string = this.widget.config.color,
backgroundColor: string,
borderColor: string) {
const buttonStyle = {};
@ -483,6 +486,7 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
this.widgetType = this.widgetInfo.widgetTypeFunction;
this.typeParameters = this.widgetInfo.typeParameters;
this.widgetContext.embedTitlePanel = this.typeParameters.embedTitlePanel;
this.widgetContext.embedActionsPanel = this.typeParameters.embedActionsPanel;
this.widgetContext.overflowVisible = this.typeParameters.overflowVisible;
if (!this.widgetType) {

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

@ -312,6 +312,7 @@ export class WidgetContext {
timeWindow?: WidgetTimewindow;
embedTitlePanel?: boolean;
embedActionsPanel?: boolean;
overflowVisible?: boolean;
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 {
argumentName: string;
entityName?: 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;
previewHeight?: string;
embedTitlePanel?: boolean;
embedActionsPanel?: boolean;
overflowVisible?: boolean;
hideDataTab?: boolean;
hideDataSettings?: boolean;

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

@ -1000,8 +1000,8 @@
"calculated-field": "calculated field",
"hint": {
"main-limited": "No more than {{msg}} {{entity}} debug messages per {{time}} will be recorded.",
"on-failure": "Log all debug messages.",
"all-messages": "Log error messages only. "
"on-failure": "Log error messages only.",
"all-messages": "Log all debug messages."
}
},
"calculated-fields": {
@ -8013,7 +8013,7 @@
"pattern-type-function": "Function",
"label-pattern": "Label (pattern examples: '${entityName}', '${entityName}: (Text ${keyName} units.)' )",
"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-trigger": "Tooltip trigger",
"tooltip-trigger-click": "Show tooltip on click",

Loading…
Cancel
Save