diff --git a/src/components/Map/MapContainer.jsx b/src/components/Map/MapContainer.jsx index 3a5966e..1b4d7e3 100644 --- a/src/components/Map/MapContainer.jsx +++ b/src/components/Map/MapContainer.jsx @@ -1,5 +1,5 @@ // components/Map/MapContainer.jsx -import React, { forwardRef, useEffect, useState, useRef } from 'react'; +import React, { forwardRef, useEffect, useState, useRef, useCallback, useMemo } from 'react'; import MapTooltip from './MapTooltip'; import ClickPopover from './ClickPopover'; import { useZoneCreator } from '../../hooks/useZoneCreator'; @@ -16,7 +16,6 @@ import ActiveToolIndicator from './ActiveToolIndicator'; import LoadingOverlay from './LoadingOverlay'; import PlacementPreview from './PlacementPreview'; import EdgeMarkers from './EdgeMarkers'; -import { useMemo } from 'react'; import TextAnnotationEditor, { AnnotationActionPill } from './TextAnnotationEditor'; import { useMapViewState } from '../../hooks/useMapViewState'; import { useRotationControls } from '../../hooks/useRotationControls'; @@ -79,6 +78,52 @@ const MapContainer = forwardRef(({ // Track current annotations popup (MapLibre Popup) so global handlers can close it const annotationPopupRef = useRef(null); + // Helper function to update cursor for subfocus mode + const updateSubFocusCursor = useCallback((isSubFocus) => { + try { + if (!map) return; + const canvas = map.getCanvas(); + const container = map.getContainer(); + if (!canvas && !container) return; + + if (isSubFocus) { + // MapboxDraw controls cursor via CSS classes on the map container + // The CSS rule is: .mapboxgl-map.mouse-add .mapboxgl-canvas-container.mapboxgl-interactive { cursor: crosshair; } + // We need to add the mouse-add class to the .mapboxgl-map element + if (container) { + container.classList.add('mouse-add'); + // Also set inline style as fallback + container.style.cursor = 'crosshair'; + + // Find the canvas container element and set cursor there too + const canvasContainer = container.querySelector('.mapboxgl-canvas-container'); + if (canvasContainer) { + canvasContainer.style.cursor = 'crosshair'; + } + } + if (canvas) { + canvas.style.cursor = 'crosshair'; + } + } else { + // Reset cursor when not in subfocus mode (unless other modes set it) + if (!placementMode) { + if (container) { + container.classList.remove('mouse-add'); + container.style.cursor = ''; + + const canvasContainer = container.querySelector('.mapboxgl-canvas-container'); + if (canvasContainer) { + canvasContainer.style.cursor = ''; + } + } + if (canvas) { + canvas.style.cursor = ''; + } + } + } + } catch (_) {} + }, [map, placementMode]); + // Map view state (single source of truth for pitch/bearing/zoom/viewType) const view = useMapViewState(map); const suppressRotateSnapRef = useRef(false); @@ -148,8 +193,14 @@ const MapContainer = forwardRef(({ // Listen for sub-focus arming/disarming events useEffect(() => { - const arm = () => { subFocusArmedRef.current = true; }; - const disarm = () => { subFocusArmedRef.current = false; }; + const arm = () => { + subFocusArmedRef.current = true; + updateSubFocusCursor(true); + }; + const disarm = () => { + subFocusArmedRef.current = false; + updateSubFocusCursor(false); + }; window.addEventListener('subfocus:arm', arm); window.addEventListener('subfocus:disarm', disarm); // Apply event: geometry comes from draw tools; compute and set subfocus without persisting draw feature @@ -159,6 +210,7 @@ const MapContainer = forwardRef(({ if (!geom || !permitAreas?.focusedArea || !permitAreas?.setSubFocusPolygon) return; const ok = permitAreas.setSubFocusPolygon({ type: 'Feature', properties: {}, geometry: geom }); subFocusArmedRef.current = false; + updateSubFocusCursor(false); } catch (_) {} }; window.addEventListener('subfocus:apply', apply); @@ -167,7 +219,44 @@ const MapContainer = forwardRef(({ window.removeEventListener('subfocus:disarm', disarm); window.removeEventListener('subfocus:apply', apply); }; - }, []); + }, [updateSubFocusCursor, permitAreas]); + + // Set cursor to pin icon when in sub-area drawing mode + useEffect(() => { + if (!map) return; + + const isSubFocusMode = drawTools?.activeTool === 'subfocus' || subFocusArmedRef.current; + updateSubFocusCursor(isSubFocusMode); + + // Also listen for map container mouse events to ensure cursor stays updated + const onMouseMove = () => { + const currentIsSubFocus = drawTools?.activeTool === 'subfocus' || subFocusArmedRef.current; + updateSubFocusCursor(currentIsSubFocus); + }; + + try { + const container = map.getContainer(); + if (container) { + container.addEventListener('mousemove', onMouseMove); + } + } catch (_) {} + + return () => { + try { + const container = map.getContainer(); + if (container) { + container.removeEventListener('mousemove', onMouseMove); + } + } catch (_) {} + // Reset cursor on cleanup + try { + const canvas = map.getCanvas(); + if (canvas && !placementMode) { + canvas.style.cursor = ''; + } + } catch (_) {} + }; + }, [map, drawTools?.activeTool, placementMode, updateSubFocusCursor]); // Build derived features (text points, shape labels, arrow lines, and arrowheads) from Draw features const derivedAnnotations = useMemo(() => { diff --git a/src/components/Modals/FocusInfoPanel.jsx b/src/components/Modals/FocusInfoPanel.jsx index 3be522d..0273969 100644 --- a/src/components/Modals/FocusInfoPanel.jsx +++ b/src/components/Modals/FocusInfoPanel.jsx @@ -62,7 +62,7 @@ const FocusInfoPanel = ({ className="text-white hover:text-blue-200 text-xs bg-blue-700 hover:bg-blue-800 px-2 py-1 rounded" title="Draw sub-area to focus" > - Focus sub-area + Define sub-area )} {onClearSubFocus && hasSubFocus && ( diff --git a/src/components/Modals/__tests__/FocusInfoPanel.test.jsx b/src/components/Modals/__tests__/FocusInfoPanel.test.jsx index c49480b..3815deb 100644 --- a/src/components/Modals/__tests__/FocusInfoPanel.test.jsx +++ b/src/components/Modals/__tests__/FocusInfoPanel.test.jsx @@ -37,7 +37,7 @@ describe('FocusInfoPanel', () => { /> ); expect(screen.getByText(/Focused on:/)).toBeInTheDocument(); - fireEvent.click(screen.getByText('Focus sub-area')); + fireEvent.click(screen.getByText('Define sub-area')); expect(onBeginSubFocus).toHaveBeenCalled(); fireEvent.click(screen.getByText('Clear Focus')); expect(onClearFocus).toHaveBeenCalled(); diff --git a/src/components/Sidebar/LayersPanel.jsx b/src/components/Sidebar/LayersPanel.jsx index 6ed53b0..97c0a96 100644 --- a/src/components/Sidebar/LayersPanel.jsx +++ b/src/components/Sidebar/LayersPanel.jsx @@ -1,6 +1,6 @@ // components/Sidebar/LayersPanel.jsx import React, { useState, useMemo } from 'react'; -import { Eye, EyeOff, X, Layers, ToggleLeft, ToggleRight, ChevronDown, ChevronRight, Loader2, CheckCircle, AlertCircle, Circle } from 'lucide-react'; +import { Eye, EyeOff, X, Layers, ToggleLeft, ToggleRight, ChevronDown, ChevronRight, Loader2, CheckCircle, AlertCircle, Circle, FileText, Download } from 'lucide-react'; import { LAYER_GROUPS, DISABLED_INFRASTRUCTURE_LAYERS, NON_RECOMMENDED_INFRASTRUCTURE_LAYERS } from '../../constants/layers'; import { INFRASTRUCTURE_ICONS, svgToDataUrl } from '../../utils/iconUtils'; import { getCandidateSrcs } from '../../utils/spriteResolver'; @@ -363,7 +363,28 @@ const LayersPanel = ({ {/* Buttons rows */}
- {/* First row: Sub-focus and Refocus buttons */} + {/* First row: Event Info and Plan Options */} +
+ + +
+ {/* Second row: Sub-focus and Refocus buttons */}
{/* Sub-focus button */} {onBeginSubFocus && !hasSubFocus && ( @@ -378,7 +399,7 @@ const LayersPanel = ({ className="flex-1 text-[11px] px-2 py-1 rounded bg-blue-600 dark:bg-blue-700 text-white hover:bg-blue-700 dark:hover:bg-blue-600 transition-colors whitespace-nowrap" title="Draw sub-area to focus" > - Focus Sub-Area + Define Sub-Area )} {/* Refocus button */} diff --git a/src/components/Sidebar/RightSidebar.jsx b/src/components/Sidebar/RightSidebar.jsx index 7e6e067..233093f 100644 --- a/src/components/Sidebar/RightSidebar.jsx +++ b/src/components/Sidebar/RightSidebar.jsx @@ -1,7 +1,7 @@ import React, { useMemo, useState, useCallback, useEffect, useRef } from 'react'; import { useWindowSize } from '../../hooks/useWindowSize'; import { useGlobalKeymap } from '../../hooks/useGlobalKeymap'; -import { ClipboardList, Download, FileImage, FileText, List, PencilRuler, Shapes } from 'lucide-react'; +import { ClipboardList, Download, FileImage, List, PencilRuler, Shapes } from 'lucide-react'; import DrawingTools from './DrawingTools'; import ShapeProperties from './ShapeProperties'; import PlaceableObjectsPanel from './PlaceableObjectsPanel'; @@ -312,25 +312,6 @@ const RightSidebar = ({
)} - {mode !== 'icon-rail' && ( -
- - -
- )} -
{sectionsToRender.map((id, idx) => ( @@ -421,24 +402,6 @@ const RightSidebar = ({ })}
- -