feat: implement Phase 3 line selection with visual feedback

This commit is contained in:
Marko Djordjevic 2026-02-12 14:24:11 +01:00
parent c3292b4f6c
commit 91c516999d
3 changed files with 56 additions and 12 deletions

View file

@ -103,7 +103,7 @@ const renderCursorCircle = () => {
---
### Phase 3: Line Selection
### Phase 3: Line Selection ✅ DONE
#### 3.1 Selection State
**File**: `src/components/SvgOverlay.tsx`

View file

@ -42,6 +42,7 @@ export default function SvgOverlay({
const [annotations, setAnnotations] = useState<Annotation[]>([]);
const [drawingLine, setDrawingLine] = useState<{ start: Point; current: Point } | null>(null);
const [mousePosition, setMousePosition] = useState<{ x: number; y: number } | null>(null);
const [selectedLineId, setSelectedLineId] = useState<number | null>(null);
// Fetch annotations
const fetchAnnotations = async () => {
@ -158,11 +159,40 @@ export default function SvgOverlay({
// Line drawing mode
if (activeTool === 'line') {
if (!drawingLine) {
// First click - start line
setDrawingLine({
start: dataPoint,
current: dataPoint,
});
// First click - check if clicking near existing line to select it
const lineAnnotations = annotations.filter((a) => a.label_type === 'line' && a.geometry);
let lineSelected = false;
for (const annotation of lineAnnotations) {
if (!annotation.geometry) continue;
const start = dataToPixel(
annotation.geometry.startTime!,
annotation.geometry.startPrice!
);
const end = dataToPixel(annotation.geometry.endTime!, annotation.geometry.endPrice!);
if (!start || !end) continue;
// Calculate distance from point to line segment
const distance = distanceToLineSegment({ x, y }, start, end);
if (distance < 10) {
// Within 10 pixels - select this line
setSelectedLineId(annotation.id);
lineSelected = true;
break;
}
}
// If no line was selected, start drawing a new line
if (!lineSelected) {
setDrawingLine({
start: dataPoint,
current: dataPoint,
});
setSelectedLineId(null); // Clear selection when starting new line
}
} else {
// Second click - save line
try {
@ -269,17 +299,27 @@ export default function SvgOverlay({
return Math.sqrt(dx * dx + dy * dy);
};
// Handle Escape key to cancel line drawing
// Handle Escape key to cancel line drawing and deselect
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape' && drawingLine) {
setDrawingLine(null);
if (e.key === 'Escape') {
if (drawingLine) {
setDrawingLine(null);
}
if (selectedLineId !== null) {
setSelectedLineId(null);
}
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [drawingLine]);
}, [drawingLine, selectedLineId]);
// Deselect line when tool changes
useEffect(() => {
setSelectedLineId(null);
}, [activeTool]);
// Render line annotations
const renderLines = () => {
@ -296,6 +336,8 @@ export default function SvgOverlay({
if (!start || !end) return null;
const isSelected = annotation.id === selectedLineId;
return (
<line
key={annotation.id}
@ -304,8 +346,9 @@ export default function SvgOverlay({
x2={end.x}
y2={end.y}
stroke={annotation.color || '#3b82f6'}
strokeWidth="2"
style={{ cursor: activeTool === 'delete' ? 'pointer' : 'default' }}
strokeWidth={isSelected ? 3 : 2}
opacity={isSelected ? 1 : 0.85}
style={{ cursor: activeTool === 'delete' ? 'pointer' : activeTool === 'line' ? 'pointer' : 'default' }}
/>
);
});

1
tsconfig.tsbuildinfo Normal file

File diff suppressed because one or more lines are too long