diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 61f16d1f67..bfaad1148d 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -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); 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 f1bb414878..24a02c6875 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 @@ -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 { 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), })); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/map-item-tooltips.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/map-item-tooltips.component.html new file mode 100644 index 0000000000..f0abe17509 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/map-item-tooltips.component.html @@ -0,0 +1,82 @@ + +
+ + {{ 'widget-action.map-item-tooltip.customize-map-item-tooltips' | translate }} + + @switch (mapItemType) { + @case (MapItemType.marker) { +
+
widget-action.map-item-tooltip.place-marker
+ + + +
+ } + @case (MapItemType.rectangle) { +
+
widget-action.map-item-tooltip.start-draw-rectangle
+ + + +
+
+
widget-action.map-item-tooltip.finish-draw-rectangle
+ + + +
+ } + @case (MapItemType.polygon) { +
+
widget-action.map-item-tooltip.start-draw-polygon
+ + + +
+
+
widget-action.map-item-tooltip.continue-draw-polygon
+ + + +
+
+
widget-action.map-item-tooltip.finish-draw-polygon
+ + + +
+ } + @case (MapItemType.circle) { +
+
widget-action.map-item-tooltip.start-draw-circle
+ + + +
+
+
widget-action.map-item-tooltip.finish-draw-circle
+ + + +
+ } + } +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/map-item-tooltips.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/map-item-tooltips.component.ts new file mode 100644 index 0000000000..830fd925b7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/map-item-tooltips.component.ts @@ -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); + } + } +} 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 5c735010f1..a58b58bb45 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 @@ -270,6 +270,10 @@ + +