import React, { useState, useCallback } from 'react' import { ReactFlow, Controls, Background, useNodesState, useEdgesState, addEdge, BackgroundVariant, getNodesBounds } from '@xyflow/react' import { toSvg } from 'html-to-image' import '@xyflow/react/dist/style.css' import './App.css' import Toolbar from './components/Toolbar'; import ControlPanel from './components/ControlPanel'; import CustomBlock from './components/CustomBlock'; const nodeTypes = { fbdBlock: CustomBlock } const EDGE_STYLES = { bool: { stroke: '#000', strokeWidth: 2 }, a: { stroke: '#000', strokeWidth: 2, strokeDasharray: '4 4' }, // Пунктир b: { stroke: '#D8000A', strokeWidth: 2 }, // Красная c: { stroke: '#000', strokeWidth: 2, strokeDasharray: '1 3' }, // Точечная d: { stroke: '#008000', strokeWidth: 2 }, // Зеленая e: { stroke: '#1717D8', strokeWidth: 2 } // Синяя }; function App() { const [nodes, setNodes, onNodesChange] = useNodesState([]) const [edges, setEdges, onEdgesChange] = useEdgesState([]) const [flowInstance, setFlowInstance] = useState(null) const [edgeType, setEdgeType] = useState('bool') const onConnect = useCallback( (params) => setEdges((edge) => { const currentStyle = EDGE_STYLES[edgeType] || EDGE_STYLES['bool'] return addEdge({ ...params, type: 'smoothstep', style: currentStyle, data: {type: edgeType} }, edge) }), [setEdges, edgeType] ) const onDragOver = useCallback((event) => { event.preventDefault() event.dataTransfer.dropEffect = 'move' }, []) const onDrop = useCallback( (event) => { event.preventDefault() if (!flowInstance) return const dataStr = event.dataTransfer.getData('application/reactflow') if (!dataStr) return const {type, label, inputCount, outputCount} = JSON.parse(dataStr) const position = flowInstance.screenToFlowPosition({ x: event.clientX, y: event.clientY }) const newNode = { id: `block_${Date.now()}`, type: 'fbdBlock', position, data: {type, label, inputCount, outputCount} } setNodes((currentNodes) => [...currentNodes, newNode]) }, [flowInstance, setNodes] ) function handleClear() { if ((nodes.length > 0 || edges.length > 0) && window.confirm("Очистить холст?")) { setNodes([]) setEdges([]) } } const handleExportSvg = useCallback(() => { const reactFlowViewport = document.querySelector('.react-flow__viewport') if (!reactFlowViewport || !nodes.length > 0) { alert('Не удалось найти холст для экспорта') return } const bounds = getNodesBounds(nodes) const padding = 30 const widthExportWindow = bounds.width + padding * 2 const heightExportWindow = bounds.height + padding * 2 toSvg(reactFlowViewport, { backgroundColor: 'white', width: widthExportWindow, height: heightExportWindow, style: { width: `${widthExportWindow}px`, height: `${heightExportWindow}px`, transform: `translate(${-bounds.x + padding}px, ${-bounds.y + padding}px) scale(1)`, transformOrigin: 'top left' } }).then(async (dataUrl) => { if ('showSaveFilePicker' in window) { try { const handle = await window.showSaveFilePicker({ suggestedName: 'fbd-scheme.svg', types: [{ description: 'Векторная диаграмма SVG', accept: {'image/svg+xml': ['.svg']} }] }) const writable = await handle.createWritable() const scheme = await fetch(dataUrl) const file = await scheme.blob() await writable.write(file) await writable.close() } catch (err) { console.log('Пользователь отменил сохранение или произошла ошибка: ', err) } } else { const link = document.createElement('a') link.download = 'fbd-scheme.svg' link.href = dataUrl link.click() } }).catch((error) => { console.error('Ошибка генерации SVG-файла: ', error) alert('Не удалось сгененировать SVG-файл') }) }, [nodes]) return(
) } export default App