From c25b57eb39f6d7e043af1aab4a8de9a8399080a8 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 19 Mar 2025 12:04:34 +0200 Subject: [PATCH 01/26] UI: Add new predefined example when created action place map item --- .../common/action/custom-action.models.ts | 12 +++ .../action/place-map-item-sample-html.raw | 82 +++++++++++++++++ .../action/place-map-item-sample-js.raw | 89 +++++++++++++++++++ .../common/action/widget-action.component.ts | 5 +- .../entity/entity-type-select.component.html | 4 +- .../entity/entity-type-select.component.ts | 4 + 6 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/place-map-item-sample-html.raw create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/place-map-item-sample-js.raw diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action.models.ts index 1b63e97ad6..40f1a6c819 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action.models.ts @@ -22,6 +22,8 @@ import { deepClone, isDefined, isUndefined } from '@core/utils'; import customSampleJs from './custom-sample-js.raw'; import customSampleCss from './custom-sample-css.raw'; import customSampleHtml from './custom-sample-html.raw'; +import placeMapItemSampleHtml from './place-map-item-sample-html.raw'; +import placeMapItemSampleJs from './place-map-item-sample-js.raw'; const customActionCompletions: TbEditorCompletions = { ...{ @@ -96,5 +98,15 @@ export const toCustomAction = (action: WidgetAction): CustomActionDescriptor => return result; }; +export const toPlaceMapItemAction = (action: WidgetAction): CustomActionDescriptor => { + const result: CustomActionDescriptor = { + customHtml: action?.customHtml ?? placeMapItemSampleHtml, + customCss: action?.customCss ?? '', + customFunction: action?.customFunction ?? placeMapItemSampleJs + }; + result.customResources = isDefined(action?.customResources) ? deepClone(action.customResources) : []; + return result; +}; + export const CustomActionEditorCompleter = new TbEditorCompleter(customActionCompletions); export const CustomPrettyActionEditorCompleter = new TbEditorCompleter(customPrettyActionCompletions); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/place-map-item-sample-html.raw b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/place-map-item-sample-html.raw new file mode 100644 index 0000000000..bb36a39986 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/place-map-item-sample-html.raw @@ -0,0 +1,82 @@ + + + + +
+ +

Add entity

+ + +
+ + +
+
+
+ + Entity Name + + + Entity name is required. + + + + Entity Label + + +
+
+ + + +
+
+
+ + Address + + + + Owner + + +
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/place-map-item-sample-js.raw b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/place-map-item-sample-js.raw new file mode 100644 index 0000000000..cb8c23faee --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/place-map-item-sample-js.raw @@ -0,0 +1,89 @@ +/*========================================================================*/ +/*========================= Add entity example =========================*/ +/*========================================================================*/ + +let $injector = widgetContext.$scope.$injector; +let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog')); +let assetService = $injector.get(widgetContext.servicesMap.get('assetService')); +let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); +let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService')); + +openAddEntityDialog(); + +function openAddEntityDialog() { + customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe(); +} + +function AddEntityDialogController(instance) { + let vm = instance; + + vm.allowedEntityTypes = ['ASSET', 'DEVICE']; + + vm.addEntityFormGroup = vm.fb.group({ + entityName: ['', [vm.validators.required]], + entityType: ['DEVICE'], + entityLabel: [null], + type: ['', [vm.validators.required]], + attributes: vm.fb.group({ + address: [null], + owner: [null] + }) + }); + + vm.cancel = function() { + vm.dialogRef.close(null); + }; + + vm.save = function() { + vm.addEntityFormGroup.markAsPristine(); + saveEntityObservable().pipe( + widgetContext.rxjs.switchMap((entity) => saveAttributes(entity.id)) + ).subscribe(() => { + widgetContext.updateAliases(); + vm.dialogRef.close(null); + }); + }; + + function saveEntityObservable() { + const formValues = vm.addEntityFormGroup.value; + let entity = { + name: formValues.entityName, + type: formValues.type, + label: formValues.entityLabel + }; + if (formValues.entityType == 'ASSET') { + return assetService.saveAsset(entity); + } else if (formValues.entityType == 'DEVICE') { + return deviceService.saveDevice(entity); + } + } + + function saveAttributes(entityId) { + let attributes = vm.addEntityFormGroup.get('attributes').value; + let attributesArray = getMapItemLocationAttributes(); + for (let key in attributes) { + if(attributes[key] !== null) { + attributesArray.push({key: key, value: attributes[key]}); + } + } + if (attributesArray.length > 0) { + return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray); + } + return widgetContext.rxjs.of([]); + } + + function getMapItemLocationAttributes() { + const attributes = []; + const mapItemType = $event.shape; + if (mapItemType === 'Marker') { + const mapType = widgetContext.mapInstance.type(); + attributes.push({key: mapType === 'image' ? 'xPos' : 'latitude', value: additionalParams.coordinates.x}); + attributes.push({key: mapType === 'image' ? 'yPos' : 'longitude', value: additionalParams.coordinates.y}); + } else if (mapItemType === 'Rectangle' || mapItemType === 'Polygon') { + attributes.push({key: 'perimeter', value: additionalParams.coordinates}); + } else if (mapItemType === 'Circle') { + attributes.push({key: 'circle', value: additionalParams.coordinates}); + } + return attributes; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts index 932c0e52c3..bd0fe36910 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts @@ -48,7 +48,8 @@ import { TranslateService } from '@ngx-translate/core'; import { PopoverPlacement, PopoverPlacements } from '@shared/components/popover.models'; import { CustomActionEditorCompleter, - toCustomAction + toCustomAction, + toPlaceMapItemAction } from '@home/components/widget/lib/settings/common/action/custom-action.models'; import { coerceBoolean } from '@shared/decorators/coercion'; @@ -336,7 +337,7 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali ); this.actionTypeFormGroup.addControl( 'customAction', - this.fb.control(toCustomAction(action), [Validators.required]) + this.fb.control(toPlaceMapItemAction(action), [Validators.required]) ); break; } diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.html index c85fc4eb21..b21734cfdd 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.html @@ -15,9 +15,9 @@ limitations under the License. --> - + {{ 'entity.type' | translate }} - + {{ displayEntityTypeFn(type) }} diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts index 773222dcec..82b3fcf572 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts @@ -32,6 +32,7 @@ import { AliasEntityType, EntityType, entityTypeTranslations } from '@app/shared import { EntityService } from '@core/http/entity.service'; import { coerceBoolean } from '@shared/decorators/coercion'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { MatFormFieldAppearance } from '@angular/material/form-field'; @Component({ selector: 'tb-entity-type-select', @@ -69,6 +70,9 @@ export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit, @Input() disabled: boolean; + @Input() + appearance: MatFormFieldAppearance = 'fill'; + @Input() additionEntityTypes: {[key in string]: string} = {}; From 8eac8ea8c9b731640d0d07b5e88c50b97902a6e4 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 20 Mar 2025 15:16:35 +0200 Subject: [PATCH 02/26] UI: Add map widgets helps --- .../map/map-data-layer-dialog.component.html | 6 +++-- .../common/map/map-settings.component.html | 4 ++-- .../widget/action/custom_additional_params.md | 23 ++++++++++++++++++- .../assets/locale/locale.constant-en_US.json | 4 ++++ 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html index 803cdd01f1..cc2d3ab031 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html @@ -430,7 +430,7 @@ }
-
{{ 'widgets.maps.data-layer.groups' | translate }}
+
{{ 'widgets.maps.data-layer.groups' | translate }}
- {{ 'widgets.maps.data-layer.enable-snapping' | translate }} + + {{ 'widgets.maps.data-layer.enable-snapping' | translate }} +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html index 38a5d13922..b8d9515564 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html @@ -36,7 +36,7 @@
-
+
{{ 'widgets.maps.overlays.overlays' | translate }}
-
+
{{ 'widgets.maps.data-layer.additional-datasources' | translate }}
formattedTs (a string value of formatted timestamp) and
timeseries values for each column declared in widget datasource configuration. - + +
  • Map widgets - additionalParams: FormattedData: +
      +
    • additionalParams: FormattedData - An object associated with a data layer (markers, polygons, circles) or with a specific data point of a route (for trips data layers).
      + It contains basic entity properties (ex. entityId, entityName) and provides access to additional attributes and timeseries defined in datasource of the data layer configuration. +
    • +
    +
  • +
  • Map widgets (Action type: Place map item) - additionalParams: {coordinates: Coordinates; layer: L.Layer}: +
      +
    • coordinates: Coordinates - Represents geographical coordinates of the placed map item. The actual format of this parameter depends on the type of the selected map item: +
        +
      • Marker: {x: number; y: number}, where x represents latitude, and y represents longitude.
      • +
      • Polygon, Rectangle: TbPolygonRawCoordinates contains an array of points defining the shape boundaries.
      • +
      • Circle: TbCircleData contains center coordinates and radius information.
      • +
      + Note: The coordinates will be automatically converted according to the selected map type. +
    • +
    • layer: L.Layer - The Leaflet map layer instance (e.g., marker, polygon, circle) associated with the placed map item. This object provides access to layer properties and methods defined in Leaflet's API. +
    • +
    +
  • Entities hierarchy widget (On node selected) - additionalParams: { nodeCtx: HierarchyNodeContext }:
    • nodeCtx: HierarchyNodeContext - An diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 61bdb48b7c..c294b0210a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -7976,6 +7976,7 @@ }, "overlays": { "overlays": "Overlays", + "overlays-hint": "Configure datasources, appearance, behavior, editing options, and grouping for map entities", "trips": "Trips", "markers": "Markers", "polygons": "Polygons", @@ -7985,6 +7986,7 @@ "source": "Source", "additional-data-keys": "Additional data keys", "additional-datasources": "Additional datasources", + "additional-datasources-hint": "Datasource for accessing attributes or telemetry from entities not displayed on the map, usable in map overlay functions.", "data-keys": "Data keys", "add-datasource": "Add datasource", "no-datasources": "No datasources configured", @@ -7993,6 +7995,7 @@ "on-click": "On click", "on-click-hint": "Action invoked when user clicks on the map item.", "groups": "Groups", + "groups-hint": "List of group names assigned to this datasource. Used to toggle visibility of datasource items on the map.", "color": "Color", "fill-color": "Fill color", "stroke": "Stroke", @@ -8030,6 +8033,7 @@ "edit-instruments": "Instruments", "persist-location-attribute-scope": "Scope of the attribute to persist location", "enable-snapping": "Enable snapping to other vertices for precision drawing", + "enable-snapping-hint": "Automatically aligns new points with existing shapes to make drawing easier and more accurate.", "drag-drop-mode": "Drag-drop mode", "trip": { "no-trips": "No trips configured", From b0758135777634a99438be470077f6b80ea17f24 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 24 Mar 2025 10:17:48 +0200 Subject: [PATCH 03/26] UI: Fixed click area for SCADA symbols --- .../src/main/data/json/system/scada_symbols/apartments-hp.svg | 2 +- .../data/json/system/scada_symbols/bottom-light-bulb-hp.svg | 2 +- .../src/main/data/json/system/scada_symbols/conical-tank.svg | 2 +- .../src/main/data/json/system/scada_symbols/consumers-hp.svg | 2 +- .../json/system/scada_symbols/dynamic-horizontal-scale-hp.svg | 2 +- .../json/system/scada_symbols/dynamic-vertical-scale-hp.svg | 2 +- .../system/scada_symbols/electrical-distribution-board-hp.svg | 2 +- .../src/main/data/json/system/scada_symbols/filter-hp.svg | 2 +- .../json/system/scada_symbols/four-rate-energy-meter-hp.svg | 2 +- .../main/data/json/system/scada_symbols/gas-preventer-hp.svg | 2 +- .../main/data/json/system/scada_symbols/heat-exchanger-hp.svg | 2 +- .../src/main/data/json/system/scada_symbols/heat-pump-hp.svg | 2 +- .../main/data/json/system/scada_symbols/horizontal-tank-hp.svg | 2 +- .../src/main/data/json/system/scada_symbols/house-hp.svg | 2 +- .../json/system/scada_symbols/industrial-fuel-generator-hp.svg | 2 +- .../json/system/scada_symbols/large-horizontal-separator-hp.svg | 2 +- .../main/data/json/system/scada_symbols/large-inverter-hp.svg | 2 +- .../json/system/scada_symbols/large-stand-cylindrical-tank.svg | 2 +- .../json/system/scada_symbols/large-stand-vertical-tank.svg | 2 +- .../json/system/scada_symbols/large-vertical-separator-hp.svg | 2 +- .../src/main/data/json/system/scada_symbols/leak-sensor.svg | 2 +- .../data/json/system/scada_symbols/low-voltage-tower-hp.svg | 2 +- application/src/main/data/json/system/scada_symbols/meter.svg | 2 +- .../src/main/data/json/system/scada_symbols/oil-pump-hp.svg | 2 +- application/src/main/data/json/system/scada_symbols/pool-hp.svg | 2 +- application/src/main/data/json/system/scada_symbols/pool.svg | 2 +- .../src/main/data/json/system/scada_symbols/power-socket-hp.svg | 2 +- .../data/json/system/scada_symbols/short-vertical-tank-hp.svg | 2 +- .../json/system/scada_symbols/simple-horizontal-scale-hp.svg | 2 +- .../data/json/system/scada_symbols/simple-vertical-scale-hp.svg | 2 +- .../data/json/system/scada_symbols/small-cylindrical-tank.svg | 2 +- .../scada_symbols/small-horizontal-separator-connector-hp.svg | 2 +- .../main/data/json/system/scada_symbols/small-left-meter.svg | 2 +- .../scada_symbols/small-vertical-separator-connector-hp.svg | 2 +- .../json/system/scada_symbols/small-vertical-separator-hp.svg | 2 +- .../src/main/data/json/system/scada_symbols/spherical-tank.svg | 2 +- .../data/json/system/scada_symbols/stand-cylindrical-tank.svg | 2 +- .../json/system/scada_symbols/stand-vertical-short-tank.svg | 2 +- .../main/data/json/system/scada_symbols/top-light-bulb-hp.svg | 2 +- .../src/main/data/json/system/scada_symbols/turbine-hp.svg | 2 +- .../scada_symbols/vertical-energy-system-controller-hp.svg | 2 +- .../main/data/json/system/scada_symbols/vertical-tank-hp.svg | 2 +- .../data/json/system/scada_symbols/voltage-stabilizer-hp.svg | 2 +- .../data/json/system/scada_symbols/wind-turbine-cluster-hp.svg | 2 +- .../src/main/data/json/system/scada_symbols/wind-turbine-hp.svg | 2 +- 45 files changed, 45 insertions(+), 45 deletions(-) diff --git a/application/src/main/data/json/system/scada_symbols/apartments-hp.svg b/application/src/main/data/json/system/scada_symbols/apartments-hp.svg index 9e2c965f79..97ac492626 100644 --- a/application/src/main/data/json/system/scada_symbols/apartments-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/apartments-hp.svg @@ -330,7 +330,7 @@ } ] }]]> - + diff --git a/application/src/main/data/json/system/scada_symbols/bottom-light-bulb-hp.svg b/application/src/main/data/json/system/scada_symbols/bottom-light-bulb-hp.svg index 4f0603b904..db107240a0 100644 --- a/application/src/main/data/json/system/scada_symbols/bottom-light-bulb-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/bottom-light-bulb-hp.svg @@ -332,7 +332,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/conical-tank.svg b/application/src/main/data/json/system/scada_symbols/conical-tank.svg index 7ae8f41934..6b37adc52e 100644 --- a/application/src/main/data/json/system/scada_symbols/conical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/conical-tank.svg @@ -371,7 +371,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/consumers-hp.svg b/application/src/main/data/json/system/scada_symbols/consumers-hp.svg index e4005d7ae2..8333d6bf55 100644 --- a/application/src/main/data/json/system/scada_symbols/consumers-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/consumers-hp.svg @@ -332,7 +332,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg index e782b07609..b3506ae54f 100644 --- a/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/dynamic-horizontal-scale-hp.svg @@ -791,5 +791,5 @@ - + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg index 960abed340..3c52dae1c3 100644 --- a/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/dynamic-vertical-scale-hp.svg @@ -791,5 +791,5 @@ 26 - + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/electrical-distribution-board-hp.svg b/application/src/main/data/json/system/scada_symbols/electrical-distribution-board-hp.svg index 0fcfe4dde0..6e4175c6d7 100644 --- a/application/src/main/data/json/system/scada_symbols/electrical-distribution-board-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/electrical-distribution-board-hp.svg @@ -318,7 +318,7 @@ }]]> - + diff --git a/application/src/main/data/json/system/scada_symbols/filter-hp.svg b/application/src/main/data/json/system/scada_symbols/filter-hp.svg index 58ff3951e5..1f4516723e 100644 --- a/application/src/main/data/json/system/scada_symbols/filter-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/filter-hp.svg @@ -273,7 +273,7 @@ } ] }]]> - + diff --git a/application/src/main/data/json/system/scada_symbols/four-rate-energy-meter-hp.svg b/application/src/main/data/json/system/scada_symbols/four-rate-energy-meter-hp.svg index de4fb13836..bba67e5fe3 100644 --- a/application/src/main/data/json/system/scada_symbols/four-rate-energy-meter-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/four-rate-energy-meter-hp.svg @@ -862,7 +862,7 @@ } ] }]]> -T1T2T3Export000223000223000223000223kWh +T1T2T3Export000223000223000223000223kWh diff --git a/application/src/main/data/json/system/scada_symbols/gas-preventer-hp.svg b/application/src/main/data/json/system/scada_symbols/gas-preventer-hp.svg index ea5a479eda..487b5e54d9 100644 --- a/application/src/main/data/json/system/scada_symbols/gas-preventer-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/gas-preventer-hp.svg @@ -347,7 +347,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/heat-exchanger-hp.svg b/application/src/main/data/json/system/scada_symbols/heat-exchanger-hp.svg index 797220ea96..c1c3d4647e 100644 --- a/application/src/main/data/json/system/scada_symbols/heat-exchanger-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/heat-exchanger-hp.svg @@ -349,7 +349,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/heat-pump-hp.svg b/application/src/main/data/json/system/scada_symbols/heat-pump-hp.svg index 1d7babc3b0..eed54682cd 100644 --- a/application/src/main/data/json/system/scada_symbols/heat-pump-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/heat-pump-hp.svg @@ -587,7 +587,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg b/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg index 18f194684b..bb6f5e55a9 100644 --- a/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/horizontal-tank-hp.svg @@ -502,7 +502,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/house-hp.svg b/application/src/main/data/json/system/scada_symbols/house-hp.svg index d95c47bb8b..e3556d4d9a 100644 --- a/application/src/main/data/json/system/scada_symbols/house-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/house-hp.svg @@ -335,7 +335,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/industrial-fuel-generator-hp.svg b/application/src/main/data/json/system/scada_symbols/industrial-fuel-generator-hp.svg index 2b7a476a92..e0ee85b8cf 100644 --- a/application/src/main/data/json/system/scada_symbols/industrial-fuel-generator-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/industrial-fuel-generator-hp.svg @@ -344,7 +344,7 @@ } ] }]]> - + diff --git a/application/src/main/data/json/system/scada_symbols/large-horizontal-separator-hp.svg b/application/src/main/data/json/system/scada_symbols/large-horizontal-separator-hp.svg index 79f6745a73..8179549c54 100644 --- a/application/src/main/data/json/system/scada_symbols/large-horizontal-separator-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/large-horizontal-separator-hp.svg @@ -336,7 +336,7 @@ } ] }]]> - + diff --git a/application/src/main/data/json/system/scada_symbols/large-inverter-hp.svg b/application/src/main/data/json/system/scada_symbols/large-inverter-hp.svg index 11bc5da0a6..bf18d64040 100644 --- a/application/src/main/data/json/system/scada_symbols/large-inverter-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/large-inverter-hp.svg @@ -564,7 +564,7 @@ } ] }]]> - + diff --git a/application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg b/application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg index 11bd47916f..09c0e2a9e1 100644 --- a/application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg @@ -565,7 +565,7 @@ 1660 gal - + diff --git a/application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg b/application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg index d9c05bde40..be8b1207a0 100644 --- a/application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg @@ -569,7 +569,7 @@ 1660 gal - + diff --git a/application/src/main/data/json/system/scada_symbols/large-vertical-separator-hp.svg b/application/src/main/data/json/system/scada_symbols/large-vertical-separator-hp.svg index 99ff420530..1951b741bf 100644 --- a/application/src/main/data/json/system/scada_symbols/large-vertical-separator-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/large-vertical-separator-hp.svg @@ -336,7 +336,7 @@ } ] }]]> - + diff --git a/application/src/main/data/json/system/scada_symbols/leak-sensor.svg b/application/src/main/data/json/system/scada_symbols/leak-sensor.svg index 0fce8be608..813116a1f5 100644 --- a/application/src/main/data/json/system/scada_symbols/leak-sensor.svg +++ b/application/src/main/data/json/system/scada_symbols/leak-sensor.svg @@ -135,7 +135,7 @@ } ] }]]> - + diff --git a/application/src/main/data/json/system/scada_symbols/low-voltage-tower-hp.svg b/application/src/main/data/json/system/scada_symbols/low-voltage-tower-hp.svg index 812e616433..002bd9cfae 100644 --- a/application/src/main/data/json/system/scada_symbols/low-voltage-tower-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/low-voltage-tower-hp.svg @@ -264,7 +264,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/meter.svg b/application/src/main/data/json/system/scada_symbols/meter.svg index 02b2833133..d5fa9f7cd7 100644 --- a/application/src/main/data/json/system/scada_symbols/meter.svg +++ b/application/src/main/data/json/system/scada_symbols/meter.svg @@ -713,7 +713,7 @@ 37% - + diff --git a/application/src/main/data/json/system/scada_symbols/oil-pump-hp.svg b/application/src/main/data/json/system/scada_symbols/oil-pump-hp.svg index e7dfa8f0c8..b1ff6ea2c0 100644 --- a/application/src/main/data/json/system/scada_symbols/oil-pump-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/oil-pump-hp.svg @@ -343,7 +343,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/pool-hp.svg b/application/src/main/data/json/system/scada_symbols/pool-hp.svg index 925ea8906a..0ce78af7d9 100644 --- a/application/src/main/data/json/system/scada_symbols/pool-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/pool-hp.svg @@ -501,7 +501,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/pool.svg b/application/src/main/data/json/system/scada_symbols/pool.svg index f5d0bd7ad7..6f8b12737c 100644 --- a/application/src/main/data/json/system/scada_symbols/pool.svg +++ b/application/src/main/data/json/system/scada_symbols/pool.svg @@ -292,7 +292,7 @@ 1660 gal - + diff --git a/application/src/main/data/json/system/scada_symbols/power-socket-hp.svg b/application/src/main/data/json/system/scada_symbols/power-socket-hp.svg index 5524b3bb14..2331242475 100644 --- a/application/src/main/data/json/system/scada_symbols/power-socket-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/power-socket-hp.svg @@ -357,7 +357,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg b/application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg index 046c5c5802..cd15a16577 100644 --- a/application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/short-vertical-tank-hp.svg @@ -502,7 +502,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg index 26cee5cd30..1b91a0cd4a 100644 --- a/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/simple-horizontal-scale-hp.svg @@ -719,5 +719,5 @@ - + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg b/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg index fa0c0fc4d1..7b8e7299b8 100644 --- a/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/simple-vertical-scale-hp.svg @@ -719,5 +719,5 @@ 26 - + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/small-cylindrical-tank.svg b/application/src/main/data/json/system/scada_symbols/small-cylindrical-tank.svg index d4ddda1895..1d6785d0c1 100644 --- a/application/src/main/data/json/system/scada_symbols/small-cylindrical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/small-cylindrical-tank.svg @@ -537,7 +537,7 @@ 1660 gal - + diff --git a/application/src/main/data/json/system/scada_symbols/small-horizontal-separator-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/small-horizontal-separator-connector-hp.svg index dedc4dc81e..abb6d9b4ab 100644 --- a/application/src/main/data/json/system/scada_symbols/small-horizontal-separator-connector-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/small-horizontal-separator-connector-hp.svg @@ -328,7 +328,7 @@ } ] }]]> - + diff --git a/application/src/main/data/json/system/scada_symbols/small-left-meter.svg b/application/src/main/data/json/system/scada_symbols/small-left-meter.svg index a480412366..129006ffd7 100644 --- a/application/src/main/data/json/system/scada_symbols/small-left-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/small-left-meter.svg @@ -713,5 +713,5 @@ 37% - + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/small-vertical-separator-connector-hp.svg b/application/src/main/data/json/system/scada_symbols/small-vertical-separator-connector-hp.svg index b248996612..e9cc074cb6 100644 --- a/application/src/main/data/json/system/scada_symbols/small-vertical-separator-connector-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/small-vertical-separator-connector-hp.svg @@ -328,7 +328,7 @@ } ] }]]> - + diff --git a/application/src/main/data/json/system/scada_symbols/small-vertical-separator-hp.svg b/application/src/main/data/json/system/scada_symbols/small-vertical-separator-hp.svg index 25aec080b3..0c7455e534 100644 --- a/application/src/main/data/json/system/scada_symbols/small-vertical-separator-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/small-vertical-separator-hp.svg @@ -328,7 +328,7 @@ } ] }]]> - + diff --git a/application/src/main/data/json/system/scada_symbols/spherical-tank.svg b/application/src/main/data/json/system/scada_symbols/spherical-tank.svg index cba49c63e0..f5d679fa96 100644 --- a/application/src/main/data/json/system/scada_symbols/spherical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/spherical-tank.svg @@ -572,7 +572,7 @@ 1660 gal - + diff --git a/application/src/main/data/json/system/scada_symbols/stand-cylindrical-tank.svg b/application/src/main/data/json/system/scada_symbols/stand-cylindrical-tank.svg index a78060dfaf..f5b8e8a892 100644 --- a/application/src/main/data/json/system/scada_symbols/stand-cylindrical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/stand-cylindrical-tank.svg @@ -567,7 +567,7 @@ 1660 gal - + diff --git a/application/src/main/data/json/system/scada_symbols/stand-vertical-short-tank.svg b/application/src/main/data/json/system/scada_symbols/stand-vertical-short-tank.svg index 96df175589..0d56901bfe 100644 --- a/application/src/main/data/json/system/scada_symbols/stand-vertical-short-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/stand-vertical-short-tank.svg @@ -1364,5 +1364,5 @@ - + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/top-light-bulb-hp.svg b/application/src/main/data/json/system/scada_symbols/top-light-bulb-hp.svg index ed6855884a..fb419bac2a 100644 --- a/application/src/main/data/json/system/scada_symbols/top-light-bulb-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/top-light-bulb-hp.svg @@ -332,7 +332,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/turbine-hp.svg b/application/src/main/data/json/system/scada_symbols/turbine-hp.svg index d798b09477..2e0e176cfd 100644 --- a/application/src/main/data/json/system/scada_symbols/turbine-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/turbine-hp.svg @@ -363,7 +363,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/vertical-energy-system-controller-hp.svg b/application/src/main/data/json/system/scada_symbols/vertical-energy-system-controller-hp.svg index cebe949c36..6da68556a2 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-energy-system-controller-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-energy-system-controller-hp.svg @@ -364,7 +364,7 @@ } ] }]]> -Connected +Connected diff --git a/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg b/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg index 491fb4477e..9dee6cc2af 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-tank-hp.svg @@ -502,7 +502,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/voltage-stabilizer-hp.svg b/application/src/main/data/json/system/scada_symbols/voltage-stabilizer-hp.svg index aafc2af5a0..2ccad581d4 100644 --- a/application/src/main/data/json/system/scada_symbols/voltage-stabilizer-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/voltage-stabilizer-hp.svg @@ -570,7 +570,7 @@ } ] }]]> -220230inout +220230inout diff --git a/application/src/main/data/json/system/scada_symbols/wind-turbine-cluster-hp.svg b/application/src/main/data/json/system/scada_symbols/wind-turbine-cluster-hp.svg index 74855dd35a..dc8af28f59 100644 --- a/application/src/main/data/json/system/scada_symbols/wind-turbine-cluster-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/wind-turbine-cluster-hp.svg @@ -356,7 +356,7 @@ - + diff --git a/application/src/main/data/json/system/scada_symbols/wind-turbine-hp.svg b/application/src/main/data/json/system/scada_symbols/wind-turbine-hp.svg index a4282c16e7..b2c65988da 100644 --- a/application/src/main/data/json/system/scada_symbols/wind-turbine-hp.svg +++ b/application/src/main/data/json/system/scada_symbols/wind-turbine-hp.svg @@ -346,7 +346,7 @@ - + From 4d18579daf06fe50c2a1636de71ac91ba547164f Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 24 Mar 2025 11:11:14 +0200 Subject: [PATCH 04/26] UI: Add new help place map item; improved map hint and additionalParams map description --- ...custom-action-pretty-editor.component.html | 4 +- .../custom-action-pretty-editor.component.ts | 40 ++++---- ...ction-pretty-resources-tabs.component.html | 2 +- ...-action-pretty-resources-tabs.component.ts | 18 ++-- .../action/widget-action.component.html | 3 +- .../entity/entity-type-select.component.ts | 25 +---- .../widget/action/custom_additional_params.md | 16 +--- .../place_map_item/create_dialog_html.md | 87 +++++++++++++++++ .../action/place_map_item/create_dialog_js.md | 94 +++++++++++++++++++ .../place_map_item/place_map_item_action.md | 66 +++++++++++++ .../assets/locale/locale.constant-en_US.json | 2 +- 11 files changed, 288 insertions(+), 69 deletions(-) create mode 100644 ui-ngx/src/assets/help/en_US/widget/action/place_map_item/create_dialog_html.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/action/place_map_item/create_dialog_js.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/action/place_map_item/place_map_item_action.md diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component.html index 0d53bc6c44..abcc4ad7af 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component.html @@ -37,6 +37,7 @@
      @@ -44,6 +45,7 @@
      @@ -58,7 +60,7 @@ [validationArgs]="[]" [editorCompleter]="customPrettyActionEditorCompleter" functionTitle="{{ 'widget-action.custom-pretty-function' | translate }}" - helpId="widget/action/custom_pretty_action_fn"> + [helpId]="helpId">
      diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component.ts index 2e177eb918..77f835a668 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-editor.component.ts @@ -14,28 +14,22 @@ /// limitations under the License. /// -// eslint-disable-next-line @typescript-eslint/triple-slash-reference -/// - import { AfterViewInit, Component, ElementRef, forwardRef, Input, - OnDestroy, - OnInit, QueryList, ViewChildren, ViewEncapsulation } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { PageComponent } from '@shared/components/page.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; import { combineLatest } from 'rxjs'; -import { CustomActionDescriptor } from '@shared/models/widget.models'; -import { CustomPrettyActionEditorCompleter } from '@home/components/widget/lib/settings/common/action/custom-action.models'; +import { CustomActionDescriptor, WidgetActionType } from '@shared/models/widget.models'; +import { + CustomPrettyActionEditorCompleter +} from '@home/components/widget/lib/settings/common/action/custom-action.models'; @Component({ selector: 'tb-custom-action-pretty-editor', @@ -50,7 +44,7 @@ import { CustomPrettyActionEditorCompleter } from '@home/components/widget/lib/s ], encapsulation: ViewEncapsulation.None }) -export class CustomActionPrettyEditorComponent extends PageComponent implements OnInit, AfterViewInit, OnDestroy, ControlValueAccessor { +export class CustomActionPrettyEditorComponent implements AfterViewInit, ControlValueAccessor { @Input() disabled: boolean; @@ -58,6 +52,17 @@ export class CustomActionPrettyEditorComponent extends PageComponent implements fullscreen = false; + helpId= 'widget/action/custom_pretty_action_fn'; + + @Input() + set widgetActionType(type: WidgetActionType) { + if (type === WidgetActionType.placeMapItem) { + this.helpId = 'widget/action/place_map_item/place_map_item_action'; + } else { + this.helpId = 'widget/action/custom_pretty_action_fn'; + } + } + @ViewChildren('leftPanel') leftPanelElmRef: QueryList>; @@ -68,15 +73,11 @@ export class CustomActionPrettyEditorComponent extends PageComponent implements private propagateChange = (_: any) => {}; - constructor(protected store: Store) { - super(store); - } - - ngOnInit(): void { + constructor() { } ngAfterViewInit(): void { - combineLatest(this.leftPanelElmRef.changes, this.rightPanelElmRef.changes).subscribe(() => { + combineLatest([this.leftPanelElmRef.changes, this.rightPanelElmRef.changes]).subscribe(() => { if (this.leftPanelElmRef.length && this.rightPanelElmRef.length) { this.initSplitLayout(this.leftPanelElmRef.first.nativeElement, this.rightPanelElmRef.first.nativeElement); @@ -92,14 +93,11 @@ export class CustomActionPrettyEditorComponent extends PageComponent implements }); } - ngOnDestroy(): void { - } - registerOnChange(fn: any): void { this.propagateChange = fn; } - registerOnTouched(fn: any): void { + registerOnTouched(_fn: any): void { } setDisabledState(isDisabled: boolean): void { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component.html index 9f351345c1..0852e7cc0d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component.html @@ -101,7 +101,7 @@ [validationArgs]="[]" [editorCompleter]="customPrettyActionEditorCompleter" functionTitle="{{ 'widget-action.custom-pretty-function' | translate }}" - helpId="widget/action/custom_pretty_action_fn"> + [helpId]="helpId"> diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component.ts index 5c65e23b58..367f90d160 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component.ts @@ -27,16 +27,15 @@ import { ViewChild, ViewEncapsulation } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; import { PageComponent } from '@shared/components/page.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; import { CustomActionDescriptor } from '@shared/models/widget.models'; import { Ace } from 'ace-builds'; import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; -import { CustomPrettyActionEditorCompleter } from '@home/components/widget/lib/settings/common/action/custom-action.models'; +import { + CustomPrettyActionEditorCompleter +} from '@home/components/widget/lib/settings/common/action/custom-action.models'; import { Observable } from 'rxjs/internal/Observable'; -import { forkJoin, from } from 'rxjs'; +import { forkJoin } from 'rxjs'; import { map, tap } from 'rxjs/operators'; import { getAce } from '@shared/models/ace/ace.models'; import { beautifyCss, beautifyHtml } from '@shared/models/beautify.models'; @@ -55,6 +54,9 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl @Input() hasCustomFunction: boolean; + @Input() + helpId: string; + @Output() actionUpdated: EventEmitter = new EventEmitter(); @@ -76,10 +78,8 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl customPrettyActionEditorCompleter = CustomPrettyActionEditorCompleter; - constructor(protected store: Store, - private translate: TranslateService, - private raf: RafService) { - super(store); + constructor(private raf: RafService) { + super(); } ngOnInit(): void { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html index ca2405b8b4..c4c90067dd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html @@ -274,7 +274,8 @@ || widgetActionFormGroup.get('type').value === widgetActionType.placeMapItem ? widgetActionFormGroup.get('type').value : ''"> + [widgetActionType]="widgetActionFormGroup.get('type').value" + formControlName="customAction"> diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts index 82b3fcf572..ce599a0c08 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts @@ -14,19 +14,8 @@ /// limitations under the License. /// -import { - AfterViewInit, - Component, - DestroyRef, - forwardRef, - Input, - OnChanges, - OnInit, - SimpleChanges -} from '@angular/core'; +import { Component, DestroyRef, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; -import { Store } from '@ngrx/store'; -import { AppState } from '@app/core/core.state'; import { TranslateService } from '@ngx-translate/core'; import { AliasEntityType, EntityType, entityTypeTranslations } from '@app/shared/models/entity-type.models'; import { EntityService } from '@core/http/entity.service'; @@ -44,7 +33,7 @@ import { MatFormFieldAppearance } from '@angular/material/form-field'; multi: true }] }) -export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnChanges { +export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit, OnChanges { entityTypeFormGroup: UntypedFormGroup; @@ -71,17 +60,16 @@ export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit, disabled: boolean; @Input() - appearance: MatFormFieldAppearance = 'fill'; + additionEntityTypes: {[key in string]: string} = {}; @Input() - additionEntityTypes: {[key in string]: string} = {}; + appearance: MatFormFieldAppearance = 'fill'; entityTypes: Array; private propagateChange = (v: any) => { }; - constructor(private store: Store, - private entityService: EntityService, + constructor(private entityService: EntityService, public translate: TranslateService, private fb: UntypedFormBuilder, private destroyRef: DestroyRef) { @@ -140,9 +128,6 @@ export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit, } } - ngAfterViewInit(): void { - } - setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { diff --git a/ui-ngx/src/assets/help/en_US/widget/action/custom_additional_params.md b/ui-ngx/src/assets/help/en_US/widget/action/custom_additional_params.md index 35e5992b9f..704e25b7f1 100644 --- a/ui-ngx/src/assets/help/en_US/widget/action/custom_additional_params.md +++ b/ui-ngx/src/assets/help/en_US/widget/action/custom_additional_params.md @@ -32,27 +32,13 @@ An optional key/value object holding additional entity parameters depending on w
  • -
  • Map widgets - additionalParams: FormattedData: +
  • Map widgets (On marker/polygon/circle click or Tag action) - additionalParams: FormattedData:
    • additionalParams: FormattedData - An object associated with a data layer (markers, polygons, circles) or with a specific data point of a route (for trips data layers).
      It contains basic entity properties (ex. entityId, entityName) and provides access to additional attributes and timeseries defined in datasource of the data layer configuration.
  • -
  • Map widgets (Action type: Place map item) - additionalParams: {coordinates: Coordinates; layer: L.Layer}: -
      -
    • coordinates: Coordinates - Represents geographical coordinates of the placed map item. The actual format of this parameter depends on the type of the selected map item: -
        -
      • Marker: {x: number; y: number}, where x represents latitude, and y represents longitude.
      • -
      • Polygon, Rectangle: TbPolygonRawCoordinates contains an array of points defining the shape boundaries.
      • -
      • Circle: TbCircleData contains center coordinates and radius information.
      • -
      - Note: The coordinates will be automatically converted according to the selected map type. -
    • -
    • layer: L.Layer - The Leaflet map layer instance (e.g., marker, polygon, circle) associated with the placed map item. This object provides access to layer properties and methods defined in Leaflet's API. -
    • -
    -
  • Entities hierarchy widget (On node selected) - additionalParams: { nodeCtx: HierarchyNodeContext }:
    • nodeCtx: HierarchyNodeContext - An diff --git a/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/create_dialog_html.md b/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/create_dialog_html.md new file mode 100644 index 0000000000..c3a58747e8 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/create_dialog_html.md @@ -0,0 +1,87 @@ +#### HTML template of dialog to create a device or an asset + +```html +{:code-style="max-height: 400px;"} +
      + +

      Add entity

      + + +
      + + +
      +
      +
      + + Entity Name + + + Entity name is required. + + + + Entity Label + + +
      +
      + + + +
      +
      +
      + + Address + + + + Owner + + +
      +
      +
      +
      + + +
      +
      +{:copy-code} +``` + +
      +
      diff --git a/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/create_dialog_js.md b/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/create_dialog_js.md new file mode 100644 index 0000000000..bc8823c777 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/create_dialog_js.md @@ -0,0 +1,94 @@ +#### Function displaying dialog to create a device or an asset + +```javascript +{:code-style="max-height: 400px;"} +let $injector = widgetContext.$scope.$injector; +let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog')); +let assetService = $injector.get(widgetContext.servicesMap.get('assetService')); +let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService')); +let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService')); + +openAddEntityDialog(); + +function openAddEntityDialog() { + customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe(); +} + +function AddEntityDialogController(instance) { + let vm = instance; + + vm.allowedEntityTypes = ['ASSET', 'DEVICE']; + + vm.addEntityFormGroup = vm.fb.group({ + entityName: ['', [vm.validators.required]], + entityType: ['DEVICE'], + entityLabel: [null], + type: ['', [vm.validators.required]], + attributes: vm.fb.group({ + address: [null], + owner: [null] + }) + }); + + vm.cancel = function() { + vm.dialogRef.close(null); + }; + + vm.save = function() { + vm.addEntityFormGroup.markAsPristine(); + saveEntityObservable().pipe( + widgetContext.rxjs.switchMap((entity) => saveAttributes(entity.id)) + ).subscribe(() => { + widgetContext.updateAliases(); + vm.dialogRef.close(null); + }); + }; + + function saveEntityObservable() { + const formValues = vm.addEntityFormGroup.value; + let entity = { + name: formValues.entityName, + type: formValues.type, + label: formValues.entityLabel + }; + if (formValues.entityType == 'ASSET') { + return assetService.saveAsset(entity); + } else if (formValues.entityType == 'DEVICE') { + return deviceService.saveDevice(entity); + } + } + + function saveAttributes(entityId) { + let attributes = vm.addEntityFormGroup.get('attributes').value; + let attributesArray = getMapItemLocationAttributes(); + for (let key in attributes) { + if(attributes[key] !== null) { + attributesArray.push({key: key, value: attributes[key]}); + } + } + if (attributesArray.length > 0) { + return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray); + } + return widgetContext.rxjs.of([]); + } + + function getMapItemLocationAttributes() { + const attributes = []; + const mapItemType = $event.shape; + if (mapItemType === 'Marker') { + const mapType = widgetContext.mapInstance.type(); + attributes.push({key: mapType === 'image' ? 'xPos' : 'latitude', value: additionalParams.coordinates.x}); + attributes.push({key: mapType === 'image' ? 'yPos' : 'longitude', value: additionalParams.coordinates.y}); + } else if (mapItemType === 'Rectangle' || mapItemType === 'Polygon') { + attributes.push({key: 'perimeter', value: additionalParams.coordinates}); + } else if (mapItemType === 'Circle') { + attributes.push({key: 'circle', value: additionalParams.coordinates}); + } + return attributes; + } +} +{:copy-code} +``` + +
      +
      diff --git a/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/place_map_item_action.md b/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/place_map_item_action.md new file mode 100644 index 0000000000..131a674099 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/place_map_item_action.md @@ -0,0 +1,66 @@ +#### Place map item function + +
      +
      + +*function ($event, widgetContext, entityId, entityName, htmlTemplate, additionalParams, entityLabel): void* + +A JavaScript function triggered after a map item is placed. Optionally uses an HTML template to render dialog. + +**Parameters:** + +
        +
      • $event: {shape: PM.SUPPORTED_SHAPES; layer: L.Layer} - Event payload containing the created shape type and its associated map layer. +
      • +
      • widgetContext: WidgetContext - A reference to WidgetContext that has all necessary API + and data used by widget instance. +
      • +
      • entityId: string - An optional string id of the target entity. +
      • +
      • entityName: string - An optional string name of the target entity. +
      • +
      • htmlTemplate: string - An optional HTML template string defined in HTML tab.
        Used to render custom dialog (see Examples for more details). +
      • +
      • additionalParams: {coordinates: Coordinates; layer: L.Layer}: +
          +
        • coordinates: Coordinates - Represents geographical coordinates of the placed map item. The actual format of this parameter depends on the type of the selected map item: +
            +
          • Marker: {x: number; y: number}, where x represents latitude, and y represents longitude.
          • +
          • Polygon, Rectangle: TbPolygonRawCoordinates contains an array of points defining the shape boundaries.
          • +
          • Circle: TbCircleData contains center coordinates and radius information.
          • +
          + Note: The coordinates will be automatically converted according to the selected map type. +
        • +
        • layer: L.Layer - The Leaflet map layer instance (e.g., marker, polygon, circle) associated with the placed map item. This object provides access to layer properties and methods defined in Leaflet's API. +
        • +
        +
      • +
      • entityLabel: string - An optional string label of the target entity. +
      • +
      + +
      + +##### Examples + +###### Display dialog to create a device or an asset + +
      + +
      +
      + +
      + +
      +
      diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index c294b0210a..9cb2070cd2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -7995,7 +7995,7 @@ "on-click": "On click", "on-click-hint": "Action invoked when user clicks on the map item.", "groups": "Groups", - "groups-hint": "List of group names assigned to this datasource. Used to toggle visibility of datasource items on the map.", + "groups-hint": "List of group names assigned to the overlay, used to toggle its visibility on the map.", "color": "Color", "fill-color": "Fill color", "stroke": "Stroke", From 9d25db32c68a58dc331524989750af002419104a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 24 Mar 2025 15:33:03 +0100 Subject: [PATCH 05/26] Refactored initialization chain, PartitionChangeEvent should be processed after actors startup --- ...faultTbCalculatedFieldConsumerService.java | 18 ++-- .../DefaultTbRuleEngineConsumerService.java | 17 ++-- .../AbstractConsumerPartitionedService.java | 94 +++++++++++++++++++ 3 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerPartitionedService.java diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java index 9ae06309c3..bce1992932 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.queue; -import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; @@ -55,7 +54,7 @@ import org.thingsboard.server.service.cf.CalculatedFieldCache; import org.thingsboard.server.service.cf.CalculatedFieldStateService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.queue.processing.AbstractConsumerService; +import org.thingsboard.server.service.queue.processing.AbstractConsumerPartitionedService; import org.thingsboard.server.service.queue.processing.IdMsgPair; import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; @@ -72,7 +71,7 @@ import java.util.stream.Collectors; @Service @TbRuleEngineComponent @Slf4j -public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerService implements TbCalculatedFieldConsumerService { +public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerPartitionedService implements TbCalculatedFieldConsumerService { @Value("${queue.calculated_fields.poll_interval:25}") private long pollInterval; @@ -99,10 +98,8 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer this.stateService = stateService; } - @PostConstruct - public void init() { - super.init("tb-cf"); - + @Override + protected void doAfterStartUp() { var queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME); PartitionedQueueConsumerManager> eventConsumer = PartitionedQueueConsumerManager.>create() .queueKey(queueKey) @@ -129,7 +126,7 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer } @Override - protected void onTbApplicationEvent(PartitionChangeEvent event) { + protected void processPartitionChangeEvent(PartitionChangeEvent event) { try { event.getNewPartitions().forEach((queueKey, partitions) -> { if (queueKey.getQueueName().equals(DataConstants.CF_QUEUE_NAME)) { @@ -146,6 +143,11 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer } } + @Override + protected String getPrefix() { + return "tb-cf"; + } + private void processMsgs(List> msgs, TbQueueConsumer> consumer, QueueConfig config) throws Exception { List> orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).toList(); ConcurrentMap> pendingMap = orderedMsgList.stream().collect( diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java index 9cc743e510..3a9be3874a 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.queue; -import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; @@ -50,7 +49,7 @@ import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.cf.CalculatedFieldCache; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.queue.processing.AbstractConsumerService; +import org.thingsboard.server.service.queue.processing.AbstractConsumerPartitionedService; import org.thingsboard.server.service.queue.ruleengine.TbRuleEngineConsumerContext; import org.thingsboard.server.service.queue.ruleengine.TbRuleEngineQueueConsumerManager; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; @@ -67,7 +66,7 @@ import java.util.stream.Collectors; @Service @TbRuleEngineComponent @Slf4j -public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService implements TbRuleEngineConsumerService { +public class DefaultTbRuleEngineConsumerService extends AbstractConsumerPartitionedService implements TbRuleEngineConsumerService { private final TbRuleEngineConsumerContext ctx; private final QueueService queueService; @@ -93,9 +92,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< this.queueService = queueService; } - @PostConstruct - public void init() { - super.init("tb-rule-engine"); + @Override + protected void doAfterStartUp() { List queues = queueService.findAllQueues(); for (Queue configuration : queues) { if (partitionService.isManagedByCurrentService(configuration.getTenantId())) { @@ -106,7 +104,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< } @Override - protected void onTbApplicationEvent(PartitionChangeEvent event) { + protected void processPartitionChangeEvent(PartitionChangeEvent event) { event.getNewPartitions().forEach((queueKey, partitions) -> { if (DataConstants.CF_QUEUE_NAME.equals(queueKey.getQueueName()) || DataConstants.CF_STATES_QUEUE_NAME.equals(queueKey.getQueueName())) { return; @@ -138,6 +136,11 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< }); } + @Override + protected String getPrefix() { + return "tb-rule-engine"; + } + @Override protected void stopConsumers() { super.stopConsumers(); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerPartitionedService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerPartitionedService.java new file mode 100644 index 0000000000..94b5390be1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerPartitionedService.java @@ -0,0 +1,94 @@ +/** + * Copyright © 2016-2025 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.queue.processing; + +import jakarta.annotation.PostConstruct; +import org.springframework.context.ApplicationEventPublisher; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; +import org.thingsboard.server.queue.util.AfterStartUp; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; +import org.thingsboard.server.service.cf.CalculatedFieldCache; +import org.thingsboard.server.service.profile.TbAssetProfileCache; +import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public abstract class AbstractConsumerPartitionedService extends AbstractConsumerService { + + private final Lock startupLock; + private volatile boolean consumersInitialized; + private PartitionChangeEvent lastPartitionChangeEvent; + + public AbstractConsumerPartitionedService(ActorSystemContext actorContext, + TbTenantProfileCache tenantProfileCache, + TbDeviceProfileCache deviceProfileCache, + TbAssetProfileCache assetProfileCache, + CalculatedFieldCache calculatedFieldCache, + TbApiUsageStateService apiUsageStateService, + PartitionService partitionService, + ApplicationEventPublisher eventPublisher, + JwtSettingsService jwtSettingsService) { + super(actorContext, tenantProfileCache, deviceProfileCache, assetProfileCache, calculatedFieldCache, apiUsageStateService, partitionService, eventPublisher, jwtSettingsService); + this.startupLock = new ReentrantLock(); + this.consumersInitialized = false; + } + + @PostConstruct + public void init() { + super.init(getPrefix()); + } + + @AfterStartUp(order = AfterStartUp.REGULAR_SERVICE) + public void afterStartUp() { + super.afterStartUp(); + doAfterStartUp(); + startupLock.lock(); + try { + processPartitionChangeEvent(lastPartitionChangeEvent); + consumersInitialized = true; + } finally { + startupLock.unlock(); + } + } + + @Override + protected void onTbApplicationEvent(PartitionChangeEvent event) { + if (!consumersInitialized) { + startupLock.lock(); + try { + if (!consumersInitialized) { + lastPartitionChangeEvent = event; + return; + } + } finally { + startupLock.unlock(); + } + } + processPartitionChangeEvent(event); + } + + protected abstract void doAfterStartUp(); + + protected abstract void processPartitionChangeEvent(PartitionChangeEvent event); + + protected abstract String getPrefix(); + +} From df9f61c1af3b0c220c543102b3b3570f314d6a87 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 24 Mar 2025 17:15:52 +0200 Subject: [PATCH 06/26] UI: Refactoring popover service. Add single config --- .../components/color-input.component.ts | 23 ++-- .../shared/components/popover.component.ts | 10 +- .../app/shared/components/popover.models.ts | 38 ++++++ .../app/shared/components/popover.service.ts | 122 +++++++++++++----- 4 files changed, 151 insertions(+), 42 deletions(-) diff --git a/ui-ngx/src/app/shared/components/color-input.component.ts b/ui-ngx/src/app/shared/components/color-input.component.ts index 00ef7059d7..d0dc7235f7 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.ts +++ b/ui-ngx/src/app/shared/components/color-input.component.ts @@ -196,14 +196,21 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const colorPickerPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, ColorPickerPanelComponent, ['left'], true, null, - { - color: this.colorFormGroup.get('color').value, - colorClearButton: this.colorClearButton, - colorCancelButton: true - }, - {}, {}, {}, false, () => {}, {padding: '12px 4px 12px 12px'}); + const colorPickerPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + hostView: this.viewContainerRef, + componentType: ColorPickerPanelComponent, + preferredPlacement: ['left'], + context: { + color: this.colorFormGroup.get('color').value, + colorClearButton: this.colorClearButton, + colorCancelButton: true + }, + showCloseButton: false, + popoverContentStyle: {padding: '12px 4px 12px 12px'}, + isModal: true + }) colorPickerPopover.tbComponentRef.instance.popover = colorPickerPopover; colorPickerPopover.tbComponentRef.instance.colorSelected.subscribe((color) => { colorPickerPopover.hide(); diff --git a/ui-ngx/src/app/shared/components/popover.component.ts b/ui-ngx/src/app/shared/components/popover.component.ts index 21cf4a83c5..4344b32fd7 100644 --- a/ui-ngx/src/app/shared/components/popover.component.ts +++ b/ui-ngx/src/app/shared/components/popover.component.ts @@ -311,6 +311,7 @@ export class TbPopoverDirective implements OnChanges, OnDestroy, AfterViewInit { #overlay="cdkConnectedOverlay" cdkConnectedOverlay [cdkConnectedOverlayHasBackdrop]="hasBackdrop" + [cdkConnectedOverlayBackdropClass]="backdropClass" [cdkConnectedOverlayOrigin]="origin" [cdkConnectedOverlayPositions]="positions" [cdkConnectedOverlayScrollStrategy]="scrollStrategy" @@ -382,6 +383,7 @@ export class TbPopoverComponent implements OnDestroy, OnInit { tbMouseLeaveDelay?: number; tbHideOnClickOutside = true; tbShowCloseButton = true; + tbModal = false; tbAnimationState = 'active'; @@ -461,7 +463,11 @@ export class TbPopoverComponent implements OnDestroy, OnInit { } get hasBackdrop(): boolean { - return this.tbTrigger === 'click' ? this.tbBackdrop : false; + return this.tbModal || (this.tbTrigger === 'click' && this.tbBackdrop); + } + + get backdropClass(): string { + return this.tbModal ? 'tb-popover-overlay-backdrop' : ''; } preferredPlacement: PopoverPlacement = 'top'; @@ -634,7 +640,7 @@ export class TbPopoverComponent implements OnDestroy, OnInit { } onClickOutside(event: MouseEvent): void { - if (this.tbHideOnClickOutside && !this.origin.elementRef.nativeElement.contains(event.target) && this.tbTrigger !== null) { + if (!this.tbModal && this.tbHideOnClickOutside && !this.origin.elementRef.nativeElement.contains(event.target) && this.tbTrigger !== null) { if (!this.isTopOverlay(event.target as Element)) { this.hide(); } diff --git a/ui-ngx/src/app/shared/components/popover.models.ts b/ui-ngx/src/app/shared/components/popover.models.ts index 15db9b1f93..9fb01d1f3d 100644 --- a/ui-ngx/src/app/shared/components/popover.models.ts +++ b/ui-ngx/src/app/shared/components/popover.models.ts @@ -18,6 +18,7 @@ import { animate, AnimationTriggerMetadata, style, transition, trigger } from '@ import { ConnectedOverlayPositionChange } from '@angular/cdk/overlay'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { POSITION_MAP } from '@shared/models/overlay.models'; +import { ComponentRef, Injector, Renderer2, Type, ViewContainerRef } from '@angular/core'; export const popoverMotion: AnimationTriggerMetadata = trigger('popoverMotion', [ transition('void => active', [ @@ -88,3 +89,40 @@ export interface PopoverWithTrigger { trigger: Element; popoverComponent: TbPopoverComponent; } + +export interface DisplayPopoverConfig extends Omit, 'componentRef'>{ + hostView: ViewContainerRef; +} + +export interface DisplayPopoverWithComponentRefConfig { + componentRef: ComponentRef + trigger: Element; + renderer: Renderer2; + componentType: Type; + preferredPlacement?: PopoverPreferredPlacement; + hideOnClickOutside?: boolean; + injector?: Injector; + context?: any; + overlayStyle?: any; + popoverStyle?: any; + style?: any, + showCloseButton?: boolean; + visibleFn?: (visible: boolean) => void; + popoverContentStyle?: any; + isModal?: boolean; +} + +export const defaultPopoverConfig: DisplayPopoverWithComponentRefConfig = { + componentRef: undefined, + trigger: undefined, + renderer: undefined, + componentType: undefined, + preferredPlacement: 'top', + hideOnClickOutside: true, + overlayStyle: {}, + popoverStyle: {}, + showCloseButton: true, + visibleFn: () => {}, + popoverContentStyle: {}, + isModal: false +}; diff --git a/ui-ngx/src/app/shared/components/popover.service.ts b/ui-ngx/src/app/shared/components/popover.service.ts index ce1288651c..15a18fb2fc 100644 --- a/ui-ngx/src/app/shared/components/popover.service.ts +++ b/ui-ngx/src/app/shared/components/popover.service.ts @@ -24,12 +24,19 @@ import { Type, ViewContainerRef } from '@angular/core'; -import { PopoverPreferredPlacement, PopoverWithTrigger } from '@shared/components/popover.models'; +import { + defaultPopoverConfig, + DisplayPopoverConfig, + DisplayPopoverWithComponentRefConfig, + PopoverPreferredPlacement, + PopoverWithTrigger +} from '@shared/components/popover.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { ComponentType } from '@angular/cdk/portal'; import { HELP_MARKDOWN_COMPONENT_TOKEN } from '@shared/components/tokens'; import { CdkOverlayOrigin } from '@angular/cdk/overlay'; import { Observable } from 'rxjs'; +import { mergeDeep } from '@core/utils'; @Injectable() export class TbPopoverService { @@ -58,57 +65,108 @@ export class TbPopoverService { return hostView.createComponent(TbPopoverComponent); } + displayPopover(config: DisplayPopoverConfig): TbPopoverComponent; displayPopover(trigger: Element, renderer: Renderer2, hostView: ViewContainerRef, - componentType: Type, preferredPlacement: PopoverPreferredPlacement = 'top', - hideOnClickOutside = true, injector?: Injector, context?: any, overlayStyle: any = {}, - popoverStyle: any = {}, style?: any, - showCloseButton = true, visibleFn: (visible: boolean) => void = () => {}, - popoverContentStyle: any = {}): TbPopoverComponent { - const componentRef = this.createPopoverRef(hostView); - return this.displayPopoverWithComponentRef(componentRef, trigger, renderer, componentType, preferredPlacement, hideOnClickOutside, - injector, context, overlayStyle, popoverStyle, style, showCloseButton, visibleFn, popoverContentStyle); + componentType: Type, preferredPlacement: PopoverPreferredPlacement, + hideOnClickOutside: boolean, injector?: Injector, context?: any, overlayStyle?: any, + popoverStyle?: any, style?: any, + showCloseButton?: boolean, visibleFn?: (visible: boolean) => void, + popoverContentStyle?: any): TbPopoverComponent; + displayPopover(config: Element | DisplayPopoverConfig, renderer?: Renderer2, hostView?: ViewContainerRef, + componentType?: Type, preferredPlacement?: PopoverPreferredPlacement, + hideOnClickOutside?: boolean, injector?: Injector, context?: any, overlayStyle?: any, + popoverStyle?: any, style?: any, + showCloseButton?: boolean, visibleFn?: (visible: boolean) => void, + popoverContentStyle?: any): TbPopoverComponent { + if (!(config instanceof Element) && 'trigger' in config && 'renderer' in config && 'componentType' in config) { + const componentRef = this.createPopoverRef(config.hostView); + return this.displayPopoverWithComponentRef({ ...config, componentRef }) + } else if (config instanceof Element) { + const componentRef = this.createPopoverRef(hostView); + return this.displayPopoverWithComponentRef(componentRef, config, renderer, componentType, preferredPlacement, hideOnClickOutside, + injector, context, overlayStyle, popoverStyle, style, showCloseButton, visibleFn, popoverContentStyle); + } else { + throw new Error("Invalid configuration provided for displayPopover"); + } } + displayPopoverWithComponentRef(config: DisplayPopoverWithComponentRefConfig): TbPopoverComponent; displayPopoverWithComponentRef(componentRef: ComponentRef, trigger: Element, renderer: Renderer2, - componentType: Type, preferredPlacement: PopoverPreferredPlacement = 'top', - hideOnClickOutside = true, injector?: Injector, context?: any, overlayStyle: any = {}, - popoverStyle: any = {}, style?: any, showCloseButton = true, - visibleFn: (visible: boolean) => void = () => {}, + componentType: Type, preferredPlacement: PopoverPreferredPlacement, + hideOnClickOutside: boolean, injector?: Injector, context?: any, overlayStyle?: any, + popoverStyle?: any, style?: any, showCloseButton?: boolean, + visibleFn?: (visible: boolean) => void, popoverContentStyle?: any): TbPopoverComponent; + displayPopoverWithComponentRef(config: ComponentRef | DisplayPopoverWithComponentRefConfig, + trigger?: Element, renderer?: Renderer2, componentType?: Type, + preferredPlacement?: PopoverPreferredPlacement, hideOnClickOutside?: boolean, + injector?: Injector, context?: any, overlayStyle?: any, + popoverStyle?: any, style?: any, showCloseButton?: boolean, + visibleFn?: (visible: boolean) => void, popoverContentStyle: any = {}): TbPopoverComponent { - const component = componentRef.instance; + let popoverConfig: DisplayPopoverWithComponentRefConfig; + if (!(config instanceof ComponentRef) && 'trigger' in config && 'renderer' in config && 'componentType' in config) { + popoverConfig = config; + } else if(config instanceof ComponentRef) { + popoverConfig = { + componentRef: config, + trigger, + renderer, + componentType, + preferredPlacement, + hideOnClickOutside, + injector, + context, + overlayStyle, + popoverStyle, + style, + showCloseButton, + visibleFn, + popoverContentStyle + } + } else { + throw new Error("Invalid configuration provided for displayPopoverWithComponentRef"); + } + popoverConfig = mergeDeep({} as any, defaultPopoverConfig, popoverConfig); + return this._displayPopoverWithComponentRef(popoverConfig); + } + + + private _displayPopoverWithComponentRef(conf: DisplayPopoverWithComponentRefConfig): TbPopoverComponent { + const component = conf.componentRef.instance; this.popoverWithTriggers.push({ - trigger, + trigger: conf.trigger, popoverComponent: component }); - renderer.removeChild( - renderer.parentNode(trigger), - componentRef.location.nativeElement + conf.renderer.removeChild( + conf.renderer.parentNode(conf.trigger), + conf.componentRef.location.nativeElement ); - const originElementRef = new ElementRef(trigger); + const originElementRef = new ElementRef(conf.trigger); component.setOverlayOrigin(new CdkOverlayOrigin(originElementRef)); - component.tbPlacement = preferredPlacement; - component.tbComponent = componentType; - component.tbComponentInjector = injector; - component.tbComponentContext = context; - component.tbOverlayStyle = overlayStyle; - component.tbPopoverInnerStyle = popoverStyle; - component.tbPopoverInnerContentStyle = popoverContentStyle; - component.tbComponentStyle = style; - component.tbHideOnClickOutside = hideOnClickOutside; - component.tbShowCloseButton = showCloseButton; + component.tbPlacement = conf.preferredPlacement; + component.tbComponent = conf.componentType; + component.tbComponentInjector = conf.injector; + component.tbComponentContext = conf.context; + component.tbOverlayStyle = conf.overlayStyle; + component.tbModal = conf.isModal; + component.tbPopoverInnerStyle = conf.popoverStyle; + component.tbPopoverInnerContentStyle = conf.popoverContentStyle; + component.tbComponentStyle = conf.style; + component.tbHideOnClickOutside = conf.hideOnClickOutside; + component.tbShowCloseButton = conf.showCloseButton; component.tbVisibleChange.subscribe((visible: boolean) => { if (!visible) { - componentRef.destroy(); + conf.componentRef.destroy(); } }); component.tbDestroy.subscribe(() => { this.removePopoverByComponent(component); }); component.tbHideStart.subscribe(() => { - visibleFn(false); + conf.visibleFn(false); }); component.show(); - visibleFn(true); + conf.visibleFn(true); return component; } From 70a9679bc848feba69e481945aef9a7ee9d20cbb Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 24 Mar 2025 18:14:49 +0200 Subject: [PATCH 07/26] fixed sending empty timeseries update --- .../DefaultTbEntityDataSubscriptionService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index 9e9ca42e83..38303a2cf9 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -725,7 +725,11 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); ctx.setInitialDataSent(true); } else { - update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription()); + // to avoid updating timeseries with empty values + List data = ctx.getData().getData().stream() + .peek(entityData -> entityData.setTimeseries(null)) + .toList(); + update = new EntityDataUpdate(ctx.getCmdId(), null, data, ctx.getMaxEntitiesPerDataSubscription()); } ctx.sendWsMsg(update); } finally { From d6739538854ac9e029560c1687320870b183f06b Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 24 Mar 2025 17:59:53 +0200 Subject: [PATCH 08/26] UI: Refactoring popover to use isModal setting --- .../debug/entity-debug-settings.service.ts | 14 ++-- .../vc/entity-types-version-load.component.ts | 15 +++- .../vc/entity-version-diff.component.ts | 15 +++- .../vc/entity-versions-table.component.ts | 81 +++++++++++++------ .../config/timewindow-style.component.ts | 14 ++-- .../get-value-action-settings.component.ts | 36 +++++---- .../set-value-action-settings.component.ts | 30 +++---- .../widget-action-settings.component.ts | 28 ++++--- .../auto-date-format-settings.component.ts | 21 ++--- .../common/background-settings.component.ts | 19 +++-- .../widget-button-custom-style.component.ts | 28 ++++--- ...et-button-toggle-custom-style.component.ts | 30 +++---- ...es-chart-axis-settings-button.component.ts | 25 +++--- ...ries-chart-threshold-settings.component.ts | 20 +++-- .../time-series-chart-y-axis-row.component.ts | 25 +++--- .../common/color-range-settings.component.ts | 21 ++--- .../common/color-settings.component.ts | 35 ++++---- .../common/date-format-select.component.ts | 39 +++++---- .../dynamic-form-property-row.component.ts | 25 +++--- .../common/font-settings.component.ts | 14 ++-- .../common/key/data-keys.component.ts | 16 ++-- .../data-layer-color-settings.component.ts | 29 ++++--- .../common/map/map-layer-row.component.ts | 19 +++-- .../map/map-tooltip-tag-actions.component.ts | 32 ++++---- .../map/marker-image-settings.component.ts | 19 +++-- .../map/marker-shape-settings.component.ts | 25 +++--- .../applications/mobile-app.component.ts | 16 ++-- .../layout/mobile-page-item-row.component.ts | 31 ++++--- .../scada-symbol-behavior-row.component.ts | 27 ++++--- .../scada-symbol-metadata-tag.component.ts | 28 ++++--- .../pages/widget/widget-editor.component.ts | 14 ++-- .../shared/components/js-func.component.ts | 19 +++-- .../timewindow-config-dialog.component.ts | 32 +++++--- .../components/time/timezone.component.ts | 15 ++-- 34 files changed, 511 insertions(+), 346 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings.service.ts b/ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings.service.ts index 873d8f0f3f..cb982b9650 100644 --- a/ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings.service.ts +++ b/ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings.service.ts @@ -37,14 +37,18 @@ export class EntityDebugSettingsService { if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const debugStrategyPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, EntityDebugSettingsPanelComponent, 'bottom', true, null, - { + const debugStrategyPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: EntityDebugSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'bottom', + context: { ...panelConfig.debugSettings, ...panelConfig.debugConfig, }, - {}, - {}, {}, true); + isModal: true, + }); debugStrategyPopover.tbComponentRef.instance.onSettingsApplied.subscribe(settings => { panelConfig.onSettingsAppliedFn(settings); debugStrategyPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.ts b/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.ts index f06abaf3a8..903712aac8 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/entity-types-version-load.component.ts @@ -232,16 +232,23 @@ export class EntityTypesVersionLoadComponent extends PageComponent implements On if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const removeOtherEntitiesConfirmPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, RemoveOtherEntitiesConfirmComponent, 'bottom', true, null, - { + const removeOtherEntitiesConfirmPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: RemoveOtherEntitiesConfirmComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'bottom', + context: { onClose: (result: boolean | null) => { removeOtherEntitiesConfirmPopover.hide(); if (result) { entityTypeControl.get('config').get('removeOtherEntities').patchValue(true, {emitEvent: true}); } } - }, {}, {}, {}, false); + }, + showCloseButton: false, + isModal: true + }); } } } diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-version-diff.component.ts b/ui-ngx/src/app/modules/home/components/vc/entity-version-diff.component.ts index 1908b5e1fa..19dc945d65 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-version-diff.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/entity-version-diff.component.ts @@ -304,9 +304,13 @@ export class EntityVersionDiffComponent extends PageComponent implements OnInit, if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const restoreVersionPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, EntityVersionRestoreComponent, 'leftTop', false, null, - { + const restoreVersionPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: EntityVersionRestoreComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'leftTop', + context: { versionName: this.versionName, versionId: this.versionId, externalEntityId: this.externalEntityId, @@ -317,7 +321,10 @@ export class EntityVersionDiffComponent extends PageComponent implements OnInit, this.versionRestored.emit(); } } - }, {}, {}, {}, false); + }, + showCloseButton: false, + isModal: true + }); restoreVersionPopover.tbComponentRef.instance.popoverComponent = restoreVersionPopover; } } diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts b/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts index 2a6d5c5962..fe9af56e4b 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.ts @@ -211,9 +211,13 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const createVersionPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, EntityVersionCreateComponent, 'leftTop', false, null, - { + const createVersionPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: EntityVersionCreateComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'leftTop', + context: { branch: this.branch, entityId: this.entityId, entityName: this.entityName, @@ -229,8 +233,11 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni } } }, - {maxHeight: '100vh', height: '100%', padding: '10px'}, - {width: '400px', minWidth: '100%', maxWidth: '100%'}, {}, false); + showCloseButton: false, + overlayStyle: {maxHeight: '100vh', height: '100%', padding: '10px'}, + popoverStyle: {width: '400px', minWidth: '100%', maxWidth: '100%'}, + isModal: true + }); createVersionPopover.tbComponentRef.instance.popoverComponent = createVersionPopover; } } @@ -243,9 +250,13 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const complexCreateVersionPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, ComplexVersionCreateComponent, 'leftTop', false, null, - { + const complexCreateVersionPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: ComplexVersionCreateComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'leftTop', + context: { branch: this.branch, onClose: (result: VersionCreationResult | null, branch: string | null) => { complexCreateVersionPopover.hide(); @@ -258,8 +269,10 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni } } }, - {maxHeight: '90vh', height: '100%', padding: '10px'}, - {}, {}, false); + showCloseButton: false, + overlayStyle: {maxHeight: '90vh', height: '100%', padding: '10px'}, + isModal: true + }); complexCreateVersionPopover.tbComponentRef.instance.popoverComponent = complexCreateVersionPopover; } } @@ -272,14 +285,21 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const diffVersionPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, EntityVersionDiffComponent, 'leftTop', true, null, - { + const diffVersionPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: EntityVersionDiffComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'leftTop', + context: { versionName: entityVersion.name, versionId: entityVersion.id, entityId: this.entityId, externalEntityId: this.externalEntityIdValue - }, {}, {}, {}, false); + }, + showCloseButton: false, + isModal: true + }); diffVersionPopover.tbComponentRef.instance.popoverComponent = diffVersionPopover; diffVersionPopover.tbComponentRef.instance.versionRestored.subscribe(() => { this.versionRestored.emit(); @@ -295,9 +315,13 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const restoreVersionPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, EntityVersionRestoreComponent, 'leftTop', false, null, - { + const restoreVersionPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: EntityVersionRestoreComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'leftTop', + context: { versionName: entityVersion.name, versionId: entityVersion.id, externalEntityId: this.externalEntityIdValue, @@ -308,8 +332,11 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni } } }, - {maxHeight: '100vh', height: '100%', padding: '10px'}, - {width: '400px', minWidth: '100%', maxWidth: '100%'}, {}, false); + showCloseButton: false, + overlayStyle: {maxHeight: '100vh', height: '100%', padding: '10px'}, + popoverStyle: {width: '400px', minWidth: '100%', maxWidth: '100%'}, + isModal: true + }); restoreVersionPopover.tbComponentRef.instance.popoverComponent = restoreVersionPopover; } } @@ -322,17 +349,23 @@ export class EntityVersionsTableComponent extends PageComponent implements OnIni if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const restoreEntitiesVersionPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, ComplexVersionLoadComponent, 'leftTop', false, null, - { + const restoreEntitiesVersionPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: ComplexVersionLoadComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'leftTop', + context: { versionName: entityVersion.name, versionId: entityVersion.id, onClose: (result: VersionLoadResult | null) => { restoreEntitiesVersionPopover.hide(); } }, - {maxHeight: '80vh', height: '100%', padding: '10px'}, - {}, {}, false); + showCloseButton: false, + overlayStyle: {maxHeight: '80vh', height: '100%', padding: '10px'}, + isModal: true + }); restoreEntitiesVersionPopover.tbComponentRef.instance.popoverComponent = restoreEntitiesVersionPopover; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style.component.ts index 790535acbc..c9675caefa 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/timewindow-style.component.ts @@ -80,11 +80,15 @@ export class TimewindowStyleComponent implements OnInit, ControlValueAccessor { timewindowStyle: this.modelValue, previewValue: this.previewValue }; - const timewindowStylePanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, TimewindowStylePanelComponent, 'left', true, null, - ctx, - {}, - {}, {}, true); + const timewindowStylePanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + hostView: this.viewContainerRef, + componentType: TimewindowStylePanelComponent, + preferredPlacement: 'left', + context: ctx, + isModal: true + }); timewindowStylePanelPopover.tbComponentRef.instance.popover = timewindowStylePanelPopover; timewindowStylePanelPopover.tbComponentRef.instance.timewindowStyleApplied.subscribe((timewindowStyle) => { timewindowStylePanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts index 5b95ef5f13..7bdb17c06a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/get-value-action-settings.component.ts @@ -129,23 +129,25 @@ export class GetValueActionSettingsComponent implements OnInit, ControlValueAcce if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - getValueSettings: this.modelValue, - panelTitle: this.panelTitle, - valueType: this.valueType, - trueLabel: this.trueLabel, - falseLabel: this.falseLabel, - stateLabel: this.stateLabel, - aliasController: this.aliasController, - targetDevice: this.targetDevice, - widgetType: this.widgetType - }; - const getValueSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, GetValueActionSettingsPanelComponent, - ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const getValueSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: GetValueActionSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], + context: { + getValueSettings: this.modelValue, + panelTitle: this.panelTitle, + valueType: this.valueType, + trueLabel: this.trueLabel, + falseLabel: this.falseLabel, + stateLabel: this.stateLabel, + aliasController: this.aliasController, + targetDevice: this.targetDevice, + widgetType: this.widgetType + }, + isModal: true + }); getValueSettingsPanelPopover.tbComponentRef.instance.popover = getValueSettingsPanelPopover; getValueSettingsPanelPopover.tbComponentRef.instance.getValueSettingsApplied.subscribe((getValueSettings) => { getValueSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts index e446e6f5b3..cf90db33ed 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings.component.ts @@ -115,20 +115,22 @@ export class SetValueActionSettingsComponent implements OnInit, ControlValueAcce if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - setValueSettings: this.modelValue, - panelTitle: this.panelTitle, - valueType: this.valueType, - aliasController: this.aliasController, - targetDevice: this.targetDevice, - widgetType: this.widgetType - }; - const setValueSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, SetValueActionSettingsPanelComponent, - ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const setValueSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: SetValueActionSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], + context: { + setValueSettings: this.modelValue, + panelTitle: this.panelTitle, + valueType: this.valueType, + aliasController: this.aliasController, + targetDevice: this.targetDevice, + widgetType: this.widgetType + }, + isModal: true + }); setValueSettingsPanelPopover.tbComponentRef.instance.popover = setValueSettingsPanelPopover; setValueSettingsPanelPopover.tbComponentRef.instance.setValueSettingsApplied.subscribe((setValueSettings) => { setValueSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings.component.ts index 51583f71fb..de29366846 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings.component.ts @@ -114,19 +114,21 @@ export class WidgetActionSettingsComponent implements OnInit, ControlValueAccess if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - widgetAction: this.modelValue, - panelTitle: this.panelTitle, - widgetType: this.widgetType, - callbacks: this.callbacks, - additionalWidgetActionTypes: this.additionalWidgetActionTypes - }; - const widgetActionSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, WidgetActionSettingsPanelComponent, - ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const widgetActionSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: WidgetActionSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], + context: { + widgetAction: this.modelValue, + panelTitle: this.panelTitle, + widgetType: this.widgetType, + callbacks: this.callbacks, + additionalWidgetActionTypes: this.additionalWidgetActionTypes + }, + isModal: true + }); widgetActionSettingsPanelPopover.tbComponentRef.instance.widgetActionApplied.subscribe((widgetAction) => { widgetActionSettingsPanelPopover.hide(); this.modelValue = widgetAction; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.ts index 1d776bcb70..f46bce6dd6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/auto-date-format-settings.component.ts @@ -78,15 +78,18 @@ export class AutoDateFormatSettingsComponent implements OnInit, ControlValueAcce if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - autoDateFormatSettings: deepClone(this.modelValue), - defaultValues: this.defaultValues - }; - const autoDateFormatSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, AutoDateFormatSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const autoDateFormatSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: AutoDateFormatSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], + context: { + autoDateFormatSettings: deepClone(this.modelValue), + defaultValues: this.defaultValues + }, + isModal: true + }); autoDateFormatSettingsPanelPopover.tbComponentRef.instance.popover = autoDateFormatSettingsPanelPopover; autoDateFormatSettingsPanelPopover.tbComponentRef.instance.autoDateFormatSettingsApplied.subscribe((autoDateFormatSettings) => { autoDateFormatSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts index cadf5fb07a..a5ed2cf96a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts @@ -107,14 +107,17 @@ export class BackgroundSettingsComponent implements OnInit, ControlValueAccessor if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - backgroundSettings: this.modelValue - }; - const backgroundSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, BackgroundSettingsPanelComponent, ['left'], false, null, - ctx, - {}, - {}, {}, true); + const backgroundSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: BackgroundSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'left', + context: { + backgroundSettings: this.modelValue + }, + isModal: true + }); backgroundSettingsPanelPopover.tbComponentRef.instance.popover = backgroundSettingsPanelPopover; backgroundSettingsPanelPopover.tbComponentRef.instance.backgroundSettingsApplied.subscribe((backgroundSettings) => { backgroundSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts index 5db91fb1bb..1cfc7a77cf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts @@ -124,19 +124,21 @@ export class WidgetButtonCustomStyleComponent implements OnInit, OnChanges, Cont if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - appearance: this.appearance, - borderRadius: this.borderRadius, - autoScale: this.autoScale, - state: this.state, - customStyle: this.modelValue - }; - const widgetButtonCustomStylePanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, WidgetButtonCustomStylePanelComponent, - ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const widgetButtonCustomStylePanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: WidgetButtonCustomStylePanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], + context: { + appearance: this.appearance, + borderRadius: this.borderRadius, + autoScale: this.autoScale, + state: this.state, + customStyle: this.modelValue + }, + isModal: true + }); widgetButtonCustomStylePanelPopover.tbComponentRef.instance.popover = widgetButtonCustomStylePanelPopover; widgetButtonCustomStylePanelPopover.tbComponentRef.instance.customStyleApplied.subscribe((customStyle) => { widgetButtonCustomStylePanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-toggle-custom-style.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-toggle-custom-style.component.ts index b2a78ff961..1e0f19ea58 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-toggle-custom-style.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-toggle-custom-style.component.ts @@ -127,20 +127,22 @@ export class WidgetButtonToggleCustomStyleComponent implements OnInit, OnChanges if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - appearance: this.appearance, - borderRadius: this.borderRadius, - autoScale: this.autoScale, - state: this.state, - value: this.value, - customStyle: this.modelValue - }; - const widgetButtonCustomStylePanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, WidgetButtonToggleCustomStylePanelComponent, - ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const widgetButtonCustomStylePanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: WidgetButtonToggleCustomStylePanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], + context: { + appearance: this.appearance, + borderRadius: this.borderRadius, + autoScale: this.autoScale, + state: this.state, + value: this.value, + customStyle: this.modelValue + }, + isModal: true + }); widgetButtonCustomStylePanelPopover.tbComponentRef.instance.popover = widgetButtonCustomStylePanelPopover; widgetButtonCustomStylePanelPopover.tbComponentRef.instance.customStyleApplied.subscribe((customStyle) => { widgetButtonCustomStylePanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-button.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-button.component.ts index 2f2067f3a8..016f213c0b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-button.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-button.component.ts @@ -85,17 +85,20 @@ export class TimeSeriesChartAxisSettingsButtonComponent implements OnInit, Contr if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - axisSettings: this.modelValue, - axisType: this.axisType, - panelTitle: this.panelTitle, - advanced: this.advanced - }; - const axisSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, TimeSeriesChartAxisSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const axisSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: TimeSeriesChartAxisSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], + context: { + axisSettings: this.modelValue, + axisType: this.axisType, + panelTitle: this.panelTitle, + advanced: this.advanced + }, + isModal: true + }); axisSettingsPanelPopover.tbComponentRef.instance.popover = axisSettingsPanelPopover; axisSettingsPanelPopover.tbComponentRef.instance.axisSettingsApplied.subscribe((axisSettings) => { axisSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings.component.ts index e3ff513e0f..d39e4b48dd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings.component.ts @@ -107,11 +107,21 @@ export class TimeSeriesChartThresholdSettingsComponent implements OnInit, Contro hideYAxis: this.hideYAxis, yAxisIds: this.yAxisIds }; - const thresholdSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, TimeSeriesChartThresholdSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const thresholdSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: TimeSeriesChartThresholdSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], + context: { + thresholdSettings: deepClone(this.modelValue), + panelTitle: this.title, + widgetConfig: this.widgetConfig, + hideYAxis: this.hideYAxis, + yAxisIds: this.yAxisIds + }, + isModal: true + }); thresholdSettingsPanelPopover.tbComponentRef.instance.popover = thresholdSettingsPanelPopover; thresholdSettingsPanelPopover.tbComponentRef.instance.thresholdSettingsApplied.subscribe((thresholdSettings) => { thresholdSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts index 69cbc29a25..dec7148b30 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts @@ -151,17 +151,20 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - axisType: 'yAxis', - panelTitle: this.translate.instant('widgets.time-series-chart.axis.y-axis-settings'), - axisSettings: deepClone(this.modelValue), - advanced: this.advanced - }; - const yAxisSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, TimeSeriesChartAxisSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const yAxisSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: TimeSeriesChartAxisSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], + context: { + axisType: 'yAxis', + panelTitle: this.translate.instant('widgets.time-series-chart.axis.y-axis-settings'), + axisSettings: deepClone(this.modelValue), + advanced: this.advanced + }, + isModal: true + }); yAxisSettingsPanelPopover.tbComponentRef.instance.popover = yAxisSettingsPanelPopover; yAxisSettingsPanelPopover.tbComponentRef.instance.axisSettingsApplied.subscribe((yAxisSettings) => { yAxisSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-settings.component.ts index 431fac44da..23b1efbce9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-settings.component.ts @@ -121,15 +121,18 @@ export class ColorRangeSettingsComponent implements OnInit, ControlValueAccessor if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - colorRangeSettings: this.modelValue, - settingsComponents: this.colorSettingsComponentService.getOtherColorSettingsComponents(this) - }; - const colorRangeSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, ColorRangePanelComponent, 'left', false, null, - ctx, - {}, - {}, {}, true); + const colorRangeSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: ColorRangePanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'left', + context: { + colorRangeSettings: this.modelValue, + settingsComponents: this.colorSettingsComponentService.getOtherColorSettingsComponents(this) + }, + isModal: true + }); colorRangeSettingsPanelPopover.tbComponentRef.instance.popover = colorRangeSettingsPanelPopover; colorRangeSettingsPanelPopover.tbComponentRef.instance.colorRangeApplied.subscribe((colorRangeSettings: Array) => { colorRangeSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts index 133fa11bd4..05c069cbf3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings.component.ts @@ -159,22 +159,25 @@ export class ColorSettingsComponent implements OnInit, ControlValueAccessor, OnD if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - colorSettings: this.modelValue, - settingsComponents: this.colorSettingsComponentService.getOtherColorSettingsComponents(this), - aliasController: this.aliasController, - dataKeyCallbacks: this.dataKeyCallbacks, - datasource: this.datasource, - rangeAdvancedMode: this.rangeAdvancedMode, - gradientAdvancedMode: this.gradientAdvancedMode, - minValue: this.minValue, - maxValue: this.maxValue - }; - const colorSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, ColorSettingsPanelComponent, 'left', false, null, - ctx, - {}, - {}, {}, true); + const colorSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: ColorSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'left', + context: { + colorSettings: this.modelValue, + settingsComponents: this.colorSettingsComponentService.getOtherColorSettingsComponents(this), + aliasController: this.aliasController, + dataKeyCallbacks: this.dataKeyCallbacks, + datasource: this.datasource, + rangeAdvancedMode: this.rangeAdvancedMode, + gradientAdvancedMode: this.gradientAdvancedMode, + minValue: this.minValue, + maxValue: this.maxValue + }, + isModal: true + }); colorSettingsPanelPopover.tbComponentRef.instance.popover = colorSettingsPanelPopover; colorSettingsPanelPopover.tbComponentRef.instance.colorSettingsApplied.subscribe((colorSettings) => { colorSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts index 92aef0ba29..847e869fe6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/date-format-select.component.ts @@ -162,14 +162,16 @@ export class DateFormatSelectComponent implements OnInit, ControlValueAccessor { if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - dateFormat: deepClone(this.modelValue) - }; - const dateFormatSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, DateFormatSettingsPanelComponent, 'top', false, null, - ctx, - {}, - {}, {}, true); + const dateFormatSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: DateFormatSettingsPanelComponent, + hostView: this.viewContainerRef, + context: { + dateFormat: deepClone(this.modelValue) + }, + isModal: true + }); dateFormatSettingsPanelPopover.tbComponentRef.instance.popover = dateFormatSettingsPanelPopover; dateFormatSettingsPanelPopover.tbComponentRef.instance.dateFormatApplied.subscribe((dateFormat) => { dateFormatSettingsPanelPopover.hide(); @@ -187,15 +189,18 @@ export class DateFormatSelectComponent implements OnInit, ControlValueAccessor { if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - autoDateFormatSettings: mergeDeep({} as AutoDateFormatSettings, - defaultAutoDateFormatSettings, this.modelValue.autoDateFormatSettings) - }; - const autoDateFormatSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, AutoDateFormatSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const autoDateFormatSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: AutoDateFormatSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], + context: { + autoDateFormatSettings: mergeDeep({} as AutoDateFormatSettings, + defaultAutoDateFormatSettings, this.modelValue.autoDateFormatSettings) + }, + isModal: true + }); autoDateFormatSettingsPanelPopover.tbComponentRef.instance.popover = autoDateFormatSettingsPanelPopover; autoDateFormatSettingsPanelPopover.tbComponentRef.instance.autoDateFormatSettingsApplied.subscribe((autoDateFormatSettings) => { autoDateFormatSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/dynamic-form/dynamic-form-property-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/dynamic-form/dynamic-form-property-row.component.ts index 79c2254a7d..58d879ba79 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/dynamic-form/dynamic-form-property-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/dynamic-form/dynamic-form-property-row.component.ts @@ -168,17 +168,20 @@ export class DynamicFormPropertyRowComponent implements ControlValueAccessor, On if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - isAdd: add, - disabled: this.disabled, - booleanPropertyIds: this.booleanPropertyIds, - property: deepClone(this.modelValue) - }; - const dynamicFormPropertyPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, DynamicFormPropertyPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const dynamicFormPropertyPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: DynamicFormPropertyPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], + context: { + isAdd: add, + disabled: this.disabled, + booleanPropertyIds: this.booleanPropertyIds, + property: deepClone(this.modelValue) + }, + isModal: true + }); dynamicFormPropertyPanelPopover.tbComponentRef.instance.popover = dynamicFormPropertyPanelPopover; dynamicFormPropertyPanelPopover.tbComponentRef.instance.propertySettingsApplied.subscribe((property) => { dynamicFormPropertyPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts index 25addab5ea..2675f9d1e6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts @@ -109,11 +109,15 @@ export class FontSettingsComponent implements OnInit, ControlValueAccessor { ctx.previewText = previewText; } } - const fontSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, FontSettingsPanelComponent, 'left', false, null, - ctx, - {}, - {}, {}, true); + const fontSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: FontSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'left', + context: ctx, + isModal: true + }); fontSettingsPanelPopover.tbComponentRef.instance.popover = fontSettingsPanelPopover; fontSettingsPanelPopover.tbComponentRef.instance.fontApplied.subscribe((font) => { fontSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-keys.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-keys.component.ts index e60d0298d9..a9be1fd352 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-keys.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-keys.component.ts @@ -565,14 +565,20 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const colorPickerPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, ColorPickerPanelComponent, ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], true, null, - { + const colorPickerPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: ColorPickerPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], + context: { color: key.color, colorCancelButton: true }, - {}, - {}, {}, false, () => {}, {padding: '12px 4px 12px 12px'}); + showCloseButton: false, + popoverContentStyle: {padding: '12px 4px 12px 12px'}, + isModal: true + }); colorPickerPopover.tbComponentRef.instance.popover = colorPickerPopover; colorPickerPopover.tbComponentRef.instance.colorSelected.subscribe((color) => { colorPickerPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings.component.ts index 8c94ed2c80..ea5da11890 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings.component.ts @@ -97,19 +97,22 @@ export class DataLayerColorSettingsComponent implements ControlValueAccessor { if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - colorSettings: this.modelValue, - context: this.context, - dsType: this.dsType, - dsEntityAliasId: this.dsEntityAliasId, - dsDeviceId: this.dsDeviceId, - helpId: this.helpId - }; - const colorSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, DataLayerColorSettingsPanelComponent, 'left', false, null, - ctx, - {}, - {}, {}, true); + const colorSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: DataLayerColorSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'left', + context: { + colorSettings: this.modelValue, + context: this.context, + dsType: this.dsType, + dsEntityAliasId: this.dsEntityAliasId, + dsDeviceId: this.dsDeviceId, + helpId: this.helpId + }, + isModal: true + }); colorSettingsPanelPopover.tbComponentRef.instance.popover = colorSettingsPanelPopover; colorSettingsPanelPopover.tbComponentRef.instance.colorSettingsApplied.subscribe((colorSettings) => { colorSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-layer-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-layer-row.component.ts index d7d4f78433..40a98b54cc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-layer-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-layer-row.component.ts @@ -180,14 +180,17 @@ export class MapLayerRowComponent implements ControlValueAccessor, OnInit { if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - mapLayerSettings: deepClone(this.modelValue) - }; - const mapLayerSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, MapLayerSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const mapLayerSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: MapLayerSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], + context: { + mapLayerSettings: deepClone(this.modelValue) + }, + isModal: true + }); mapLayerSettingsPanelPopover.tbComponentRef.instance.popover = mapLayerSettingsPanelPopover; mapLayerSettingsPanelPopover.tbComponentRef.instance.mapLayerSettingsApplied.subscribe((layer) => { mapLayerSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.ts index 6cf5791406..26f17055b6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.ts @@ -151,21 +151,23 @@ export class MapTooltipTagActionsComponent implements ControlValueAccessor, OnIn } else { const title = this.translate.instant(isAdd ? 'widgets.maps.data-layer.add-tooltip-tag-action' : 'widgets.maps.data-layer.edit-tooltip-tag-action'); const applyTitle = this.translate.instant(isAdd ? 'action.add' : 'action.apply'); - const ctx: any = { - widgetAction: action, - withName: true, - actionNames, - panelTitle: title, - applyTitle, - widgetType: widgetType.latest, - callbacks: this.context.callbacks - }; - const widgetActionSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, WidgetActionSettingsPanelComponent, - ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const widgetActionSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: WidgetActionSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftTopOnly', 'leftOnly', 'leftBottomOnly'], + context: { + widgetAction: action, + withName: true, + actionNames, + panelTitle: title, + applyTitle, + widgetType: widgetType.latest, + callbacks: this.context.callbacks + }, + isModal: true + }); widgetActionSettingsPanelPopover.tbComponentRef.instance.widgetActionApplied.subscribe((widgetAction) => { widgetActionSettingsPanelPopover.hide(); callback(widgetAction); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings.component.ts index 2d8feb817f..eac0dc9121 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings.component.ts @@ -76,14 +76,17 @@ export class MarkerImageSettingsComponent implements ControlValueAccessor { if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - markerImageSettings: this.modelValue, - }; - const markerImageSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, MarkerImageSettingsPanelComponent, 'left', false, null, - ctx, - {}, - {}, {}, true); + const markerImageSettingsPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: MarkerImageSettingsPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'left', + context: { + markerImageSettings: this.modelValue, + }, + isModal: true + }); markerImageSettingsPanelPopover.tbComponentRef.instance.popover = markerImageSettingsPanelPopover; markerImageSettingsPanelPopover.tbComponentRef.instance.markerImageSettingsApplied.subscribe((markerImageSettings) => { markerImageSettingsPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-shape-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-shape-settings.component.ts index 4400c5a151..bfb6c0c76d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-shape-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-shape-settings.component.ts @@ -177,17 +177,20 @@ export class MarkerShapeSettingsComponent implements ControlValueAccessor, OnIni ); }); } else if (this.markerType === MarkerType.icon) { - const ctx: any = { - iconContainer: (this.modelValue as MarkerIconSettings).iconContainer, - icon: (this.modelValue as MarkerIconSettings).icon, - color: this.modelValue.color.color, - trip: this.trip - }; - const markerIconShapesPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, MarkerIconShapesComponent, 'left', true, null, - ctx, - {}, - {}, {}, true); + const markerIconShapesPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: MarkerIconShapesComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'left', + context: { + iconContainer: (this.modelValue as MarkerIconSettings).iconContainer, + icon: (this.modelValue as MarkerIconSettings).icon, + color: this.modelValue.color.color, + trip: this.trip + }, + isModal: true + }); markerIconShapesPopover.tbComponentRef.instance.popover = markerIconShapesPopover; markerIconShapesPopover.tbComponentRef.instance.markerIconSelected.subscribe((iconInfo) => { markerIconShapesPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/pages/mobile/applications/mobile-app.component.ts b/ui-ngx/src/app/modules/home/pages/mobile/applications/mobile-app.component.ts index bbd096bbaf..eceda5ac9a 100644 --- a/ui-ngx/src/app/modules/home/pages/mobile/applications/mobile-app.component.ts +++ b/ui-ngx/src/app/modules/home/pages/mobile/applications/mobile-app.component.ts @@ -166,11 +166,17 @@ export class MobileAppComponent extends EntityComponent { ? this.entityForm.get('versionInfo.latestVersionReleaseNotes').value : this.entityForm.get('versionInfo.minVersionReleaseNotes').value }; - const releaseNotesPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, EditorPanelComponent, ['leftOnly', 'leftBottomOnly', 'leftTopOnly'], true, null, - ctx, - {}, - {}, {}, false, () => {}, {padding: '16px 24px'}); + const releaseNotesPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + hostView: this.viewContainerRef, + componentType: EditorPanelComponent, + preferredPlacement: ['leftOnly', 'leftBottomOnly', 'leftTopOnly'], + context: ctx, + showCloseButton: false, + popoverContentStyle: {padding: '16px 24px'}, + isModal: false + }); releaseNotesPanelPopover.tbComponentRef.instance.popover = releaseNotesPanelPopover; releaseNotesPanelPopover.tbComponentRef.instance.editorContentApplied.subscribe((releaseNotes) => { releaseNotesPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/pages/mobile/bundes/layout/mobile-page-item-row.component.ts b/ui-ngx/src/app/modules/home/pages/mobile/bundes/layout/mobile-page-item-row.component.ts index 33be223815..9f4add3195 100644 --- a/ui-ngx/src/app/modules/home/pages/mobile/bundes/layout/mobile-page-item-row.component.ts +++ b/ui-ngx/src/app/modules/home/pages/mobile/bundes/layout/mobile-page-item-row.component.ts @@ -55,6 +55,7 @@ import { TbPopoverService } from '@shared/components/popover.service'; import { CustomMobilePagePanelComponent } from '@home/pages/mobile/bundes/layout/custom-mobile-page-panel.component'; import { DefaultMobilePagePanelComponent } from '@home/pages/mobile/bundes/layout/default-mobile-page-panel.component'; import { TranslateService } from '@ngx-translate/core'; +import { DisplayPopoverConfig } from '@shared/components/popover.models'; @Component({ selector: 'tb-mobile-menu-item-row', @@ -222,27 +223,31 @@ export class MobilePageItemRowComponent implements ControlValueAccessor, OnInit, if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - disabled: this.disabled, - pageItem: deepClone(this.modelValue) + const config: DisplayPopoverConfig = { + trigger, + renderer: this.renderer, + componentType: undefined, + hostView: this.viewContainerRef, + preferredPlacement: ['right', 'bottom', 'top'], + context: { + disabled: this.disabled, + pageItem: deepClone(this.modelValue) + }, + showCloseButton: false, + popoverContentStyle: {padding: '16px 24px'}, + isModal: true }; if (this.isDefaultMenuItem) { - const defaultMobilePagePanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, DefaultMobilePagePanelComponent, ['right', 'bottom', 'top'], true, null, - ctx, - {}, - {}, {}, false, () => {}, {padding: '16px 24px'}); + config.componentType = DefaultMobilePagePanelComponent; + const defaultMobilePagePanelPopover = this.popoverService.displayPopover(config); defaultMobilePagePanelPopover.tbComponentRef.instance.popover = defaultMobilePagePanelPopover; defaultMobilePagePanelPopover.tbComponentRef.instance.defaultMobilePageApplied.subscribe((menuItem) => { defaultMobilePagePanelPopover.hide(); this.afterPageEdit(menuItem); }); } else { - const customMobilePagePanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, CustomMobilePagePanelComponent, ['right', 'bottom', 'top'], true, null, - ctx, - {}, - {}, {}, false, () => {}, {padding: '16px 24px'}); + config.componentType = CustomMobilePagePanelComponent; + const customMobilePagePanelPopover = this.popoverService.displayPopover(config); customMobilePagePanelPopover.tbComponentRef.instance.popover = customMobilePagePanelPopover; customMobilePagePanelPopover.tbComponentRef.instance.customMobilePageApplied.subscribe((page) => { customMobilePagePanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts index 3b1db9a595..7cde50ef29 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts @@ -203,18 +203,21 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - isAdd: add, - disabled: this.disabled, - aliasController: this.aliasController, - callbacks: this.callbacks, - behavior: deepClone(this.modelValue) - }; - const scadaSymbolBehaviorPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, ScadaSymbolBehaviorPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null, - ctx, - {}, - {}, {}, true); + const scadaSymbolBehaviorPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: ScadaSymbolBehaviorPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], + context: { + isAdd: add, + disabled: this.disabled, + aliasController: this.aliasController, + callbacks: this.callbacks, + behavior: deepClone(this.modelValue) + }, + isModal: true + }); scadaSymbolBehaviorPanelPopover.tbComponentRef.instance.popover = scadaSymbolBehaviorPanelPopover; scadaSymbolBehaviorPanelPopover.tbComponentRef.instance.behaviorSettingsApplied.subscribe((behavior) => { scadaSymbolBehaviorPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts index 95b49d47c2..339ac3a358 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts @@ -158,19 +158,21 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On tagFunctionControl = this.tagFormGroup.get('clickAction'); completer = this.clickActionFunctionCompleter; } - const ctx: any = { - tagFunction: tagFunctionControl.value, - tagFunctionType, - tag: this.tagFormGroup.get('tag').value, - completer, - disabled: this.disabled - }; - const scadaSymbolTagFunctionPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, ScadaSymbolMetadataTagFunctionPanelComponent, - ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null, - ctx, - {}, - {}, {}, true); + const scadaSymbolTagFunctionPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: ScadaSymbolMetadataTagFunctionPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], + context: { + tagFunction: tagFunctionControl.value, + tagFunctionType, + tag: this.tagFormGroup.get('tag').value, + completer, + disabled: this.disabled + }, + isModal: true + }); scadaSymbolTagFunctionPanelPopover.tbComponentRef.instance.popover = scadaSymbolTagFunctionPanelPopover; scadaSymbolTagFunctionPanelPopover.tbComponentRef.instance.tagFunctionApplied.subscribe((tagFunction) => { scadaSymbolTagFunctionPanelPopover.hide(); diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts index 6675425f32..93c411aabe 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts @@ -816,11 +816,15 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe const ctx: any = { modules: deepClone(this.controllerScriptModules) }; - const modulesPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, JsFuncModulesComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null, - ctx, - {}, - {}, {}, true); + const modulesPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + hostView: this.viewContainerRef, + componentType: JsFuncModulesComponent, + preferredPlacement: ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], + context: ctx, + isModal: true + }); modulesPanelPopover.tbComponentRef.instance.popover = modulesPanelPopover; modulesPanelPopover.tbComponentRef.instance.modulesApplied.subscribe((modules) => { modulesPanelPopover.hide(); diff --git a/ui-ngx/src/app/shared/components/js-func.component.ts b/ui-ngx/src/app/shared/components/js-func.component.ts index c424df8242..435b0495ec 100644 --- a/ui-ngx/src/app/shared/components/js-func.component.ts +++ b/ui-ngx/src/app/shared/components/js-func.component.ts @@ -518,14 +518,17 @@ export class JsFuncComponent implements OnInit, OnChanges, OnDestroy, ControlVal if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const ctx: any = { - modules: deepClone(this.modules) - }; - const modulesPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, JsFuncModulesComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], false, null, - ctx, - {}, - {}, {}, true); + const modulesPanelPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: JsFuncModulesComponent, + hostView: this.viewContainerRef, + preferredPlacement: 'leftTop', + context: { + modules: deepClone(this.modules) + }, + isModal: true + }); modulesPanelPopover.tbComponentRef.instance.popover = modulesPanelPopover; modulesPanelPopover.tbComponentRef.instance.modulesApplied.subscribe((modules) => { modulesPanelPopover.hide(); diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index 68ec6bcdd9..1ad4dab88c 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -558,9 +558,13 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const aggregationConfigPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, AggregationOptionsConfigPanelComponent, ['left', 'leftTop', 'leftBottom'], true, null, - { + const aggregationConfigPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + hostView: this.viewContainerRef, + componentType: AggregationOptionsConfigPanelComponent, + preferredPlacement: ['left', 'leftTop', 'leftBottom'], + context: { allowedAggregationTypes: deepClone(this.timewindowForm.get('allowedAggTypes').value), onClose: (result: Array | null) => { aggregationConfigPopover.hide(); @@ -570,8 +574,10 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On } } }, - {maxHeight: '500px', height: '100%'}, - {}, {}, true, () => {}, {padding: 0}); + overlayStyle: {maxHeight: '500px', height: '100%'}, + popoverContentStyle: {padding: 0}, + isModal: true + }); aggregationConfigPopover.tbComponentRef.instance.popoverComponent = aggregationConfigPopover; } this.cd.detectChanges(); @@ -612,9 +618,13 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const intervalsConfigPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, IntervalOptionsConfigPanelComponent, ['left', 'leftTop', 'leftBottom'], true, null, - { + const intervalsConfigPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + hostView: this.viewContainerRef, + componentType: IntervalOptionsConfigPanelComponent, + preferredPlacement: ['left', 'leftTop', 'leftBottom'], + context: { aggregation: this.aggregation, allowedIntervals: deepClone(this.timewindowForm.get(allowedIntervalsControlName).value), aggIntervalsConfig: deepClone(this.timewindowForm.get(aggIntervalsConfigControlName).value), @@ -629,8 +639,10 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On } } }, - {maxHeight: '500px', height: '100%'}, - {}, {}, true, () => {}, {padding: 0}); + overlayStyle: {maxHeight: '500px', height: '100%'}, + popoverContentStyle: {padding: 0}, + isModal: true + }); intervalsConfigPopover.tbComponentRef.instance.popoverComponent = intervalsConfigPopover; } this.cd.detectChanges(); diff --git a/ui-ngx/src/app/shared/components/time/timezone.component.ts b/ui-ngx/src/app/shared/components/time/timezone.component.ts index 78a938f53f..34e2486106 100644 --- a/ui-ngx/src/app/shared/components/time/timezone.component.ts +++ b/ui-ngx/src/app/shared/components/time/timezone.component.ts @@ -155,9 +155,13 @@ export class TimezoneComponent implements ControlValueAccessor, OnInit { if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); } else { - const timezoneSelectionPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, TimezonePanelComponent, ['bottomRight', 'leftBottom'], true, null, - { + const timezoneSelectionPopover = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + hostView: this.viewContainerRef, + componentType: TimezonePanelComponent, + preferredPlacement: ['bottomRight', 'leftBottom'], + context: { timezone: this.modelValue, userTimezoneByDefault: this.userTimezoneByDefaultValue, localBrowserTimezonePlaceholderOnEmpty: this.localBrowserTimezonePlaceholderOnEmptyValue, @@ -173,8 +177,9 @@ export class TimezoneComponent implements ControlValueAccessor, OnInit { } } }, - {}, - {}, {}, false); + showCloseButton: false, + isModal: true + }); timezoneSelectionPopover.tbComponentRef.instance.popoverComponent = timezoneSelectionPopover; } this.cd.detectChanges(); From 336d60268b818f5ecd5359a5fbb9afdd6ba6d93a Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 25 Mar 2025 10:56:32 +0200 Subject: [PATCH 09/26] UI: Refactoring popover model --- ui-ngx/src/app/shared/components/popover.models.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ui-ngx/src/app/shared/components/popover.models.ts b/ui-ngx/src/app/shared/components/popover.models.ts index 9fb01d1f3d..dc7e1f9ba7 100644 --- a/ui-ngx/src/app/shared/components/popover.models.ts +++ b/ui-ngx/src/app/shared/components/popover.models.ts @@ -112,11 +112,7 @@ export interface DisplayPopoverWithComponentRefConfig { isModal?: boolean; } -export const defaultPopoverConfig: DisplayPopoverWithComponentRefConfig = { - componentRef: undefined, - trigger: undefined, - renderer: undefined, - componentType: undefined, +export const defaultPopoverConfig: Partial> = { preferredPlacement: 'top', hideOnClickOutside: true, overlayStyle: {}, From 887c291deb71e26d8c743ed106afd626f3e34c84 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 25 Mar 2025 11:14:44 +0200 Subject: [PATCH 10/26] UI: Refactoring popover to use isModal setting --- .../calculated-field-arguments-table.component.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts index 945fc67ad4..512480279b 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts @@ -174,11 +174,15 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces entityHasError: this.entityNameErrorSet.has(argument.refEntityId?.id), usedArgumentNames: this.argumentsFormArray.value.map(({ argumentName }) => argumentName).filter(name => name !== argument.argumentName), }; - this.popoverComponent = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, CalculatedFieldArgumentPanelComponent, isDefined(index) ? 'left' : 'right', false, null, - ctx, - {}, - {}, {}, true); + this.popoverComponent = this.popoverService.displayPopover({ + trigger, + renderer: this.renderer, + componentType: CalculatedFieldArgumentPanelComponent, + hostView: this.viewContainerRef, + preferredPlacement: isDefined(index) ? 'left' : 'right', + context: ctx, + isModal: true + }); this.popoverComponent.tbComponentRef.instance.argumentsDataApplied.subscribe(({ value, index }) => { this.popoverComponent.hide(); const formGroup = this.fb.group(value); From f432ccc182c9fb7b1cbaac8e3b04db631bb51c7f Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 25 Mar 2025 11:49:50 +0200 Subject: [PATCH 11/26] UI: Fixed not work decline settings in advanced map settings --- .../lib/settings/map/map-widget-settings.component.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-widget-settings.component.ts index 221f820d68..3dc10f34cf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/map-widget-settings.component.ts @@ -54,9 +54,17 @@ export class MapWidgetSettingsComponent extends WidgetSettingsComponent { return mergeDeepIgnoreArray({} as MapWidgetSettings, mapWidgetDefaultSettings); } + protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { + return { + mapSettings: settings, + background: settings.background, + padding: settings.padding + }; + } + protected onSettingsSet(settings: WidgetSettings) { this.mapWidgetSettingsForm = this.fb.group({ - mapSettings: [settings, []], + mapSettings: [settings.mapSettings, []], background: [settings.background, []], padding: [settings.padding, []] }); From 7af6199cfc7588e8fb44b7fb275f2280f82d1f2e Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 25 Mar 2025 11:51:11 +0200 Subject: [PATCH 12/26] UI: Fixed decimals value for analogue charts --- .../home/components/widget/lib/analogue-gauge.models.ts | 5 ++--- .../gauge/analogue-gauge-widget-settings.component.ts | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/analogue-gauge.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/analogue-gauge.models.ts index 110d6b34a0..4b4ccce7c2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/analogue-gauge.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/analogue-gauge.models.ts @@ -17,7 +17,7 @@ import * as CanvasGauges from 'canvas-gauges'; import { FontSettings, getFontFamily } from '@home/components/widget/lib/settings.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 Highlight = CanvasGauges.Highlight; import BaseGauge = CanvasGauges.BaseGauge; @@ -264,8 +264,7 @@ function getValueDec(ctx: WidgetContext, settings: AnalogueGaugeSettings): numbe if (dataKey && isDefined(dataKey.decimals)) { return dataKey.decimals; } else { - return (isDefined(settings.valueDec) && settings.valueDec !== null) - ? settings.valueDec : ctx.decimals; + return isDefinedAndNotNull(ctx.decimals) ? ctx.decimals : (settings.valueDec || 0); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.ts index e30c75fe18..8836228fac 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.ts @@ -49,6 +49,7 @@ export class AnalogueGaugeWidgetSettingsComponent extends WidgetSettingsComponen minorTicks: 2, valueBox: true, valueInt: 3, + valueDec: 0, defaultColor: null, colorPlate: '#fff', colorMajorTicks: '#444', From 9d2c309d04a46ac3ca200718faef24239445e295 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 25 Mar 2025 11:53:53 +0200 Subject: [PATCH 13/26] Fix msa tests after docker service renaming --- .../java/org/thingsboard/server/msa/ContainerTestSuite.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java index 65def6a964..f2e721bdcb 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java @@ -188,8 +188,8 @@ public class ContainerTestSuite { .waitingFor("tb-vc-executor1", Wait.forLogMessage(TB_VC_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) .waitingFor("tb-vc-executor2", Wait.forLogMessage(TB_VC_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) .waitingFor("tb-js-executor", Wait.forLogMessage(TB_JS_EXECUTOR_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) - .waitingFor("tb-edqs-1", Wait.forLogMessage(TB_EDQS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) - .waitingFor("tb-edqs-2", Wait.forLogMessage(TB_EDQS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)); + .waitingFor("tb-edqs1", Wait.forLogMessage(TB_EDQS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-edqs2", Wait.forLogMessage(TB_EDQS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)); testContainer.start(); setActive(true); } catch (Exception e) { From f6ad9fed48144080082b198462bb77bbf486460c Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 25 Mar 2025 12:23:31 +0200 Subject: [PATCH 14/26] updated comment, fixed ctx data changing --- .../DefaultTbEntityDataSubscriptionService.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index 38303a2cf9..0f308d85ea 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -725,11 +725,12 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); ctx.setInitialDataSent(true); } else { - // to avoid updating timeseries with empty values - List data = ctx.getData().getData().stream() - .peek(entityData -> entityData.setTimeseries(null)) + // 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 preparedData = ctx.getData().getData().stream() + .map(entityData -> new EntityData(entityData.getEntityId(), entityData.getLatest(), null)) .toList(); - update = new EntityDataUpdate(ctx.getCmdId(), null, data, ctx.getMaxEntitiesPerDataSubscription()); + update = new EntityDataUpdate(ctx.getCmdId(), null, preparedData, ctx.getMaxEntitiesPerDataSubscription()); } ctx.sendWsMsg(update); } finally { From 16ff4e1e97cdca3068fed703b3405d1c7f246049 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 25 Mar 2025 11:51:20 +0100 Subject: [PATCH 15/26] added default debugDuration to the profileData --- .../src/main/data/upgrade/basic/schema_update.sql | 10 ++++++++++ .../install/DefaultSystemDataLoaderService.java | 4 +++- .../server/dao/tenant/TenantProfileServiceImpl.java | 4 +++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/application/src/main/data/upgrade/basic/schema_update.sql b/application/src/main/data/upgrade/basic/schema_update.sql index 29c7a084f4..e91fbb823c 100644 --- a/application/src/main/data/upgrade/basic/schema_update.sql +++ b/application/src/main/data/upgrade/basic/schema_update.sql @@ -83,3 +83,13 @@ SET profile_data = profile_data WHERE profile_data->'configuration'->>'maxCalculatedFieldsPerEntity' IS NULL; -- UPDATE TENANT PROFILE CALCULATED FIELD LIMITS END + +-- UPDATE TENANT PROFILE DEBUG DURATION START + +UPDATE tenant_profile +SET profile_data = jsonb_set(profile_data, '{configuration,maxDebugModeDurationMinutes}', '15', true) +WHERE + profile_data->'configuration' ? 'maxDebugModeDurationMinutes' = false + OR (profile_data->'configuration'->>'maxDebugModeDurationMinutes')::int = 0; + +-- UPDATE TENANT PROFILE DEBUG DURATION END diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 870ce3838b..49811942af 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -199,7 +199,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { tenantProfileService.findOrCreateDefaultTenantProfile(TenantId.SYS_TENANT_ID); TenantProfileData isolatedRuleEngineTenantProfileData = new TenantProfileData(); - isolatedRuleEngineTenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration()); + DefaultTenantProfileConfiguration configuration = new DefaultTenantProfileConfiguration(); + configuration.setMaxDebugModeDurationMinutes(15); + isolatedRuleEngineTenantProfileData.setConfiguration(configuration); TenantProfileQueueConfiguration mainQueueConfiguration = new TenantProfileQueueConfiguration(); mainQueueConfiguration.setName(DataConstants.MAIN_QUEUE_NAME); diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java index 04a57c3fea..a4d613f850 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java @@ -160,7 +160,9 @@ public class TenantProfileServiceImpl extends AbstractCachedEntityService Date: Tue, 25 Mar 2025 13:01:15 +0200 Subject: [PATCH 16/26] UI: Fixed close autocomplete in LWM2M list model; Minor refactoring --- .../lwm2m/lwm2m-object-list.component.html | 2 +- .../device/lwm2m/lwm2m-object-list.component.ts | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html index 706076e3ff..d05097bd5b 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html @@ -48,7 +48,7 @@
  • - {{ 'device-profile.lwm2m.no-objects-matching' | translate:{object: truncate.transform(searchText, true, 6, '...')} }} + {{ 'device-profile.lwm2m.no-objects-matching' | translate:{object: (searchText | truncate: true: 6: '...')} }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts index 7ea253cd9d..01e79fcadf 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts @@ -33,8 +33,8 @@ import { DeviceProfileService } from '@core/http/device-profile.service'; import { Direction } from '@shared/models/page/sort-order'; import { isDefined, isDefinedAndNotNull, isObject, isString } from '@core/utils'; import { PageLink } from '@shared/models/page/page-link'; -import { TruncatePipe } from '@shared/pipe/truncate.pipe'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; @Component({ selector: 'tb-profile-lwm2m-object-list', @@ -79,13 +79,12 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V @Output() removeList = new EventEmitter(); - @ViewChild('objectInput') objectInput: ElementRef; + @ViewChild('objectInput', {static: true}) objectInput: ElementRef; + @ViewChild('objectInput', {static: true, read: MatAutocompleteTrigger}) matAutocompleteTrigger: MatAutocompleteTrigger; - private propagateChange = (v: any) => { - } + private propagateChange: (value: any) => void = () => {}; - constructor(public truncate: TruncatePipe, - private deviceProfileService: DeviceProfileService, + constructor(private deviceProfileService: DeviceProfileService, private fb: UntypedFormBuilder) { this.lwm2mListFormGroup = this.fb.group({ objectsList: [this.objectsList], @@ -113,7 +112,7 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V this.propagateChange = fn; } - registerOnTouched(fn: any): void { + registerOnTouched(_fn: any): void { } ngOnInit() { @@ -140,6 +139,9 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V if (isDefined(this.objectInput)) { this.clear('', false); } + if (this.matAutocompleteTrigger.panelOpen) { + this.matAutocompleteTrigger.closePanel(); + } } else { this.lwm2mListFormGroup.enable({emitEvent: false}); } From dedf3db9287dcc2a4bf1c7e0ebb0b110eb7c5ab3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 25 Mar 2025 13:55:11 +0200 Subject: [PATCH 17/26] UI: Minor fixes. --- .../widget/lib/chart/latest-chart.ts | 60 +++++++------- .../widget/lib/chart/time-series-chart.ts | 12 +-- .../home/models/dashboard-component.models.ts | 78 ++++++++++--------- 3 files changed, 78 insertions(+), 72 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts index 13e9dd7d5e..fad0d84632 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts @@ -223,40 +223,42 @@ export abstract class TbLatestChart { } protected updateSeriesData(initial = false) { - this.total = 0; - this.totalText = 'N/A'; - let hasValue = false; - for (const dataItem of this.dataItems) { - if (dataItem.enabled && dataItem.hasValue) { - hasValue = true; - this.total += dataItem.value; - } - if (this.settings.showLegend) { - const legendItem = this.legendItems.find(item => item.dataKey === dataItem.dataKey); - if (dataItem.hasValue) { - legendItem.hasValue = true; - legendItem.value = formatValue(dataItem.value, this.decimals, this.units, false); - } else { - legendItem.hasValue = false; - legendItem.value = '--'; + if (!this.latestChart.isDisposed()) { + this.total = 0; + this.totalText = 'N/A'; + let hasValue = false; + for (const dataItem of this.dataItems) { + if (dataItem.enabled && dataItem.hasValue) { + hasValue = true; + this.total += dataItem.value; + } + if (this.settings.showLegend) { + const legendItem = this.legendItems.find(item => item.dataKey === dataItem.dataKey); + if (dataItem.hasValue) { + legendItem.hasValue = true; + legendItem.value = formatValue(dataItem.value, this.decimals, this.units, false); + } else { + legendItem.hasValue = false; + legendItem.value = '--'; + } } } - } - if (this.settings.showTotal || this.settings.showLegend) { - if (hasValue) { - this.totalText = formatValue(this.total, this.decimals, this.units, false); - if (this.settings.showLegend && !this.settings.showTotal) { - this.legendItems[this.legendItems.length - 1].hasValue = true; - this.legendItems[this.legendItems.length - 1].value = this.totalText; + if (this.settings.showTotal || this.settings.showLegend) { + if (hasValue) { + this.totalText = formatValue(this.total, this.decimals, this.units, false); + if (this.settings.showLegend && !this.settings.showTotal) { + this.legendItems[this.legendItems.length - 1].hasValue = true; + this.legendItems[this.legendItems.length - 1].value = this.totalText; + } + } else if (this.settings.showLegend && !this.settings.showTotal) { + this.legendItems[this.legendItems.length - 1].hasValue = false; + this.legendItems[this.legendItems.length - 1].value = '--'; } - } else if (this.settings.showLegend && !this.settings.showTotal) { - this.legendItems[this.legendItems.length - 1].hasValue = false; - this.legendItems[this.legendItems.length - 1].value = '--'; } + this.doUpdateSeriesData(); + this.latestChart.setOption(this.latestChartOption); + this.afterUpdateSeriesData(initial); } - this.doUpdateSeriesData(); - this.latestChart.setOption(this.latestChartOption); - this.afterUpdateSeriesData(initial); } private drawChart() { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index ad121a1eb0..2b4b52ccfb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -690,12 +690,14 @@ export class TbTimeSeriesChart { } private updateSeriesData(updateScale = false): void { - this.updateSeries(); - if (updateScale && this.updateYAxisScale(this.yAxisList)) { - this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); + if (!this.timeSeriesChart.isDisposed()) { + this.updateSeries(); + if (updateScale && this.updateYAxisScale(this.yAxisList)) { + this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); + } + this.timeSeriesChart.setOption(this.timeSeriesChartOptions); + this.updateAxes(); } - this.timeSeriesChart.setOption(this.timeSeriesChartOptions); - this.updateAxes(); } private updateSeries(): void { diff --git a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts index 458a1d1eeb..facb159af4 100644 --- a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts @@ -468,49 +468,51 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { const resizable = item.resize; - this.heightValue = resizable.height; - this.widthValue = resizable.width; - - const setItemHeight = resizable.setItemHeight.bind(resizable); - const setItemWidth = resizable.setItemWidth.bind(resizable); - resizable.setItemHeight = (height) => { - setItemHeight(height); - this.heightValue = height; - if (this.preserveAspectRatio) { - setItemWidth(height * this.aspectRatio); - } - }; - resizable.setItemWidth = (width) => { - setItemWidth(width); - this.widthValue = width; - if (this.preserveAspectRatio) { - setItemHeight(width / this.aspectRatio); - } - }; - - Object.defineProperty(resizable, 'height', { - get: () => this.heightValue, - set: v => { - if (this.heightValue !== v) { - if (this.preserveAspectRatio) { - this.widthValue = v * this.aspectRatio; + if (resizable) { + this.heightValue = resizable.height; + this.widthValue = resizable.width; + + const setItemHeight = resizable.setItemHeight.bind(resizable); + const setItemWidth = resizable.setItemWidth.bind(resizable); + resizable.setItemHeight = (height) => { + setItemHeight(height); + this.heightValue = height; + if (this.preserveAspectRatio) { + setItemWidth(height * this.aspectRatio); + } + }; + resizable.setItemWidth = (width) => { + setItemWidth(width); + this.widthValue = width; + if (this.preserveAspectRatio) { + setItemHeight(width / this.aspectRatio); + } + }; + + Object.defineProperty(resizable, 'height', { + get: () => this.heightValue, + set: v => { + if (this.heightValue !== v) { + if (this.preserveAspectRatio) { + this.widthValue = v * this.aspectRatio; + } + this.heightValue = v; } - this.heightValue = v; } - } - }); + }); - Object.defineProperty(resizable, 'width', { - get: () => this.widthValue, - set: v => { - if (this.widthValue !== v) { - if (this.preserveAspectRatio) { - this.heightValue = v / this.aspectRatio; + Object.defineProperty(resizable, 'width', { + get: () => this.widthValue, + set: v => { + if (this.widthValue !== v) { + if (this.preserveAspectRatio) { + this.heightValue = v / this.aspectRatio; + } + this.widthValue = v; } - this.widthValue = v; } - } - }); + }); + } this.preserveAspectRatioApplied = true; } } From ace521da5b04334bb8945b5e95b891f01236e0d0 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 25 Mar 2025 16:03:19 +0200 Subject: [PATCH 18/26] UI: New maps - prevent click event on marker drag. --- .../widget/lib/maps/data-layer/markers-data-layer.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/markers-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/markers-data-layer.ts index 99cb5a9b7d..146ace1120 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/markers-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/markers-data-layer.ts @@ -480,10 +480,14 @@ class TbMarkerDataLayerItem extends TbLatestDataLayerItem { + (this.marker.dragging as any)._draggable = { _moved: true }; + (this.marker.dragging as any)._enabled = true; this.moving = true; }); this.marker.on('pm:dragend', () => { this.saveMarkerLocation(); + delete (this.marker.dragging as any)._draggable; + delete (this.marker.dragging as any)._enabled; this.moving = false; }); } From 4b63d929c0ec4af7756a27c267112abfc24a11b8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 25 Mar 2025 16:24:11 +0200 Subject: [PATCH 19/26] UI: Fix markdown widget change detection. --- .../home/components/widget/lib/markdown-widget.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts index 61f5073953..10d5f9b1a0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts @@ -149,7 +149,7 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit { if (this.markdownText !== markdownText) { this.markdownText = this.utils.customTranslation(markdownText, markdownText); } - this.cd.markForCheck(); + this.cd.detectChanges(); } markdownClick($event: MouseEvent) { From f0f2b30269cd1c126c99025eb668e88a2b8a1bae Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 25 Mar 2025 17:16:26 +0200 Subject: [PATCH 20/26] UI: New maps - improve markers draggable mode. --- .../widget/lib/maps/data-layer/latest-map-data-layer.ts | 2 ++ .../components/widget/lib/maps/data-layer/markers-data-layer.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/latest-map-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/latest-map-data-layer.ts index 3a537ba8e7..fb79611d71 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/latest-map-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/latest-map-data-layer.ts @@ -117,6 +117,7 @@ export abstract class TbLatestDataLayerItem { - (this.marker.dragging as any)._draggable = { _moved: true }; + (this.marker.dragging as any)._draggable = { _moved: true, off: (_args: any) => { return { disable: () => {}} } }; (this.marker.dragging as any)._enabled = true; this.moving = true; }); From 7e55523465eb77e2772d578aba0a4056f08a4aaa Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 26 Mar 2025 10:29:15 +0200 Subject: [PATCH 21/26] UI: Clear getting start widget style --- .../widget/lib/home-page/getting-started-widget.component.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss index 43ff26793a..a1959c74e8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.scss @@ -38,9 +38,6 @@ .tb-get-started-container { flex: 1; overflow: auto; - mat-stepper { - transform: scale(1); //fixed blur content - } } } From 2f18c8e3c985aeef590d3013cdae48c5f573a5bd Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 26 Mar 2025 12:31:48 +0100 Subject: [PATCH 22/26] minor refactoring --- .../server/service/install/DefaultSystemDataLoaderService.java | 3 ++- .../main/java/org/thingsboard/common/util/DebugModeUtil.java | 2 +- .../server/dao/tenant/TenantProfileServiceImpl.java | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 49811942af..e9ef8c5ace 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -122,6 +122,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import static org.thingsboard.common.util.DebugModeUtil.DEBUG_MODE_DEFAULT_DURATION_MINUTES; import static org.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE; import static org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService.isSigningKeyDefault; import static org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService.validateKeyLength; @@ -200,7 +201,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { TenantProfileData isolatedRuleEngineTenantProfileData = new TenantProfileData(); DefaultTenantProfileConfiguration configuration = new DefaultTenantProfileConfiguration(); - configuration.setMaxDebugModeDurationMinutes(15); + configuration.setMaxDebugModeDurationMinutes(DEBUG_MODE_DEFAULT_DURATION_MINUTES); isolatedRuleEngineTenantProfileData.setConfiguration(configuration); TenantProfileQueueConfiguration mainQueueConfiguration = new TenantProfileQueueConfiguration(); diff --git a/common/util/src/main/java/org/thingsboard/common/util/DebugModeUtil.java b/common/util/src/main/java/org/thingsboard/common/util/DebugModeUtil.java index c4b062a0d8..1b035f61f1 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/DebugModeUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/DebugModeUtil.java @@ -22,7 +22,7 @@ import java.util.Set; public final class DebugModeUtil { - private static final int DEBUG_MODE_DEFAULT_DURATION_MINUTES = 15; + public static final int DEBUG_MODE_DEFAULT_DURATION_MINUTES = 15; private DebugModeUtil() { } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java index a4d613f850..e165d6cfe6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java @@ -44,6 +44,7 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import static org.thingsboard.common.util.DebugModeUtil.DEBUG_MODE_DEFAULT_DURATION_MINUTES; import static org.thingsboard.server.dao.service.Validator.validateId; @Service("TenantProfileDaoService") @@ -161,7 +162,7 @@ public class TenantProfileServiceImpl extends AbstractCachedEntityService Date: Wed, 26 Mar 2025 14:42:32 +0200 Subject: [PATCH 23/26] UI: Remove unused setting --- .../src/main/data/json/system/widget_types/radial_gauge.json | 2 +- .../modules/home/components/widget/lib/analogue-gauge.models.ts | 2 +- .../settings/gauge/analogue-gauge-widget-settings.component.ts | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/application/src/main/data/json/system/widget_types/radial_gauge.json b/application/src/main/data/json/system/widget_types/radial_gauge.json index ce09d31988..b463994347 100644 --- a/application/src/main/data/json/system/widget_types/radial_gauge.json +++ b/application/src/main/data/json/system/widget_types/radial_gauge.json @@ -17,7 +17,7 @@ "settingsDirective": "tb-analogue-radial-gauge-widget-settings", "hasBasicMode": true, "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": [ { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/analogue-gauge.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/analogue-gauge.models.ts index 4b4ccce7c2..1cbc41c368 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/analogue-gauge.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/analogue-gauge.models.ts @@ -264,7 +264,7 @@ function getValueDec(ctx: WidgetContext, settings: AnalogueGaugeSettings): numbe if (dataKey && isDefined(dataKey.decimals)) { return dataKey.decimals; } else { - return isDefinedAndNotNull(ctx.decimals) ? ctx.decimals : (settings.valueDec || 0); + return isDefinedAndNotNull(ctx.decimals) ? ctx.decimals : 0; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.ts index 8836228fac..e30c75fe18 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/analogue-gauge-widget-settings.component.ts @@ -49,7 +49,6 @@ export class AnalogueGaugeWidgetSettingsComponent extends WidgetSettingsComponen minorTicks: 2, valueBox: true, valueInt: 3, - valueDec: 0, defaultColor: null, colorPlate: '#fff', colorMajorTicks: '#444', From 1ae9b01bd95164b8dd282e734c367cef24c5ccda Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 26 Mar 2025 15:46:51 +0200 Subject: [PATCH 24/26] UI: New map widgets - improve update bounds. --- .../app/modules/home/components/widget/lib/maps/map.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts index 6672941045..2b8d20c93d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts @@ -937,7 +937,13 @@ export abstract class TbMap { bounds = new L.LatLngBounds(null, null); dataLayersBounds.forEach(b => bounds.extend(b)); 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; if (!this.ignoreUpdateBounds && !this.isPlacingItem) { this.fitBounds(bounds); From 82db1ceaef3e0a8a30d6bb682d9649de86a07816 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 26 Mar 2025 15:48:18 +0200 Subject: [PATCH 25/26] UI: Fixed incorrect update date in widgets action table when switching widgets; Refactoring --- .../manage-widget-actions.component.models.ts | 11 ++-- .../action/manage-widget-actions.component.ts | 51 +++++++++---------- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts index 621bfd523f..c108d673d3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts @@ -21,11 +21,11 @@ import { widgetActionTypeTranslationMap } from '@app/shared/models/widget.models'; 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 { TranslateService } from '@ngx-translate/core'; 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 { deepClone } from '@core/utils'; @@ -68,11 +68,11 @@ export class WidgetActionsDatasource implements DataSource> { + connect(_collectionViewer: CollectionViewer): Observable> { return this.actionsSubject.asObservable(); } - disconnect(collectionViewer: CollectionViewer): void { + disconnect(_collectionViewer: CollectionViewer): void { this.actionsSubject.complete(); this.pageDataSubject.complete(); } @@ -115,8 +115,7 @@ export class WidgetActionsDatasource implements DataSource}; + private viewsInited = false; + private dirtyValue = false; private widgetResize$: ResizeObserver; private destroyed = false; @@ -101,15 +98,14 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni private propagateChange = (_: any) => {}; - constructor(protected store: Store, - private translate: TranslateService, + constructor(private translate: TranslateService, private utils: UtilsService, private dialog: MatDialog, private dialogs: DialogService, private cd: ChangeDetectorRef, private elementRef: ElementRef, private zone: NgZone) { - super(store); + super(); const sortOrder: SortOrder = { property: 'actionSourceName', direction: Direction.ASC }; this.pageLink = new PageLink(10, 0, null, sortOrder); this.dataSource = new WidgetActionsDatasource(this.translate, this.utils); @@ -137,7 +133,6 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni } ngAfterViewInit() { - fromEvent(this.searchInputField.nativeElement, 'keyup') .pipe( debounceTime(150), @@ -162,10 +157,9 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni this.dirtyValue = false; this.updateData(true); } - } - updateData(reload: boolean = false) { + private updateData(reload: boolean = false) { this.pageLink.page = this.paginator.pageIndex; this.pageLink.pageSize = this.paginator.pageSize; this.pageLink.sortOrder.property = this.sort.active; @@ -198,7 +192,7 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni this.openWidgetActionDialog($event, action); } - openWidgetActionDialog($event: Event, action: WidgetActionDescriptorInfo = null) { + private openWidgetActionDialog($event: Event, action: WidgetActionDescriptorInfo = null) { if ($event) { $event.stopPropagation(); } @@ -208,15 +202,15 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni prevActionSourceId = action.actionSourceId; } const availableActionSources: {[actionSourceId: string]: WidgetActionSource} = {}; - for (const id of Object.keys(this.innerValue.actionSources)) { - const actionSource = this.innerValue.actionSources[id]; + for (const id of Object.keys(this.actionSources)) { + const actionSource = this.actionSources[id]; if (actionSource.multiple) { availableActionSources[id] = actionSource; } else { if (!isAdd && action.actionSourceId === id) { availableActionSources[id] = actionSource; } else { - const existing = this.innerValue.actionsMap[id]; + const existing = this.actionsMap[id]; if (!existing || !existing.length) { availableActionSources[id] = actionSource; } @@ -225,7 +219,7 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni } const actionsData: WidgetActionsData = { - actionsMap: this.innerValue.actionsMap, + actionsMap: this.actionsMap, actionSources: availableActionSources }; @@ -277,7 +271,7 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni } private getOrCreateTargetActions(actionSourceId: string): Array { - const actionsMap = this.innerValue.actionsMap; + const actionsMap = this.actionsMap; let targetActions = actionsMap[actionSourceId]; if (!targetActions) { targetActions = []; @@ -323,7 +317,7 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni this.updateData(); } - resetSortAndFilter(update: boolean = true) { + private resetSortAndFilter(update: boolean = true) { this.pageLink.textSearch = null; this.paginator.pageIndex = 0; const sortable = this.sort.sortables.get('actionSourceName'); @@ -338,7 +332,7 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni this.propagateChange = fn; } - registerOnTouched(fn: any): void { + registerOnTouched(_fn: any): void { } setDisabledState(isDisabled: boolean): void { @@ -346,13 +340,14 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni } writeValue(actions?: {[actionSourceId: string]: Array}): void { - this.innerValue = { - actionsMap: actions || {}, - actionSources: this.actionSources || {} - }; + this.actionsMap = actions ?? {}; setTimeout(() => { if (!this.destroyed) { - this.dataSource.setActions(this.innerValue); + const actionData: WidgetActionsData = { + actionsMap: this.actionsMap, + actionSources: this.actionSources + }; + this.dataSource.setActions(actionData); if (this.viewsInited) { this.resetSortAndFilter(true); } else { @@ -364,6 +359,6 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni private onActionsUpdated() { this.updateData(true); - this.propagateChange(this.innerValue.actionsMap); + this.propagateChange(this.actionsMap); } } From 29eec5d13aff82543090c870eedbdb44b46128bf Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 26 Mar 2025 17:02:10 +0200 Subject: [PATCH 26/26] UI: New map widgets - change scale control position to bottom-right in order to avoid overlap with attribution control. --- ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts index 2b8d20c93d..c4df07336b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts @@ -176,6 +176,7 @@ export abstract class TbMap { private setupControls(): Observable { if (this.settings.scales?.length) { L.control.scale({ + position: 'bottomright', metric: this.settings.scales.includes(MapScale.metric), imperial: this.settings.scales.includes(MapScale.imperial) }).addTo(this.map);