реализация экспорта/импорта xml
This commit is contained in:
parent
fffca3af9a
commit
948cbfe361
798
src/App.jsx
798
src/App.jsx
@ -29,6 +29,130 @@ function App() {
|
||||
const [edgeType, setEdgeType] = useState('bool')
|
||||
const [modalData, setModalData] = useState(null)
|
||||
|
||||
function checkEdge(edge, currentNodes) {
|
||||
const sourceNode = currentNodes.find(node => node.id === edge.source)
|
||||
const targetNode = currentNodes.find(node => node.id === edge.target)
|
||||
if (!sourceNode || !targetNode) return null
|
||||
|
||||
const sourceIndex = parseInt(edge.sourceHandle.split('-')[1], 10)
|
||||
const targetIndex = parseInt(edge.targetHandle.split('-')[1], 10)
|
||||
|
||||
const sourceType = sourceNode.data.outputs[sourceIndex].replace('$/', '') || 'INT'
|
||||
const targetType = targetNode.data.inputs[targetIndex].replace('$/', '') || 'INT'
|
||||
if (edge.source == 1000000006)
|
||||
if (sourceType === targetType && edge.data.type === sourceType.toLowerCase())
|
||||
return null
|
||||
|
||||
let generalType = ''
|
||||
for (let t of sourceType.split('/'))
|
||||
if (targetType.includes(t))
|
||||
generalType += t + '/'
|
||||
if (generalType.endsWith('/'))
|
||||
generalType = generalType.slice(0, -1)
|
||||
if (!generalType)
|
||||
generalType = sourceType.split('/')[0]
|
||||
return generalType
|
||||
}
|
||||
|
||||
function updateTwoNode(edge, newType, currentNodes, currentEdges) {
|
||||
const sourceIndex = parseInt(edge.sourceHandle.split('-')[1], 10)
|
||||
const targetIndex = parseInt(edge.targetHandle.split('-')[1], 10)
|
||||
let change = false
|
||||
|
||||
const nextNodes = currentNodes.map(node => {
|
||||
if (node.id === edge.source) {
|
||||
let newInputs = [...node.data.inputs]
|
||||
let newOutputs = [...node.data.outputs]
|
||||
|
||||
if (node.data.outputs[sourceIndex]?.startsWith('$')) {
|
||||
for (let ind = 0; ind < newInputs.length; ind++)
|
||||
if (newInputs[ind].startsWith('$') && ('$/' + newType) !== newInputs[ind]) {
|
||||
newInputs[ind] = '$/' + newType
|
||||
change = true
|
||||
}
|
||||
for (let ind = 0; ind < newOutputs.length; ind++)
|
||||
if (newOutputs[ind].startsWith('$') && ('$/' + newType) !== newOutputs[ind]) {
|
||||
newOutputs[ind] = '$/' + newType
|
||||
change = true
|
||||
}
|
||||
}
|
||||
else
|
||||
newOutputs[sourceIndex] = newType
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
inputs: newInputs,
|
||||
outputs: newOutputs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (node.id === edge.target) {
|
||||
let newInputs = [...node.data.inputs]
|
||||
let newOutputs = [...node.data.outputs]
|
||||
|
||||
if (node.data.inputs[targetIndex]?.startsWith('$')) {
|
||||
for (let ind = 0; ind < newInputs.length; ind++)
|
||||
if (newInputs[ind].startsWith('$') && ('$/' + newType) !== newInputs[ind]) {
|
||||
newInputs[ind] = '$/' + newType
|
||||
change = true
|
||||
}
|
||||
for (let ind = 0; ind < newOutputs.length; ind++)
|
||||
if (newOutputs[ind].startsWith('$') && ('$/' + newType) !== newOutputs[ind]) {
|
||||
newOutputs[ind] = '$/' + newType
|
||||
change = true
|
||||
}
|
||||
}
|
||||
else
|
||||
newInputs[targetIndex] = newType
|
||||
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
inputs: newInputs,
|
||||
outputs: newOutputs
|
||||
}
|
||||
}
|
||||
}
|
||||
return node
|
||||
})
|
||||
|
||||
const nextEdges = currentEdges.map(ed => {
|
||||
if (edge.id === ed.id) {
|
||||
const currentStyle = EDGE_STYLES[newType.toLowerCase().split('/')[0]] || EDGE_STYLES['bool']
|
||||
return {
|
||||
...ed,
|
||||
style: currentStyle,
|
||||
data: {type: newType.toLowerCase().split('/')[0]}
|
||||
}
|
||||
}
|
||||
return ed
|
||||
})
|
||||
|
||||
return {nextNodes, nextEdges, change}
|
||||
}
|
||||
|
||||
function updateGraphTypes(currentEdges, currentNodes) {
|
||||
let globalChange = true
|
||||
while (globalChange) {
|
||||
globalChange = false
|
||||
|
||||
for (const edge of currentEdges) {
|
||||
const newType = checkEdge(edge, currentNodes)
|
||||
|
||||
if (newType) {
|
||||
const { nextNodes, nextEdges, change } = updateTwoNode(edge, newType, currentNodes, currentEdges)
|
||||
currentNodes = nextNodes
|
||||
currentEdges = nextEdges
|
||||
globalChange = globalChange || change
|
||||
}
|
||||
}
|
||||
}
|
||||
return {currentNodes, currentEdges}
|
||||
}
|
||||
|
||||
const onConnect = useCallback(
|
||||
(params) => {
|
||||
const sourceNode = nodes.find(node => node.id === params.source)
|
||||
@ -40,137 +164,13 @@ function App() {
|
||||
id: `e_${Date.now()}`,
|
||||
type: 'smoothstep',
|
||||
style: EDGE_STYLES[sourceType.toLowerCase().split('/')[0]] || EDGE_STYLES['bool'],
|
||||
data: { type: edgeType }
|
||||
data: { type: sourceType.toLowerCase().split('/')[0] }
|
||||
}
|
||||
|
||||
const allEdges = [...edges, newEdge]
|
||||
const { currentNodes, currentEdges } = updateGraphTypes(allEdges)
|
||||
const { currentNodes, currentEdges } = updateGraphTypes(allEdges, [...nodes])
|
||||
setNodes(currentNodes)
|
||||
setEdges(currentEdges)
|
||||
|
||||
function checkEdge(edge, currentNodes) {
|
||||
const sourceNode = currentNodes.find(node => node.id === edge.source)
|
||||
const targetNode = currentNodes.find(node => node.id === edge.target)
|
||||
if (!sourceNode || !targetNode) return null
|
||||
|
||||
const sourceIndex = parseInt(edge.sourceHandle.split('-')[1], 10)
|
||||
const targetIndex = parseInt(edge.targetHandle.split('-')[1], 10)
|
||||
|
||||
const sourceType = sourceNode.data.outputs[sourceIndex].replace('$/', '') || 'INT'
|
||||
const targetType = targetNode.data.inputs[targetIndex].replace('$/', '') || 'INT'
|
||||
if (sourceType === targetType)
|
||||
return null
|
||||
|
||||
let generalType = ''
|
||||
for (let t of sourceType.split('/'))
|
||||
if (targetType.includes(t))
|
||||
generalType += t + '/'
|
||||
if (generalType.endsWith('/'))
|
||||
generalType = generalType.slice(0, -1)
|
||||
if (!generalType)
|
||||
generalType = sourceType.split('/')[0]
|
||||
return generalType
|
||||
}
|
||||
|
||||
function updateTwoNode(edge, newType, currentNodes, currentEdges) {
|
||||
const sourceIndex = parseInt(edge.sourceHandle.split('-')[1], 10)
|
||||
const targetIndex = parseInt(edge.targetHandle.split('-')[1], 10)
|
||||
let change = false
|
||||
|
||||
const nextNodes = currentNodes.map(node => {
|
||||
if (node.id === edge.source) {
|
||||
let newInputs = [...node.data.inputs]
|
||||
let newOutputs = [...node.data.outputs]
|
||||
|
||||
if (node.data.outputs[sourceIndex]?.startsWith('$')) {
|
||||
for (let ind = 0; ind < newInputs.length; ind++)
|
||||
if (newInputs[ind].startsWith('$') && ('$/' + newType) !== newInputs[ind]) {
|
||||
newInputs[ind] = '$/' + newType
|
||||
change = true
|
||||
}
|
||||
for (let ind = 0; ind < newOutputs.length; ind++)
|
||||
if (newOutputs[ind].startsWith('$') && ('$/' + newType) !== newOutputs[ind]) {
|
||||
newOutputs[ind] = '$/' + newType
|
||||
change = true
|
||||
}
|
||||
}
|
||||
else
|
||||
newOutputs[sourceIndex] = newType
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
inputs: newInputs,
|
||||
outputs: newOutputs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (node.id === edge.target) {
|
||||
let newInputs = [...node.data.inputs]
|
||||
let newOutputs = [...node.data.outputs]
|
||||
|
||||
if (node.data.inputs[targetIndex]?.startsWith('$')) {
|
||||
for (let ind = 0; ind < newInputs.length; ind++)
|
||||
if (newInputs[ind].startsWith('$') && ('$/' + newType) !== newInputs[ind]) {
|
||||
newInputs[ind] = '$/' + newType
|
||||
change = true
|
||||
}
|
||||
for (let ind = 0; ind < newOutputs.length; ind++)
|
||||
if (newOutputs[ind].startsWith('$') && ('$/' + newType) !== newOutputs[ind]) {
|
||||
newOutputs[ind] = '$/' + newType
|
||||
change = true
|
||||
}
|
||||
}
|
||||
else
|
||||
newInputs[targetIndex] = newType
|
||||
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
inputs: newInputs,
|
||||
outputs: newOutputs
|
||||
}
|
||||
}
|
||||
}
|
||||
return node
|
||||
})
|
||||
|
||||
const nextEdges = currentEdges.map(ed => {
|
||||
if (edge.id === ed.id) {
|
||||
const currentStyle = EDGE_STYLES[newType.toLowerCase().split('/')[0]] || EDGE_STYLES['bool']
|
||||
return {
|
||||
...ed,
|
||||
style: currentStyle
|
||||
}
|
||||
}
|
||||
return ed
|
||||
})
|
||||
|
||||
return {nextNodes, nextEdges, change}
|
||||
}
|
||||
|
||||
function updateGraphTypes(currentEdges) {
|
||||
let globalChange = true
|
||||
let currentNodes = [...nodes]
|
||||
|
||||
while (globalChange) {
|
||||
globalChange = false
|
||||
|
||||
for (const edge of currentEdges) {
|
||||
const newType = checkEdge(edge, currentNodes)
|
||||
|
||||
if (newType) {
|
||||
const { nextNodes, nextEdges, change } = updateTwoNode(edge, newType, currentNodes, currentEdges)
|
||||
currentNodes = nextNodes
|
||||
currentEdges = nextEdges
|
||||
globalChange = globalChange || change
|
||||
}
|
||||
}
|
||||
}
|
||||
return {currentNodes, currentEdges}
|
||||
}
|
||||
}, [nodes, setNodes, setEdges, edgeType]
|
||||
)
|
||||
|
||||
@ -189,22 +189,23 @@ function App() {
|
||||
if (!dataStr)
|
||||
return
|
||||
|
||||
const {type, label, inputs = [], outputs = [], offsetX = 0, offsetY = 0} = JSON.parse(dataStr)
|
||||
const {type, label, width, height, inputs = [], outputs = [],
|
||||
inputsLabels = [], outputsLabels = [], offsetX = 0, offsetY = 0} = JSON.parse(dataStr)
|
||||
const position = flowInstance.screenToFlowPosition({
|
||||
x: event.clientX - offsetX,
|
||||
y: event.clientY - offsetY
|
||||
})
|
||||
|
||||
if (['const', 'switch_type', 'input', 'output'].includes(type)) {
|
||||
setModalData({ type, label, position, inputs, outputs })
|
||||
setModalData({ type, label, width, height, position, inputs, outputs, inputsLabels, outputsLabels })
|
||||
return
|
||||
}
|
||||
|
||||
const newNode = {
|
||||
id: `block_${Date.now()}`,
|
||||
id: `${Date.now()}`,
|
||||
type: 'fbdBlock',
|
||||
position,
|
||||
data: { type, label, inputs, outputs }
|
||||
data: { type, label, width, height, inputs, outputs, inputsLabels, outputsLabels }
|
||||
}
|
||||
setNodes((currentNodes) => [...currentNodes, newNode])
|
||||
}, [flowInstance, setNodes]
|
||||
@ -219,7 +220,7 @@ function App() {
|
||||
let outputs = []
|
||||
|
||||
if (modalData.type === 'const') {
|
||||
finalLabel = `${formData.value} (${formData.fromType})`
|
||||
finalLabel = `${formData.value} (${formData.toType})`
|
||||
outputs = [formData.fromType]
|
||||
}
|
||||
else if (modalData.type === 'switch_type') {
|
||||
@ -237,15 +238,19 @@ function App() {
|
||||
}
|
||||
|
||||
const newNode = {
|
||||
id: `block_${Date.now()}`,
|
||||
id: `${Date.now()}`,
|
||||
type: 'fbdBlock',
|
||||
position: modalData.position,
|
||||
data: {
|
||||
type: modalData.type,
|
||||
label: finalLabel,
|
||||
width: modalData.width,
|
||||
height: modalData.height,
|
||||
value: formData.value,
|
||||
inputs,
|
||||
outputs,
|
||||
inputsLabels: modalData.inputsLabels,
|
||||
outputsLabels: modalData.outputsLabels,
|
||||
metaFromType: formData.fromType,
|
||||
metaToType: formData.toType
|
||||
}
|
||||
@ -359,11 +364,540 @@ function App() {
|
||||
})
|
||||
}, [nodes])
|
||||
|
||||
const handleExportXml = useCallback(() => {
|
||||
if (nodes.length === 0) {
|
||||
alert('На холсте нет блоков для экспорта')
|
||||
return
|
||||
}
|
||||
|
||||
let xml = `<?xml version="1.0" encoding="utf-8"?>\n`
|
||||
xml += `<project xmlns="http://www.plcopen.org/xml/tc6_0201" `
|
||||
xml += `xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance" `
|
||||
xml += `xmlns:ns1="http://www.plcopen.org/xml/tc6_0201" `
|
||||
xml += `xmlns:xhtml="http://www.w3.org/1999/xhtml\">\n`
|
||||
|
||||
xml += ` <fileHeader companyName="University" productName="FBD Editor" productVersion="1" creationDateTime="${new Date().toISOString().split('.')[0] + 'Z'}"/>\n`
|
||||
xml += ` <contentHeader name="diagram" modificationDateTime="${new Date().toISOString().split('.')[0] + 'Z'}">\n`
|
||||
xml += ` <coordinateInfo>\n`
|
||||
xml += ` <fbd><scaling x="1" y="1"/></fbd>\n`
|
||||
xml += ` <ld><scaling x="1" y="1"/></ld>\n`
|
||||
xml += ` <sfc><scaling x="1" y="1"/></sfc>\n`
|
||||
xml += ` </coordinateInfo>\n`
|
||||
xml += ` </contentHeader>\n`
|
||||
xml += ` <types>\n`
|
||||
xml += ` <dataTypes/>\n`
|
||||
xml += ` <pous>\n`
|
||||
xml += ` <pou name="main" pouType="program">\n`
|
||||
|
||||
function getPortType(type) {
|
||||
const newType = type.toUpperCase().replace('$/', '')
|
||||
|
||||
if (!newType.includes('/'))
|
||||
return newType
|
||||
|
||||
const types = newType.split('/')
|
||||
const hasAll = (...neededTypes) => neededTypes.every(t => types.includes(t))
|
||||
|
||||
if (hasAll('BOOL', 'INT', 'DINT', 'REAL', 'TIME', 'STRING'))
|
||||
return 'ANY'
|
||||
if (hasAll('INT', 'DINT', 'REAL', 'TIME'))
|
||||
return 'ANY_MAGNITUDE'
|
||||
if (hasAll('INT', 'DINT', 'REAL'))
|
||||
return 'ANY_NUM'
|
||||
if (hasAll('INT', 'DINT'))
|
||||
return 'ANY_INT'
|
||||
return 'ANY'
|
||||
}
|
||||
|
||||
let dopInd = 1
|
||||
const nodesIndexes = new Map()
|
||||
nodes.forEach(node => {
|
||||
let counts = 0
|
||||
if ((['input', 'const'].includes(node.data.type)) && node.data.value) {
|
||||
edges.forEach(edge => {
|
||||
if (edge.source === node.id)
|
||||
counts += 1
|
||||
})
|
||||
}
|
||||
else if (node.data.type === 'output' && node.data.value) {
|
||||
edges.forEach(edge => {
|
||||
if (edge.target === node.id)
|
||||
counts += 1
|
||||
})
|
||||
}
|
||||
else
|
||||
counts += 1
|
||||
|
||||
if (counts !== 0) {
|
||||
nodesIndexes.set(node.id, [1000000000 + dopInd])
|
||||
dopInd += 1
|
||||
}
|
||||
|
||||
for (let ind = 1; ind < counts; ind++, dopInd++)
|
||||
nodesIndexes.get(node.id).push(1000000000 + dopInd)
|
||||
})
|
||||
|
||||
function getNewBlockId(id) {
|
||||
const newInd = nodesIndexes.get(id)
|
||||
if (newInd.length > 1) {
|
||||
const t = nodesIndexes.get(id).splice(0, 1)[0]
|
||||
return t
|
||||
}
|
||||
else if (newInd)
|
||||
return newInd[0]
|
||||
|
||||
dopInd += 1
|
||||
return 1000000000 + dopInd
|
||||
}
|
||||
|
||||
xml += ` <interface>\n`
|
||||
xml += ` <localVars>\n`
|
||||
|
||||
const values = new Map()
|
||||
nodes.forEach(node => {
|
||||
if ((node.data.type === 'input' || node.data.type === 'output') && node.data.value)
|
||||
values.set(node.data.value, node.data.metaFromType || 'ANY')
|
||||
})
|
||||
|
||||
values.forEach((type, value) => {
|
||||
xml += ` <variable name="${value}">\n`
|
||||
xml += ` <type><${getPortType(type)}/></type>\n`
|
||||
xml += ` </variable>\n`
|
||||
})
|
||||
|
||||
xml += ` </localVars>\n`
|
||||
xml += ` </interface>\n`
|
||||
|
||||
xml += ` <body>\n`
|
||||
xml += ` <FBD>\n\n`
|
||||
|
||||
nodes.forEach(node => {
|
||||
const newInd = nodesIndexes.get(node.id)
|
||||
if (node && node.data.type === 'input' || node.data.type === 'const') {
|
||||
for (let i = 0; i < newInd.length; i++) {
|
||||
xml += ` <inVariable localId="${newInd[i]}" width="${node.data.width}" height="${node.data.height}">\n`
|
||||
xml += ` <position x="${Math.round(node.position.x)}" y="${Math.round(node.position.y)}"/>\n`
|
||||
xml += ` <connectionPointOut/>\n`
|
||||
xml += ` <expression>${node.data.value}</expression>\n`
|
||||
xml += ` </inVariable>\n\n`
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
nodes.forEach(node => {
|
||||
const newInd = nodesIndexes.get(node.id)
|
||||
if (node.data.type === 'output') {
|
||||
for (let i = 0; i < newInd.length; i++) {
|
||||
xml += ` <outVariable localId="${newInd[i]}" width="${node.data.width}" height="${node.data.height}">\n`
|
||||
xml += ` <position x="${Math.round(node.position.x)}" y="${Math.round(node.position.y)}"/>\n`
|
||||
xml += ` <connectionPointIn>\n`
|
||||
|
||||
edges.forEach(edge => {
|
||||
if (edge.target === node.id) {
|
||||
const targetIndex = parseInt(edge.targetHandle.split('-')[1], 10)
|
||||
const targetLabel = node.data.inputsLabels[targetIndex]
|
||||
|
||||
if (targetLabel === 'IN') {
|
||||
const sourceNode = nodes.find(node => node.id === edge.source)
|
||||
if (['const', 'input'].includes(sourceNode.data.type))
|
||||
xml += ` <connection refLocalId="${getNewBlockId(edge.source)}"/>\n`
|
||||
else {
|
||||
const sourceIndex = parseInt(edge.sourceHandle.split('-')[1], 10)
|
||||
const sourceLabel = sourceNode.data.outputsLabels[sourceIndex]
|
||||
xml += ` <connection refLocalId="${getNewBlockId(edge.source)}" formalParameter="${sourceLabel}"/>\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
xml += ` </connectionPointIn>\n`
|
||||
xml += ` <expression>${node.data.value}</expression>\n`
|
||||
xml += ` </outVariable>\n\n`
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
nodes.forEach(node => {
|
||||
if (!['output', 'input', 'const'].includes(node.data.type)) {
|
||||
xml += ` <block localId="${getNewBlockId(node.id)}" typeName="${node.data.type === 'switch_type' ? node.data.label.toUpperCase() : node.data.type.toUpperCase()}" width="${node.data.width}" height="${node.data.height}">\n`
|
||||
xml += ` <position x="${Math.round(node.position.x)}" y="${Math.round(node.position.y)}"/>\n`
|
||||
xml += ` <inputVariables>\n`
|
||||
for (const portType of node.data.inputsLabels) {
|
||||
xml += ` <variable formalParameter="${portType}">\n`
|
||||
xml += ` <connectionPointIn>\n`
|
||||
edges.forEach(edge => {
|
||||
if (edge.target === node.id) {
|
||||
const targetIndex = parseInt(edge.targetHandle.split('-')[1], 10)
|
||||
const targetLabel = node.data.inputsLabels[targetIndex]
|
||||
|
||||
if (targetLabel === portType) {
|
||||
const sourceNode = nodes.find(node => node.id === edge.source)
|
||||
if (['const', 'input'].includes(sourceNode.data.type))
|
||||
xml += ` <connection refLocalId="${getNewBlockId(edge.source)}"/>\n`
|
||||
else {
|
||||
const sourceIndex = parseInt(edge.sourceHandle.split('-')[1], 10)
|
||||
const sourceLabel = sourceNode.data.outputsLabels[sourceIndex]
|
||||
xml += ` <connection refLocalId="${getNewBlockId(edge.source)}" formalParameter="${sourceLabel}"/>\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
xml += ` </connectionPointIn>\n`
|
||||
xml += ` </variable>\n`
|
||||
}
|
||||
xml += ` </inputVariables>\n`
|
||||
xml += ` <inOutVariables/>\n`
|
||||
xml += ` <outputVariables>\n`
|
||||
for (const portType of node.data.outputsLabels) {
|
||||
xml += ` <variable formalParameter="${portType}">\n`
|
||||
xml += ` <connectionPointOut/>\n`
|
||||
xml += ` </variable>\n`
|
||||
}
|
||||
xml += ` </outputVariables>\n`
|
||||
xml += ` </block>\n\n`
|
||||
}
|
||||
})
|
||||
|
||||
xml += ` </FBD>\n`
|
||||
xml += ` </body>\n`
|
||||
xml += ` </pou>\n`
|
||||
xml += ` </pous>\n`
|
||||
xml += ` </types>\n`
|
||||
xml += ` <instances>\n`
|
||||
xml += ` <configurations/>\n`
|
||||
xml += ` </instances>\n`
|
||||
xml += `</project>`
|
||||
|
||||
console.log(xml)
|
||||
|
||||
const file = new Blob([xml], {type: 'application/xml'})
|
||||
const link = document.createElement('a')
|
||||
link.download = 'fbd-program.xml'
|
||||
link.href = URL.createObjectURL(file)
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
}, [nodes, edges])
|
||||
|
||||
const handleImportXml = useCallback((event) => {
|
||||
const file = event.target.files[0]
|
||||
if (!file)
|
||||
return
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
const xmlText = e.target.result
|
||||
const parser = new DOMParser()
|
||||
const xml = parser.parseFromString(xmlText, 'application/xml')
|
||||
if (xml.querySelector('parsererror')) {
|
||||
alert('Ошибка импорта (невалидный XML-код)')
|
||||
return
|
||||
}
|
||||
|
||||
const importedNodes = []
|
||||
const importedEdges = []
|
||||
const xmlIdToReactId = new Map()
|
||||
const interfaceVars = new Map() // name: { dataType, initialValue, varGroup }
|
||||
|
||||
const xmlVariables = xml.querySelectorAll('interface * > variable')
|
||||
xmlVariables.forEach(v => {
|
||||
const name = v.getAttribute('name')
|
||||
const varType = v.parentElement.tagName.toLowerCase()
|
||||
const type = v.querySelector('type *')
|
||||
const value = v.querySelector('initialValue simpleValue')?.getAttribute('value') || ''
|
||||
if (name && type)
|
||||
interfaceVars.set(name, {
|
||||
dataType: type.tagName.toUpperCase(),
|
||||
value,
|
||||
varType
|
||||
})
|
||||
})
|
||||
|
||||
function restoreType(recivedType) {
|
||||
const type = recivedType.toUpperCase()
|
||||
if (type === 'ANY') return 'BOOl/INT/DINT/REAL/TIME/STRING'
|
||||
if (type === 'ANY_MAGNITUDE') return 'INT/DINT/REAL/TIME'
|
||||
if (type === 'ANY_NUM') return 'INT/DINT/REAL'
|
||||
if (type === 'ANY_INT') return 'INT/DINT'
|
||||
return type
|
||||
}
|
||||
|
||||
const xmlInVariables = xml.querySelectorAll('inVariable')
|
||||
xmlInVariables.forEach(inVar => {
|
||||
const xmlId = inVar.getAttribute('localId')
|
||||
const width = parseFloat(inVar.getAttribute('width') || '80')
|
||||
const height = parseFloat(inVar.getAttribute('height') || '40')
|
||||
const pos = inVar.querySelector('position')
|
||||
const x = parseFloat(pos?.getAttribute('x') || '0')
|
||||
const y = parseFloat(pos?.getAttribute('y') || '0')
|
||||
const value = inVar.querySelector('expression')?.textContent || ''
|
||||
|
||||
function isItConst(recivedValue) {
|
||||
const value = recivedValue.toLowerCase().trim()
|
||||
const timeRegex = /^(t|time)#(-)?\d+(d|h|m|s|ms)(\d+(d|h|m|s|ms))*/
|
||||
const realRegex = /^-?\d+\.\d+$/
|
||||
const intRegex = /^-?\d+$/
|
||||
|
||||
if (value === 'true' || value === 'false')
|
||||
return 'BOOL'
|
||||
if (timeRegex.test(value))
|
||||
return 'TIME'
|
||||
if (value.startsWith("'") && value.endsWith("'"))
|
||||
return 'STRING'
|
||||
if (realRegex.test(value))
|
||||
return 'REAL'
|
||||
if (intRegex.test(value)) {
|
||||
const num = Number(value)
|
||||
if (num >= -32768 && num <= 32767)
|
||||
return 'INT'
|
||||
if (num >= -2147483648 && num <= 2147483647)
|
||||
return 'DINT'
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
let isConst = isItConst(value) ? true : false
|
||||
let varInfo = interfaceVars.get(value)
|
||||
const outputPortType = varInfo?.dataType || isItConst(value) || 'ANY'
|
||||
|
||||
if (varInfo?.varType === 'tempvars')
|
||||
isConst = true
|
||||
|
||||
const finalType = isConst ? 'const' : 'input'
|
||||
let finalValue = value
|
||||
if (varInfo?.varType === 'tempvars' && varInfo.value)
|
||||
finalValue = varInfo.value
|
||||
|
||||
let existingNode = importedNodes.find(node => node.position.x === x && node.position.y === y && node.data.value === finalValue)
|
||||
if (!existingNode) {
|
||||
const newId = `${xmlId}`
|
||||
const label = finalType === 'const' ? `${finalValue} (${outputPortType})` : `${finalValue} (${outputPortType})`
|
||||
existingNode = {
|
||||
id: newId,
|
||||
type: 'fbdBlock',
|
||||
position: {x, y},
|
||||
data: {
|
||||
type: finalType, label,
|
||||
value: finalValue,
|
||||
width, height,
|
||||
inputs: [],
|
||||
outputs: [restoreType(outputPortType)],
|
||||
inputsLabels: [],
|
||||
outputsLabels: ['OUT']
|
||||
}
|
||||
}
|
||||
importedNodes.push(existingNode)
|
||||
}
|
||||
xmlIdToReactId.set(xmlId, {reactId: existingNode.id, portIndex: 0, handleType: 'out'})
|
||||
})
|
||||
|
||||
const xmlOutVariables = xml.querySelectorAll('outVariable')
|
||||
xmlOutVariables.forEach(outVar => {
|
||||
const xmlId = outVar.getAttribute('localId')
|
||||
const width = parseFloat(outVar.getAttribute('width') || '80')
|
||||
const height = parseFloat(outVar.getAttribute('height') || '40')
|
||||
const pos = outVar.querySelector('position')
|
||||
const x = parseFloat(pos?.getAttribute('x') || '0')
|
||||
const y = parseFloat(pos?.getAttribute('y') || '0')
|
||||
const value = outVar.querySelector('expression')?.textContent || ''
|
||||
let varInfo = interfaceVars.get(value)
|
||||
|
||||
const dataType = varInfo?.dataType || 'ANY'
|
||||
let existingNode = importedNodes.find(node => node.position.x === x && node.position.y === y && node.data.value === value)
|
||||
if (!existingNode) {
|
||||
existingNode = {
|
||||
id: xmlId,
|
||||
type: 'fbdBlock',
|
||||
position: {x, y},
|
||||
data: {
|
||||
type: 'output',
|
||||
label: `${value} (${dataType})`,
|
||||
value,
|
||||
width, height,
|
||||
inputs: [restoreType(dataType)],
|
||||
outputs: [],
|
||||
inputsLabels: ['IN'],
|
||||
outputsLabels: []
|
||||
}
|
||||
}
|
||||
importedNodes.push(existingNode)
|
||||
}
|
||||
xmlIdToReactId.set(xmlId, {reactId: existingNode.id, portIndex: 0, handleType: 'in'})
|
||||
|
||||
const connection = outVar.querySelector('connectionPointIn connection')
|
||||
if (connection) {
|
||||
const sourceNode = connection.getAttribute('refLocalId')
|
||||
const sourcePortLabel = connection.getAttribute('formalParameter')
|
||||
importedEdges.push({
|
||||
xmlSourceNode: sourceNode,
|
||||
xmlSourcePortLabel: sourcePortLabel,
|
||||
reactTargetId: existingNode.id,
|
||||
reactTargetPort: 'in-0'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const libraryBlocks = {
|
||||
and: { label: 'AND', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
or: { label: 'OR', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
not: { label: 'NOT', inputs: ['BOOL'], outputs: ['BOOL'], inputsLabels: ['IN'], outputsLabels: ['OUT'] },
|
||||
xor: { label: 'XOR', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
add: { label: 'ADD', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
sub: { label: 'SUB', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
mul: { label: 'MUL', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
div: { label: 'DIV', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
ton: { label: 'TON', inputs: ['BOOL', 'TIME'], outputs: ['BOOL', 'TIME'], inputsLabels: ['IN', 'PT'], outputsLabels: ['Q', 'ET'] },
|
||||
tof: { label: 'TOF', inputs: ['BOOL', 'TIME'], outputs: ['BOOL', 'TIME'], inputsLabels: ['IN', 'PT'], outputsLabels: ['Q', 'ET'] },
|
||||
tp: { label: 'TP', inputs: ['BOOL', 'TIME'], outputs: ['BOOL', 'TIME'], inputsLabels: ['IN', 'PT'], outputsLabels: ['Q', 'ET'] },
|
||||
ctu: { label: 'CTU', inputs: ['BOOL', 'BOOL', '$/INT/DINT'], outputs: ['BOOL', '$/INT/DINT'], inputsLabels: ['CU', 'R', 'PV'], outputsLabels: ['Q', 'CV'] },
|
||||
ctd: { label: 'CTD', inputs: ['BOOL', 'BOOL', '$/INT/DINT'], outputs: ['BOOL', '$/INT/DINT'], inputsLabels: ['CD', 'LD', 'PV'], outputsLabels: ['Q', 'CV'] },
|
||||
ctud: { label: 'CTUD', inputs: ['BOOL', 'BOOL', 'BOOL', 'BOOL', '$/INT/DINT'], outputs: ['BOOL', 'BOOL', '$/INT/DINT'], inputsLabels: ['CU', 'CD', 'R', 'LD', 'PV'], outputsLabels: ['QU', 'QD', 'CV'] },
|
||||
gt: { label: '>', inputs: ['$/INT/DINT/REAL/TIME', '$/INT/DINT/REAL/TIME'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
lt: { label: '<', inputs: ['$/INT/DINT/REAL/TIME', '$/INT/DINT/REAL/TIME'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
eq: { label: '=', inputs: ['$/BOOl/INT/DINT/REAL/TIME/STRING', '$/BOOl/INT/DINT/REAL/TIME/STRING'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
sr: { label: 'SR', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['S1', 'R'], outputsLabels: ['Q1'] },
|
||||
rs: { label: 'RS', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['S', 'R1'], outputsLabels: ['Q'] },
|
||||
move: { label: 'MOVE', inputs: ['$/BOOl/INT/DINT/REAL/TIME/STRING'], outputs: ['$/BOOl/INT/DINT/REAL/TIME/STRING'], inputsLabels: ['IN'], outputsLabels: ['OUT'] },
|
||||
switch_type: { label: '*_TO_**', inputs: ['BOOl/INT/DINT/REAL/TIME/STRING'], outputs: ['BOOl/INT/DINT/REAL/TIME/STRING'], inputsLabels: ['IN'], outputsLabels: ['OUT'] }
|
||||
}
|
||||
|
||||
const xmlBlocks = xml.querySelectorAll('FBD > block')
|
||||
xmlBlocks.forEach(block => {
|
||||
const xmlId = block.getAttribute('localId')
|
||||
const xmlType = block.getAttribute('typeName')
|
||||
const pos = block.querySelector('position')
|
||||
const x = parseFloat(pos?.getAttribute('x') || '0')
|
||||
const y = parseFloat(pos?.getAttribute('y') || '0')
|
||||
const label = block.querySelector('content')?.textContent || libraryBlocks[xmlType.toLowerCase()]?.label || xmlType
|
||||
const value = block.querySelector('additionalData')?.getAttribute('value') || ''
|
||||
let inputsLabels = Array.from(block.querySelectorAll('inputVariables variable')).map(v => v.getAttribute('formalParameter'))
|
||||
let inputs = Array.from(block.querySelectorAll('inputVariables variable')).map(v => restoreType(v.getAttribute('dataType') || 'ANY'))
|
||||
let outputsLabels = Array.from(block.querySelectorAll('outputVariables variable')).map(v => v.getAttribute('formalParameter'))
|
||||
let outputs = Array.from(block.querySelectorAll('outputVariables variable')).map(v => restoreType(v.getAttribute('dataType') || 'ANY'))
|
||||
|
||||
if (inputsLabels.length === libraryBlocks[xmlType.toLowerCase()]?.inputsLabels.length)
|
||||
inputsLabels = libraryBlocks[xmlType.toLowerCase()].inputsLabels
|
||||
if (outputsLabels.length === libraryBlocks[xmlType.toLowerCase()]?.outputsLabels.length)
|
||||
outputsLabels = libraryBlocks[xmlType.toLowerCase()].outputsLabels
|
||||
if (inputs.length === libraryBlocks[xmlType.toLowerCase()]?.inputs.length)
|
||||
inputs = libraryBlocks[xmlType.toLowerCase()].inputs
|
||||
if (outputs.length === libraryBlocks[xmlType.toLowerCase()]?.outputs.length)
|
||||
outputs = libraryBlocks[xmlType.toLowerCase()].outputs
|
||||
|
||||
const isSwitchType = xmlType.includes('_TO_')
|
||||
const finalType = isSwitchType ? 'switch_type' : xmlType.toLowerCase()
|
||||
const width = parseFloat(block.getAttribute('width') || (isSwitchType ? '80' : 50 + (Math.max(input.length, output.length, 2) - 1) * 34))
|
||||
const height = parseFloat(block.getAttribute('height') || (isSwitchType ? '40' : '60'))
|
||||
const newNode = {
|
||||
id: xmlId,
|
||||
type: 'fbdBlock',
|
||||
position: {x, y},
|
||||
data: { type: finalType, label, value, width, height, inputs, outputs, inputsLabels, outputsLabels}
|
||||
}
|
||||
importedNodes.push(newNode)
|
||||
xmlIdToReactId.set(xmlId, { reactId: xmlId, inputsLabels, outputsLabels })
|
||||
|
||||
const variable = block.querySelectorAll('inputVariables variable')
|
||||
variable.forEach((v, inputIndex) => {
|
||||
const connection = v.querySelector('connectionPointIn connection')
|
||||
if (connection) {
|
||||
const sourceNode = connection.getAttribute('refLocalId')
|
||||
const sourcePortLabel = connection.getAttribute('formalParameter')
|
||||
importedEdges.push({
|
||||
xmlSourceNode: sourceNode,
|
||||
xmlSourcePortLabel: sourcePortLabel,
|
||||
reactTargetId: xmlId,
|
||||
reactTargetPort: `in-${inputIndex}`
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const finalEdges = importedEdges.map((edgeData, index) => {
|
||||
const sourceMeta = xmlIdToReactId.get(edgeData.xmlSourceNode)
|
||||
if (!sourceMeta)
|
||||
return null
|
||||
let sourceHandle = 'out-0'
|
||||
if (sourceMeta.outputsLabels) {
|
||||
const outIndex = sourceMeta.outputsLabels.indexOf(edgeData.xmlSourcePortLabel)
|
||||
sourceHandle = `out-${outIndex >= 0 ? outIndex : 0}`
|
||||
}
|
||||
const sourceNode = importedNodes.find(node => node.id === sourceMeta.reactId)
|
||||
const sourceIndex = parseInt(sourceHandle.split('-')[1], 10)
|
||||
const sourceType = sourceNode?.data?.outputs[sourceIndex] || 'ANY'
|
||||
|
||||
return {
|
||||
id: `e_${Date.now()}_${index}`,
|
||||
source: sourceMeta.reactId,
|
||||
target: edgeData.reactTargetId,
|
||||
sourceHandle: sourceHandle,
|
||||
targetHandle: edgeData.reactTargetPort,
|
||||
type: 'smoothstep',
|
||||
style: EDGE_STYLES[sourceType.toLowerCase().split('/')[0]] || EDGE_STYLES['bool'],
|
||||
data: {type: sourceType.toLowerCase().split('/')[0]}
|
||||
}
|
||||
}).filter(Boolean)
|
||||
|
||||
const finalNodes = importedNodes.map(node => {
|
||||
if (['add', 'sub', 'mul', 'div', 'move'].includes(node.data.type))
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
inputs: node.data.inputs.map(i => '$/' + i),
|
||||
outputs: node.data.outputs.map(i => '$/' + i)
|
||||
}
|
||||
}
|
||||
else if (['ctu', 'ctd', 'ctud'].includes(node.data.type))
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
inputs: node.data.inputs.map((i, ind) => {
|
||||
if (node.data.inputsLabels.toLowerCase() === 'pv')
|
||||
return '$/' + i
|
||||
else return i
|
||||
}),
|
||||
outputs: node.data.outputs.map((i, ind) => {
|
||||
if (node.data.inputsLabels.toLowerCase() === 'cv')
|
||||
return '$/' + i
|
||||
else return i
|
||||
})
|
||||
}
|
||||
}
|
||||
else if (['gt', 'lt', 'eq'].includes(node.data.type))
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
inputs: node.data.inputs.map(i => '$/' + i)
|
||||
}
|
||||
}
|
||||
else return node
|
||||
})
|
||||
|
||||
try {
|
||||
const { currentNodes, currentEdges } = updateGraphTypes(finalEdges, finalNodes)
|
||||
setNodes(currentNodes)
|
||||
setEdges(currentEdges)
|
||||
console.log(currentNodes)
|
||||
console.log(currentEdges)
|
||||
}
|
||||
catch {
|
||||
alert('Произошла ошибка при обработке соединений')
|
||||
setEdges([])
|
||||
setNodes([finalNodes])
|
||||
}
|
||||
}
|
||||
reader.readAsText(file)
|
||||
event.target.value = ''
|
||||
}, [nodes, edges])
|
||||
|
||||
return(
|
||||
<div className="App">
|
||||
<ControlPanel
|
||||
onClear = {handleClear}
|
||||
onExportSvg = {handleExportSvg}
|
||||
onExportXml = {handleExportXml}
|
||||
onImportXml = {handleImportXml}
|
||||
/>
|
||||
<div className = "main-container">
|
||||
<Toolbar/>
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
import React from 'react';
|
||||
import React, {useRef} from 'react';
|
||||
|
||||
function ControlPanel({ onClear, onExportSvg, onExportXml, onImportXml }) {
|
||||
const fileInputRef = useRef(null)
|
||||
|
||||
const handleImportButtonClick = () => {
|
||||
if (fileInputRef.current)
|
||||
fileInputRef.current.click();
|
||||
};
|
||||
|
||||
function ControlPanel({ onClear, onExportSvg }) {
|
||||
return (
|
||||
<div className = "controlPanel">
|
||||
<button
|
||||
@ -9,6 +16,25 @@ function ControlPanel({ onClear, onExportSvg }) {
|
||||
>
|
||||
Экспорт в SVG
|
||||
</button>
|
||||
<button
|
||||
onClick = {onExportXml}
|
||||
className = "control-button svg-export-button"
|
||||
>
|
||||
Экспорт в XML
|
||||
</button>
|
||||
<button
|
||||
onClick = {handleImportButtonClick}
|
||||
className = "control-button svg-export-button"
|
||||
>
|
||||
Импорт XML
|
||||
</button>
|
||||
<input
|
||||
type = "file"
|
||||
ref = {fileInputRef}
|
||||
onChange = {onImportXml}
|
||||
accept = ".xml"
|
||||
style = {{ display: 'none' }}
|
||||
/>
|
||||
<button
|
||||
onClick={onClear}
|
||||
className = 'control-button clear-button'
|
||||
|
||||
@ -58,7 +58,12 @@ function ModalData({ modalData, onClose, onSave }) {
|
||||
alert('Не удалось получить тип переменной (не корректный ввод)')
|
||||
return
|
||||
}
|
||||
setToType(getedType)
|
||||
onSave({
|
||||
value: trimmed,
|
||||
fromType: getedType,
|
||||
toType: getedType
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
onSave({
|
||||
|
||||
@ -14,49 +14,39 @@ const NAME_CATEGORIES = {
|
||||
|
||||
const BLOCK_CATEGORIES = {
|
||||
logical: [
|
||||
{ type: 'and', label: 'AND', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'] },
|
||||
{ type: 'or', label: 'OR', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'] },
|
||||
{ type: 'not', label: 'NOT', inputs: ['BOOL'], outputs: ['BOOL'] },
|
||||
{ type: 'xor', label: 'XOR', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'] }],
|
||||
{ type: 'and', label: 'AND', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
{ type: 'or', label: 'OR', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
{ type: 'not', label: 'NOT', inputs: ['BOOL'], outputs: ['BOOL'], inputsLabels: ['IN'], outputsLabels: ['OUT'] },
|
||||
{ type: 'xor', label: 'XOR', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] }],
|
||||
arithmetic: [
|
||||
{ type: 'add', label: 'ADD', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'] },
|
||||
{ type: 'sub', label: 'SUB', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'] },
|
||||
{ type: 'mul', label: 'MUL', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'] },
|
||||
{ type: 'div', label: 'DIV', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'] }],
|
||||
{ type: 'add', label: 'ADD', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
{ type: 'sub', label: 'SUB', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
{ type: 'mul', label: 'MUL', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
{ type: 'div', label: 'DIV', inputs: ['$/INT/DINT/REAL', '$/INT/DINT/REAL'], outputs: ['$/INT/DINT/REAL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] }],
|
||||
timers: [
|
||||
{ type: 'ton', label: 'TON', inputs: ['BOOL', 'TIME'], outputs: ['BOOL', 'TIME'] },
|
||||
{ type: 'tof', label: 'TOF', inputs: ['BOOL', 'TIME'], outputs: ['BOOL', 'TIME'] },
|
||||
{ type: 'tp', label: 'TP', inputs: ['BOOL', 'TIME'], outputs: ['BOOL', 'TIME'] }],
|
||||
{ type: 'ton', label: 'TON', inputs: ['BOOL', 'TIME'], outputs: ['BOOL', 'TIME'], inputsLabels: ['IN', 'PT'], outputsLabels: ['Q', 'ET'] },
|
||||
{ type: 'tof', label: 'TOF', inputs: ['BOOL', 'TIME'], outputs: ['BOOL', 'TIME'], inputsLabels: ['IN', 'PT'], outputsLabels: ['Q', 'ET'] },
|
||||
{ type: 'tp', label: 'TP', inputs: ['BOOL', 'TIME'], outputs: ['BOOL', 'TIME'], inputsLabels: ['IN', 'PT'], outputsLabels: ['Q', 'ET'] }],
|
||||
counters: [
|
||||
{ type: 'ctu', label: 'CTU', inputs: ['BOOL', 'BOOL', '$/INT/DINT'], outputs: ['BOOL', '$/INT/DINT'] },
|
||||
{ type: 'ctd', label: 'CTD', inputs: ['BOOL', 'BOOL', '$/INT/DINT'], outputs: ['BOOL', '$/INT/DINT'] },
|
||||
{ type: 'ctud', label: 'CTUD', inputs: ['BOOL', 'BOOL', 'BOOL', 'BOOL', '$/INT/DINT'], outputs: ['BOOL', 'BOOL', '$/INT/DINT'] }],
|
||||
{ type: 'ctu', label: 'CTU', inputs: ['BOOL', 'BOOL', '$/INT/DINT'], outputs: ['BOOL', '$/INT/DINT'], inputsLabels: ['CU', 'R', 'PV'], outputsLabels: ['Q', 'CV'] },
|
||||
{ type: 'ctd', label: 'CTD', inputs: ['BOOL', 'BOOL', '$/INT/DINT'], outputs: ['BOOL', '$/INT/DINT'], inputsLabels: ['CD', 'LD', 'PV'], outputsLabels: ['Q', 'CV'] },
|
||||
{ type: 'ctud', label: 'CTUD', inputs: ['BOOL', 'BOOL', 'BOOL', 'BOOL', '$/INT/DINT'], outputs: ['BOOL', 'BOOL', '$/INT/DINT'], inputsLabels: ['CU', 'CD', 'R', 'LD', 'PV'], outputsLabels: ['QU', 'QD', 'CV'] }],
|
||||
comparisons: [
|
||||
{ type: 'gt', label: '>', inputs: ['$/INT/DINT/REAL/TIME', '$/INT/DINT/REAL/TIME'], outputs: ['BOOL'] },
|
||||
{ type: 'lt', label: '<', inputs: ['$/INT/DINT/REAL/TIME', '$/INT/DINT/REAL/TIME'], outputs: ['BOOL'] },
|
||||
{ type: 'eq', label: '=', inputs: ['$/BOOl/INT/DINT/REAL/TIME/STRING', '$/BOOl/INT/DINT/REAL/TIME/STRING'], outputs: ['BOOL'] }],
|
||||
{ type: 'gt', label: '>', inputs: ['$/INT/DINT/REAL/TIME', '$/INT/DINT/REAL/TIME'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
{ type: 'lt', label: '<', inputs: ['$/INT/DINT/REAL/TIME', '$/INT/DINT/REAL/TIME'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] },
|
||||
{ type: 'eq', label: '=', inputs: ['$/BOOl/INT/DINT/REAL/TIME/STRING', '$/BOOl/INT/DINT/REAL/TIME/STRING'], outputs: ['BOOL'], inputsLabels: ['IN1', 'IN2'], outputsLabels: ['OUT'] }],
|
||||
other: [
|
||||
{ type: 'sr', label: 'SR', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'] },
|
||||
{ type: 'rs', label: 'RS', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'] },
|
||||
{ type: 'move', label: 'MOVE', inputs: ['$/BOOl/INT/DINT/REAL/TIME/STRING'], outputs: ['$/BOOl/INT/DINT/REAL/TIME/STRING'] }],
|
||||
{ type: 'sr', label: 'SR', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['S1', 'R'], outputsLabels: ['Q1'] },
|
||||
{ type: 'rs', label: 'RS', inputs: ['BOOL', 'BOOL'], outputs: ['BOOL'], inputsLabels: ['S', 'R1'], outputsLabels: ['Q'] },
|
||||
{ type: 'move', label: 'MOVE', inputs: ['$/BOOl/INT/DINT/REAL/TIME/STRING'], outputs: ['$/BOOl/INT/DINT/REAL/TIME/STRING'], inputsLabels: ['IN'], outputsLabels: ['OUT'] }],
|
||||
data: [
|
||||
{ type: 'input', label: 'input', inputs: [], outputs: ['BOOl/INT/DINT/REAL/TIME/STRING'] },
|
||||
{ type: 'output', label: 'output', inputs: ['BOOl/INT/DINT/REAL/TIME/STRING'], outputs: [] },
|
||||
{ type: 'const', label: 'const', inputs: [], outputs: ['BOOl/INT/DINT/REAL/TIME/STRING'] },
|
||||
{ type: 'switch_type', label: '*_TO_**', inputs: ['BOOl/INT/DINT/REAL/TIME/STRING'], outputs: ['BOOl/INT/DINT/REAL/TIME/STRING'] }
|
||||
{ type: 'input', label: 'input', inputs: [], outputs: ['BOOl/INT/DINT/REAL/TIME/STRING'], inputsLabels: [], outputsLabels: ['OUT'] },
|
||||
{ type: 'output', label: 'output', inputs: ['BOOl/INT/DINT/REAL/TIME/STRING'], outputs: [], inputsLabels: ['IN'], outputsLabels: [] },
|
||||
{ type: 'const', label: 'const', inputs: [], outputs: ['BOOl/INT/DINT/REAL/TIME/STRING'], inputsLabels: [], outputsLabels: ['OUT'] },
|
||||
{ type: 'switch_type', label: '*_TO_**', inputs: ['BOOl/INT/DINT/REAL/TIME/STRING'], outputs: ['BOOl/INT/DINT/REAL/TIME/STRING'], inputsLabels: ['IN'], outputsLabels: ['OUT'] }
|
||||
]
|
||||
// customized: []
|
||||
}
|
||||
|
||||
const LINES_TYPES = [
|
||||
{ type: 'bool', stroke: '#000', dasharray: '0' },
|
||||
{ type: 'a', stroke: '#000', dasharray: '4 4' }, // Пунктир
|
||||
{ type: 'b', stroke: '#D8000A', dasharray: '0' }, // Красная
|
||||
{ type: 'c', stroke: '#000', dasharray: '1 3' }, // Точечная
|
||||
{ type: 'd', stroke: '#008000', dasharray: '0' }, // Зеленая
|
||||
{ type: 'e', stroke: '#1717D8', dasharray: '0' } // Синяя
|
||||
];
|
||||
|
||||
const BLOCK_SCALE = 1
|
||||
|
||||
function Toolbar() {
|
||||
@ -87,11 +77,26 @@ function Toolbar() {
|
||||
const offsetX = event.nativeEvent.offsetX * BLOCK_SCALE
|
||||
const offsetY = event.nativeEvent.offsetY * BLOCK_SCALE
|
||||
|
||||
const inputCount = block.inputs.length
|
||||
const outputCount = block.outputs.length
|
||||
let countForHeight = Math.max(inputCount, outputCount, 2)
|
||||
let blockHeight = 50 + (countForHeight - 1) * 34
|
||||
let blockWidth = 60
|
||||
if (['input', 'output', 'const', 'switch_type'].includes(block.type)) {
|
||||
countForHeight = Math.max(inputCount, outputCount)
|
||||
blockHeight = 40 + (countForHeight - 1) * 34
|
||||
blockWidth = blockHeight * 2
|
||||
}
|
||||
|
||||
event.dataTransfer.setData('application/reactflow', JSON.stringify({
|
||||
type: block.type,
|
||||
label: block.label,
|
||||
width: blockWidth,
|
||||
height: blockHeight,
|
||||
inputs: block.inputs,
|
||||
outputs: block.outputs,
|
||||
inputsLabels: block.inputsLabels,
|
||||
outputsLabels: block.outputsLabels,
|
||||
offsetX,
|
||||
offsetY
|
||||
}))
|
||||
|
||||
@ -11,6 +11,8 @@ export const ToolbarBlock = memo(({ block, onDragStart, scale = 1 }) => {
|
||||
blockHeight = (40 + (countForHeight - 1) * 34) / scale
|
||||
blockWidth = blockHeight * 2
|
||||
}
|
||||
block.width = blockWidth
|
||||
block.height = blockHeight
|
||||
|
||||
function getCoords(count) {
|
||||
const coords = []
|
||||
|
||||
Loading…
Reference in New Issue
Block a user