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
1,089 changes: 129 additions & 960 deletions src/components/Map/MapContainer.jsx

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions src/components/Sidebar/CustomShapesList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const CustomShapesList = ({
const handleSaveEdit = (e) => {
e.stopPropagation();
if (onShapeRename && editingShape) {
onShapeRename(editingShape, editValue);
onShapeRename(editingShape, { label: editValue });
}
setEditingShape(null);
setEditValue('');
Expand Down Expand Up @@ -150,9 +150,15 @@ const CustomShapesList = ({



const SHAPE_TYPE_NAMES = {
'Point': 'Point',
'LineString': 'Line',
'Polygon': 'Polygon'
};

const customShapes = filteredFeatures.map(feature => ({
id: feature.id,
type: feature.geometry.type,
type: SHAPE_TYPE_NAMES[feature.geometry.type] || feature.geometry.type,
label: feature.properties?.label || '',
properties: feature.properties
}));
Expand Down
6 changes: 2 additions & 4 deletions src/components/Sidebar/DrawingTools.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ const DrawingTools = ({ activeTool, onToolSelect, selectedShape, onDelete, drawA
const tools = [
{ id: 'point', icon: Dot, title: 'Point' },
{ id: 'line', icon: DashedLineIcon, title: 'Line' },
{ id: 'polygon', icon: Square, title: 'Polygon' },
{ id: 'text', icon: Type, title: 'Text' },
{ id: 'arrow', icon: ArrowRight, title: 'Arrow' }
{ id: 'polygon', icon: Square, title: 'Polygon' }
];

return (
Expand Down Expand Up @@ -43,7 +41,7 @@ const DrawingTools = ({ activeTool, onToolSelect, selectedShape, onDelete, drawA
</button>
</div>
)}
<div className="grid grid-cols-5 gap-2">
<div className="grid grid-cols-3 gap-2">
{tools.map(({ id, icon: Icon, title }) => (
<div key={id} className="relative group">
<span aria-hidden="true" className="block pb-[100%]" />
Expand Down
10 changes: 6 additions & 4 deletions src/components/Sidebar/RightSidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,11 @@ const RightSidebar = ({
</div>
{drawTools.selectedShape && (
<ShapeProperties
shapeLabel={drawTools.shapeLabel}
onLabelChange={drawTools.setShapeLabel}
onApply={drawTools.updateShapeLabel}
selectedShape={drawTools.selectedShape}
customShapes={(drawTools.draw?.current?.getAll()?.features || []).map(f => ({ id: f.id, type: f.geometry.type, label: f.properties?.label }))}
draw={drawTools.draw}
onUpdateShape={drawTools.updateShape}
onDeleteShape={drawTools.deleteSelectedShape}
/>
)}
</div>
Expand All @@ -179,7 +181,7 @@ const RightSidebar = ({
selectedShape={drawTools.selectedShape}
onShapeSelect={drawTools.selectShape}
draw={drawTools.draw}
onShapeRename={drawTools.renameShape}
onShapeRename={drawTools.updateShape}
showLabels={drawTools.showLabels}
onToggleLabels={drawTools.setShowLabels}
onCountChange={(n) => setAnnotationCount(n)}
Expand Down
159 changes: 131 additions & 28 deletions src/components/Sidebar/ShapeProperties.jsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,82 @@
import React, { useState, useEffect } from 'react';
import { Type } from 'lucide-react';
import { Type, X } from 'lucide-react';

const ShapeProperties = ({
selectedShape,
customShapes,
draw,
onUpdateShape
onUpdateShape,
onDeleteShape
}) => {
const [shapeLabel, setShapeLabel] = useState('');
const [textSize, setTextSize] = useState(14);
const [textColor, setTextColor] = useState('#111827');
const [halo, setHalo] = useState(true);
const [arrowStart, setArrowStart] = useState(false);
const [arrowEnd, setArrowEnd] = useState(false);
const selectedShapeRef = React.useRef(null);

// Update local state when selected shape changes
useEffect(() => {
if (selectedShape) {
const shape = customShapes.find(s => s.id === selectedShape);
setShapeLabel(shape?.label || '');
const feature = draw?.current ? draw.current.get(selectedShape) : null;
const p = feature?.properties || {};
setTextSize(Number(p.textSize || 14));
setTextColor(p.textColor || '#111827');
setHalo(p.halo !== false);
// Only reset local fields if the selected shape ID has actually changed
if (selectedShapeRef.current !== selectedShape) {
const shape = customShapes.find(s => s.id === selectedShape);
setShapeLabel(shape?.label || '');
const feature = draw?.current ? draw.current.get(selectedShape) : null;
const p = feature?.properties || {};
setTextSize(Number(p.textSize || 14));
setTextColor(p.textColor || '#111827');
setHalo(p.halo !== false);
setArrowStart(!!p.arrowStart);
setArrowEnd(!!p.arrowEnd);
selectedShapeRef.current = selectedShape;
}
} else {
setShapeLabel('');
setTextSize(14);
setTextColor('#111827');
setHalo(true);
setArrowStart(false);
setArrowEnd(false);
selectedShapeRef.current = null;
}
}, [selectedShape, customShapes]);
}, [selectedShape, customShapes, draw]);

const updateShapeLabel = () => {
if (selectedShape && draw?.current) {
// Update shape in customShapes array
onUpdateShape(selectedShape, { label: shapeLabel });

// Update the draw feature properties
const feature = draw.current.get(selectedShape);
if (feature) {
feature.properties.label = shapeLabel;
feature.properties.textSize = Number(textSize) || 14;
feature.properties.textColor = textColor;
feature.properties.halo = !!halo;
draw.current.add(feature);
}
const updateShapeProperties = () => {
if (selectedShape && onUpdateShape) {
onUpdateShape(selectedShape, {
label: shapeLabel,
textSize: Number(textSize) || 14,
textColor,
halo: !!halo,
arrowStart: !!arrowStart,
arrowEnd: !!arrowEnd
});
}
};

const handleKeyPress = (e) => {
if (e.key === 'Enter') {
updateShapeLabel();
updateShapeProperties();
}
};

if (!selectedShape) return null;

const SHAPE_TYPE_NAMES = {
'Point': 'Point',
'LineString': 'Line',
'Polygon': 'Polygon'
};

const selectedShapeData = customShapes.find(s => s.id === selectedShape);

// If the shape ID is set but it doesn't exist in our list anymore,
// it was likely just deleted. Don't render the properties panel.
if (!selectedShapeData) return null;

const displayType = selectedShapeData ? (SHAPE_TYPE_NAMES[selectedShapeData.type] || selectedShapeData.type) : '';

return (
<div className="p-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
Expand All @@ -66,7 +87,7 @@ const ShapeProperties = ({

{selectedShapeData && (
<div className="mb-3 text-xs text-gray-600 dark:text-gray-300">
<span className="font-medium">Type:</span> {selectedShapeData.type}
<span className="font-medium">Type:</span> {displayType}
</div>
)}

Expand All @@ -83,13 +104,64 @@ const ShapeProperties = ({
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
/>
<button
onClick={updateShapeLabel}
onClick={updateShapeProperties}
className="px-3 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm"
>
Apply
</button>
</div>
</div>

{selectedShapeData && selectedShapeData.type === 'LineString' && (
<div className="space-y-2 border-t border-gray-200 dark:border-gray-700 pt-3">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Arrow Tips</label>
<div className="flex space-x-4">
<label className="flex items-center text-sm text-gray-700 dark:text-gray-200">
<input
type="checkbox"
checked={arrowStart}
onChange={(e) => {
const newVal = e.target.checked;
setArrowStart(newVal);
// Immediate apply
onUpdateShape(selectedShape, {
label: shapeLabel,
textSize: Number(textSize) || 14,
textColor,
halo: !!halo,
arrowStart: newVal,
arrowEnd
});
}}
className="mr-2"
/>
Start
</label>
<label className="flex items-center text-sm text-gray-700 dark:text-gray-200">
<input
type="checkbox"
checked={arrowEnd}
onChange={(e) => {
const newVal = e.target.checked;
setArrowEnd(newVal);
// Immediate apply
onUpdateShape(selectedShape, {
label: shapeLabel,
textSize: Number(textSize) || 14,
textColor,
halo: !!halo,
arrowStart,
arrowEnd: newVal
});
}}
className="mr-2"
/>
End
</label>
</div>
</div>
)}

<div className="grid grid-cols-3 gap-2 items-center">
<div className="col-span-2">
<label className="block text-sm text-gray-600 dark:text-gray-300 mb-1">Text Color</label>
Expand All @@ -101,9 +173,40 @@ const ShapeProperties = ({
</div>
</div>
<label className="flex items-center text-sm text-gray-700 dark:text-gray-200">
<input type="checkbox" checked={!!halo} onChange={(e) => setHalo(e.target.checked)} className="mr-2" />
<input
type="checkbox"
checked={!!halo}
onChange={(e) => {
const newVal = e.target.checked;
setHalo(newVal);
// Immediate apply
onUpdateShape(selectedShape, {
label: shapeLabel,
textSize: Number(textSize) || 14,
textColor,
halo: newVal,
arrowStart,
arrowEnd
});
}}
className="mr-2"
/>
Text halo for contrast
</label>

<div className="pt-3 border-t border-gray-200 dark:border-gray-700 mt-4">
<button
onClick={() => {
if (onDeleteShape) {
onDeleteShape();
}
}}
className="w-full px-3 py-2 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 border border-red-200 dark:border-red-800/50 rounded-lg hover:bg-red-100 dark:hover:bg-red-900/30 transition-colors text-sm font-medium flex items-center justify-center gap-2"
>
<X className="w-4 h-4" />
Delete {displayType || 'Shape'}
</button>
</div>
</div>
</div>
);
Expand Down
Loading
Loading