Browse Source

UI: Maps - implement tooltip tag actions.

pull/12723/head
Igor Kulikov 2 years ago
parent
commit
f290246aad
  1. 2
      application/src/main/data/json/system/widget_types/image_map.json
  2. 2
      application/src/main/data/json/system/widget_types/map.json
  3. 22
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts
  4. 36
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts
  5. 3
      ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts
  6. 6
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.html
  7. 36
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.ts
  8. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings.component.ts
  9. 17
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html
  10. 39
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts
  11. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html
  12. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.ts
  13. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html
  14. 10
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts
  15. 67
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.html
  16. 49
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.scss
  17. 176
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.ts
  18. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  19. 1
      ui-ngx/src/app/shared/models/widget.models.ts
  20. 4
      ui-ngx/src/assets/locale/locale.constant-en_US.json

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

@ -11,7 +11,7 @@
"resources": [],
"templateHtml": "<tb-map-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-map-widget>",
"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",

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

@ -11,7 +11,7 @@
"resources": [],
"templateHtml": "<tb-map-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-map-widget>",
"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",

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

@ -60,7 +60,7 @@ export abstract class TbDataLayerItem<S extends MapDataLayerSettings = MapDataLa
this.data = data;
this.layer = this.create(data, dsData);
if (this.settings.tooltip?.show) {
this.createTooltip(data.$datasource);
this.createTooltip();
this.updateTooltip(data, dsData);
}
this.bindEvents();
@ -224,7 +224,7 @@ export abstract class TbDataLayerItem<S extends MapDataLayerSettings = MapDataLa
tooltipTemplate = processTooltipTemplate(tooltipTemplate);
this.tooltip.setContent(tooltipTemplate);
if (this.tooltip.isOpen() && this.tooltip.getElement()) {
this.bindTooltipActions(data.$datasource);
this.bindTooltipActions();
}
}
}
@ -253,7 +253,7 @@ export abstract class TbDataLayerItem<S extends MapDataLayerSettings = MapDataLa
protected abstract removeDataItem(): Observable<any>;
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<S extends MapDataLayerSettings = MapDataLa
});
}
this.layer.on('popupopen', () => {
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 {

36
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<S extends BaseMapSettings> {
private readonly mapResize$: ResizeObserver;
private readonly tooltipActions: { [name: string]: MapActionHandler };
private tooltipInstances: TooltipInstancesData[] = [];
private currentPopover: TbPopoverComponent;
@ -115,8 +112,6 @@ export abstract class TbMap<S extends BaseMapSettings> {
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 = $('<div class="tb-map"></div>');
@ -595,27 +590,6 @@ export abstract class TbMap<S extends BaseMapSettings> {
}
}
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<S extends BaseMapSettings> {
}, 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;

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

6
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.html

@ -21,7 +21,9 @@
<tb-widget-action
formControlName="widgetAction"
[callbacks]="callbacks"
[widgetType]="widgetType">
[widgetType]="widgetType"
[withName]="withName"
[actionNames]="actionNames">
</tb-widget-action>
</div>
<div class="tb-action-settings-panel-buttons">
@ -36,7 +38,7 @@
type="button"
(click)="applyWidgetAction()"
[disabled]="widgetActionFormGroup.invalid || !widgetActionFormGroup.dirty">
{{ 'action.apply' | translate }}
{{ applyTitle }}
</button>
</div>
</div>

36
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<WidgetActionSettingsPanelComponent>;
@coerceBoolean()
withName = false;
@Input()
actionNames: string[];
@Input()
applyTitle = this.translate.instant('action.apply');
@Output()
widgetActionApplied = new EventEmitter<WidgetAction>();
@ -65,8 +59,8 @@ export class WidgetActionSettingsPanelComponent extends PageComponent implements
widgetActionFormGroup: UntypedFormGroup;
constructor(private fb: UntypedFormBuilder,
protected store: Store<AppState>) {
super(store);
private translate: TranslateService,
private popover: TbPopoverComponent) {
}
ngOnInit(): void {

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

17
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html

@ -16,6 +16,23 @@
-->
<div class="tb-form-panel no-padding no-border" [formGroup]="widgetActionFormGroup">
<div *ngIf="withName" class="tb-form-row">
<div class="fixed-title-width">{{'widget-config.action-name' | translate}}*</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<input required matInput formControlName="name" placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="(widgetActionFormGroup.get('name').hasError('required')
? 'widget-config.action-name-required'
: 'widget-config.action-name-not-unique') | translate"
*ngIf="widgetActionFormGroup.get('name').invalid
&& (widgetActionFormGroup.get('name').touched || widgetActionFormGroup.get('name').hasError('actionNameNotUnique'))"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
</div>
<div class="tb-form-row">
<div translate>widget-config.action</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">

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

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-pattern-settings.component.html

@ -79,6 +79,10 @@
</mat-form-field>
</div>
</div>
<tb-map-tooltip-tag-actions-panel
[context]="context"
formControlName="tagActions">
</tb-map-tooltip-tag-actions-panel>
</ng-container>
</ng-template>
</mat-expansion-panel>

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

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html

@ -244,10 +244,12 @@
</ng-container>
<tb-data-layer-pattern-settings
patternType="label"
[context]="context"
formControlName="label">
</tb-data-layer-pattern-settings>
<tb-data-layer-pattern-settings
patternType="tooltip"
[context]="context"
[hasTooltipOffset]="dataLayerType === 'markers'"
formControlName="tooltip">
</tb-data-layer-pattern-settings>
@ -259,7 +261,7 @@
<tb-widget-action-settings class="flex-1"
panelTitle="{{ 'widgets.maps.data-layer.on-click' | translate }}"
[callbacks]="context.callbacks"
[widgetType]="widgetType"
[widgetType]="widgetType.latest"
formControlName="click">
</tb-widget-action-settings>
</div>

10
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<MapDataLayerDia
const settings: MapDataLayerSettings = this.dataLayerFormGroup.getRawValue();
this.dialogRef.close(settings);
}
protected readonly mapProvider = MapProviders;
protected readonly mapZoomActions = mapZoomActions;
protected readonly mapZoomActionTranslationMap = mapZoomActionTranslationMap;
}

67
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component.html

@ -0,0 +1,67 @@
<!--
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.
-->
<div class="tb-form-row gap-4">
<div translate>widgets.maps.data-layer.tooltip-tag-actions</div>
<div class="flex flex-1 flex-row items-center justify-end gap-4">
<mat-chip-listbox>
<mat-chip *ngFor="let action of this.actionsFormGroup.get('actions').value; let $index = index;"
class="tb-tag-action-chip"
[removable]="!disabled" (removed)="removeAction($index)">
<div class="flex flex-row items-center justify-start gap-1">
<div class="tb-chip-labels">
<div class="tb-chip-label tb-chip-text">
<strong>
{{ action.name }}
</strong>
</div>
</div>
<button *ngIf="!disabled"
#editButton
type="button"
mat-icon-button
class="tb-mat-24"
matTooltip="{{ 'widgets.maps.data-layer.edit-tooltip-tag-action' | translate }}"
matTooltipPosition="above"
(click)="editAction($event, editButton, $index)">
<mat-icon class="tb-mat-18">edit</mat-icon>
</button>
<button *ngIf="!disabled"
matChipRemove
type="button"
mat-icon-button
class="tb-mat-24"
matTooltip="{{ 'widgets.maps.data-layer.remove-tooltip-tag-action' | translate }}"
matTooltipPosition="above">
<mat-icon class="tb-mat-18">close</mat-icon>
</button>
</div>
</mat-chip>
</mat-chip-listbox>
<button *ngIf="!disabled"
#addButton
type="button"
mat-icon-button
class="tb-mat-40"
color="primary"
matTooltip="{{ 'widgets.maps.data-layer.add-tooltip-tag-action' | translate }}"
matTooltipPosition="above"
(click)="addAction($event, addButton)">
<mat-icon>add</mat-icon>
</button>
</div>
</div>

49
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;
}
}

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

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

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

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

Loading…
Cancel
Save