diff --git a/src/layers/ContextMenu.tsx b/src/layers/ContextMenu.tsx index df87b7cf..ff426a03 100644 --- a/src/layers/ContextMenu.tsx +++ b/src/layers/ContextMenu.tsx @@ -1,11 +1,15 @@ import { Map, Overlay } from 'ol' import { ContextMenuContent } from '@/map/ContextMenuContent' -import { useEffect, useRef, useState } from 'react' +import { useCallback, useContext, useEffect, useRef, useState } from 'react' import { QueryPoint } from '@/stores/QueryStore' import { fromLonLat, toLonLat } from 'ol/proj' import styles from '@/layers/ContextMenu.module.css' import { RouteStoreState } from '@/stores/RouteStore' import { Coordinate } from '@/utils' +import Dispatcher from '@/stores/Dispatcher' +import { AddPoint, SetPoint } from '@/actions/Actions' +import { coordinateToText } from '@/Converters' +import { SettingsContext } from '@/contexts/SettingsContext' interface ContextMenuProps { map: Map @@ -20,17 +24,61 @@ const overlay = new Overlay({ export default function ContextMenu({ map, route, queryPoints }: ContextMenuProps) { const [menuCoordinate, setMenuCoordinate] = useState(null) const container = useRef(null) + const settings = useContext(SettingsContext) + + const openContextMenu = useCallback( + (e: any) => { + e.preventDefault() + const coordinate = map.getEventCoordinate(e) + const lonLat = toLonLat(coordinate) + setMenuCoordinate({ lng: lonLat[0], lat: lonLat[1] }) + }, + [map] + ) - const openContextMenu = (e: any) => { - e.preventDefault() - const coordinate = map.getEventCoordinate(e) - const lonLat = toLonLat(coordinate) - setMenuCoordinate({ lng: lonLat[0], lat: lonLat[1] }) - } - - const closeContextMenu = () => { - setMenuCoordinate(null) - } + const handleClick = useCallback( + (e: any) => { + if (e.dragging) return + + // If click is inside the context menu, do nothing + const clickedElement = document.elementFromPoint(e.pixel[0], e.pixel[1]) + if (container.current?.contains(clickedElement)) { + return + } + + if (menuCoordinate) { + // Context menu is open -> close it and skip adding a point + setMenuCoordinate(null) + return + } + + if (!settings.addPointOnClick) return + + const lonLat = toLonLat(e.coordinate) + const myCoord = { lng: lonLat[0], lat: lonLat[1] } + + let idx = queryPoints.length + if (idx == 2) { + if (!queryPoints[1].isInitialized) idx-- + } + if (idx == 1) { + if (!queryPoints[0].isInitialized) idx-- + } + if (idx < 2) { + const setPoint = new SetPoint( + { + ...queryPoints[idx], + coordinate: myCoord, + queryText: coordinateToText(myCoord), + isInitialized: true, + }, + false + ) + Dispatcher.dispatch(setPoint) + } else Dispatcher.dispatch(new AddPoint(idx, myCoord, true, false)) + }, + [menuCoordinate, settings.addPointOnClick, queryPoints] + ) useEffect(() => { overlay.setElement(container.current!) @@ -50,18 +98,23 @@ export default function ContextMenu({ map, route, queryPoints }: ContextMenuProp map.getTargetElement().addEventListener('touchstart', e => longTouchHandler.onTouchStart(e)) map.getTargetElement().addEventListener('touchmove', () => longTouchHandler.onTouchEnd()) map.getTargetElement().addEventListener('touchend', () => longTouchHandler.onTouchEnd()) - - map.getTargetElement().addEventListener('click', closeContextMenu) } + map.on('singleclick', handleClick) map.on('change:target', onMapTargetChange) return () => { - map.getTargetElement().removeEventListener('contextmenu', openContextMenu) - map.getTargetElement().removeEventListener('click', closeContextMenu) + const targetElement = map.getTargetElement() + if (targetElement) { + targetElement.removeEventListener('contextmenu', openContextMenu) + targetElement.removeEventListener('touchstart', e => longTouchHandler.onTouchStart(e)) + targetElement.removeEventListener('touchmove', () => longTouchHandler.onTouchEnd()) + targetElement.removeEventListener('touchend', () => longTouchHandler.onTouchEnd()) + } + map.un('singleclick', handleClick) map.removeOverlay(overlay) map.un('change:target', onMapTargetChange) } - }, [map]) + }, [map, openContextMenu, handleClick]) useEffect(() => { overlay.setPosition(menuCoordinate ? fromLonLat([menuCoordinate.lng, menuCoordinate.lat]) : undefined) @@ -74,7 +127,7 @@ export default function ContextMenu({ map, route, queryPoints }: ContextMenuProp coordinate={menuCoordinate!} queryPoints={queryPoints} route={route} - onSelect={closeContextMenu} + onSelect={() => setMenuCoordinate(null)} /> )} diff --git a/src/layers/UsePathsLayer.tsx b/src/layers/UsePathsLayer.tsx index a9629b3e..c502c48c 100644 --- a/src/layers/UsePathsLayer.tsx +++ b/src/layers/UsePathsLayer.tsx @@ -133,7 +133,7 @@ function addAccessNetworkLayer(map: Map, selectedPath: Path, queryPoints: QueryP }) layer.setStyle(style) for (let i = 0; i < selectedPath.snapped_waypoints.coordinates.length; i++) { - if(i >= queryPoints.length) break // can happen if deleted too fast + if (i >= queryPoints.length) break // can happen if deleted too fast const start = fromLonLat([queryPoints[i].coordinate.lng, queryPoints[i].coordinate.lat]) const end = fromLonLat(selectedPath.snapped_waypoints.coordinates[i]) layer.getSource()?.addFeature(new Feature(createBezierLineString(start, end))) diff --git a/src/sidebar/SettingsBox.tsx b/src/sidebar/SettingsBox.tsx index eb36ae57..9338ce7b 100644 --- a/src/sidebar/SettingsBox.tsx +++ b/src/sidebar/SettingsBox.tsx @@ -51,6 +51,13 @@ export default function SettingsBox({ profile }: { profile: RoutingProfile }) { Dispatcher.dispatch(new UpdateSettings({ showDistanceInMiles: !settings.showDistanceInMiles })) } /> + + Dispatcher.dispatch(new UpdateSettings({ addPointOnClick: !settings.addPointOnClick })) + } + />
{tr('settings_gpx_export')}
diff --git a/src/stores/MapOptionsStore.ts b/src/stores/MapOptionsStore.ts index e2f27676..df41eeb9 100644 --- a/src/stores/MapOptionsStore.ts +++ b/src/stores/MapOptionsStore.ts @@ -6,6 +6,7 @@ import { ToggleExternalMVTLayer, ToggleRoutingGraph, ToggleUrbanDensityLayer, + UpdateSettings, } from '@/actions/Actions' import config from 'config' @@ -198,6 +199,7 @@ export default class MapOptionsStore extends Store { ...state, selectedStyle: styleOption, } + } else if (action instanceof UpdateSettings) { } else if (action instanceof ToggleRoutingGraph) { return { ...state, diff --git a/src/stores/SettingsStore.ts b/src/stores/SettingsStore.ts index 406c0bb9..2f7adef7 100644 --- a/src/stores/SettingsStore.ts +++ b/src/stores/SettingsStore.ts @@ -1,10 +1,12 @@ import Store from '@/stores/Store' import { Action } from '@/stores/Dispatcher' import { SetCustomModelEnabled, UpdateSettings } from '@/actions/Actions' +import { getMap } from '@/map/map' export interface Settings { showDistanceInMiles: boolean drawAreasEnabled: boolean + addPointOnClick: boolean gpxExportRte: boolean gpxExportWpt: boolean gpxExportTrk: boolean @@ -13,6 +15,7 @@ export interface Settings { export const defaultSettings: Settings = { showDistanceInMiles: false, drawAreasEnabled: false, + addPointOnClick: false, gpxExportRte: false, gpxExportWpt: false, gpxExportTrk: true, @@ -31,6 +34,9 @@ export default class SettingsStore extends Store { drawAreasEnabled: false, } } else if (action instanceof UpdateSettings) { + if ('addPointOnClick' in action.updatedSettings) + getMap().getViewport().style.cursor = action.updatedSettings.addPointOnClick ? 'crosshair' : '' + return { ...state, ...action.updatedSettings,