diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/latest-map-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/latest-map-data-layer.ts index fe8639f7a0..7c77056894 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/latest-map-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/latest-map-data-layer.ts @@ -114,6 +114,18 @@ export abstract class TbLatestDataLayerItem, dsData: FormattedData[]): void { this.data = data; this.doUpdate(data, dsData); @@ -151,7 +163,7 @@ export abstract class TbLatestDataLayerItem item.editModeUpdated()); } + private updateItemsDragMode() { + this.layerItems.forEach(item => item.dragModeUpdated()); + } + public abstract placeItem(item: UnplacedMapDataItem, layer: L.Layer): void; protected abstract isValidLayerData(layerData: FormattedData): boolean; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-layer.ts index a139eeb4c0..c6f8f446a6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-layer.ts @@ -161,7 +161,7 @@ class TbGoogleMapLayer extends TbMapLayer { } private loadGoogle(): Observable { - const apiKey = this.settings.apiKey; + const apiKey = this.settings.apiKey || defaultGoogleMapLayerSettings.apiKey; if (TbGoogleMapLayer.loadedApiKeysGlobal[apiKey]) { return of(true); } else { @@ -213,7 +213,8 @@ class TbHereMapLayer extends TbMapLayer { } protected createLayer(): Observable { - const layer = L.tileLayer.provider(this.settings.layerType, {useV3: true, apiKey: this.settings.apiKey} as any); + const apiKey = this.settings.apiKey || defaultHereMapLayerSettings.apiKey; + const layer = L.tileLayer.provider(this.settings.layerType, {useV3: true, apiKey} as any); return of(layer); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.scss b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.scss index 9c764cbb0e..fc60862be4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.scss @@ -138,6 +138,9 @@ &.tb-rotate { mask-image: url('data:image/svg+xml,'); } + &.tb-drag-mode { + mask-image: url('data:image/svg+xml,'); + } &.tb-place-marker { 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 f279a9fb68..6225ff878d 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 @@ -109,6 +109,7 @@ export abstract class TbMap { protected customActionsToolbar: L.TB.TopToolbarControl; protected editToolbar: L.TB.BottomToolbarControl; + protected dragModeButton: L.TB.ToolbarButton; protected addMarkerButton: L.TB.ToolbarButton; protected addRectangleButton: L.TB.ToolbarButton; protected addPolygonButton: L.TB.ToolbarButton; @@ -127,10 +128,12 @@ export abstract class TbMap { private tooltipInstances: TooltipInstancesData[] = []; private currentPopover: TbPopoverComponent; - private currentAddButton: L.TB.ToolbarButton; + private currentEditButton: L.TB.ToolbarButton; + + private dragMode = true; private get isPlacingItem(): boolean { - return !!this.currentAddButton; + return !!this.currentEditButton; } protected constructor(protected ctx: WidgetContext, @@ -188,6 +191,7 @@ export abstract class TbMap { if (this.map.zoomControl) { this.map.zoomControl.setPosition(this.settings.controlsPosition); } + this.dragMode = !this.settings.dragModeButton; const setup = [this.doSetupControls()]; if (this.timeline && this.settings.tripTimeline.snapToRealLocation) { setup.push(parseTbFunction(this.getCtx().http, this.settings.tripTimeline.locationSnapFilter, ['data', 'dsData']).pipe( @@ -393,12 +397,24 @@ export abstract class TbMap { this.map.pm.applyGlobalOptions(); } + const dragSupportedDataLayers = this.latestDataLayers.filter(dl => dl.isDragEnabled()); + const showDragModeButton = this.settings.dragModeButton && dragSupportedDataLayers.length; const addSupportedDataLayers = this.latestDataLayers.filter(dl => dl.isAddEnabled()); - if (addSupportedDataLayers.length) { + if (showDragModeButton || addSupportedDataLayers.length) { const drawToolbar = L.TB.toolbar({ position: this.settings.controlsPosition }).addTo(this.map); + if (showDragModeButton) { + this.dragModeButton = drawToolbar.toolbarButton({ + id: 'dragMode', + title: this.ctx.translate.instant('widgets.maps.data-layer.drag-drop-mode'), + iconClass: 'tb-drag-mode', + click: (e, button) => { + this.toggleDragMode(e, button); + } + }); + } this.addMarkerDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'markers'); if (this.addMarkerDataLayers.length) { this.addMarkerButton = drawToolbar.toolbarButton({ @@ -448,6 +464,32 @@ export abstract class TbMap { } } + private toggleDragMode(e: MouseEvent, button: L.TB.ToolbarButton): void { + if (this.dragMode) { + this.disableDragMode(); + } else { + this.dragMode = true; + this.latestDataLayers.forEach(dl => dl.dragModeUpdated()); + this.updatePlaceItemState(button); + this.editToolbar.open([ + { + id: 'cancel', + iconClass: 'tb-close', + title: this.ctx.translate.instant('action.cancel'), + showText: true, + click: this.disableDragMode + } + ], false); + } + } + + private disableDragMode = () => { + this.dragMode = false; + this.latestDataLayers.forEach(dl => dl.dragModeUpdated()); + this.updatePlaceItemState(); + this.editToolbar.close(); + } + private placeMarker(e: MouseEvent, button: L.TB.ToolbarButton): void { this.placeItem(e, button, this.addMarkerDataLayers, (entity) => this.prepareDrawMode('Marker', { placeMarker: this.ctx.translate.instant('widgets.maps.data-layer.marker.place-marker-hint-with-entity', {entityName: entity.entity.entityDisplayName}) @@ -479,6 +521,7 @@ export abstract class TbMap { private placeItem(e: MouseEvent, button: L.TB.ToolbarButton, dataLayers: TbLatestMapDataLayer[], prepareDrawMode: (entity: UnplacedMapDataItem) => void): void { if (this.isPlacingItem) { + this.finishAdd(); return; } this.updatePlaceItemState(button); @@ -692,6 +735,10 @@ export abstract class TbMap { } private finishAdd = () => { + if (this.currentPopover) { + this.currentPopover.hide(); + this.currentPopover = null; + } this.map.off('pm:create'); this.map.pm.disableDraw(); this.latestDataLayers.forEach(dl => dl.enableEditMode()); @@ -706,15 +753,15 @@ export abstract class TbMap { L.DomUtil.addClass(this.map.pm.Draw[shape]._hintMarker.getTooltip()._container, 'tb-place-item-label'); } - private updatePlaceItemState(addButton?: L.TB.ToolbarButton, disabled = false): void { - if (addButton) { + private updatePlaceItemState(editButton?: L.TB.ToolbarButton, disabled = false): void { + if (editButton) { this.deselectItem(false, true); - addButton.setActive(true); - } else if (this.currentAddButton) { - this.currentAddButton.setActive(false); + editButton.setActive(true); + } else if (this.currentEditButton) { + this.currentEditButton.setActive(false); } - this.currentAddButton = addButton; - this.updateAddButtonsStates(disabled); + this.currentEditButton = editButton; + this.updateEditButtonsStates(disabled); } private createdControlButtonTooltip(root: HTMLElement, side: TooltipPositioningSide) { @@ -784,7 +831,7 @@ export abstract class TbMap { this.updateTripsAppearance(); this.updateTripsAnchors(); this.updateBounds(); - this.updateAddButtonsStates(); + this.updateEditButtonsStates(); } private updateTrips(subscription: IWidgetSubscription) { @@ -886,22 +933,28 @@ export abstract class TbMap { } } - private updateAddButtonsStates(disabled = false) { - if (this.currentAddButton || disabled) { - if (this.addMarkerButton && this.addMarkerButton !== this.currentAddButton) { + private updateEditButtonsStates(disabled = false) { + if (this.currentEditButton || disabled) { + if (this.dragModeButton && this.dragModeButton !== this.currentEditButton) { + this.dragModeButton.setDisabled(true); + } + if (this.addMarkerButton && this.addMarkerButton !== this.currentEditButton) { this.addMarkerButton.setDisabled(true); } - if (this.addRectangleButton && this.addRectangleButton !== this.currentAddButton) { + if (this.addRectangleButton && this.addRectangleButton !== this.currentEditButton) { this.addRectangleButton.setDisabled(true); } - if (this.addPolygonButton && this.addPolygonButton !== this.currentAddButton) { + if (this.addPolygonButton && this.addPolygonButton !== this.currentEditButton) { this.addPolygonButton.setDisabled(true); } - if (this.addCircleButton && this.addCircleButton !== this.currentAddButton) { + if (this.addCircleButton && this.addCircleButton !== this.currentEditButton) { this.addCircleButton.setDisabled(true); } - this.customActionsToolbar.setDisabled(true); + this.customActionsToolbar?.setDisabled(true); } else { + if (this.dragModeButton) { + this.dragModeButton.setDisabled(false); + } if (this.addMarkerButton) { this.addMarkerButton.setDisabled(!this.addMarkerDataLayers.some(dl => dl.isEnabled() && dl.hasUnplacedItems())); } @@ -914,7 +967,7 @@ export abstract class TbMap { if (this.addCircleButton) { this.addCircleButton.setDisabled(!this.addCircleDataLayers.some(dl => dl.isEnabled() && dl.hasUnplacedItems())); } - this.customActionsToolbar.setDisabled(false); + this.customActionsToolbar?.setDisabled(false); } } @@ -979,7 +1032,7 @@ export abstract class TbMap { } public enabledDataLayersUpdated() { - this.updateAddButtonsStates(); + this.updateEditButtonsStates(); this.updateTripsAnchors(); } @@ -1027,6 +1080,14 @@ export abstract class TbMap { return this.editToolbar; } + public useDragModeButton(): boolean { + return this.settings.dragModeButton; + } + + public dragModeEnabled(): boolean { + return this.dragMode; + } + public saveItemData(datasource: TbMapDatasource, data: DataKeyValuePair[]): Observable { const attributeService = this.ctx.$injector.get(AttributeService); const attributes: AttributeData[] = []; 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 d28e04b4fc..0f0e64f5b7 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 @@ -660,6 +660,7 @@ export interface BaseMapSettings { additionalDataSources: AdditionalMapDataSourceSettings[]; controlsPosition: MapControlsPosition; zoomActions: MapZoomAction[]; + dragModeButton: boolean; fitMapBounds: boolean; useDefaultCenterPosition: boolean; defaultCenterPosition?: string; @@ -682,6 +683,7 @@ export const defaultBaseMapSettings: BaseMapSettings = { additionalDataSources: [], controlsPosition: MapControlsPosition.topleft, zoomActions: [MapZoomAction.scroll, MapZoomAction.doubleClick, MapZoomAction.controlButtons], + dragModeButton: false, fitMapBounds: true, useDefaultCenterPosition: false, defaultCenterPosition: '0,0', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html index 0ee29c54c5..97c686732a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html @@ -99,6 +99,11 @@ +
+ + {{ 'widgets.maps.control.switch-to-drag-mode-using-button' | translate }} + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts index 74536f8fd5..6f1650b19f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts @@ -26,10 +26,13 @@ import { Validators } from '@angular/forms'; import { + DataLayerEditAction, defaultImageMapSourceSettings, - ImageMapSourceSettings, imageMapSourceSettingsValidator, + ImageMapSourceSettings, + imageMapSourceSettingsValidator, mapControlPositions, mapControlsPositionTranslationMap, + MapDataLayerSettings, MapDataLayerType, MapSetting, MapType, @@ -109,6 +112,8 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid dataLayerMode: MapDataLayerType = 'markers'; + showDragButtonModeButtonSettings = false; + constructor(private fb: UntypedFormBuilder, private dialog: MatDialog, private destroyRef: DestroyRef) { @@ -139,6 +144,7 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid additionalDataSources: [null, []], controlsPosition: [null, []], zoomActions: [null, []], + dragModeButton: [null, []], fitMapBounds: [null, []], useDefaultCenterPosition: [null, []], defaultCenterPosition: [null, []], @@ -167,6 +173,14 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid ).subscribe((mapType: MapType) => { this.mapTypeChanged(mapType); }); + merge(this.mapSettingsFormGroup.get('markers').valueChanges, + this.mapSettingsFormGroup.get('polygons').valueChanges, + this.mapSettingsFormGroup.get('circles').valueChanges + ).pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + this.updateDragButtonModeSettings(); + }); } registerOnChange(fn: any): void { @@ -192,6 +206,7 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid value, {emitEvent: false} ); this.updateValidators(); + this.updateDragButtonModeSettings(); } public validate(_c: UntypedFormControl) { @@ -237,6 +252,26 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid } } + private updateDragButtonModeSettings() { + const markers: MapDataLayerSettings[] = this.mapSettingsFormGroup.get('markers').value; + const circles: MapDataLayerSettings[] = this.mapSettingsFormGroup.get('circles').value; + let dragModeButtonSettingsEnabled = markers.some(d => d.edit && d.edit.enabledActions && d.edit.enabledActions.includes(DataLayerEditAction.move)); + if (!dragModeButtonSettingsEnabled) { + const polygons: MapDataLayerSettings[] = this.mapSettingsFormGroup.get('polygons').value; + dragModeButtonSettingsEnabled = polygons.some(d => d.edit && d.edit.enabledActions && d.edit.enabledActions.includes(DataLayerEditAction.move)); + } + if (!dragModeButtonSettingsEnabled) { + const circles: MapDataLayerSettings[] = this.mapSettingsFormGroup.get('circles').value; + dragModeButtonSettingsEnabled = circles.some(d => d.edit && d.edit.enabledActions && d.edit.enabledActions.includes(DataLayerEditAction.move)); + } + this.showDragButtonModeButtonSettings = dragModeButtonSettingsEnabled; + if (dragModeButtonSettingsEnabled) { + this.mapSettingsFormGroup.get('dragModeButton').enable({emitEvent: false}); + } else { + this.mapSettingsFormGroup.get('dragModeButton').disable({emitEvent: false}); + } + } + private updateModel() { this.modelValue = this.mapSettingsFormGroup.getRawValue(); this.propagateChange(this.modelValue); 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 6eaa7cd206..4c336831fa 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -7704,7 +7704,8 @@ "zoom-actions": "Zoom actions", "zoom-scroll": "Scroll", "zoom-double-click": "Double click", - "zoom-control-buttons": "Control buttons" + "zoom-control-buttons": "Control buttons", + "switch-to-drag-mode-using-button": "Switch to drag mode using button" }, "timeline": { "control-panel": "Timeline control panel", @@ -7839,6 +7840,7 @@ "action-remove": "Remove", "edit-instruments": "Instruments", "enable-snapping": "Enable snapping to other vertices for precision drawing", + "drag-drop-mode": "Drag-drop mode", "trip": { "no-trips": "No trips configured", "add-trip": "Add trip",