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;"}
+
+{: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",