'use client' /** * AnnotationLayer * * SVG overlay component for displaying and creating annotations on documents. * Renders positioned rectangles with color-coding by annotation type. */ import { useState, useRef, useCallback } from 'react' import type { Annotation, AnnotationType, AnnotationPosition } from '../types' import { ANNOTATION_COLORS } from '../types' interface AnnotationLayerProps { annotations: Annotation[] selectedTool: AnnotationType | null onCreateAnnotation: (position: AnnotationPosition, type: AnnotationType) => void onSelectAnnotation: (annotation: Annotation) => void selectedAnnotationId?: string disabled?: boolean } export default function AnnotationLayer({ annotations, selectedTool, onCreateAnnotation, onSelectAnnotation, selectedAnnotationId, disabled = false, }: AnnotationLayerProps) { const svgRef = useRef(null) const [isDrawing, setIsDrawing] = useState(false) const [startPos, setStartPos] = useState<{ x: number; y: number } | null>(null) const [currentRect, setCurrentRect] = useState(null) // Convert mouse position to percentage const getPercentPosition = useCallback((e: React.MouseEvent) => { if (!svgRef.current) return null const rect = svgRef.current.getBoundingClientRect() const x = ((e.clientX - rect.left) / rect.width) * 100 const y = ((e.clientY - rect.top) / rect.height) * 100 return { x: Math.max(0, Math.min(100, x)), y: Math.max(0, Math.min(100, y)) } }, []) // Handle mouse down - start drawing const handleMouseDown = useCallback( (e: React.MouseEvent) => { if (disabled || !selectedTool) return const pos = getPercentPosition(e) if (!pos) return setIsDrawing(true) setStartPos(pos) setCurrentRect({ x: pos.x, y: pos.y, width: 0, height: 0 }) }, [disabled, selectedTool, getPercentPosition] ) // Handle mouse move - update rectangle const handleMouseMove = useCallback( (e: React.MouseEvent) => { if (!isDrawing || !startPos) return const pos = getPercentPosition(e) if (!pos) return const x = Math.min(startPos.x, pos.x) const y = Math.min(startPos.y, pos.y) const width = Math.abs(pos.x - startPos.x) const height = Math.abs(pos.y - startPos.y) setCurrentRect({ x, y, width, height }) }, [isDrawing, startPos, getPercentPosition] ) // Handle mouse up - finish drawing const handleMouseUp = useCallback(() => { if (!isDrawing || !currentRect || !selectedTool) { setIsDrawing(false) setStartPos(null) setCurrentRect(null) return } // Only create annotation if rectangle is large enough (min 1% x 0.5%) if (currentRect.width > 1 && currentRect.height > 0.5) { onCreateAnnotation(currentRect, selectedTool) } setIsDrawing(false) setStartPos(null) setCurrentRect(null) }, [isDrawing, currentRect, selectedTool, onCreateAnnotation]) // Handle clicking on existing annotation const handleAnnotationClick = useCallback( (e: React.MouseEvent, annotation: Annotation) => { e.stopPropagation() onSelectAnnotation(annotation) }, [onSelectAnnotation] ) return ( {/* SVG Defs for patterns */} {/* Wavy pattern for Rechtschreibung errors */} {/* Straight underline pattern for Grammatik errors */} {/* Existing annotations */} {annotations.map((annotation) => { const isSelected = annotation.id === selectedAnnotationId const color = ANNOTATION_COLORS[annotation.type] || '#6b7280' const isRS = annotation.type === 'rechtschreibung' const isGram = annotation.type === 'grammatik' return ( handleAnnotationClick(e, annotation)}> {/* Background rectangle - different styles for RS/Gram */} {isRS || isGram ? ( <> {/* Light highlight background */} {/* Underline - wavy for RS, straight for Gram */} {/* Border when selected */} {isSelected && ( )} ) : ( /* Standard rectangle for other annotation types */ )} {/* Type indicator icon (small circle in corner) */} {/* Type letter */} {annotation.type.charAt(0).toUpperCase()} {/* Severity indicator (small dot) */} {annotation.severity === 'critical' && ( )} {/* Selection indicator */} {isSelected && ( <> {/* Corner handles */} {[ { cx: annotation.position.x, cy: annotation.position.y }, { cx: annotation.position.x + annotation.position.width, cy: annotation.position.y }, { cx: annotation.position.x, cy: annotation.position.y + annotation.position.height }, { cx: annotation.position.x + annotation.position.width, cy: annotation.position.y + annotation.position.height, }, ].map((corner, i) => ( ))} )} ) })} {/* Currently drawing rectangle */} {currentRect && selectedTool && ( )} ) }