UI: Add to place map item action customize tooltip

This commit is contained in:
Vladyslav_Prykhodko 2025-04-01 14:57:36 +03:00
parent cac1704dd5
commit 3cefb63436
9 changed files with 284 additions and 12 deletions

View File

@ -208,7 +208,7 @@ export class UtilsService {
return parseException(exception, lineOffset);
}
public customTranslation(translationValue: string, defaultValue: string): string {
public customTranslation(translationValue: string, defaultValue: string = translationValue): string {
if (translationValue && isString(translationValue)) {
if (translationValue.includes(`{${i18nPrefix}`)) {
const matches = translationValue.match(i18nRegExp);

View File

@ -50,7 +50,14 @@ import {
UnplacedMapDataItem,
} from '@home/components/widget/lib/maps/data-layer/latest-map-data-layer';
import { IWidgetSubscription, PlaceMapItemActionData, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { FormattedData, MapItemType, WidgetAction, WidgetActionType, widgetType } from '@shared/models/widget.models';
import {
FormattedData,
mapItemTooltipsTranslation,
MapItemType,
WidgetAction,
WidgetActionType,
widgetType
} from '@shared/models/widget.models';
import { EntityDataPageLink } from '@shared/models/query/query.models';
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
import { TbMarkersDataLayer } from '@home/components/widget/lib/maps/data-layer/markers-data-layer';
@ -661,29 +668,45 @@ export abstract class TbMap<S extends BaseMapSettings> {
private createMarker(actionData: PlaceMapItemActionData) {
this.createItem(actionData, () => this.prepareDrawMode('Marker', {
placeMarker: this.ctx.translate.instant('widgets.maps.data-layer.marker.place-marker-hint')
placeMarker: actionData.action.mapItemTooltips.placeMarker
? this.ctx.utilsService.customTranslation(actionData.action.mapItemTooltips.placeMarker)
: this.ctx.translate.instant(mapItemTooltipsTranslation.placeMarker)
}));
}
private createRectangle(actionData: PlaceMapItemActionData): void {
this.createItem(actionData, () => this.prepareDrawMode('Rectangle', {
firstVertex: this.ctx.translate.instant('widgets.maps.data-layer.polygon.rectangle-place-first-point-hint'),
finishRect: this.ctx.translate.instant('widgets.maps.data-layer.polygon.finish-rectangle-hint')
firstVertex: actionData.action.mapItemTooltips.startRect
? this.ctx.utilsService.customTranslation(actionData.action.mapItemTooltips.startRect)
: this.ctx.translate.instant(mapItemTooltipsTranslation.startRect),
finishRect: actionData.action.mapItemTooltips.finishRect
? this.ctx.utilsService.customTranslation(actionData.action.mapItemTooltips.finishRect)
: this.ctx.translate.instant(mapItemTooltipsTranslation.finishRect),
}));
}
private createPolygon(actionData: PlaceMapItemActionData): void {
this.createItem(actionData, () => this.prepareDrawMode('Polygon', {
firstVertex: this.ctx.translate.instant('widgets.maps.data-layer.polygon.polygon-place-first-point-hint'),
continueLine: this.ctx.translate.instant('widgets.maps.data-layer.polygon.continue-polygon-hint'),
finishPoly: this.ctx.translate.instant('widgets.maps.data-layer.polygon.finish-polygon-hint')
firstVertex: actionData.action.mapItemTooltips.firstVertex
? this.ctx.utilsService.customTranslation(actionData.action.mapItemTooltips.firstVertex)
: this.ctx.translate.instant(mapItemTooltipsTranslation.firstVertex),
continueLine: actionData.action.mapItemTooltips.continueLine
? this.ctx.utilsService.customTranslation(actionData.action.mapItemTooltips.continueLine)
: this.ctx.translate.instant(mapItemTooltipsTranslation.continueLine),
finishPoly: actionData.action.mapItemTooltips.finishPoly
? this.ctx.utilsService.customTranslation(actionData.action.mapItemTooltips.finishPoly)
: this.ctx.translate.instant(mapItemTooltipsTranslation.finishPoly),
}));
}
private createCircle(actionData: PlaceMapItemActionData): void {
this.createItem(actionData, () => this.prepareDrawMode('Circle', {
startCircle: this.ctx.translate.instant('widgets.maps.data-layer.circle.place-circle-center-hint'),
finishCircle: this.ctx.translate.instant('widgets.maps.data-layer.circle.finish-circle-hint')
startCircle: actionData.action.mapItemTooltips.startCircle
? this.ctx.utilsService.customTranslation(actionData.action.mapItemTooltips.startCircle)
: this.ctx.translate.instant(mapItemTooltipsTranslation.startCircle),
finishCircle: actionData.action.mapItemTooltips.finishCircle
? this.ctx.utilsService.customTranslation(actionData.action.mapItemTooltips.finishCircle)
: this.ctx.translate.instant(mapItemTooltipsTranslation.finishCircle),
}));
}

View File

@ -0,0 +1,82 @@
<!--
Copyright © 2016-2025 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-form-panel stroked" [formGroup]="tooltipsForm">
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel-header>{{ 'widget-action.map-item-tooltip.tooltips' | translate }}</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
@switch (mapItemType) {
@case (MapItemType.marker) {
<div class="tb-form-row">
<div class="fixed-title-width" translate>widget-action.map-item-tooltip.place-marker</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<input matInput placeholder="{{ mapItemTooltipsDefaultTranslate.placeMarker | translate }}" formControlName="placeMarker">
</mat-form-field>
</div>
}
@case (MapItemType.rectangle) {
<div class="tb-form-row">
<div class="fixed-title-width" translate>widget-action.map-item-tooltip.start-draw-rectangle</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<input matInput placeholder="{{ mapItemTooltipsDefaultTranslate.startRect | translate }}" formControlName="startRect">
</mat-form-field>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>widget-action.map-item-tooltip.finish-draw-rectangle</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<input matInput placeholder="{{ mapItemTooltipsDefaultTranslate.finishRect | translate }}" formControlName="finishRect">
</mat-form-field>
</div>
}
@case (MapItemType.polygon) {
<div class="tb-form-row">
<div class="fixed-title-width" translate>widget-action.map-item-tooltip.start-draw-polygon</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<input matInput placeholder="{{ mapItemTooltipsDefaultTranslate.firstVertex | translate }}" formControlName="firstVertex">
</mat-form-field>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>widget-action.map-item-tooltip.continue-draw-polygon</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<input matInput placeholder="{{ mapItemTooltipsDefaultTranslate.continueLine | translate }}" formControlName="continueLine">
</mat-form-field>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>widget-action.map-item-tooltip.finish-draw-polygon</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<input matInput placeholder="{{ mapItemTooltipsDefaultTranslate.finishPoly | translate }}" formControlName="finishPoly">
</mat-form-field>
</div>
}
@case (MapItemType.circle) {
<div class="tb-form-row">
<div class="fixed-title-width" translate>widget-action.map-item-tooltip.start-draw-circle</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<input matInput placeholder="{{ mapItemTooltipsDefaultTranslate.startCircle | translate }}" formControlName="startCircle">
</mat-form-field>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>widget-action.map-item-tooltip.finish-draw-circle</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<input matInput placeholder="{{ mapItemTooltipsDefaultTranslate.finishCircle | translate }}" formControlName="finishCircle">
</mat-form-field>
</div>
}
}
</ng-template>
</mat-expansion-panel>
</div>

View File

@ -0,0 +1,123 @@
///
/// Copyright © 2016-2025 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MapItemTooltips, MapItemType, mapItemTooltipsTranslation } from '@shared/models/widget.models';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { deepTrim, isEqual } from '@core/utils';
@Component({
selector: 'tb-map-item-tooltips',
templateUrl: './map-item-tooltips.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MapItemTooltipsComponent),
multi: true
}
]
})
export class MapItemTooltipsComponent implements ControlValueAccessor, OnChanges {
@Input({required: true})
mapItemType: MapItemType;
tooltipsForm: FormGroup;
MapItemType = MapItemType;
readonly mapItemTooltipsDefaultTranslate = mapItemTooltipsTranslation;
private modelValue: MapItemTooltips;
private propagateChange = (_val: any) => {};
constructor(private fd: FormBuilder) {
this.tooltipsForm = this.fd.group({
placeMarker: [''],
firstVertex: [''],
continueLine: [''],
finishPoly: [''],
startRect: [''],
finishRect: [''],
startCircle: [''],
finishCircle: ['']
});
this.tooltipsForm.valueChanges.pipe(
takeUntilDestroyed()
).subscribe(this.updatedModel.bind(this));
}
ngOnChanges(changes: SimpleChanges) {
if (changes.mapItemType) {
const mapItemTypeChanges = changes.mapItemType;
if (!mapItemTypeChanges.firstChange && mapItemTypeChanges.currentValue !== mapItemTypeChanges.previousValue) {
this.updatedValidators(true);
}
}
}
registerOnChange(fn: any) {
this.propagateChange = fn;
}
registerOnTouched(_fn: any) {
}
setDisabledState(isDisabled: boolean) {
if (isDisabled) {
this.tooltipsForm.disable({emitEvent: false});
} else {
this.tooltipsForm.enable({emitEvent: false});
}
}
writeValue(obj: MapItemTooltips) {
this.modelValue = obj;
this.tooltipsForm.patchValue(obj, {emitEvent: false});
this.updatedValidators();
}
private updatedValidators(emitNewValue = false) {
this.tooltipsForm.disable({emitEvent: false});
switch (this.mapItemType) {
case MapItemType.marker:
this.tooltipsForm.get('placeMarker').enable({emitEvent: false});
break;
case MapItemType.rectangle:
this.tooltipsForm.get('startRect').enable({emitEvent: false});
this.tooltipsForm.get('finishRect').enable({emitEvent: false});
break;
case MapItemType.polygon:
this.tooltipsForm.get('firstVertex').enable({emitEvent: false});
this.tooltipsForm.get('continueLine').enable({emitEvent: false});
this.tooltipsForm.get('finishPoly').enable({emitEvent: false});
break;
case MapItemType.circle:
this.tooltipsForm.get('startCircle').enable({emitEvent: false});
this.tooltipsForm.get('finishCircle').enable({emitEvent: false});
break;
}
this.tooltipsForm.updateValueAndValidity({emitEvent: emitNewValue})
}
private updatedModel(value: MapItemTooltips) {
const currentValue = Object.fromEntries(Object.entries(deepTrim(value)).filter(([_, v]) => v != ''));
if (!isEqual(currentValue, this.modelValue)) {
this.modelValue = currentValue;
this.propagateChange(currentValue);
}
}
}

View File

@ -270,6 +270,10 @@
</mat-select>
</mat-form-field>
</div>
<tb-map-item-tooltips
[mapItemType]="actionTypeFormGroup.get('mapItemType').value"
formControlName="mapItemTooltips">
</tb-map-item-tooltips>
</ng-template>
<ng-template [ngSwitchCase]="widgetActionFormGroup.get('type').value === widgetActionType.customPretty
|| widgetActionFormGroup.get('type').value === widgetActionType.placeMapItem

View File

@ -329,6 +329,7 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
'mapItemType',
this.fb.control(action?.mapItemType ?? MapItemType.marker, [Validators.required])
);
this.actionTypeFormGroup.addControl('mapItemTooltips', this.fb.control(action?.mapItemTooltips ?? {}));
this.actionTypeFormGroup.addControl(
'customAction',
this.fb.control(toPlaceMapItemAction(action), [Validators.required])
@ -528,7 +529,8 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
result = {
...this.widgetActionFormGroup.value,
...this.actionTypeFormGroup.get('customAction').value,
mapItemType: this.actionTypeFormGroup.get('mapItemType').value
mapItemType: this.actionTypeFormGroup.get('mapItemType').value,
mapItemTooltips: this.actionTypeFormGroup.get('mapItemTooltips').value,
};
} else {
result = {...this.widgetActionFormGroup.value, ...this.actionTypeFormGroup.value};

View File

@ -69,6 +69,9 @@ import {
} from '@home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component';
import { CssSizeInputComponent } from '@home/components/widget/lib/settings/common/css-size-input.component';
import { WidgetActionComponent } from '@home/components/widget/lib/settings/common/action/widget-action.component';
import {
MapItemTooltipsComponent
} from '@home/components/widget/lib/settings/common/action/map-item-tooltips.component';
import {
CustomActionPrettyResourcesTabsComponent
} from '@home/components/widget/lib/settings/common/action/custom-action-pretty-resources-tabs.component';
@ -283,6 +286,7 @@ import {
SetValueActionSettingsComponent,
SetValueActionSettingsPanelComponent,
WidgetActionComponent,
MapItemTooltipsComponent,
CustomActionPrettyResourcesTabsComponent,
CustomActionPrettyEditorComponent,
MobileActionEditorComponent,

View File

@ -770,8 +770,31 @@ export interface WidgetAction extends CustomActionDescriptor {
mobileAction?: WidgetMobileActionDescriptor;
url?: string;
mapItemType?: MapItemType;
mapItemTooltips?: MapItemTooltips;
}
export interface MapItemTooltips {
placeMarker?: string;
firstVertex?: string;
continueLine?: string;
finishPoly?: string;
startRect?: string;
finishRect?: string;
startCircle?: string;
finishCircle?: string;
}
export const mapItemTooltipsTranslation: Required<MapItemTooltips> = Object.freeze({
placeMarker: 'widgets.maps.data-layer.marker.place-marker-hint',
firstVertex: 'widgets.maps.data-layer.polygon.polygon-place-first-point-hint',
continueLine: 'widgets.maps.data-layer.polygon.continue-polygon-hint',
finishPoly: 'widgets.maps.data-layer.polygon.finish-polygon-hint',
startRect: 'widgets.maps.data-layer.polygon.rectangle-place-first-point-hint',
finishRect: 'widgets.maps.data-layer.polygon.finish-rectangle-hint',
startCircle: 'widgets.maps.data-layer.circle.place-circle-center-hint',
finishCircle: 'widgets.maps.data-layer.circle.finish-circle-hint'
})
export interface WidgetActionDescriptor extends WidgetAction {
id: string;
name: string;

View File

@ -6592,7 +6592,18 @@
"rectangle": "Rectangle",
"circle": "Circle"
},
"place-map-item": "Place map item"
"place-map-item": "Place map item",
"map-item-tooltip": {
"tooltips": "Map item tooltips",
"place-marker": "Place marker",
"start-draw-rectangle": "Start draw rectangle",
"finish-draw-rectangle": "Finish draw rectangle",
"start-draw-polygon": "Start draw polygon",
"continue-draw-polygon": "Continue draw polygon",
"finish-draw-polygon": "Finish draw polygon",
"start-draw-circle": "Start draw circle",
"finish-draw-circle": "Finish draw circle"
}
},
"widgets-bundle": {
"current": "Current bundle",