fbd-editor/src/App.jsx

168 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(
<div className="App">
<ControlPanel
onClear = {handleClear}
onExportSvg = {handleExportSvg}
/>
<div className = "main-container">
<Toolbar
activeEdgeType = {edgeType}
onChangeEdgeType = {setEdgeType}
/>
<div className = "main-canvas-container">
<div className="canvas-container">
<ReactFlow
nodes = {nodes}
edges = {edges}
onNodesChange = {onNodesChange}
onEdgesChange = {onEdgesChange}
onConnect = {onConnect}
nodeTypes = {nodeTypes}
onInit = {setFlowInstance}
onDrop = {onDrop}
onDragOver = {onDragOver}
//fitView
>
<Controls/>
<Background variant = {BackgroundVariant.Lines} color = "#eee" gap = {10}/>
</ReactFlow>
</div>
</div>
</div>
</div>
)
}
export default App