import { RefObject, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { Line, Circle, Group, Text, Rect } from "react-konva"; import { minMax, toRGBColorString, dragBoundFunc, flattenPoints, } from "@/utils/canvasUtil"; import type { KonvaEventObject } from "konva/lib/Node"; import Konva from "konva"; import { Vector2d } from "konva/lib/types"; type PolygonDrawerProps = { stageRef: RefObject; points: number[][]; distances: number[]; isActive: boolean; isHovered: boolean; isFinished: boolean; color: number[]; handlePointDragMove: (e: KonvaEventObject) => void; handleGroupDragEnd: (e: KonvaEventObject) => void; activeLine?: number; snapToLines: (point: number[]) => number[] | null; snapPoints: boolean; }; export default function PolygonDrawer({ stageRef, points, distances, isActive, isHovered, isFinished, color, handlePointDragMove, handleGroupDragEnd, activeLine, snapToLines, snapPoints, }: PolygonDrawerProps) { const vertexRadius = 6; const flattenedPoints = useMemo(() => flattenPoints(points), [points]); const [minMaxX, setMinMaxX] = useState([0, 0]); const [minMaxY, setMinMaxY] = useState([0, 0]); const groupRef = useRef(null); const [cursor, setCursor] = useState("default"); const handleMouseOverPoint = ( e: KonvaEventObject, ) => { if (!e.target) return; if (!isFinished && points.length >= 3 && e.target.name() === "point-0") { e.target.scale({ x: 2, y: 2 }); setCursor("crosshair"); } else { setCursor("move"); } }; const handleMouseOutPoint = ( e: KonvaEventObject, ) => { if (!e.target) return; if (isFinished) { setCursor("default"); } else { setCursor("crosshair"); } if (e.target.name() === "point-0") { e.target.scale({ x: 1, y: 1 }); } }; const handleGroupDragStart = () => { const arrX = points.map((p) => p[0]); const arrY = points.map((p) => p[1]); setMinMaxX(minMax(arrX)); setMinMaxY(minMax(arrY)); }; const groupDragBound = (pos: Vector2d) => { if (!stageRef.current) { return pos; } let { x, y } = pos; const sw = stageRef.current.width(); const sh = stageRef.current.height(); if (minMaxY[0] + y < 0) y = -1 * minMaxY[0]; if (minMaxX[0] + x < 0) x = -1 * minMaxX[0]; if (minMaxY[1] + y > sh) y = sh - minMaxY[1]; if (minMaxX[1] + x > sw) x = sw - minMaxX[1]; return { x, y }; }; const colorString = useCallback( (darkened: boolean) => { return toRGBColorString(color, darkened); }, [color], ); useEffect(() => { if (!stageRef.current) { return; } stageRef.current.container().style.cursor = cursor; }, [stageRef, cursor]); // Calculate midpoints for distance labels based on sorted points const midpoints = useMemo(() => { const midpointsArray = []; for (let i = 0; i < points.length; i++) { const p1 = points[i]; const p2 = points[(i + 1) % points.length]; const midpointX = (p1[0] + p2[0]) / 2; const midpointY = (p1[1] + p2[1]) / 2; midpointsArray.push([midpointX, midpointY]); } return midpointsArray; }, [points]); // Determine the points for the active line const activeLinePoints = useMemo(() => { if ( activeLine === undefined || activeLine < 1 || activeLine > points.length ) { return []; } const p1 = points[activeLine - 1]; const p2 = points[activeLine % points.length]; return [p1[0], p1[1], p2[0], p2[1]]; }, [activeLine, points]); return ( isActive ? isFinished ? setCursor("move") : setCursor("crosshair") : setCursor("default") } onMouseOut={() => isActive ? isFinished ? setCursor("default") : setCursor("crosshair") : setCursor("default") } /> {isFinished && isActive && ( setCursor("crosshair")} onMouseOut={() => isFinished ? setCursor("default") : setCursor("crosshair") } /> )} {isActive && activeLinePoints.length > 0 && ( )} {points.map((point, index) => { if (!isActive) { return; } const x = point[0]; const y = point[1]; return ( { if (isActive) { if (snapPoints) { const snappedPos = snapToLines([e.target.x(), e.target.y()]); if (snappedPos) { e.target.position({ x: snappedPos[0], y: snappedPos[1] }); } } handlePointDragMove(e); } }} dragBoundFunc={(pos) => { if (stageRef.current) { const boundPos = dragBoundFunc( stageRef.current.width(), stageRef.current.height(), vertexRadius, pos, ); if (snapPoints) { const snappedPos = snapToLines([boundPos.x, boundPos.y]); return snappedPos ? { x: snappedPos[0], y: snappedPos[1] } : boundPos; } return boundPos; } else { return pos; } }} /> ); })} {isFinished && ( {midpoints.map((midpoint, index) => { const [x, y] = midpoint; const distance = distances[index]; if (distance === undefined) return null; const squareSize = 22; return ( ); })} )} ); }