Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 94 additions & 5 deletions src/components/Map/MapContainer.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Modals/FocusInfoPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
</button>
)}
{onClearSubFocus && hasSubFocus && (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Modals/__tests__/FocusInfoPanel.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
27 changes: 24 additions & 3 deletions src/components/Sidebar/LayersPanel.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -363,7 +363,28 @@ const LayersPanel = ({

{/* Buttons rows */}
<div className="flex flex-col gap-2">
{/* First row: Sub-focus and Refocus buttons */}
{/* First row: Event Info and Plan Options */}
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => window.dispatchEvent(new CustomEvent('ui:show-event-info'))}
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 flex items-center justify-center gap-1"
title="Event Information"
>
<FileText className="w-3 h-3" />
Event Info
</button>
<button
type="button"
onClick={() => window.dispatchEvent(new CustomEvent('ui:show-export-options'))}
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 flex items-center justify-center gap-1"
title="Plan Options"
>
<Download className="w-3 h-3" />
Plan Options
</button>
</div>
{/* Second row: Sub-focus and Refocus buttons */}
<div className="flex items-center gap-2">
{/* Sub-focus button */}
{onBeginSubFocus && !hasSubFocus && (
Expand All @@ -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
</button>
)}
{/* Refocus button */}
Expand Down
39 changes: 1 addition & 38 deletions src/components/Sidebar/RightSidebar.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -312,25 +312,6 @@ const RightSidebar = ({
</div>
)}

{mode !== 'icon-rail' && (
<div className="flex items-center gap-2 px-3 py-2 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900">
<button
type="button"
className="flex-1 px-3 py-2 rounded-md border border-gray-200 dark:border-gray-700 text-xs font-medium bg-white dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700"
onClick={() => window.dispatchEvent(new CustomEvent('ui:show-event-info'))}
>
Event Info
</button>
<button
type="button"
className="flex-1 px-3 py-2 rounded-md border border-gray-200 dark:border-gray-700 text-xs font-medium bg-white dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700"
onClick={() => window.dispatchEvent(new CustomEvent('ui:show-export-options'))}
>
Plan Options
</button>
</div>
)}

<div className="flex-1 min-h-0 overflow-y-auto p-0 space-y-0">
{sectionsToRender.map((id, idx) => (
<React.Fragment key={id}>
Expand Down Expand Up @@ -421,24 +402,6 @@ const RightSidebar = ({
})}
</div>
<div className="flex flex-col items-center space-y-4 mt-6">
<button
type="button"
className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700"
onClick={() => window.dispatchEvent(new CustomEvent('ui:show-event-info'))}
title="Event Information"
aria-label="Event Information"
>
<FileText className="w-5 h-5" />
</button>
<button
type="button"
className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700"
onClick={() => window.dispatchEvent(new CustomEvent('ui:show-export-options'))}
title="Plan Options"
aria-label="Plan Options"
>
<Download className="w-5 h-5" />
</button>
<button
type="button"
className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700"
Expand Down
Loading