diff --git a/admin/inertia/components/maps/MapComponent.tsx b/admin/inertia/components/maps/MapComponent.tsx index bac307d..1e1f00a 100644 --- a/admin/inertia/components/maps/MapComponent.tsx +++ b/admin/inertia/components/maps/MapComponent.tsx @@ -13,22 +13,34 @@ import { Protocol } from 'pmtiles' import { useEffect, useRef, useState, useCallback } from 'react' type ScaleUnit = 'imperial' | 'metric' + import { useMapMarkers, PIN_COLORS } from '~/hooks/useMapMarkers' import type { PinColorId } from '~/hooks/useMapMarkers' import MarkerPin from './MarkerPin' import MarkerPanel from './MarkerPanel' -export default function MapComponent() { +export default function MapComponent({ isHoveringUI, showCoordinatesEnabled, }: {isHoveringUI: boolean, showCoordinatesEnabled: boolean}) { const mapRef = useRef(null) const { markers, addMarker, deleteMarker } = useMapMarkers() + const [placingMarker, setPlacingMarker] = useState<{ lng: number; lat: number } | null>(null) const [markerName, setMarkerName] = useState('') const [markerColor, setMarkerColor] = useState('orange') const [selectedMarkerId, setSelectedMarkerId] = useState(null) + const [scaleUnit, setScaleUnit] = useState( () => (localStorage.getItem('nomad:map-scale-unit') as ScaleUnit) || 'metric' ) + const [cursorLngLat, setCursorLngLat] = useState<{ + lng: number + lat: number + x: number + y: number + } | null>(null) + + const [showCoordinates, setShowCoordinates] = useState(false) + const toggleScaleUnit = useCallback(() => { setScaleUnit((prev) => { const next = prev === 'metric' ? 'imperial' : 'metric' @@ -37,15 +49,45 @@ export default function MapComponent() { }) }, []) - // Add the PMTiles protocol to maplibre-gl useEffect(() => { - let protocol = new Protocol() + const protocol = new Protocol() maplibregl.addProtocol('pmtiles', protocol.tile) + return () => { maplibregl.removeProtocol('pmtiles') } }, []) + const hideCoordinates = useCallback(() => { + setShowCoordinates(false) + setCursorLngLat(null) + }, []) + + const handleMouseMove = useCallback((e: MapLayerMouseEvent) => { + const target = e.originalEvent.target as HTMLElement | null + + if (target?.closest('.maplibregl-control-container, .maplibregl-ctrl')) { + hideCoordinates() + return + } + + if (!showCoordinatesEnabled || + isHoveringUI || + target?.closest('.maplibregl-control-container, .maplibregl-ctrl') + ) { + hideCoordinates() + return + } + + setShowCoordinates(true) + setCursorLngLat({ + lng: e.lngLat.lng, + lat: e.lngLat.lat, + x: e.point.x, + y: e.point.y, + }) + }, [hideCoordinates, isHoveringUI]) + const handleMapClick = useCallback((e: MapLayerMouseEvent) => { setPlacingMarker({ lng: e.lngLat.lng, lat: e.lngLat.lat }) setMarkerName('') @@ -78,6 +120,21 @@ export default function MapComponent() { return ( +
{ + const target = e.target as HTMLElement | null + + if ( + target?.closest( + '.maplibregl-control-container, .maplibregl-ctrl, .maplibregl-ctrl-group, .maplibregl-ctrl-scale' + ) + ) { + hideCoordinates() + } + }} + > + + {showCoordinates && showCoordinates && cursorLngLat && ( +
+ {cursorLngLat.lat.toFixed(6)}, {cursorLngLat.lng.toFixed(6)} +
+ )} +
+
- {/* Existing markers */} {markers.map((marker) => ( ))} - {/* Popup for selected marker */} {selectedMarker && ( )} - {/* Popup for placing a new marker */} {placingMarker && ( setPlacingMarker(null)} closeOnClick={false} > -
+
+
{PIN_COLORS.map((c) => ( ))}
+
+
- {/* Marker panel overlay */} - +
+ +
) } diff --git a/admin/inertia/pages/maps.tsx b/admin/inertia/pages/maps.tsx index 9fe20e0..d8585b1 100644 --- a/admin/inertia/pages/maps.tsx +++ b/admin/inertia/pages/maps.tsx @@ -5,10 +5,13 @@ import StyledButton from '~/components/StyledButton' import { IconArrowLeft } from '@tabler/icons-react' import { FileEntry } from '../../types/files' import Alert from '~/components/Alert' +import { useState } from 'react' export default function Maps(props: { maps: { baseAssetsExist: boolean; regionFiles: FileEntry[] } }) { + const [isHoveringUI, setIsHoveringUI] = useState(false) + const [showMapCoordinates, setShowMapCoordinates] = useState(true) const alertMessage = !props.maps.baseAssetsExist ? 'The base map assets have not been installed. Please download them first to enable map functionality.' : props.maps.regionFiles.length === 0 @@ -20,19 +23,35 @@ export default function Maps(props: {
{/* Nav and alerts are overlayed */} -
+
setIsHoveringUI(true)} + onMouseLeave={() => setIsHoveringUI(false)} + >

Back to Home

- - - Manage Map Regions - - +
+ + + + + Manage Map Regions + + +
{alertMessage && ( -
+
setIsHoveringUI(true)} + onMouseLeave={() => setIsHoveringUI(false)} + > )}
- +
diff --git a/install/management_compose.yaml b/install/management_compose.yaml index f4c7b5f..1ddcb00 100644 --- a/install/management_compose.yaml +++ b/install/management_compose.yaml @@ -9,8 +9,8 @@ name: project-nomad services: admin: - image: ghcr.io/crosstalk-solutions/project-nomad:latest - pull_policy: always + image: project-nomad:local + pull_policy: never #ghcr.io/crosstalk-solutions/project-nomad:latest container_name: nomad_admin restart: unless-stopped extra_hosts: @@ -27,18 +27,18 @@ services: - PORT=8080 - LOG_LEVEL=info # APP_KEY needs to be at least 16 chars or will fail validation and container won't start! - - APP_KEY=replaceme + - APP_KEY=ce9579a56b32e05ee432be91e6d1d9b95cb3a00eb92789559569decdb76c4db2 # # Leave HOST as is so the admin server listens all interfaces within the container - this doesn't affect how you access it from the host, it's just for internal container networking - HOST=0.0.0.0 # URL should be set to the URL you will access the admin interface at (e.g. http://localhost:8080 or http://192.168.1.x:8080) - - URL=replaceme + - URL=http://nomad-dev.brewerhomestead.com/ - DB_HOST=mysql # If you change the MySQL port, make sure to update this accordingly - DB_PORT=3306 - DB_DATABASE=nomad - DB_USER=nomad_user # Needs to match the MYSQL_PASSWORD in the mysql service! - - DB_PASSWORD=replaceme + - DB_PASSWORD=2bc4dad18d5a0ecf5914428d91ebde27d42e8afafd72c73089a0dd1eba9ae1fc - DB_NAME=nomad - DB_SSL=false - REDIS_HOST=redis @@ -72,11 +72,11 @@ services: container_name: nomad_mysql restart: unless-stopped environment: - - MYSQL_ROOT_PASSWORD=replaceme + - MYSQL_ROOT_PASSWORD=d797ecbd5cee07758909a628cb0a2a75285dc3bac42d1513a8d2aed95c632577 - MYSQL_DATABASE=nomad - MYSQL_USER=nomad_user # Needs to match DB_PASSWORD in the admin service! - - MYSQL_PASSWORD=replaceme + - MYSQL_PASSWORD=2bc4dad18d5a0ecf5914428d91ebde27d42e8afafd72c73089a0dd1eba9ae1fc volumes: - /opt/project-nomad/mysql:/var/lib/mysql # Persist MySQL data on the host. This path can be changed if needed, just make sure it's writable by the container. Host persistence is important for the database to ensure your data isn't lost when the container is removed or updated. healthcheck: