diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts index a4bb547ec7..84b0d2a688 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts @@ -99,6 +99,7 @@ class SidebarPaneControl extends L.Contr this.button = $("") .attr('class', 'tb-control-button') .attr('href', '#') + .attr('role', 'button') .html('
') .on('click', (e) => { this.toggle(e); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-data-layer.ts index 767e44c90c..2b825e09be 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-data-layer.ts @@ -16,6 +16,7 @@ import { CirclesDataLayerSettings, + createColorMarkerURI, DataLayerColorSettings, DataLayerColorType, defaultBaseCirclesDataLayerSettings, @@ -487,19 +488,6 @@ export class TbMarkersDataLayer extends TbMapDataLayer` + - ``; - return 'data:image/svg+xml;base64,' + btoa(svg); - } - public createDefaultMarkerIcon(): MarkerIconInfo { const color = this.settings.markerColor.color || '#FE7569'; return this.createColoredMarkerIcon(tinycolor(color)); @@ -509,7 +497,7 @@ export class TbMarkersDataLayer extends TbMapDataLayer { + const svg = `` + + ``; + return 'data:image/svg+xml;base64,' + btoa(svg); +} export enum MapType { geoMap = 'geoMap', @@ -200,7 +214,12 @@ export const defaultBaseMarkersDataLayerSettings: Partial div { + background: $tb-primary-color; // primary color + } } > div { width: 30px; height: 30px; - color: #5B5B5B; + background: rgba(0, 0, 0, 0.54); line-height: 30px; - background-repeat: no-repeat; - background-position: center; + mask-repeat: no-repeat; + mask-position: center; &.tb-layers { - background-image: url('data:image/svg+xml,'); + mask-image: url('data:image/svg+xml,'); } &.tb-groups { - background-image: url('data:image/svg+xml,'); + mask-image: url('data:image/svg+xml,'); } } } 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 b61f5c8afd..973d035683 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,6 +50,7 @@ import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget import { widgetType } from '@shared/models/widget.models'; import { EntityDataPageLink } from '@shared/models/query/query.models'; import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe'; +import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; export abstract class TbMap { @@ -81,6 +82,8 @@ export abstract class TbMap { private readonly mapResize$: ResizeObserver; + private tooltipInstances: ITooltipsterInstance[] = []; + protected constructor(protected ctx: WidgetContext, protected inputSettings: DeepPartial, protected containerElement: HTMLElement) { @@ -133,6 +136,7 @@ export abstract class TbMap { this.map.panTo(this.defaultCenterPosition); } this.setupDataLayers(); + this.createdControlButtonTooltip(); } private setupDataLayers() { @@ -229,6 +233,51 @@ export abstract class TbMap { } } + private createdControlButtonTooltip() { + import('tooltipster').then(() => { + if ($.tooltipster) { + this.tooltipInstances.forEach((instance) => { + instance.destroy(); + }); + this.tooltipInstances = []; + } + $(this.mapElement) + .find('a[role="button"]:not(.leaflet-pm-action)') + .each((_index, element) => { + let title: string; + if (element.title) { + title = element.title; + $(element).removeAttr('title'); + } else if (element.parentElement.title) { + title = element.parentElement.title; + $(element).parent().removeAttr('title'); + } + const tooltip = $(element).tooltipster( + { + content: title, + theme: 'tooltipster-shadow', + delay: 10, + triggerClose: { + click: true, + tap: true, + scroll: true, + mouseleave: true + }, + side: ['topleft', 'bottomleft'].includes(this.settings.controlsPosition) ? 'right' : 'left', + distance: 2, + trackOrigin: true, + functionBefore: (instance, helper) => { + if (helper.origin.ariaDisabled === 'true' || helper.origin.parentElement.classList.contains('active')) { + return false; + } + }, + } + ); + this.tooltipInstances.push(tooltip.tooltipster('instance')); + }); + }); + } + private update(subscription: IWidgetSubscription) { const dsData = formattedDataFormDatasourceData(subscription.data, undefined, undefined, el => el.datasource.entityId + el.datasource.mapDataIds[0]); @@ -328,6 +377,9 @@ export abstract class TbMap { if (this.map) { this.map.remove(); } + this.tooltipInstances.forEach((instance) => { + instance.destroy(); + }); } public abstract positionToLatLng(position: {x: number; y: number}): L.LatLng; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.html new file mode 100644 index 0000000000..8b19ac16cd --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.html @@ -0,0 +1,69 @@ + +
+
widgets.maps.data-layer.color-settings
+
+ + + {{ 'widgets.maps.data-layer.color-type-constant' | translate }} + + + {{ 'widgets.maps.data-layer.color-type-function' | translate }} + + +
+
+
widgets.maps.data-layer.color
+ + +
+
+
+
+ +
+
+ + + +
+
+ + +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.scss new file mode 100644 index 0000000000..a1c9d5bacc --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.scss @@ -0,0 +1,53 @@ +/** + * 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 '../../../../../../../../../scss/constants'; + +.tb-data-layer-color-settings-panel { + width: 700px; + max-width: 90vw; + min-height: 300px; + max-height: 90vh; + display: flex; + flex-direction: column; + gap: 16px; + @media #{$mat-xs} { + width: 90vw; + } + .tb-data-layer-color-settings-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-form-row { + height: auto; + } + .tb-data-layer-color-settings-panel-body { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + } + .tb-data-layer-color-settings-panel-buttons { + height: 40px; + display: flex; + flex-direction: row; + gap: 16px; + justify-content: flex-end; + align-items: flex-end; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.ts new file mode 100644 index 0000000000..ff41d2772b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component.ts @@ -0,0 +1,82 @@ +/// +/// 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, 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 } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { WidgetService } from '@core/http/widget.service'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { DataLayerColorSettings, DataLayerColorType } from '@home/components/widget/lib/maps/map.models'; + +@Component({ + selector: 'tb-data-layer-color-settings-panel', + templateUrl: './data-layer-color-settings-panel.component.html', + providers: [], + styleUrls: ['./data-layer-color-settings-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class DataLayerColorSettingsPanelComponent extends PageComponent implements OnInit { + + @Input() + colorSettings: DataLayerColorSettings; + + @Input() + popover: TbPopoverComponent; + + @Output() + colorSettingsApplied = new EventEmitter(); + + DataLayerColorType = DataLayerColorType; + + colorSettingsFormGroup: UntypedFormGroup; + + functionScopeVariables = this.widgetService.getWidgetScopeVariables(); + + constructor(private fb: UntypedFormBuilder, + private widgetService: WidgetService, + protected store: Store, + private destroyRef: DestroyRef) { + super(store); + } + + ngOnInit(): void { + this.colorSettingsFormGroup = this.fb.group( + { + type: [this.colorSettings?.type || DataLayerColorType.constant, []], + color: [this.colorSettings?.color, []], + colorFunction: [this.colorSettings?.colorFunction, []] + } + ); + this.colorSettingsFormGroup.get('type').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + setTimeout(() => {this.popover?.updatePosition();}, 0); + }); + } + + cancel() { + this.popover?.hide(); + } + + applyColorSettings() { + const colorSettings: DataLayerColorSettings = this.colorSettingsFormGroup.value; + this.colorSettingsApplied.emit(colorSettings); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings.component.html new file mode 100644 index 0000000000..2122c580f3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings.component.html @@ -0,0 +1,30 @@ + + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings.component.ts new file mode 100644 index 0000000000..79249a5cee --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/data-layer-color-settings.component.ts @@ -0,0 +1,113 @@ +/// +/// 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, forwardRef, Input, Renderer2, ViewContainerRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ComponentStyle } from '@shared/models/widget-settings.models'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { DataLayerColorSettings, DataLayerColorType } from '@home/components/widget/lib/maps/map.models'; +import { + DataLayerColorSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component'; + +@Component({ + selector: 'tb-data-layer-color-settings', + templateUrl: './data-layer-color-settings.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DataLayerColorSettingsComponent), + multi: true + } + ] +}) +export class DataLayerColorSettingsComponent implements ControlValueAccessor { + + @Input() + disabled: boolean; + + DataLayerColorType = DataLayerColorType; + + modelValue: DataLayerColorSettings; + + colorStyle: ComponentStyle = {}; + + private propagateChange: (v: any) => void = () => { }; + + constructor(private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef) {} + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + this.updateColorStyle(); + } + + writeValue(value: DataLayerColorSettings): void { + if (value) { + this.modelValue = value; + this.updateColorStyle(); + } + } + + openColorSettingsPopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + colorSettings: this.modelValue, + }; + const colorSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, DataLayerColorSettingsPanelComponent, 'left', true, null, + ctx, + {}, + {}, {}, true); + colorSettingsPanelPopover.tbComponentRef.instance.popover = colorSettingsPanelPopover; + colorSettingsPanelPopover.tbComponentRef.instance.colorSettingsApplied.subscribe((colorSettings) => { + colorSettingsPanelPopover.hide(); + this.modelValue = colorSettings; + this.updateColorStyle(); + this.propagateChange(this.modelValue); + }); + } + } + + private updateColorStyle() { + if (!this.disabled && this.modelValue) { + if (this.modelValue.type === DataLayerColorType.constant) { + this.colorStyle = {backgroundColor: this.modelValue.color}; + } else { + this.colorStyle = {}; + } + } else { + this.colorStyle = {}; + } + } + +} 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 872b94a20a..711a004e4e 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 @@ -170,6 +170,40 @@
{{ 'widget-config.appearance' | translate }}
+ +
+
+
{{ 'widgets.maps.data-layer.marker.marker-type' | translate }}
+ + {{ 'widgets.maps.data-layer.marker.marker-type-default' | translate }} + {{ 'widgets.maps.data-layer.marker.marker-type-image' | translate }} + +
+
+
widgets.maps.data-layer.color
+ +
+
+
widgets.maps.data-layer.marker.image
+ +
+
+
+
widgets.maps.data-layer.marker.marker-offset
+
+
widgets.maps.data-layer.marker.offset-horizontal
+ + + +
widgets.maps.data-layer.marker.offset-vertical
+ + + +
+
+
{{ 'widgets.maps.data-layer.groups' | translate }}
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 9b7ea1b757..afcb164be8 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 @@ -22,6 +22,7 @@ import { MapDataLayerType, MapType, MarkersDataLayerSettings, + MarkerType, PolygonsDataLayerSettings } from '@home/components/widget/lib/maps/map.models'; import { Store } from '@ngrx/store'; @@ -61,6 +62,8 @@ export class MapDataLayerDialogComponent extends DialogComponent = []; datasourceTypesTranslations = datasourceTypeTranslationMap; @@ -103,7 +106,17 @@ export class MapDataLayerDialogComponent extends DialogComponent + this.updateValidators() + ); break; case 'polygons': const polygonsDataLayer = this.settings as PolygonsDataLayerSettings; @@ -179,6 +192,16 @@ export class MapDataLayerDialogComponent extends DialogComponent +
+
widgets.maps.data-layer.marker.marker-image
+
+ + + {{ 'widgets.maps.data-layer.marker.marker-image-type-image' | translate }} + + + {{ 'widgets.maps.data-layer.marker.marker-image-type-function' | translate }} + + +
+
+
+ +
+
{{ 'widgets.maps.data-layer.marker.custom-marker-image-size' | translate }}
+ + +
px
+
+
+
+
+
+
+ + + + +
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings-panel.component.scss new file mode 100644 index 0000000000..e37052f11a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings-panel.component.scss @@ -0,0 +1,53 @@ +/** + * 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 '../../../../../../../../../scss/constants'; + +.tb-marker-image-settings-panel { + width: 700px; + max-width: 90vw; + min-height: 300px; + max-height: 90vh; + display: flex; + flex-direction: column; + gap: 16px; + @media #{$mat-xs} { + width: 90vw; + } + .tb-marker-image-settings-title { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + color: rgba(0, 0, 0, 0.87); + } + .tb-form-row { + height: auto; + } + .tb-marker-image-settings-panel-body { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + } + .tb-marker-image-settings-panel-buttons { + height: 40px; + display: flex; + flex-direction: row; + gap: 16px; + justify-content: flex-end; + align-items: flex-end; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings-panel.component.ts new file mode 100644 index 0000000000..9184f26c5b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings-panel.component.ts @@ -0,0 +1,101 @@ +/// +/// 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, 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 { WidgetService } from '@core/http/widget.service'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { MarkerImageSettings, MarkerImageType } from '@home/components/widget/lib/maps/map.models'; + +@Component({ + selector: 'tb-marker-image-settings-panel', + templateUrl: './marker-image-settings-panel.component.html', + providers: [], + styleUrls: ['./marker-image-settings-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class MarkerImageSettingsPanelComponent extends PageComponent implements OnInit { + + @Input() + markerImageSettings: MarkerImageSettings; + + @Input() + popover: TbPopoverComponent; + + @Output() + markerImageSettingsApplied = new EventEmitter(); + + MarkerImageType = MarkerImageType; + + markerImageSettingsFormGroup: UntypedFormGroup; + + functionScopeVariables = this.widgetService.getWidgetScopeVariables(); + + constructor(private fb: UntypedFormBuilder, + private widgetService: WidgetService, + protected store: Store, + private destroyRef: DestroyRef) { + super(store); + } + + ngOnInit(): void { + this.markerImageSettingsFormGroup = this.fb.group( + { + type: [this.markerImageSettings?.type || MarkerImageType.image, []], + image: [this.markerImageSettings?.image, [Validators.required]], + imageSize: [this.markerImageSettings?.imageSize, [Validators.min(1)]], + imageFunction: [this.markerImageSettings?.imageFunction, [Validators.required]], + images: [this.markerImageSettings?.images, []] + } + ); + this.markerImageSettingsFormGroup.get('type').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + this.updateValidators(); + setTimeout(() => {this.popover?.updatePosition();}, 0); + }); + this.updateValidators(); + } + + cancel() { + this.popover?.hide(); + } + + applyMarkerImageSettings() { + const markerImageSettings: MarkerImageSettings = this.markerImageSettingsFormGroup.value; + this.markerImageSettingsApplied.emit(markerImageSettings); + } + + private updateValidators() { + const type: MarkerImageType = this.markerImageSettingsFormGroup.get('type').value; + if (type === MarkerImageType.image) { + this.markerImageSettingsFormGroup.get('image').enable({emitEvent: false}); + this.markerImageSettingsFormGroup.get('imageSize').enable({emitEvent: false}); + this.markerImageSettingsFormGroup.get('imageFunction').disable({emitEvent: false}); + this.markerImageSettingsFormGroup.get('images').disable({emitEvent: false}); + } else { + this.markerImageSettingsFormGroup.get('image').disable({emitEvent: false}); + this.markerImageSettingsFormGroup.get('imageSize').disable({emitEvent: false}); + this.markerImageSettingsFormGroup.get('imageFunction').enable({emitEvent: false}); + this.markerImageSettingsFormGroup.get('images').enable({emitEvent: false}); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings.component.html new file mode 100644 index 0000000000..a3e3a41a6c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings.component.html @@ -0,0 +1,28 @@ + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings.component.ts new file mode 100644 index 0000000000..c8f2bd2725 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/marker-image-settings.component.ts @@ -0,0 +1,96 @@ +/// +/// 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 { ChangeDetectorRef, Component, forwardRef, Input, Renderer2, ViewContainerRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { MarkerImageSettings, MarkerImageType } from '@home/components/widget/lib/maps/map.models'; +import { + MarkerImageSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/map/marker-image-settings-panel.component'; + +@Component({ + selector: 'tb-marker-image-settings', + templateUrl: './marker-image-settings.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MarkerImageSettingsComponent), + multi: true + } + ] +}) +export class MarkerImageSettingsComponent implements ControlValueAccessor { + + @Input() + disabled: boolean; + + MarkerImageType = MarkerImageType; + + modelValue: MarkerImageSettings; + + private propagateChange: (v: any) => void = () => { }; + + constructor(private popoverService: TbPopoverService, + private renderer: Renderer2, + private cd: ChangeDetectorRef, + private viewContainerRef: ViewContainerRef) {} + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: MarkerImageSettings): void { + if (value) { + this.modelValue = value; + } + } + + openImageSettingsPopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + markerImageSettings: this.modelValue, + }; + const markerImageSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, MarkerImageSettingsPanelComponent, 'left', true, null, + ctx, + {}, + {}, {}, true); + markerImageSettingsPanelPopover.tbComponentRef.instance.popover = markerImageSettingsPanelPopover; + markerImageSettingsPanelPopover.tbComponentRef.instance.markerImageSettingsApplied.subscribe((markerImageSettings) => { + markerImageSettingsPanelPopover.hide(); + this.modelValue = markerImageSettings; + this.propagateChange(this.modelValue); + this.cd.detectChanges(); + }); + } + } +} 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 2416b32ade..12805131d5 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 @@ -204,6 +204,18 @@ import { } from '@home/components/widget/lib/settings/common/key/data-key-config-dialog.component'; import { DataKeyConfigComponent } from '@home/components/widget/lib/settings/common/key/data-key-config.component'; import { WidgetSettingsComponent } from '@home/components/widget/lib/settings/common/widget/widget-settings.component'; +import { + DataLayerColorSettingsComponent +} from '@home/components/widget/lib/settings/common/map/data-layer-color-settings.component'; +import { + DataLayerColorSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/map/data-layer-color-settings-panel.component'; +import { + MarkerImageSettingsComponent +} from '@home/components/widget/lib/settings/common/map/marker-image-settings.component'; +import { + MarkerImageSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/map/marker-image-settings-panel.component'; @NgModule({ declarations: [ @@ -277,6 +289,10 @@ import { WidgetSettingsComponent } from '@home/components/widget/lib/settings/co MapLayerSettingsPanelComponent, MapLayerRowComponent, MapLayersComponent, + DataLayerColorSettingsComponent, + DataLayerColorSettingsPanelComponent, + MarkerImageSettingsComponent, + MarkerImageSettingsPanelComponent, MapDataLayerDialogComponent, MapDataLayerRowComponent, MapDataLayersComponent, 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 a100ae7b18..15609560db 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6875,6 +6875,11 @@ "source": "Source", "additional-data-keys": "Additional data keys", "groups": "Groups", + "color": "Color", + "color-settings": "Color settings", + "color-type-constant": "Constant", + "color-type-function": "Function", + "color-function": "Color function", "marker": { "latitude-key": "Latitude key", "longitude-key": "Longitude key", @@ -6887,7 +6892,20 @@ "no-markers": "No markers configured", "add-marker": "Add marker", "marker-configuration": "Marker configuration", - "remove-marker": "Remove marker" + "remove-marker": "Remove marker", + "marker-type": "Marker type", + "marker-type-default": "Default", + "marker-type-image": "Image", + "image": "Image", + "marker-image": "Marker image", + "marker-image-type-image": "Constant", + "marker-image-type-function": "Function", + "custom-marker-image-size": "Custom marker image size", + "marker-image-function": "Function", + "marker-images": "Marker images", + "marker-offset": "Marker offset", + "offset-horizontal": "Horizontal", + "offset-vertical": "Vertical" }, "polygon": { "polygon-key": "Polygon key",