import { useEffect, useRef } from "react"; import { Stage, Layer, Transformer, Rect } from "react-konva"; import Konva from "konva"; import { useEditorStore } from "../store/editorStore"; import { useDrawingHandlers } from "./canvas/useDrawingHandlers"; import { useStageSize } from "./canvas/useStageSize"; import { useKeyboardMovement } from "./canvas/useKeyboardMovement"; import ObjectRenderer from "./canvas/ObjectRenderer"; import { getMaskForObject } from "./canvas/maskUtils"; import CellEditor from "@/components/CellEditor"; import ZoomControls from "./ZoomControls"; const EditorCanvas = () => { const stageRef = useRef(null); const layerRef = useRef(null); const { tool, objects, updateObject, selectedObjectId, setSelectedObjectId, setSelectedTableId, selectedCellId, setSelectedCellId, editingCell, setEditingCell, updateCellValue, applyTableResize, setStageRef, setLayerRef, zoom, } = useEditorStore(); const { transformerRef, handleStageMouseDown, handleStageMouseMove, handleStageMouseUp } = useDrawingHandlers(); const stageSize = useStageSize(); useKeyboardMovement(); useEffect(() => { if (stageRef.current) { setStageRef(stageRef); } if (layerRef.current) { setLayerRef(layerRef); } }, [setStageRef, setLayerRef]); const handleSelect = (id: string, node: Konva.Node) => { const obj = objects.find((o) => o.id === id); setSelectedObjectId(id); if (obj?.type === "grid") { setSelectedTableId(id); } else { setSelectedTableId(null); } setSelectedCellId(null); if (transformerRef.current) { transformerRef.current.nodes([node]); transformerRef.current.getLayer()?.batchDraw(); } }; const handleCellClick = (tableId: string, cellId: string) => { setSelectedTableId(tableId); setSelectedObjectId(tableId); setSelectedCellId(cellId); const obj = objects.find((o) => o.id === tableId); if (obj && transformerRef.current && layerRef.current) { const tableNode = layerRef.current.findOne(`#${tableId}`); if (tableNode) { transformerRef.current.nodes([tableNode]); transformerRef.current.getLayer()?.batchDraw(); } } }; const handleCellDblClick = (tableId: string, cellId: string, x: number, y: number, width: number, height: number, text: string) => { setEditingCell({ tableId, cellId, x, y, width, height, value: text }); }; const handleCellEditorSave = (text: string) => { if (editingCell) { updateCellValue(editingCell.tableId, editingCell.cellId, text); setEditingCell(null); } }; const handleCellEditorCancel = () => { setEditingCell(null); }; // Attach transformer to selected object when it changes useEffect(() => { if (!transformerRef.current || !selectedObjectId || !layerRef.current) return; const selectedObject = objects.find((obj) => obj.id === selectedObjectId); if (!selectedObject) return; // For grid objects, handle separately if (selectedObject.type === "grid") { const tableNode = layerRef.current.findOne(`#${selectedObjectId}`); if (tableNode && transformerRef.current) { transformerRef.current.nodes([tableNode]); transformerRef.current.getLayer()?.batchDraw(); } return; } // For other objects (like images), find the node by id and attach transformer // Use a small delay to ensure the node is mounted (especially for images that need to load) const timeoutId = setTimeout(() => { if (!layerRef.current || !transformerRef.current) return; // Check if object is masked - if so, find parent Group const maskShape = getMaskForObject(selectedObject, objects); let node: Konva.Node | undefined; if (maskShape) { node = layerRef.current.findOne(`.masked-group-${selectedObjectId}`); } else { node = layerRef.current.findOne(`#${selectedObjectId}`); } if (node && transformerRef.current) { transformerRef.current.nodes([node]); transformerRef.current.getLayer()?.batchDraw(); } }, 100); return () => clearTimeout(timeoutId); }, [selectedObjectId, objects, transformerRef, layerRef]); useEffect(() => { if (!transformerRef.current || !selectedObjectId) return; const selectedObject = objects.find((obj) => obj.id === selectedObjectId); if (selectedObject?.type !== "grid") return; const transformer = transformerRef.current; const handleTransformEnd = () => { const node = transformer.nodes()[0]; if (!node || !selectedObject) return; const scaleX = node.scaleX(); const scaleY = node.scaleY(); node.scaleX(1); node.scaleY(1); const newWidth = (selectedObject.width || 300) * scaleX; const newHeight = (selectedObject.height || 200) * scaleY; applyTableResize(selectedObject.id, newWidth, newHeight); updateObject(selectedObject.id, { x: node.x(), y: node.y(), }); }; transformer.on("transformend", handleTransformEnd); return () => { transformer.off("transformend", handleTransformEnd); }; }, [selectedObjectId, objects, transformerRef, applyTableResize, updateObject]); const finalScale = stageSize.scale * zoom; return (
{objects.map((obj) => { const maskShape = getMaskForObject(obj, objects); return ( ); })} {selectedObjectId && (() => { const selectedObject = objects.find(obj => obj.id === selectedObjectId); const isText = selectedObject?.type === "text"; return ( { if (Math.abs(newBox.width) < 5 || Math.abs(newBox.height) < 5) { return oldBox; } return newBox; }} ignoreStroke={isText} perfectDrawEnabled={false} anchorFill="#3b82f6" anchorStroke="#ffffff" borderStroke="#3b82f6" borderStrokeWidth={2} anchorSize={8} rotateEnabled={true} enabledAnchors={[ "top-left", "top-right", "bottom-left", "bottom-right", "top-center", "bottom-center", "middle-left", "middle-right", ]} /> ); })()}
{editingCell && ( )}
); }; export default EditorCanvas;