From c25b57eb39f6d7e043af1aab4a8de9a8399080a8 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 19 Mar 2025 12:04:34 +0200 Subject: [PATCH 1/3] 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 2/3] 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 4d18579daf06fe50c2a1636de71ac91ba547164f Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 24 Mar 2025 11:11:14 +0200 Subject: [PATCH 3/3] 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",