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(
+
+ Входной тип данных:
+ setFromType(e.target.value)}
+ style={{ width: '100%', padding: '6px', marginBottom: '10px' }}
+ >
+ {types.map((type) => (
+ {type}
+ ))}
+
+
+ Выходной тип данных:
+ setToType(e.target.value)}
+ style={{ width: '100%', padding: '6px' }}
+ >
+ {types.map((type) => (
+ {type}
+ ))}
+
+
+ )
+ 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
+ />
+ Тип данных:
+ setFromType(e.target.value)}
+ style = {{ width: '100%', padding: '6px' }}
+ >
+ {types.map((type) => (
+ {type}
+ ))}
+
+
+ )
+ 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 }) {
setActiveTab(tab)}
title = {NAME_CATEGORIES[tab]}
> { NAME_CATEGORIES[tab] }
@@ -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 (
-
onClick && onClick(line.type)}
- style={{
- width: `${caseWidth}px`,
- height: `${caseWidth / 2.5}px`
- }}
- >
-
-
-
-
- )
-}
\ No newline at end of file
+})
\ No newline at end of file