import React, { useRef, useState, useEffect, memo } from 'react'; import { useDrop, useDrag } from 'react-dnd'; import Connection from './Connection'; import DraggableBlock from './DraggableBlock'; const Canvas = ({ blocks = [], connections = [], activePort = null, addConnection, addBlock, moveBlock, removeBlock, onPortClick }) => { const blocksOnCanvas = blocks || []; const connectionsOnCanvas = connections || []; const [zoom, setZoom] = useState(1); const [pan, setPan] = useState({x: 0, y: 0}); const [panStart, setPanStart] = useState({x: 0, y: 0}); const [isPanning, setIsPanning] = useState(false); const canvasRef = useRef(null); const [{isOver, monitor}, drop] = useDrop(() => ({ accept: ['block', 'newBlock'], hover: (item, monitor) => { const offset = monitor.getClientOffset(); if (!offset || !canvasRef.current) return; const svgRect = canvasRef.current.getBoundingClientRect(); //координаты мыши const mouseOnCanvasX = offset.x - svgRect.x; const mouseOnCanvasY = offset.y - svgRect.y; //координаты на canvas const mouseX = (mouseOnCanvasX - pan.x) / zoom; const mouseY = (mouseOnCanvasY - pan.y) / zoom; //смещение const blockWidth = 68; const blockHeight = 84; const x = mouseX - blockWidth / 3; const y = mouseY - blockHeight / 3; if (item.id && !item.isNew) moveBlock(item.id, x, y); else { if (!item.tempId) { const newId = Date.now(); item.tempId = newId; item.isNew = false; addBlock(item, newId, x, y); } else moveBlock(item.tempId, x, y); } }, drop: (item) => { item.tempId = null; }, collect: (monitor) => ({ isOver: monitor.isOver(), monitor: monitor, }), }), [moveBlock, addBlock, pan, zoom]); useEffect(() => { //удаление блока при выходе за пределы холста const draggingItem = monitor?.getItem(); if (!isOver && draggingItem?.tempId) { removeBlock(draggingItem.tempId); draggingItem.tempId = null; } }, [isOver, monitor, removeBlock]); const handleWheel = (e) => { e.preventDefault(); const rect = canvasRef.current.getBoundingClientRect(); const mouseOnCanvasX = e.clientX - rect.x; const mouseOnCanvasY = e.clientY - rect.y; const mouseX = (mouseOnCanvasX - pan.x) / zoom; const mouseY = (mouseOnCanvasY - pan.y) / zoom; const stepZoom = 0.075; const prewZoom = zoom + (e.deltaY > 0 ? -stepZoom : stepZoom); const newZoom = Math.max(0.6, Math.min(1.7, prewZoom)); const newPanX = mouseOnCanvasX - mouseX * newZoom; const newPanY = mouseOnCanvasY - mouseY * newZoom; setZoom(newZoom); setPan({x: newPanX, y: newPanY}); }; const handleMouseDown = (e) => { if (e.target.closest('.block-container') || e.target.closest('circle')) return; if (e.button === 0) { setIsPanning(true); setPanStart({ x: e.clientX - pan.x, y: e.clientY - pan.y }); } }; const handleMouseMove = (e) => { if (isPanning) setPan({ x: e.clientX - panStart.x, y: e.clientY - panStart.y }); }; const handleMouseUp = () => { setIsPanning(false); }; useEffect(() => { const handleGlobalMouseUp = () => setIsPanning(false); window.addEventListener('mouseup', handleGlobalMouseUp); return() => window.removeEventListener('mouseup', handleGlobalMouseUp); }, []); return(
{ if (node) { canvasRef.current = node; drop(node); } }} className = "canvas" onWheel = {handleWheel} onMouseDown = {handleMouseDown} onMouseMove = {handleMouseMove} onMouseUp = {handleMouseUp} > {/* Сетка */} {// } {/* Блоки и соединения */} {connectionsOnCanvas.map((connection, index) => ( ))} {// } {blocksOnCanvas.map((block) => ( ))}
); }; export default memo(Canvas);