diff --git a/src/App.css b/src/App.css index 49e5a2b..6d77015 100644 --- a/src/App.css +++ b/src/App.css @@ -97,7 +97,7 @@ html, body, #root { .toolbarBlocks { width: 100%; - max-height: calc((100% - 58px) / 3 * 2); + max-height: calc(100% - 58px); display: grid; grid-template-columns: 1fr 1fr; gap: 12px; @@ -122,12 +122,28 @@ html, body, #root { } .custom-fbd-block .block-title { - padding-top: 6px; - font-size: 14px; + box-sizing: border-box; + font-size: 12px; + text-align: right; + padding-top: 10px; + padding-right: 7px; + pointer-events: none; +} + +.custom-fbd-block .block-inputs-title { + box-sizing: border-box; + font-size: 10px; text-align: left; pointer-events: none; } +.custom-fbd-block .block-outputs-title { + box-sizing: border-box; + font-size: 10px; + text-align: right; + pointer-events: none; +} + .toolbarLines svg { background-color: white; @@ -188,9 +204,25 @@ html, body, #root { } .toolbar-block .preview-title { + box-sizing: border-box; font-size: 12px; - text-align: left; + text-align: right; padding-top: 10px; + padding-right: 7px; + pointer-events: none; +} + +.toolbar-block .preview-inputs-title { + box-sizing: border-box; + font-size: 10px; + text-align: left; + pointer-events: none; +} + +.toolbar-block .preview-outputs-title { + box-sizing: border-box; + font-size: 10px; + text-align: right; pointer-events: none; } @@ -234,4 +266,27 @@ html, body, #root { .clear-button:hover { background-color: #a80007; +} + +.modalDataBack { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modalDataMain { + background: white; + padding: 20px; + border-radius: 8px; + width: 320px; + text-align: left; + font-family: Arial, sans-serif; + box-shadow: 0 4px 15px rgba(0,0,0,0.3); } \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index 94617f3..87239f4 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -6,6 +6,7 @@ import './App.css' import Toolbar from './components/Toolbar'; import ControlPanel from './components/ControlPanel'; import CustomBlock from './components/CustomBlock'; +import ModalData from './components/ModalData' const nodeTypes = { fbdBlock: CustomBlock @@ -13,29 +14,164 @@ const nodeTypes = { 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 } // Синяя -}; + int: { stroke: '#040089', strokeWidth: 3 }, + dint: { stroke: '#771061', strokeWidth: 3 }, + real: { stroke: '#EB3B13', strokeWidth: 3 }, + time: { stroke: '#AB7001', strokeWidth: 2.5 }, + string: { stroke: '#58E9D0', strokeWidth: 2.5 } +} + function App() { const [nodes, setNodes, onNodesChange] = useNodesState([]) const [edges, setEdges, onEdgesChange] = useEdgesState([]) const [flowInstance, setFlowInstance] = useState(null) const [edgeType, setEdgeType] = useState('bool') + const [modalData, setModalData] = useState(null) const onConnect = useCallback( - (params) => setEdges((edge) => { - const currentStyle = EDGE_STYLES[edgeType] || EDGE_STYLES['bool'] - return addEdge({ - ...params, + (params) => { + const sourceNode = nodes.find(node => node.id === params.source) + const sourceIndex = parseInt(params.sourceHandle.split('-')[1], 10) + const sourceType = sourceNode?.data?.outputs[sourceIndex].replace('$/', '') || 'INT' + + const newEdge = { + ...params, + id: `e_${Date.now()}`, type: 'smoothstep', - style: currentStyle, - data: {type: edgeType} - }, edge) - }), [setEdges, edgeType] + style: EDGE_STYLES[sourceType.toLowerCase().split('/')[0]] || EDGE_STYLES['bool'], + data: { type: edgeType } + } + + const allEdges = [...edges, newEdge] + const { currentNodes, currentEdges } = updateGraphTypes(allEdges) + 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] ) const onDragOver = useCallback((event) => { @@ -53,22 +189,116 @@ function App() { if (!dataStr) return - const {type, label, inputCount, outputCount} = JSON.parse(dataStr) + const {type, label, inputs = [], outputs = [], offsetX = 0, offsetY = 0} = JSON.parse(dataStr) const position = flowInstance.screenToFlowPosition({ - x: event.clientX, - y: event.clientY + x: event.clientX - offsetX, + y: event.clientY - offsetY }) + if (['const', 'switch_type', 'input', 'output'].includes(type)) { + setModalData({ type, label, position, inputs, outputs }) + return + } + const newNode = { id: `block_${Date.now()}`, type: 'fbdBlock', position, - data: {type, label, inputCount, outputCount} + data: { type, label, inputs, outputs } } setNodes((currentNodes) => [...currentNodes, newNode]) }, [flowInstance, setNodes] ) + function handleSaveModalData(formData) { + if (!modalData) + return + + let finalLabel = modalData.label + let inputs = [] + let outputs = [] + + if (modalData.type === 'const') { + finalLabel = `${formData.value} (${formData.fromType})` + outputs = [formData.fromType] + } + else if (modalData.type === 'switch_type') { + finalLabel = `${formData.fromType}_TO_${formData.toType}` + inputs = [formData.fromType] + outputs = [formData.toType] + } + else if (modalData.type === 'input') { + finalLabel = `${formData.value} (${formData.fromType})` + outputs = [formData.fromType] + } + else if (modalData.type === 'output') { + finalLabel = `${formData.value} (${formData.fromType})` + inputs = [formData.fromType] + } + + const newNode = { + id: `block_${Date.now()}`, + type: 'fbdBlock', + position: modalData.position, + data: { + type: modalData.type, + label: finalLabel, + value: formData.value, + inputs, + outputs, + metaFromType: formData.fromType, + metaToType: formData.toType + } + } + + setNodes((currentNodes) => [...currentNodes, newNode]) + setModalData(null) + } + + const isValidConnection = useCallback((connection) => { + if (connection.source === connection.target) + return false + + function checkIfCycleExists(sourceId, targetId) { + const visited = new Set() + + function dfs(currentId) { + if (currentId === sourceId) + return true + if (visited.has(currentId)) + return false + visited.add(currentId) + + const outgoingEdges = edges.filter(edge => edge.source === currentId) + for (const edge of outgoingEdges) + if (dfs(edge.target)) + return true + return false + } + return dfs(targetId) + } + + if (checkIfCycleExists(connection.source, connection.target)) + return false + + const sourceNode = nodes.find(node => node.id === connection.source) + const targetNode = nodes.find(node => node.id === connection.target) + if (!sourceNode || !targetNode) + return false + + const sourceIndex = parseInt(connection.sourceHandle.split('-')[1], 10) + const targetIndex = parseInt(connection.targetHandle.split('-')[1], 10) + + const sourceType = sourceNode.data.outputs[sourceIndex].replace('$/', '') + const targetType = targetNode.data.inputs[targetIndex].replace('$/', '') + + for (let t of sourceType.split('/')) + if (targetType.includes(t)) + return true + + return false + }, [nodes, edges]) + function handleClear() { if ((nodes.length > 0 || edges.length > 0) && window.confirm("Очистить холст?")) { setNodes([]) @@ -129,7 +359,6 @@ function App() { }) }, [nodes]) - return(
- +
@@ -161,6 +388,11 @@ function App() {
+ setModalData(null)} + onSave = {handleSaveModalData} + />
) } diff --git a/src/components/CustomBlock.jsx b/src/components/CustomBlock.jsx index 55dc4de..816faf4 100644 --- a/src/components/CustomBlock.jsx +++ b/src/components/CustomBlock.jsx @@ -2,12 +2,19 @@ import React, { memo } from 'react'; import { Handle, Position, useEdges } from '@xyflow/react'; function CustomBlock({ id: blockId, data }) { - const {type, label, inputCount = 2, outputCount = 1 } = data + const {type, label, inputs = [], outputs = [], metaFromType, metaToType } = data const edges = useEdges() - const countForHeight = Math.max(inputCount, outputCount, 2) - const blockHeight = 50 + (countForHeight - 1) * 34 - const blockWidth = blockHeight / 1.4 + const inputCount = inputs.length + const outputCount = 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(type)) { + countForHeight = Math.max(inputCount, outputCount) + blockHeight = 40 + (countForHeight - 1) * 34 + blockWidth = blockHeight * 2 + } function getPortStyle(portType, portIndex) { const used = edges.some(edge => { @@ -36,6 +43,13 @@ function CustomBlock({ id: blockId, data }) { const coords = [] if (count === 1) coords.push(50) + else if (count < countForHeight) { + const step = blockHeight / (count + 1) + for (let i = 0; i < count; i++) { + const y = (i + 1) * step + coords.push((y / blockHeight) * 100) + } + } else { const step = (blockHeight - 50) / (count - 1) for (let i = 0; i < count; i++) { @@ -49,6 +63,29 @@ function CustomBlock({ id: blockId, data }) { const inputsCoords = getCoords(inputCount) const outputsCoords = getCoords(outputCount) + function getAddElements() { + if (['ton', 'tof', 'tp'].includes(type)) + return( + <> +
in
+
pt
+
q
+
et
+ + ) + if (type === 'ctu') + return {/*cu r pv, q cv*/} + if (type === 'ctd') + return {/*cd ld pv, q cv*/} + if (type === 'ctud') + return {/*cu cd r ld pv, qu qd cv*/} + if (type === 'rs') + return {/*s1 r, q1*/} + if (type === 'sr') + return {/*s r1, q*/} + return + } + return(
-
{label}
+
{label}
+ {getAddElements()} {inputsCoords.map((coord, ind) => ( { + setInputValue('') + setFromType('INT') + setToType('REAL') + }, [modalData]) + + if (!modalData) + return null + + function getTypeValue() { + const value = inputValue.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 + } + + function handleConfirm() { + const trimmed = inputValue.trim() + + if (modalData.type === 'const') { + if (trimmed === '') { + alert('Переменная пуста') + return + } + } + else if (trimmed === '' && (modalData.type === 'input' || modalData.type === 'output')) { + alert('Не заданно имя переменной') + return + } + + if (modalData.type === 'const') { + const getedType = getTypeValue() + if (!getedType) { + alert('Не удалось получить тип переменной (не корректный ввод)') + return + } + setToType(getedType) + } + + onSave({ + value: trimmed, + fromType, + toType + }) + } + + function getWindowWithType() { + const types = ["BOOL", "INT", "DINT", "REAL", "STRING", "TIME"] + switch(modalData.type) { + case 'switch_type': + return( +
+ + + + + +
+ ) + case 'const': + return( +
+ + setInputValue(e.target.value)} + style = {{ width: '100%', padding: '6px', boxSizing: 'border-box', marginBottom: '12px' }} + placeholder = {12.5} + autoFocus + /> +
+ ) + case 'input': + case 'output': + return( +
+ + setInputValue(e.target.value)} + style = {{ width: '100%', padding: '6px', boxSizing: 'border-box', marginBottom: '12px' }} + placeholder = {'Name'} + autoFocus + /> + + +
+ ) + default: + return; + } + } + + return( +
+
+

Настройка: {modalData.label}

+ {getWindowWithType()} +
+ + +
+
+
+ ) +} + +export default ModalData \ No newline at end of file diff --git a/src/components/Toolbar.jsx b/src/components/Toolbar.jsx index 344e2c6..535fe50 100644 --- a/src/components/Toolbar.jsx +++ b/src/components/Toolbar.jsx @@ -1,5 +1,5 @@ import React, { useState, useMemo, useLayoutEffect, useRef } from 'react' -import { ToolbarBlock, ToolbarLine } from './ToolbarElements' +import {ToolbarBlock} from './ToolbarElements' const NAME_CATEGORIES = { logical: 'Логические', @@ -8,38 +8,45 @@ const NAME_CATEGORIES = { counters: 'Счётчики', comparisons: 'Сравнения', other: 'Другие', + data: 'Данные', customized: 'Пользовательские' }; const BLOCK_CATEGORIES = { logical: [ - { type: 'and', label: 'AND', inputCount: 2, outputCount: 1 }, - { type: 'or', label: 'OR', inputCount: 2, outputCount: 1 }, - { type: 'not', label: 'NOT', inputCount: 1, outputCount: 1 }, - { type: 'xor', label: 'XOR', inputCount: 2, outputCount: 1 }], + { 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'] }], arithmetic: [ - { type: 'add', label: 'ADD', inputCount: 2, outputCount: 1 }, - { type: 'sub', label: 'SUB', inputCount: 2, outputCount: 1 }, - { type: 'mul', label: 'MUL', inputCount: 2, outputCount: 1 }, - { type: 'div', label: 'DIV', inputCount: 2, outputCount: 1 }], + { 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'] }], timers: [ - { type: 'ton', label: 'TON', inputCount: 2, outputCount: 1 }, - { type: 'tof', label: 'TOF', inputCount: 2, outputCount: 1 }, - { type: 'tp', label: 'TP', inputCount: 2, outputCount: 1 }], + { 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'] }], counters: [ - { type: 'ctu', label: 'CTU', inputCount: 2, outputCount: 1 }, - { type: 'ctd', label: 'CTD', inputCount: 2, outputCount: 1 }, - { type: 'ctud', label: 'CTUD',inputCount: 2, outputCount: 1 }], + { 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'] }], comparisons: [ - { type: 'gt', label: 'GT', inputCount: 2, outputCount: 1 }, - { type: 'lt', label: 'LT', inputCount: 2, outputCount: 1 }, - { type: 'eq', label: 'EQ', inputCount: 2, outputCount: 1 }], + { 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'] }], other: [ - { type: 'sr', label: 'SR', inputCount: 2, outputCount: 1 }, - { type: 'rs', label: 'RS', inputCount: 2, outputCount: 1 }, - { type: 'move', label: 'MOVE', inputCount: 2, outputCount: 1 }], - customized: [] -}; + { 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'] }], + 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'] } + ] + // customized: [] +} const LINES_TYPES = [ { type: 'bool', stroke: '#000', dasharray: '0' }, @@ -50,7 +57,9 @@ const LINES_TYPES = [ { type: 'e', stroke: '#1717D8', dasharray: '0' } // Синяя ]; -function Toolbar({ activeEdgeType, onChangeEdgeType }) { +const BLOCK_SCALE = 1 + +function Toolbar() { const [activeTab, setActiveTab] = useState('logical') const containerRef = useRef(null) @@ -75,11 +84,16 @@ function Toolbar({ activeEdgeType, onChangeEdgeType }) { }, []) function onDragStart(event, block) { + const offsetX = event.nativeEvent.offsetX * BLOCK_SCALE + const offsetY = event.nativeEvent.offsetY * BLOCK_SCALE + event.dataTransfer.setData('application/reactflow', JSON.stringify({ type: block.type, label: block.label, - inputCount: block.inputCount, - outputCount: block.outputCount + inputs: block.inputs, + outputs: block.outputs, + offsetX, + offsetY })) event.dataTransfer.effectAllowed = 'move' } @@ -94,7 +108,11 @@ function Toolbar({ activeEdgeType, onChangeEdgeType }) { @@ -108,19 +126,7 @@ function Toolbar({ activeEdgeType, onChangeEdgeType }) { key = {block.type} block = {block} onDragStart = {onDragStart} - /> - ))} -
- - {/*Соединения*/} -
- {LINES_TYPES.map((line) => ( - ))}
diff --git a/src/components/ToolbarElements.jsx b/src/components/ToolbarElements.jsx index 299b0f2..31f3bde 100644 --- a/src/components/ToolbarElements.jsx +++ b/src/components/ToolbarElements.jsx @@ -1,15 +1,28 @@ import React, {memo} from 'react'; -export const ToolbarBlock = memo(({ block, onDragStart }) => { - const scale = 1 - const countForHeight = Math.max(block.inputCount, block.outputCount, 2) - const blockHeight = (50 + (countForHeight - 1) * 34) / scale - const blockWidth = blockHeight / 1.4 +export const ToolbarBlock = memo(({ block, onDragStart, scale = 1 }) => { + const inputCount = block.inputs.length + const outputCount = block.outputs.length + let countForHeight = Math.max(inputCount, outputCount, 2) + let blockHeight = (50 + (countForHeight - 1) * 34) / scale + let blockWidth = 60 / scale + if (['input', 'output', 'const', 'switch_type'].includes(block.type)) { + countForHeight = Math.max(inputCount, outputCount) + blockHeight = (40 + (countForHeight - 1) * 34) / scale + blockWidth = blockHeight * 2 + } function getCoords(count) { const coords = [] if (count === 1) coords.push(50) + else if (count < countForHeight) { + const step = blockHeight / (count + 1) + for (let i = 0; i < count; i++) { + const y = (i + 1) * step + coords.push((y / blockHeight) * 100) + } + } else { const step = (blockHeight - (50 / scale)) / (count - 1) for (let i = 0; i < count; i++) { @@ -20,8 +33,8 @@ export const ToolbarBlock = memo(({ block, onDragStart }) => { return coords } - const inputsCoords = getCoords(block.inputCount) - const outputsCoords = getCoords(block.outputCount) + const inputsCoords = getCoords(inputCount) + const outputsCoords = getCoords(outputCount) return (
{ height: `${blockHeight}px` }} > -
{block.label}
+
{block.label}
{inputsCoords.map((coord, ind) => (
{ ))}
); -}) - -export function ToolbarLine({ line, caseWidth, isActive, onClick }) { - return ( - - ) -} \ No newline at end of file +}) \ No newline at end of file