feat: implement Phase 3 line selection with visual feedback
This commit is contained in:
parent
c3292b4f6c
commit
91c516999d
3 changed files with 56 additions and 12 deletions
|
|
@ -103,7 +103,7 @@ const renderCursorCircle = () => {
|
|||
|
||||
---
|
||||
|
||||
### Phase 3: Line Selection
|
||||
### Phase 3: Line Selection ✅ DONE
|
||||
|
||||
#### 3.1 Selection State
|
||||
**File**: `src/components/SvgOverlay.tsx`
|
||||
|
|
|
|||
|
|
@ -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
1
tsconfig.tsbuildinfo
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue