154 lines
7.5 KiB
Markdown
154 lines
7.5 KiB
Markdown
|
|
# Phase 3: Drawing Tools — React Wiring & State Management
|
|||
|
|
|
|||
|
|
## Goal
|
|||
|
|
Wire Phase 1 canvas/interaction engines to React state so drawing tools actually create, render, and manage elements. Implement undo/redo, command-line shortcuts, and drawing flow improvements.
|
|||
|
|
|
|||
|
|
## Requirements Covered
|
|||
|
|
- F-CAD-01: Zeichnen von Grundelementen (Linie, Kreis, Bogen, Rechteck, Polygon, Polylinie)
|
|||
|
|
- F-CAD-03: Bemaßung
|
|||
|
|
- F-CAD-05: Command Line (L, C, PL, R, A, T, DIM shortcuts)
|
|||
|
|
- F-CAD-09: Undo/Redo mit History
|
|||
|
|
- F-CAD-11: Text
|
|||
|
|
|
|||
|
|
## Current State
|
|||
|
|
- `src/interaction/index.ts` (591 lines): InteractionEngine with drawing logic for all types, callbacks (onElementCreated, onElementsDeleted, onToolStateChanged, onCursorMoved, onCommandTrigger)
|
|||
|
|
- `src/canvas/RenderEngine.ts` (588 lines): Renders all element types
|
|||
|
|
- `src/components/CanvasArea.tsx` (103 lines): Creates engine instances in useEffect but does NOT wire callbacks to React state
|
|||
|
|
- `src/App.tsx` (223 lines): Has mock state but no element management, no engine ref, no undo/redo
|
|||
|
|
|
|||
|
|
## Tasks
|
|||
|
|
|
|||
|
|
### Task 1: Refactor CanvasArea to expose engine instances
|
|||
|
|
File: `src/components/CanvasArea.tsx`
|
|||
|
|
|
|||
|
|
Current: engines created in useEffect, no external access.
|
|||
|
|
|
|||
|
|
Required:
|
|||
|
|
- Use `useRef` to hold engine instances (InteractionEngine, RenderEngine, ZoomPanController, etc.)
|
|||
|
|
- Accept new props: `onElementCreated`, `onElementsDeleted`, `onCursorMoved`, `onToolStateChanged`, `onToolChange` (tool from App → engine), `elements` (element array → engine.setElements), `snapEnabled`, `orthoEnabled`, `gridEnabled` (from App → engine state)
|
|||
|
|
- In useEffect: create engines, wire all callbacks to props, call `interaction.setCallbacks({...})`
|
|||
|
|
- Add a separate useEffect for `elements` changes → `interaction.setElements(elements)` + `spatialIndex.bulkInsert(elements)` + `renderEngine.render()`
|
|||
|
|
- Add a separate useEffect for `snapEnabled/orthoEnabled/gridEnabled` → update engine state + renderEngine options
|
|||
|
|
- Add a separate useEffect for `activeTool` → `interaction.setTool(tool)`
|
|||
|
|
- Expose zoomPan via ref callback prop `onZoomPanReady` so App can call zoomFit
|
|||
|
|
- Add `onZoomIn`, `onZoomOut`, `onZoomFit` to actually call zoomPan methods (not just log)
|
|||
|
|
- Add double-click handler: on dblclick, if drawing polyline/polygon → call confirmDraw via interaction
|
|||
|
|
|
|||
|
|
New CanvasAreaProps (add to `src/types/ui.types.ts`):
|
|||
|
|
```typescript
|
|||
|
|
export interface CanvasAreaProps {
|
|||
|
|
cursorPos: CursorPos;
|
|||
|
|
viewMode: ViewMode;
|
|||
|
|
onViewChange: (mode: ViewMode) => void;
|
|||
|
|
gridEnabled: boolean;
|
|||
|
|
orthoEnabled: boolean;
|
|||
|
|
snapEnabled: boolean;
|
|||
|
|
activeTool: string;
|
|||
|
|
elements: CADElement[];
|
|||
|
|
onElementCreated: (el: CADElement) => void;
|
|||
|
|
onElementsDeleted: (ids: string[]) => void;
|
|||
|
|
onCursorMoved: (x: number, y: number) => void;
|
|||
|
|
onToolStateChanged: (state: ToolState) => void;
|
|||
|
|
onToggleGrid: () => void;
|
|||
|
|
onToggleOrtho: () => void;
|
|||
|
|
onToggleSnap: () => void;
|
|||
|
|
onZoomIn: () => void;
|
|||
|
|
onZoomOut: () => void;
|
|||
|
|
onZoomFit: () => void;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Task 2: Implement undo/redo stack in App.tsx
|
|||
|
|
File: `src/App.tsx`
|
|||
|
|
|
|||
|
|
Required:
|
|||
|
|
- Add `elements` state: `useState<CADElement[]>([])`
|
|||
|
|
- Add `undoStack` and `redoStack` as `useState<CADElement[][]>`
|
|||
|
|
- `handleElementCreated`: push current elements to undoStack, add new element, clear redoStack
|
|||
|
|
- `handleElementsDeleted`: push current elements to undoStack, remove deleted, clear redoStack
|
|||
|
|
- `handleUndo`: if undoStack not empty, push current to redoStack, pop undoStack → set elements
|
|||
|
|
- `handleRedo`: if redoStack not empty, push current to undoStack, pop redoStack → set elements
|
|||
|
|
- Pass `elements` to CanvasArea
|
|||
|
|
- Pass `onElementCreated`, `onElementsDeleted` to CanvasArea
|
|||
|
|
- Wire `onCursorMoved` to update `cursorPos` state
|
|||
|
|
- Wire `onToolStateChanged` to update any relevant UI state
|
|||
|
|
|
|||
|
|
### Task 3: Wire tool selection from LeftSidebar to CanvasArea
|
|||
|
|
File: `src/App.tsx`
|
|||
|
|
|
|||
|
|
Required:
|
|||
|
|
- `activeTool` state already exists in App.tsx
|
|||
|
|
- Pass `activeTool` to CanvasArea as prop
|
|||
|
|
- CanvasArea useEffect watches `activeTool` → calls `interaction.setTool(tool as ToolType)`
|
|||
|
|
- LeftSidebar `onToolChange` already updates `activeTool` in App
|
|||
|
|
|
|||
|
|
### Task 4: Command line shortcuts (F-CAD-05)
|
|||
|
|
File: `src/App.tsx`
|
|||
|
|
|
|||
|
|
Required:
|
|||
|
|
- In `handleCommand(cmd)`, parse command and map to tool:
|
|||
|
|
- `L` or `LINE` → setTool('line')
|
|||
|
|
- `C` or `CIRCLE` → setTool('circle')
|
|||
|
|
- `A` or `ARC` → setTool('arc')
|
|||
|
|
- `R` or `RECT` or `RECTANGLE` → setTool('rect')
|
|||
|
|
- `PL` or `POLYLINE` → setTool('polyline')
|
|||
|
|
- `POL` or `POLYGON` → setTool('polygon')
|
|||
|
|
- `T` or `TEXT` → setTool('text')
|
|||
|
|
- `DIM` or `DIMENSION` → setTool('dimension')
|
|||
|
|
- `V` or `SELECT` → setTool('select')
|
|||
|
|
- `P` or `PAN` → setTool('pan')
|
|||
|
|
- `M` or `MOVE` → setTool('move')
|
|||
|
|
- `CO` or `COPY` → setTool('copy')
|
|||
|
|
- `RO` or `ROTATE` → setTool('rotate')
|
|||
|
|
- `SC` or `SCALE` → setTool('scale')
|
|||
|
|
- `MI` or `MIRROR` → setTool('mirror')
|
|||
|
|
- `DEL` or `ERASE` → setTool('delete')
|
|||
|
|
- `UNDO` or `U` → handleUndo()
|
|||
|
|
- `REDO` → handleRedo()
|
|||
|
|
- Add command to history with prefix '›'
|
|||
|
|
- Add response to history with prefix '·' (e.g. 'Linie-Werkzeug aktiv · Klicken zum Starten')
|
|||
|
|
|
|||
|
|
### Task 5: Drawing flow improvements
|
|||
|
|
File: `src/interaction/index.ts`
|
|||
|
|
|
|||
|
|
Required:
|
|||
|
|
- **Polyline/Polygon**: Add `handleDoubleClick` method — if activeTool is polyline/polygon and phase is drawing, call confirmDraw()
|
|||
|
|
- Wire dblclick event in `attach()`: `this.addListener('dblclick', this.onDoubleClick.bind(this))`
|
|||
|
|
- **Arc**: Improve 3-point arc — first click = center, second click = start point (sets radius + startAngle), third click = end point (sets endAngle). Update handleDrawDown to handle multi-point arc.
|
|||
|
|
- **Text**: After placing text element, trigger an inline edit prompt. For now, use a simple `prompt()` dialog or add `onTextEdit` callback.
|
|||
|
|
|
|||
|
|
### Task 6: Grid/Snap/Ortho toggle wiring
|
|||
|
|
File: `src/components/CanvasArea.tsx`
|
|||
|
|
|
|||
|
|
Required:
|
|||
|
|
- When `gridEnabled` prop changes: `renderEngine.setOptions({ showGrid: gridEnabled })` + re-render
|
|||
|
|
- When `snapEnabled` prop changes: `interaction.state.snapEnabled = snapEnabled` + `renderEngine.setOptions({ showSnapPoints: snapEnabled })` + re-render
|
|||
|
|
- When `orthoEnabled` prop changes: `interaction.state.ortho = orthoEnabled` + `renderEngine.setOptions({ showOrtho: orthoEnabled })` + re-render
|
|||
|
|
|
|||
|
|
## Constraints
|
|||
|
|
- TypeScript functional components, no inline styles (except where mockup uses style)
|
|||
|
|
- No styled-components, no Tailwind
|
|||
|
|
- `npx tsc --noEmit` must pass with 0 errors
|
|||
|
|
- `npx vite build` must succeed
|
|||
|
|
- Minimal changes — extend existing files, don't rewrite
|
|||
|
|
- Keep all existing CSS class names
|
|||
|
|
- Import ToolState type from interaction/index.ts where needed
|
|||
|
|
|
|||
|
|
## File Summary
|
|||
|
|
1. `src/types/ui.types.ts` — update CanvasAreaProps
|
|||
|
|
2. `src/components/CanvasArea.tsx` — expose engines, wire callbacks, add useEffects
|
|||
|
|
3. `src/App.tsx` — element state, undo/redo, command shortcuts, wire all callbacks
|
|||
|
|
4. `src/interaction/index.ts` — double-click handler, arc 3-point, text edit callback
|
|||
|
|
|
|||
|
|
## Test Criteria
|
|||
|
|
1. `npx tsc --noEmit` — 0 errors
|
|||
|
|
2. `npx vite build` — success
|
|||
|
|
3. Drawing a line: select Line tool → click start → move mouse → Enter → element appears
|
|||
|
|
4. Drawing a rect: select Rect tool → click corner → move mouse → Enter → element appears
|
|||
|
|
5. Drawing a circle: select Circle tool → click center → move mouse → Enter → element appears
|
|||
|
|
6. Polyline: select Polyline → click multiple points → double-click → element appears
|
|||
|
|
7. Command line: type 'L' → line tool activates
|
|||
|
|
8. Undo/Redo: draw element → Ctrl+Z → element disappears → Ctrl+Y → element reappears
|
|||
|
|
9. Grid toggle: click grid button → grid appears/disappears
|
|||
|
|
10. Snap toggle: click snap button → snap points appear/disappear on hover
|