Browse Source

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

pull/13040/head
IrynaMatveieva 2 years ago
parent
commit
c5989cb5e2
  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. 2
      application/src/main/data/json/system/widget_types/radial_gauge.json
  15. 4
      application/src/main/data/json/system/widget_types/range_chart.json
  16. 4
      application/src/main/data/json/system/widget_types/route_map.json
  17. 4
      application/src/main/data/json/system/widget_types/state_chart.json
  18. 4
      application/src/main/data/json/system/widget_types/time_series_chart.json
  19. 4
      application/src/main/data/json/system/widget_types/trip_map.json
  20. 2
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java
  21. 7
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  22. 2
      ui-ngx/src/app/core/api/widget-api.models.ts
  23. 2
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts
  24. 6
      ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html
  25. 91
      ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts
  26. 1
      ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.html
  27. 25
      ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.ts
  28. 1
      ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings-button.component.ts
  29. 2
      ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings-panel.component.html
  30. 1
      ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.html
  31. 1
      ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.ts
  32. 11
      ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts
  33. 54
      ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts
  34. 6
      ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts
  35. 1
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/widget-actions-panel.component.ts
  36. 5
      ui-ngx/src/app/modules/home/components/widget/lib/analogue-gauge.models.ts
  37. 9
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html
  38. 7
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss
  39. 7
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts
  40. 1
      ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart-widget.component.html
  41. 9
      ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.html
  42. 7
      ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss
  43. 7
      ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.ts
  44. 9
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.html
  45. 6
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.scss
  46. 7
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts
  47. 11
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.html
  48. 6
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss
  49. 3
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts
  50. 9
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts
  51. 11
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.html
  52. 6
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.scss
  53. 4
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.component.ts
  54. 28
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts
  55. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component.html
  56. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component.html
  57. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html
  58. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.html
  59. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.scss
  60. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html
  61. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-clustering-settings.component.html
  62. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings-panel.component.scss
  63. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/trip-timeline-settings.component.html
  64. 3
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  65. 1
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  66. 63
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.html
  67. 7
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts
  68. 6
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  69. 1
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  70. 1
      ui-ngx/src/app/shared/models/calculated-field.models.ts
  71. 1
      ui-ngx/src/app/shared/models/widget.models.ts
  72. 32
      ui-ngx/src/app/shared/models/widget/maps/marker-shape.models.ts
  73. 6
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  74. 14
      ui-ngx/src/assets/markers/iconContainer4.svg
  75. 15
      ui-ngx/src/assets/markers/iconContainer5.svg
  76. 27
      ui-ngx/src/assets/markers/iconContainer6.svg
  77. 27
      ui-ngx/src/assets/markers/iconContainer7.svg

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",

2
application/src/main/data/json/system/widget_types/radial_gauge.json

@ -17,7 +17,7 @@
"settingsDirective": "tb-analogue-radial-gauge-widget-settings", "settingsDirective": "tb-analogue-radial-gauge-widget-settings",
"hasBasicMode": true, "hasBasicMode": true,
"basicModeDirective": "tb-radial-gauge-basic-config", "basicModeDirective": "tb-radial-gauge-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < -100) {\\n\\tvalue = -100;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":true,\"defaultColor\":\"#e65100\",\"needleCircleSize\":10,\"highlights\":[],\"showUnitTitle\":true,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":10,\"valueInt\":3,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"cycle\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"numbersFont\":{\"family\":\"Roboto\",\"size\":18,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"size\":36,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\"},\"minValue\":-100,\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Radial gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"decimals\":0,\"noDataDisplayMessage\":\"\",\"configMode\":\"basic\"}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < -100) {\\n\\tvalue = -100;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":100,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":true,\"defaultColor\":\"#e65100\",\"needleCircleSize\":10,\"highlights\":[],\"showUnitTitle\":true,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":10,\"valueInt\":3,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":500,\"animationRule\":\"cycle\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"numbersFont\":{\"family\":\"Roboto\",\"size\":18,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"Roboto\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"Roboto\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"family\":\"Segment7Standard\",\"size\":36,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\"},\"minValue\":-100,\"colorNeedleShadowDown\":\"rgba(188,143,143,0.45)\",\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Radial gauge\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"decimals\":0,\"noDataDisplayMessage\":\"\",\"configMode\":\"basic\"}"
}, },
"resources": [ "resources": [
{ {

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);

7
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java

@ -725,7 +725,12 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
ctx.setInitialDataSent(true); ctx.setInitialDataSent(true);
} else { } else {
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription()); // if ctx has timeseries subscription, timeseries values are cleared after each update and is empty in ctx data,
// so to avoid sending timeseries update with empty map we set it to null
List<EntityData> preparedData = ctx.getData().getData().stream()
.map(entityData -> new EntityData(entityData.getEntityId(), entityData.getLatest(), null))
.toList();
update = new EntityDataUpdate(ctx.getCmdId(), null, preparedData, ctx.getMaxEntitiesPerDataSubscription());
} }
ctx.sendWsMsg(update); ctx.sendWsMsg(update);
} finally { } finally {

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[];
} }

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

@ -21,11 +21,11 @@ import {
widgetActionTypeTranslationMap widgetActionTypeTranslationMap
} from '@app/shared/models/widget.models'; } from '@app/shared/models/widget.models';
import { CollectionViewer, DataSource } from '@angular/cdk/collections'; import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs'; import { BehaviorSubject, Observable, of, ReplaySubject, shareReplay } from 'rxjs';
import { emptyPageData, PageData } from '@shared/models/page/page-data'; import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import { catchError, map, publishReplay, refCount } from 'rxjs/operators'; import { catchError, map } from 'rxjs/operators';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { deepClone } from '@core/utils'; import { deepClone } from '@core/utils';
@ -68,11 +68,11 @@ export class WidgetActionsDatasource implements DataSource<WidgetActionDescripto
constructor(private translate: TranslateService, constructor(private translate: TranslateService,
private utils: UtilsService) {} private utils: UtilsService) {}
connect(collectionViewer: CollectionViewer): Observable<WidgetActionDescriptorInfo[] | ReadonlyArray<WidgetActionDescriptorInfo>> { connect(_collectionViewer: CollectionViewer): Observable<WidgetActionDescriptorInfo[] | ReadonlyArray<WidgetActionDescriptorInfo>> {
return this.actionsSubject.asObservable(); return this.actionsSubject.asObservable();
} }
disconnect(collectionViewer: CollectionViewer): void { disconnect(_collectionViewer: CollectionViewer): void {
this.actionsSubject.complete(); this.actionsSubject.complete();
this.pageDataSubject.complete(); this.pageDataSubject.complete();
} }
@ -115,8 +115,7 @@ export class WidgetActionsDatasource implements DataSource<WidgetActionDescripto
}); });
} }
this.allActions = of(actions).pipe( this.allActions = of(actions).pipe(
publishReplay(1), shareReplay(1)
refCount()
); );
} }
return this.allActions; return this.allActions;

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

@ -20,7 +20,8 @@ import {
Component, Component,
ElementRef, ElementRef,
forwardRef, forwardRef,
Input, NgZone, Input,
NgZone,
OnDestroy, OnDestroy,
OnInit, OnInit,
ViewChild ViewChild
@ -28,8 +29,6 @@ import {
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { DialogService } from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
@ -73,24 +72,24 @@ 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};
@Input() additionalWidgetActionTypes: WidgetActionType[]; @Input() additionalWidgetActionTypes: WidgetActionType[];
innerValue: WidgetActionsData;
displayedColumns: string[]; displayedColumns: string[];
pageLink: PageLink; pageLink: PageLink;
textSearchMode = false; textSearchMode = false;
hidePageSize = false; hidePageSize = false;
dataSource: WidgetActionsDatasource; dataSource: WidgetActionsDatasource;
viewsInited = false;
dirtyValue = false;
dragDisabled = true; dragDisabled = true;
private actionsMap: {[actionSourceId: string]: Array<WidgetActionDescriptor>};
private viewsInited = false;
private dirtyValue = false;
private widgetResize$: ResizeObserver; private widgetResize$: ResizeObserver;
private destroyed = false; private destroyed = false;
@ -101,15 +100,14 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
private propagateChange = (_: any) => {}; private propagateChange = (_: any) => {};
constructor(protected store: Store<AppState>, constructor(private translate: TranslateService,
private translate: TranslateService,
private utils: UtilsService, private utils: UtilsService,
private dialog: MatDialog, private dialog: MatDialog,
private dialogs: DialogService, private dialogs: DialogService,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef,
private elementRef: ElementRef, private elementRef: ElementRef,
private zone: NgZone) { private zone: NgZone) {
super(store); super();
const sortOrder: SortOrder = { property: 'actionSourceName', direction: Direction.ASC }; const sortOrder: SortOrder = { property: 'actionSourceName', direction: Direction.ASC };
this.pageLink = new PageLink(10, 0, null, sortOrder); this.pageLink = new PageLink(10, 0, null, sortOrder);
this.dataSource = new WidgetActionsDatasource(this.translate, this.utils); this.dataSource = new WidgetActionsDatasource(this.translate, this.utils);
@ -137,7 +135,6 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
} }
ngAfterViewInit() { ngAfterViewInit() {
fromEvent(this.searchInputField.nativeElement, 'keyup') fromEvent(this.searchInputField.nativeElement, 'keyup')
.pipe( .pipe(
debounceTime(150), debounceTime(150),
@ -162,10 +159,9 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
this.dirtyValue = false; this.dirtyValue = false;
this.updateData(true); this.updateData(true);
} }
} }
updateData(reload: boolean = false) { private updateData(reload: boolean = false) {
this.pageLink.page = this.paginator.pageIndex; this.pageLink.page = this.paginator.pageIndex;
this.pageLink.pageSize = this.paginator.pageSize; this.pageLink.pageSize = this.paginator.pageSize;
this.pageLink.sortOrder.property = this.sort.active; this.pageLink.sortOrder.property = this.sort.active;
@ -198,7 +194,7 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
this.openWidgetActionDialog($event, action); this.openWidgetActionDialog($event, action);
} }
openWidgetActionDialog($event: Event, action: WidgetActionDescriptorInfo = null) { private openWidgetActionDialog($event: Event, action: WidgetActionDescriptorInfo = null) {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
@ -208,15 +204,15 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
prevActionSourceId = action.actionSourceId; prevActionSourceId = action.actionSourceId;
} }
const availableActionSources: {[actionSourceId: string]: WidgetActionSource} = {}; const availableActionSources: {[actionSourceId: string]: WidgetActionSource} = {};
for (const id of Object.keys(this.innerValue.actionSources)) { for (const id of Object.keys(this.actionSources)) {
const actionSource = this.innerValue.actionSources[id]; const actionSource = this.actionSources[id];
if (actionSource.multiple) { if (actionSource.multiple) {
availableActionSources[id] = actionSource; availableActionSources[id] = actionSource;
} else { } else {
if (!isAdd && action.actionSourceId === id) { if (!isAdd && action.actionSourceId === id) {
availableActionSources[id] = actionSource; availableActionSources[id] = actionSource;
} else { } else {
const existing = this.innerValue.actionsMap[id]; const existing = this.actionsMap[id];
if (!existing || !existing.length) { if (!existing || !existing.length) {
availableActionSources[id] = actionSource; availableActionSources[id] = actionSource;
} }
@ -225,7 +221,7 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
} }
const actionsData: WidgetActionsData = { const actionsData: WidgetActionsData = {
actionsMap: this.innerValue.actionsMap, actionsMap: this.actionsMap,
actionSources: availableActionSources actionSources: availableActionSources
}; };
@ -239,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(
@ -277,7 +274,7 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
} }
private getOrCreateTargetActions(actionSourceId: string): Array<WidgetActionDescriptor> { private getOrCreateTargetActions(actionSourceId: string): Array<WidgetActionDescriptor> {
const actionsMap = this.innerValue.actionsMap; const actionsMap = this.actionsMap;
let targetActions = actionsMap[actionSourceId]; let targetActions = actionsMap[actionSourceId];
if (!targetActions) { if (!targetActions) {
targetActions = []; targetActions = [];
@ -323,7 +320,7 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
this.updateData(); this.updateData();
} }
resetSortAndFilter(update: boolean = true) { private resetSortAndFilter(update: boolean = true) {
this.pageLink.textSearch = null; this.pageLink.textSearch = null;
this.paginator.pageIndex = 0; this.paginator.pageIndex = 0;
const sortable = this.sort.sortables.get('actionSourceName'); const sortable = this.sort.sortables.get('actionSourceName');
@ -338,7 +335,7 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
this.propagateChange = fn; this.propagateChange = fn;
} }
registerOnTouched(fn: any): void { registerOnTouched(_fn: any): void {
} }
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
@ -346,13 +343,14 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
} }
writeValue(actions?: {[actionSourceId: string]: Array<WidgetActionDescriptor>}): void { writeValue(actions?: {[actionSourceId: string]: Array<WidgetActionDescriptor>}): void {
this.innerValue = { this.actionsMap = actions ?? {};
actionsMap: actions || {},
actionSources: this.actionSources || {}
};
setTimeout(() => { setTimeout(() => {
if (!this.destroyed) { if (!this.destroyed) {
this.dataSource.setActions(this.innerValue); const actionData: WidgetActionsData = {
actionsMap: this.actionsMap,
actionSources: this.actionSources
};
this.dataSource.setActions(actionData);
if (this.viewsInited) { if (this.viewsInited) {
this.resetSortAndFilter(true); this.resetSortAndFilter(true);
} else { } else {
@ -364,6 +362,6 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
private onActionsUpdated() { private onActionsUpdated() {
this.updateData(true); this.updateData(true);
this.propagateChange(this.innerValue.actionsMap); this.propagateChange(this.actionsMap);
} }
} }

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(

5
ui-ngx/src/app/modules/home/components/widget/lib/analogue-gauge.models.ts

@ -17,7 +17,7 @@
import * as CanvasGauges from 'canvas-gauges'; import * as CanvasGauges from 'canvas-gauges';
import { FontSettings, getFontFamily } from '@home/components/widget/lib/settings.models'; import { FontSettings, getFontFamily } from '@home/components/widget/lib/settings.models';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { isDefined } from '@core/utils'; import { isDefined, isDefinedAndNotNull } from '@core/utils';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import Highlight = CanvasGauges.Highlight; import Highlight = CanvasGauges.Highlight;
import BaseGauge = CanvasGauges.BaseGauge; import BaseGauge = CanvasGauges.BaseGauge;
@ -264,8 +264,7 @@ function getValueDec(ctx: WidgetContext, settings: AnalogueGaugeSettings): numbe
if (dataKey && isDefined(dataKey.decimals)) { if (dataKey && isDefined(dataKey.decimals)) {
return dataKey.decimals; return dataKey.decimals;
} else { } else {
return (isDefined(settings.valueDec) && settings.valueDec !== null) return isDefinedAndNotNull(ctx.decimals) ? ctx.decimals : 0;
? settings.valueDec : ctx.decimals;
} }
} }

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;

9
ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts

@ -351,10 +351,10 @@ class TbTripDataItem extends TbDataLayerItem<TripsDataLayerSettings, TbTripsData
rotationAngle: this.settings.rotateMarker ? this.settings.offsetAngle : 0 rotationAngle: this.settings.rotateMarker ? this.settings.offsetAngle : 0
}; };
} }
const timeStamp = Object.keys(result);
if (timeline) { if (timeline) {
const xKey = this.settings.xKey.label; const xKey = this.settings.xKey.label;
const yKey = this.settings.yKey.label; const yKey = this.settings.yKey.label;
const timeStamp = Object.keys(result);
for (let i = 0; i < timeStamp.length - 1; i++) { for (let i = 0; i < timeStamp.length - 1; i++) {
if (isUndefined(result[timeStamp[i + 1]][xKey]) || isUndefined(result[timeStamp[i + 1]][yKey])) { if (isUndefined(result[timeStamp[i + 1]][xKey]) || isUndefined(result[timeStamp[i + 1]][yKey])) {
for (let j = i + 2; j < timeStamp.length - 1; j++) { for (let j = i + 2; j < timeStamp.length - 1; j++) {
@ -374,6 +374,13 @@ class TbTripDataItem extends TbDataLayerItem<TripsDataLayerSettings, TbTripsData
result[timeStamp[i]].rotationAngle += findRotationAngle(startPoint, endPoint); result[timeStamp[i]].rotationAngle += findRotationAngle(startPoint, endPoint);
} }
} }
if (this.settings.rotateMarker && timeStamp.length > 1) {
result[timeStamp[timeStamp.length - 1]].rotationAngle = result[timeStamp[timeStamp.length - 2]].rotationAngle;
}
} else if (this.settings.rotateMarker && timeStamp.length > 1) {
const startPoint = this.dataLayer.dataProcessor.extractLocation(result[timeStamp[timeStamp.length - 2]], dsData);
const endPoint = this.dataLayer.dataProcessor.extractLocation(result[timeStamp[timeStamp.length - 1]], dsData);
result[timeStamp[timeStamp.length - 1]].rotationAngle += findRotationAngle(startPoint, endPoint);
} }
return result; return result;
} }

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;

28
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;
@ -176,6 +177,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
private setupControls(): Observable<any> { private setupControls(): Observable<any> {
if (this.settings.scales?.length) { if (this.settings.scales?.length) {
L.control.scale({ L.control.scale({
position: 'bottomright',
metric: this.settings.scales.includes(MapScale.metric), metric: this.settings.scales.includes(MapScale.metric),
imperial: this.settings.scales.includes(MapScale.imperial) imperial: this.settings.scales.includes(MapScale.imperial)
}).addTo(this.map); }).addTo(this.map);
@ -685,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) => {
@ -700,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();
@ -713,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:
@ -748,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();
@ -937,7 +951,13 @@ export abstract class TbMap<S extends BaseMapSettings> {
bounds = new L.LatLngBounds(null, null); bounds = new L.LatLngBounds(null, null);
dataLayersBounds.forEach(b => bounds.extend(b)); dataLayersBounds.forEach(b => bounds.extend(b));
const mapBounds = this.map.getBounds(); const mapBounds = this.map.getBounds();
if (bounds.isValid() && (!this.bounds || !this.bounds.isValid() || (!this.bounds.equals(bounds) || force) && this.settings.fitMapBounds && !mapBounds.contains(bounds))) { if (bounds.isValid() &&
(
(!this.bounds || !this.bounds.isValid() || (!this.bounds.equals(bounds) || force) && this.settings.fitMapBounds)
&& !mapBounds.contains(bounds)
)
)
{
this.bounds = bounds; this.bounds = bounds;
if (!this.ignoreUpdateBounds && !this.isPlacingItem) { if (!this.ignoreUpdateBounds && !this.isPlacingItem) {
this.fitBounds(bounds); this.fitBounds(bounds);

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;

32
ui-ngx/src/app/shared/models/widget/maps/marker-shape.models.ts

@ -50,6 +50,10 @@ export enum MarkerIconContainer {
iconContainer1 = 'iconContainer1', iconContainer1 = 'iconContainer1',
iconContainer2 = 'iconContainer2', iconContainer2 = 'iconContainer2',
iconContainer3 = 'iconContainer3', iconContainer3 = 'iconContainer3',
iconContainer4 = 'iconContainer4',
iconContainer5 = 'iconContainer5',
iconContainer6 = 'iconContainer6',
iconContainer7 = 'iconContainer7',
tripIconContainer1 = 'tripIconContainer1', tripIconContainer1 = 'tripIconContainer1',
tripIconContainer2 = 'tripIconContainer2', tripIconContainer2 = 'tripIconContainer2',
tripIconContainer3 = 'tripIconContainer3' tripIconContainer3 = 'tripIconContainer3'
@ -85,6 +89,10 @@ const markerIconContainerMap = new Map<MarkerIconContainer, string>(
[MarkerIconContainer.iconContainer1, '/assets/markers/iconContainer1.svg'], [MarkerIconContainer.iconContainer1, '/assets/markers/iconContainer1.svg'],
[MarkerIconContainer.iconContainer2, '/assets/markers/iconContainer2.svg'], [MarkerIconContainer.iconContainer2, '/assets/markers/iconContainer2.svg'],
[MarkerIconContainer.iconContainer3, '/assets/markers/iconContainer3.svg'], [MarkerIconContainer.iconContainer3, '/assets/markers/iconContainer3.svg'],
[MarkerIconContainer.iconContainer4, '/assets/markers/iconContainer4.svg'],
[MarkerIconContainer.iconContainer5, '/assets/markers/iconContainer5.svg'],
[MarkerIconContainer.iconContainer6, '/assets/markers/iconContainer6.svg'],
[MarkerIconContainer.iconContainer7, '/assets/markers/iconContainer7.svg'],
[MarkerIconContainer.tripIconContainer1, '/assets/markers/tripIconContainer1.svg'], [MarkerIconContainer.tripIconContainer1, '/assets/markers/tripIconContainer1.svg'],
[MarkerIconContainer.tripIconContainer2, '/assets/markers/tripIconContainer2.svg'], [MarkerIconContainer.tripIconContainer2, '/assets/markers/tripIconContainer2.svg'],
[MarkerIconContainer.tripIconContainer3, '/assets/markers/tripIconContainer3.svg'] [MarkerIconContainer.tripIconContainer3, '/assets/markers/tripIconContainer3.svg']
@ -106,11 +114,11 @@ const emptyIconContainerDefinition: MarkerIconContainerDefinition = {
const defaultIconContainerDefinition: MarkerIconContainerDefinition = { const defaultIconContainerDefinition: MarkerIconContainerDefinition = {
iconSize: 12, iconSize: 12,
iconColor: (color) => tinycolor.mix(color.clone().setAlpha(1), tinycolor('rgba(0,0,0,0.38)')), iconColor: (color) => color,
iconAlpha: color => color.getAlpha() iconAlpha: color => color.getAlpha()
} }
const defaultTripIconContainerDefinition: MarkerIconContainerDefinition = { const defaultMaskIconContainerDefinition: MarkerIconContainerDefinition = {
iconSize: 24, iconSize: 24,
iconColor: () => tinycolor('#000'), iconColor: () => tinycolor('#000'),
iconAlpha: () => 1, iconAlpha: () => 1,
@ -142,9 +150,13 @@ const markerIconContainerDefinitionMap = new Map<MarkerIconContainer, MarkerIcon
[MarkerIconContainer.iconContainer1, defaultIconContainerDefinition], [MarkerIconContainer.iconContainer1, defaultIconContainerDefinition],
[MarkerIconContainer.iconContainer2, defaultIconContainerDefinition], [MarkerIconContainer.iconContainer2, defaultIconContainerDefinition],
[MarkerIconContainer.iconContainer3, defaultIconContainerDefinition], [MarkerIconContainer.iconContainer3, defaultIconContainerDefinition],
[MarkerIconContainer.tripIconContainer1, defaultTripIconContainerDefinition], [MarkerIconContainer.iconContainer4, defaultMaskIconContainerDefinition],
[MarkerIconContainer.tripIconContainer2, {...defaultTripIconContainerDefinition, iconSize: 16}], [MarkerIconContainer.iconContainer5, defaultMaskIconContainerDefinition],
[MarkerIconContainer.tripIconContainer3, {...defaultTripIconContainerDefinition, iconSize: 16}] [MarkerIconContainer.iconContainer6, {...defaultMaskIconContainerDefinition, iconSize: 28}],
[MarkerIconContainer.iconContainer7, {...defaultMaskIconContainerDefinition, iconSize: 28}],
[MarkerIconContainer.tripIconContainer1, defaultMaskIconContainerDefinition],
[MarkerIconContainer.tripIconContainer2, {...defaultMaskIconContainerDefinition, iconSize: 16}],
[MarkerIconContainer.tripIconContainer3, {...defaultMaskIconContainerDefinition, iconSize: 16}]
] ]
); );
@ -177,13 +189,19 @@ export const tripMarkerShapes = [
export const markerIconContainers = [ export const markerIconContainers = [
MarkerIconContainer.iconContainer1, MarkerIconContainer.iconContainer1,
MarkerIconContainer.iconContainer2, MarkerIconContainer.iconContainer2,
MarkerIconContainer.iconContainer3 MarkerIconContainer.iconContainer3,
MarkerIconContainer.iconContainer4,
MarkerIconContainer.iconContainer5,
MarkerIconContainer.iconContainer6,
MarkerIconContainer.iconContainer7
]; ];
export const tripMarkerIconContainers = [ export const tripMarkerIconContainers = [
MarkerIconContainer.tripIconContainer1, MarkerIconContainer.tripIconContainer1,
MarkerIconContainer.tripIconContainer2, MarkerIconContainer.tripIconContainer2,
MarkerIconContainer.tripIconContainer3 MarkerIconContainer.tripIconContainer3,
MarkerIconContainer.iconContainer4,
MarkerIconContainer.iconContainer5
]; ];
const generateElementId = () => { const generateElementId = () => {

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",

14
ui-ngx/src/assets/markers/iconContainer4.svg

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34" class="marker-color" fill="#307FE5">
<circle cx="17" cy="17" r="17" mask="url(#icon-mask)"/>
<circle cx="17" cy="17" r="17" mask="url(#icon-mask-2)" fill="#fff"/>
<mask id="icon-mask" class="icon-mask-exclude">
<g class="marker-icon-container" transform="translate(17,17)">
<rect width="100%" height="100%" fill="#fff" transform="translate(-17,-17)"></rect>
</g>
</mask>
<mask id="icon-mask-2" class="icon-mask-overlay">
<g class="marker-icon-container" transform="translate(17,17)">
<rect width="100%" height="100%" fill="#000" transform="translate(-17,-17)"></rect>
</g>
</mask>
</svg>

After

Width:  |  Height:  |  Size: 711 B

15
ui-ngx/src/assets/markers/iconContainer5.svg

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34" class="marker-color" fill="#307FE5">
<path d="m17 0a17 17 0 0 0-17 17 17 17 0 0 0 17 17 17 17 0 0 0 17-17 17 17 0 0 0-17-17zm0 3a14 14 0 0 1 14 14 14 14 0 0 1-14 14 14 14 0 0 1-14-14 14 14 0 0 1 14-14z" />
<circle cx="17" cy="17" r="14" mask="url(#icon-mask)" fill="#fff"/>
<circle cx="17" cy="17" r="14" mask="url(#icon-mask-2)"/>
<mask id="icon-mask" class="icon-mask-exclude">
<g class="marker-icon-container" transform="translate(17,17)">
<rect width="100%" height="100%" fill="#fff" transform="translate(-17,-17)"></rect>
</g>
</mask>
<mask id="icon-mask-2" class="icon-mask-overlay">
<g class="marker-icon-container" transform="translate(17,17)">
<rect width="100%" height="100%" fill="#000" transform="translate(-17,-17)"></rect>
</g>
</mask>
</svg>

After

Width:  |  Height:  |  Size: 882 B

27
ui-ngx/src/assets/markers/iconContainer6.svg

@ -0,0 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34" class="marker-color" fill="#307FE5">
<rect x="2" y="2" width="30" height="30" rx="4" mask="url(#rect-mask)" fill="#fff" fill-opacity="1" filter="url(#shadow-filter)"></rect>
<rect x="2" y="2" width="30" height="30" rx="4" mask="url(#icon-mask)" fill="#fff"></rect>
<rect x="2" y="2" width="30" height="30" mask="url(#icon-mask-2)"></rect>
<mask id="rect-mask" class="rect-mask-exclude">
<rect width="100%" height="100%" fill="#fff"></rect>
</mask>
<mask id="icon-mask" class="icon-mask-exclude">
<g class="marker-icon-container" transform="translate(17,17)">
<rect width="100%" height="100%" fill="#fff" transform="translate(-17,-17)"></rect>
</g>
</mask>
<mask id="icon-mask-2" class="icon-mask-overlay">
<g class="marker-icon-container" transform="translate(17,17)">
<rect width="100%" height="100%" fill="#000" transform="translate(-17,-17)"></rect>
</g>
</mask>
<defs>
<filter id="shadow-filter" x="-.085714" y="-.085714" width="1.1714" height="1.1714" style="color-interpolation-filters:sRGB">
<feFlood flood-color="rgb(0,0,0)" flood-opacity=".49804" result="flood"/>
<feComposite in="flood" in2="SourceGraphic" operator="in" result="composite1"/>
<feGaussianBlur in="composite1" result="blur" stdDeviation="1"/>
<feOffset dx="0" dy="0" result="offset"/>
<feComposite in="SourceGraphic" in2="offset" result="composite2"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

27
ui-ngx/src/assets/markers/iconContainer7.svg

@ -0,0 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34" class="marker-color" fill="#307FE5">
<rect x="2" y="2" width="30" height="30" rx="4" mask="url(#rect-mask)" fill="#fff" fill-opacity="1" filter="url(#shadow-filter)"></rect>
<rect x="2" y="2" width="30" height="30" rx="4" mask="url(#icon-mask)"></rect>
<rect x="2" y="2" width="30" height="30" mask="url(#icon-mask-2)" fill="#fff"></rect>
<mask id="rect-mask" class="rect-mask-exclude">
<rect width="100%" height="100%" fill="#fff"></rect>
</mask>
<mask id="icon-mask" class="icon-mask-exclude">
<g class="marker-icon-container" transform="translate(17,17)">
<rect width="100%" height="100%" fill="#fff" transform="translate(-17,-17)"></rect>
</g>
</mask>
<mask id="icon-mask-2" class="icon-mask-overlay">
<g class="marker-icon-container" transform="translate(17,17)">
<rect width="100%" height="100%" fill="#000" transform="translate(-17,-17)"></rect>
</g>
</mask>
<defs>
<filter id="shadow-filter" x="-.085714" y="-.085714" width="1.1714" height="1.1714" style="color-interpolation-filters:sRGB">
<feFlood flood-color="rgb(0,0,0)" flood-opacity=".49804" result="flood"/>
<feComposite in="flood" in2="SourceGraphic" operator="in" result="composite1"/>
<feGaussianBlur in="composite1" result="blur" stdDeviation="1"/>
<feOffset dx="0" dy="0" result="offset"/>
<feComposite in="SourceGraphic" in2="offset" result="composite2"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Loading…
Cancel
Save