From bbc23dbf90e4a426e9ffb019104b5d3f529c204d Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 4 Apr 2026 14:56:25 -0600 Subject: [PATCH] Adding map default view option to the map context menu. --- app/src/api.js | 12 ++++++++++ app/src/components/MapViewer.js | 39 ++++++++++++++++++++++++++++----- modules/map/test/test_api.py | 23 +++++++++++++++++++ wrolpi/common.py | 10 +++++++++ wrolpi/root_api.py | 1 + 5 files changed, 79 insertions(+), 6 deletions(-) diff --git a/app/src/api.js b/app/src/api.js index 3c8ab85e..a0e87203 100644 --- a/app/src/api.js +++ b/app/src/api.js @@ -317,6 +317,7 @@ export async function getSettings() { ignore_outdated_zims: content.ignore_outdated_zims, ignored_directories: content.ignored_directories, log_level: content.log_level, + map_default_location: content.map_default_location, map_destination: content.map_destination, nav_color: content.nav_color, media_directory: content.media_directory, @@ -1568,6 +1569,17 @@ export async function updateMapPin(pinId, label, color) { } } +export async function setMapDefaultLocation(lat, lon, zoom) { + try { + await updateConfig('wrolpi.yaml', {map_default_location: {lat, lon, zoom}}); + toast({type: 'success', title: 'Default Location', description: 'Default map location saved', time: 3000}); + return true; + } catch (e) { + toast({type: 'error', title: 'Map Error', description: 'Could not set default location', time: 5000}); + return false; + } +} + export async function clearCompletedDownloads() { const response = await apiPost(`${API_URI}/download/clear_completed`); if (!response.ok) { diff --git a/app/src/components/MapViewer.js b/app/src/components/MapViewer.js index 3b84b6bb..fac3910b 100644 --- a/app/src/components/MapViewer.js +++ b/app/src/components/MapViewer.js @@ -5,11 +5,11 @@ import "maplibre-gl/dist/maplibre-gl.css"; import {Protocol} from "pmtiles"; import layers from "protomaps-themes-base"; import mlcontour from "@acalcutt/maplibre-contour-pmtiles"; -import {addMapPin, deleteMapPin, getMapFiles, getMapPins} from "../api"; +import {addMapPin, deleteMapPin, getMapFiles, getMapPins, setMapDefaultLocation} from "../api"; import {MAP_VIEWER_URI} from "./Vars"; import {Checkbox, Icon, Segment} from "semantic-ui-react"; import {Header} from "./Theme"; -import {ThemeContext} from "../contexts/contexts"; +import {SettingsContext, ThemeContext} from "../contexts/contexts"; // Terrain DEM file prefix for hillshade and contours. const TERRAIN_PREFIX = "terrain-"; @@ -354,11 +354,20 @@ export default function MapViewer() { }); const [searchParams, setSearchParams] = useSearchParams(); const {theme} = useContext(ThemeContext); + const {settings, fetchSettings} = useContext(SettingsContext); - // Parse initial coordinates from URL. - const initialLat = parseFloat(searchParams.get("lat")) || 0; - const initialLon = parseFloat(searchParams.get("lon")) || 0; - const initialZoom = parseFloat(searchParams.get("z")) || 2; + // Use URL params if provided, otherwise fall back to saved default location, then 0,0,z2. + const hasUrlParams = searchParams.has("lat") || searchParams.has("lon") || searchParams.has("z"); + const defaultLoc = settings?.map_default_location; + const initialLat = hasUrlParams + ? (parseFloat(searchParams.get("lat")) || 0) + : (parseFloat(defaultLoc?.lat) || 0); + const initialLon = hasUrlParams + ? (parseFloat(searchParams.get("lon")) || 0) + : (parseFloat(defaultLoc?.lon) || 0); + const initialZoom = hasUrlParams + ? (parseFloat(searchParams.get("z")) || 2) + : (parseFloat(defaultLoc?.zoom) || 2); useEffect(() => { if (!mapContainer.current) return; @@ -705,6 +714,16 @@ export default function MapViewer() { setContextMenu(null); }, [contextMenu]); + const handleSetDefaultLocation = useCallback(async () => { + const map = mapRef.current; + if (!map) return; + const center = map.getCenter(); + const zoom = map.getZoom(); + await setMapDefaultLocation(center.lat, center.lng, zoom); + await fetchSettings(); + setContextMenu(null); + }, [fetchSettings]); + const handleSubmitPin = useCallback(async (label, color) => { if (!addingPin) return; const {lat, lon} = addingPin; @@ -770,6 +789,14 @@ export default function MapViewer() { > Add Pin Here +
e.target.style.background = "#f0f0f0"} + onMouseLeave={e => e.target.style.background = "transparent"} + onClick={handleSetDefaultLocation} + > + Set as Default View +
} {addingPin && dict: + return self._config.get('map_default_location') + + @map_default_location.setter + def map_default_location(self, value: dict): + self.update({'map_default_location': value}) + @property def map_destination(self) -> str: return self._config['map_destination'] diff --git a/wrolpi/root_api.py b/wrolpi/root_api.py index 3656d44d..e4c1e2a9 100644 --- a/wrolpi/root_api.py +++ b/wrolpi/root_api.py @@ -171,6 +171,7 @@ def get_settings(_: Request): 'ignore_outdated_zims': wrolpi_config.ignore_outdated_zims, 'ignored_directories': ignored_directories, 'log_level': api_app.shared_ctx.log_level.value, + 'map_default_location': wrolpi_config.map_default_location, 'map_destination': wrolpi_config.map_destination, 'nav_color': wrolpi_config.nav_color, 'media_directory': str(get_media_directory()), # Convert to string to avoid conversion to relative.