From f290246aade5d0d4302179373dd74b0e2442e0d2 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 31 Jan 2025 16:54:33 +0200 Subject: [PATCH] UI: Maps - implement tooltip tag actions. --- .../json/system/widget_types/image_map.json | 2 +- .../data/json/system/widget_types/map.json | 2 +- .../lib/maps/data-layer/map-data-layer.ts | 22 ++- .../home/components/widget/lib/maps/map.ts | 36 ---- .../widget/lib/maps/models/map.models.ts | 3 +- ...idget-action-settings-panel.component.html | 6 +- .../widget-action-settings-panel.component.ts | 36 ++-- .../widget-action-settings.component.ts | 1 - .../action/widget-action.component.html | 17 ++ .../common/action/widget-action.component.ts | 39 +++- ...data-layer-pattern-settings.component.html | 4 + .../data-layer-pattern-settings.component.ts | 5 + .../map/map-data-layer-dialog.component.html | 4 +- .../map/map-data-layer-dialog.component.ts | 10 +- .../map-tooltip-tag-actions.component.html | 67 +++++++ .../map-tooltip-tag-actions.component.scss | 49 +++++ .../map/map-tooltip-tag-actions.component.ts | 176 ++++++++++++++++++ .../common/widget-settings-common.module.ts | 4 + ui-ngx/src/app/shared/models/widget.models.ts | 1 + .../assets/locale/locale.constant-en_US.json | 4 + 20 files changed, 407 insertions(+), 81 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.ts diff --git a/application/src/main/data/json/system/widget_types/image_map.json b/application/src/main/data/json/system/widget_types/image_map.json index b7e8644662..0c10599757 100644 --- a/application/src/main/data/json/system/widget_types/image_map.json +++ b/application/src/main/data/json/system/widget_types/image_map.json @@ -11,7 +11,7 @@ "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n tooltipAction: {\n name: 'widget-action.tooltip-tag-action',\n multiple: true\n }\n };\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true\n };\n}\n", "settingsForm": [], "dataKeySettingsForm": [], "settingsDirective": "tb-map-widget-settings", diff --git a/application/src/main/data/json/system/widget_types/map.json b/application/src/main/data/json/system/widget_types/map.json index 7ec5b35320..fb19914741 100644 --- a/application/src/main/data/json/system/widget_types/map.json +++ b/application/src/main/data/json/system/widget_types/map.json @@ -11,7 +11,7 @@ "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n tooltipAction: {\n name: 'widget-action.tooltip-tag-action',\n multiple: true\n }\n };\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true\n };\n}\n", "settingsForm": [], "dataKeySettingsForm": [], "settingsDirective": "tb-map-widget-settings", diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts index 861c02a560..ebee9f8c59 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts @@ -60,7 +60,7 @@ export abstract class TbDataLayerItem; - private createTooltip(datasource: TbMapDatasource) { + private createTooltip() { this.tooltip = L.popup(); this.layer.bindPopup(this.tooltip, {autoClose: this.settings.tooltip.autoclose, closeOnClick: false}); this.layer.off('click'); @@ -279,22 +279,30 @@ export abstract class TbDataLayerItem { - this.bindTooltipActions(datasource); + this.bindTooltipActions(); (this.layer as any)._popup._closeButton.addEventListener('click', (event: Event) => { event.preventDefault(); }); }); } - private bindTooltipActions(datasource: TbMapDatasource) { + private bindTooltipActions() { const actions = this.tooltip.getElement().getElementsByClassName('tb-custom-action'); Array.from(actions).forEach( (element: HTMLElement) => { const actionName = element.getAttribute('data-action-name'); - this.dataLayer.getMap().tooltipElementClick(element, actionName, datasource); + if (this.settings.tooltip?.tagActions) { + const action = this.settings.tooltip.tagActions.find(action => action.name === actionName); + if (action) { + element.onclick = ($event) => + { + this.dataLayer.getMap().dataItemClick($event, action, this.data.$datasource); + return false; + }; + } + } }); } - } export enum MapDataLayerType { 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 58f042f475..1e7165a85f 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 @@ -18,7 +18,6 @@ import { additionalMapDataSourcesToDatasources, BaseMapSettings, DataKeyValuePair, - MapActionHandler, MapType, mergeMapDatasources, mergeUnplacedDataItemsArrays, @@ -99,8 +98,6 @@ export abstract class TbMap { private readonly mapResize$: ResizeObserver; - private readonly tooltipActions: { [name: string]: MapActionHandler }; - private tooltipInstances: TooltipInstancesData[] = []; private currentPopover: TbPopoverComponent; @@ -115,8 +112,6 @@ export abstract class TbMap { protected containerElement: HTMLElement) { this.settings = mergeDeepIgnoreArray({} as S, this.defaultSettings(), this.inputSettings as S); - this.tooltipActions = this.loadActions('tooltipAction'); - $(containerElement).empty(); $(containerElement).addClass('tb-map-layout'); const mapElement = $('
'); @@ -595,27 +590,6 @@ export abstract class TbMap { } } - private loadActions(name: string): { [name: string]: MapActionHandler } { - const descriptors = this.ctx.actionsApi.getActionDescriptors(name); - const actions: { [name: string]: MapActionHandler } = {}; - descriptors.forEach(descriptor => { - actions[descriptor.name] = ($event: Event, datasource: TbMapDatasource) => this.onCustomAction(descriptor, $event, datasource); - }); - return actions; - } - - private onCustomAction(descriptor: WidgetActionDescriptor, $event: Event, entityInfo: TbMapDatasource) { - if ($event) { - $event.preventDefault(); - $event.stopPropagation(); - } - const { entityId, entityName, entityLabel, entityType } = entityInfo; - this.ctx.actionsApi.handleWidgetAction($event, descriptor, { - entityType, - id: entityId - }, entityName, null, entityLabel); - } - private updateAddButtonsStates() { if (this.currentAddButton) { if (this.addMarkerButton && this.addMarkerButton !== this.currentAddButton) { @@ -705,16 +679,6 @@ export abstract class TbMap { }, entityName, null, entityLabel); } - public tooltipElementClick(element: HTMLElement, action: string, datasource: TbMapDatasource): void { - if (element && this.tooltipActions[action]) { - element.onclick = ($event) => - { - this.tooltipActions[action]($event, datasource); - return false; - }; - } - } - public selectItem(item: TbDataLayerItem, cancel = false, force = false): boolean { if (this.isPlacingItem) { return false; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts index 76b8566692..947d394c2d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts @@ -95,6 +95,7 @@ export interface DataLayerTooltipSettings extends DataLayerPatternSettings { autoclose: boolean; offsetX: number; offsetY: number; + tagActions?: WidgetAction[]; } export enum DataLayerEditAction { @@ -879,8 +880,6 @@ export type MapSetting = GeoMapSettings & ImageMapSettings; export const defaultMapSettings: MapSetting = defaultGeoMapSettings; -export type MapActionHandler = ($event: Event, datasource: TbMapDatasource) => void; - export interface MarkerImageInfo { url: string; size: number; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.html index 3fffd3f424..7a7900214e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.html @@ -21,7 +21,9 @@ + [widgetType]="widgetType" + [withName]="withName" + [actionNames]="actionNames">
@@ -36,7 +38,7 @@ type="button" (click)="applyWidgetAction()" [disabled]="widgetActionFormGroup.invalid || !widgetActionFormGroup.dirty"> - {{ 'action.apply' | translate }} + {{ applyTitle }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.ts index 8ed53fd95e..a4df5e9250 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.ts @@ -15,25 +15,12 @@ /// import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; -import { PageComponent } from '@shared/components/page.component'; import { TbPopoverComponent } from '@shared/components/popover.component'; -import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { merge } from 'rxjs'; -import { - DataToValueType, - GetValueAction, - getValueActions, - getValueActionTranslations, - GetValueSettings -} from '@shared/models/action-widget-settings.models'; -import { ValueType } from '@shared/models/constants'; -import { TargetDevice, WidgetAction, widgetType } from '@shared/models/widget.models'; -import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models'; -import { IAliasController } from '@core/api/widget-api.models'; -import { WidgetService } from '@core/http/widget.service'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { WidgetAction, widgetType } from '@shared/models/widget.models'; import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'tb-widget-action-settings-panel', @@ -42,7 +29,7 @@ import { WidgetActionCallbacks } from '@home/components/widget/action/manage-wid styleUrls: ['./action-settings-panel.component.scss'], encapsulation: ViewEncapsulation.None }) -export class WidgetActionSettingsPanelComponent extends PageComponent implements OnInit { +export class WidgetActionSettingsPanelComponent implements OnInit { @Input() widgetAction: WidgetAction; @@ -57,7 +44,14 @@ export class WidgetActionSettingsPanelComponent extends PageComponent implements callbacks: WidgetActionCallbacks; @Input() - popover: TbPopoverComponent; + @coerceBoolean() + withName = false; + + @Input() + actionNames: string[]; + + @Input() + applyTitle = this.translate.instant('action.apply'); @Output() widgetActionApplied = new EventEmitter(); @@ -65,8 +59,8 @@ export class WidgetActionSettingsPanelComponent extends PageComponent implements widgetActionFormGroup: UntypedFormGroup; constructor(private fb: UntypedFormBuilder, - protected store: Store) { - super(store); + private translate: TranslateService, + private popover: TbPopoverComponent) { } ngOnInit(): void { 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 95ec0b1262..d860667d2a 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 @@ -118,7 +118,6 @@ export class WidgetActionSettingsComponent implements OnInit, ControlValueAccess ctx, {}, {}, {}, true); - widgetActionSettingsPanelPopover.tbComponentRef.instance.popover = widgetActionSettingsPanelPopover; 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/action/widget-action.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html index 362e8eac2e..643b36e32a 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 @@ -16,6 +16,23 @@ -->
+
+
{{'widget-config.action-name' | translate}}*
+ + + + warning + + +
widget-config.action
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 6c515ad0a7..a7ad209b85 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 @@ -15,13 +15,13 @@ /// import { - ControlValueAccessor, + ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, - Validator, + Validator, ValidatorFn, Validators } from '@angular/forms'; import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; @@ -46,6 +46,7 @@ import { CustomActionEditorCompleter, toCustomAction } from '@home/components/widget/lib/settings/common/action/custom-action.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; const stateDisplayTypes = ['normal', 'separateDialog', 'popover'] as const; type stateDisplayTypeTuple = typeof stateDisplayTypes; @@ -89,6 +90,13 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali @Input() callbacks: WidgetActionCallbacks; + @Input() + @coerceBoolean() + withName = false; + + @Input() + actionNames: string[]; + widgetActionTypes = widgetActionTypes; widgetActionTypeTranslations = widgetActionTypeTranslationMap; widgetActionType = WidgetActionType; @@ -147,6 +155,10 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali ngOnInit() { this.widgetActionFormGroup = this.fb.group({}); + if (this.withName) { + this.widgetActionFormGroup.addControl('name', + this.fb.control(null, [this.validateActionName(), Validators.required])); + } this.widgetActionFormGroup.addControl('type', this.fb.control(null, [Validators.required])); this.widgetActionFormGroup.get('type').valueChanges.pipe( @@ -162,6 +174,11 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali } writeValue(widgetAction?: WidgetAction): void { + if (this.withName) { + this.widgetActionFormGroup.patchValue({ + name: widgetAction?.name + }, {emitEvent: false}); + } this.widgetActionFormGroup.patchValue({ type: widgetAction?.type }, {emitEvent: false}); @@ -457,6 +474,24 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali return res; } + private validateActionName(): ValidatorFn { + return (c: FormControl) => { + const newName = c.value; + const valid = this.checkActionName(newName); + return !valid ? { + actionNameNotUnique: true + } : null; + }; + } + + private checkActionName(name: string): boolean { + let actionNameIsUnique = true; + if (this.actionNames?.length) { + actionNameIsUnique = !this.actionNames.includes(name); + } + return actionNameIsUnique; + } + private widgetActionUpdated() { const type: WidgetActionType = this.widgetActionFormGroup.get('type').value; let result: WidgetAction; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html index 892558b047..b7d723b991 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html @@ -79,6 +79,10 @@
+ + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.ts index ee00d92043..dd57a7896b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.ts @@ -34,6 +34,7 @@ import { DataLayerTooltipSettings, dataLayerTooltipTriggers, dataLayerTooltipTriggerTranslationMap } from '@home/components/widget/lib/maps/models/map.models'; import { coerceBoolean } from '@shared/decorators/coercion'; +import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models'; @Component({ selector: 'tb-data-layer-pattern-settings', @@ -74,6 +75,9 @@ export class DataLayerPatternSettingsComponent implements OnInit, ControlValueAc @coerceBoolean() hasTooltipOffset = false; + @Input() + context: MapSettingsContext; + private modelValue: DataLayerPatternSettings | DataLayerTooltipSettings; private propagateChange = null; @@ -100,6 +104,7 @@ export class DataLayerPatternSettingsComponent implements OnInit, ControlValueAc this.patternSettingsFormGroup.addControl('offsetX', this.fb.control(null, [])); this.patternSettingsFormGroup.addControl('offsetY', this.fb.control(null, [])); } + this.patternSettingsFormGroup.addControl('tagActions', this.fb.control(null, [])); } this.patternSettingsFormGroup.valueChanges.pipe( takeUntilDestroyed(this.destroyRef) 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 aaf04f5550..1845b4a793 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 @@ -244,10 +244,12 @@ @@ -259,7 +261,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts index 5244e7e266..9751e87621 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts @@ -18,12 +18,13 @@ import { Component, DestroyRef, Inject, ViewEncapsulation } from '@angular/core' import { DialogComponent } from '@shared/components/dialog.component'; import { CirclesDataLayerSettings, - DataLayerEditAction, dataLayerEditActions, + DataLayerEditAction, + dataLayerEditActions, dataLayerEditActionTranslationMap, defaultBaseMapDataLayerSettings, MapDataLayerSettings, MapDataLayerType, - MapType, mapZoomActions, mapZoomActionTranslationMap, + MapType, MarkersDataLayerSettings, MarkerType, PolygonsDataLayerSettings, @@ -40,7 +41,6 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { EntityType } from '@shared/models/entity-type.models'; import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models'; import { genNextLabelForDataKeys, mergeDeepIgnoreArray } from '@core/utils'; -import { MapProviders } from '@home/components/widget/lib/maps-legacy/map-models'; import { WidgetService } from '@core/http/widget.service'; export interface MapDataLayerDialogData { @@ -318,8 +318,4 @@ export class MapDataLayerDialogComponent extends DialogComponent +
+
widgets.maps.data-layer.tooltip-tag-actions
+
+ + +
+
+
+ + {{ action.name }} + +
+
+ + +
+
+
+ +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.scss new file mode 100644 index 0000000000..bb18ee4012 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.scss @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2024 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. + */ +.mat-mdc-chip.mat-mdc-standard-chip.tb-tag-action-chip { + overflow: hidden; + line-height: 20px; + height: 32px; + + &.mdc-evolution-chip--with-trailing-action { + .mdc-evolution-chip__action--primary { + padding-left: 4px; + padding-right: 12px; + } + } + + .tb-chip-labels { + display: flex; + flex-direction: row; + align-items: center; + min-width: 0; + padding: 2px 10px; + .tb-chip-label { + font-weight: normal; + font-size: 14px; + line-height: 20px; + &.tb-chip-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } + .mat-mdc-chip-remove.mat-mdc-icon-button { + color: inherit; + opacity: inherit; + } +} 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 new file mode 100644 index 0000000000..fb3d690fe6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.ts @@ -0,0 +1,176 @@ +/// +/// Copyright © 2016-2024 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. +/// + +import { + Component, + DestroyRef, + forwardRef, + Input, + OnInit, + Renderer2, + ViewContainerRef, + ViewEncapsulation +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { WidgetAction, WidgetActionType, widgetType } from '@shared/models/widget.models'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models'; +import { + WidgetActionSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/action/widget-action-settings-panel.component'; +import { MatButton } from '@angular/material/button'; +import { TranslateService } from '@ngx-translate/core'; +import { deepClone } from '@core/utils'; + +@Component({ + selector: 'tb-map-tooltip-tag-actions-panel', + templateUrl: './map-tooltip-tag-actions.component.html', + styleUrls: ['./map-tooltip-tag-actions.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MapTooltipTagActionsComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class MapTooltipTagActionsComponent implements ControlValueAccessor, OnInit { + + @Input() + disabled: boolean; + + @Input() + context: MapSettingsContext; + + actionsFormGroup: UntypedFormGroup; + + private propagateChange = (_val: any) => {}; + + constructor(private fb: UntypedFormBuilder, + private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef, + private translate: TranslateService, + private destroyRef: DestroyRef) { + } + + ngOnInit() { + this.actionsFormGroup = this.fb.group({ + actions: [null, []] + }); + this.actionsFormGroup.get('actions').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe( + (val) => this.propagateChange(val) + ); + } + + writeValue(actions?: WidgetAction[]): void { + this.actionsFormGroup.get('actions').patchValue(actions || [], {emitEvent: false}); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.actionsFormGroup.disable({emitEvent: false}); + } else { + this.actionsFormGroup.enable({emitEvent: false}); + } + } + + removeAction(index: number): void { + const actions: WidgetAction[] = this.actionsFormGroup.get('actions').value; + if (actions[index]) { + actions.splice(index, 1); + this.actionsFormGroup.get('actions').patchValue(actions); + } + } + + addAction($event: Event, matButton: MatButton): void { + if ($event) { + $event.stopPropagation(); + } + const action: WidgetAction = { + name: '', + type: WidgetActionType.doNothing + }; + const trigger = matButton._elementRef.nativeElement; + const actionNames = (this.actionsFormGroup.get('actions').value as WidgetAction[] || []).map(action => action.name); + this.openActionSettingsPopup(trigger, action, actionNames, true, (added) => { + if (added) { + const actions: WidgetAction[] = this.actionsFormGroup.get('actions').value || []; + actions.push(added); + this.actionsFormGroup.get('actions').patchValue(actions); + } + }); + } + + editAction($event: Event, matButton: MatButton, index: number): void { + if ($event) { + $event.stopPropagation(); + } + const actions: WidgetAction[] = this.actionsFormGroup.get('actions').value; + if (actions[index]) { + const action = deepClone(actions[index]); + const trigger = matButton._elementRef.nativeElement; + const actionNames = actions.filter((_action, current) => current !== index).map(action => action.name); + this.openActionSettingsPopup(trigger, action, actionNames, false, (updated) => { + if (updated) { + actions[index] = updated; + this.actionsFormGroup.get('actions').patchValue(actions); + } + }); + } + } + + private openActionSettingsPopup(trigger: Element, action: WidgetAction, actionNames: string[], isAdd: boolean, callback: (action?: WidgetAction) => void) { + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } 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'], true, null, + ctx, + {}, + {}, {}, 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/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index 8dd52cb1fe..351c203d3b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -233,6 +233,9 @@ import { import { ImageMapSourceSettingsComponent } from '@home/components/widget/lib/settings/common/map/image-map-source-settings.component'; +import { + MapTooltipTagActionsComponent +} from '@home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component'; @NgModule({ declarations: [ @@ -309,6 +312,7 @@ import { ImageMapSourceSettingsComponent, DataLayerColorSettingsComponent, DataLayerColorSettingsPanelComponent, + MapTooltipTagActionsComponent, DataLayerPatternSettingsComponent, MarkerShapeSettingsComponent, MarkerShapesComponent, diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 8b6558a11f..36d8966c6a 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -692,6 +692,7 @@ export interface CustomActionDescriptor { } export interface WidgetAction extends CustomActionDescriptor { + name?: string; type: WidgetActionType; targetDashboardId?: string; targetDashboardStateId?: string; 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 b6085c2a40..bb1f184a45 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -7667,6 +7667,10 @@ "tooltip-offset": "Tooltip offset", "tooltip-offset-horizontal": "Horizontal", "tooltip-offset-vertical": "Vertical", + "tooltip-tag-actions": "Tag actions", + "add-tooltip-tag-action": "Add tag action", + "edit-tooltip-tag-action": "Edit tag action", + "remove-tooltip-tag-action": "Remove tag action", "action-add": "Add", "action-edit": "Edit", "action-move": "Move",