feat: added ReactFlow-based node graph #11

Merged
j.gerla merged 68 commits from feat/visual-programming-interface into dev 2025-10-28 11:59:03 +00:00
2 changed files with 112 additions and 69 deletions
Showing only changes of commit f6fcd20462 - Show all commits

View File

@@ -1,3 +1,34 @@
/* editor UI */
.outer-editor-container {
margin-inline: auto;
display: flex;
justify-self: center;
padding: 10px;
align-items: center;
width: 80vw;
height: 80vh;
}
.inner-editor-container {
outline-style: solid;
border-radius: 10pt;
width: 90%;
height: 100%;
}
.dnd-panel {
margin-inline-start: auto;
margin-inline-end: auto;
background-color: canvas;
margin-bottom: 0.5rem;
margin-top:auto;
width: 50%;
height:7%;
}
/* Node Styles */
.default-node { .default-node {
padding: 10px 15px; padding: 10px 15px;
background-color: canvas; background-color: canvas;

View File

@@ -1,22 +1,22 @@
import './VisProgUI.css' import './VisProgUI.css'
import { import {
Background, Background,
Controls, Controls,
Panel, Panel,
ReactFlow, ReactFlow,
ReactFlowProvider, ReactFlowProvider,
MarkerType, MarkerType,
} from '@xyflow/react'; } from '@xyflow/react';
import '@xyflow/react/dist/style.css'; import '@xyflow/react/dist/style.css';
import { import {
StartNode, StartNode,
EndNode, EndNode,
PhaseNode, PhaseNode,
NormNode NormNode
} from './components/NodeDefinitions.tsx'; } from './components/NodeDefinitions.tsx';
import { Sidebar } from './components/DragDropSidebar.tsx'; import {Sidebar} from './components/DragDropSidebar.tsx';
import useFlowStore from './VisProgStores.tsx'; import useFlowStore from './VisProgStores.tsx';
import {useShallow} from 'zustand/react/shallow'; import {useShallow} from 'zustand/react/shallow';
@@ -24,82 +24,94 @@ import type {FlowState} from './VisProgTypes.tsx';
// --| config starting params for flow |-- // --| config starting params for flow |--
const nodeTypes = { /**
start: StartNode, * contains the types of all nodes that are available in the editor
end: EndNode, */
phase: PhaseNode, const NODE_TYPES = {
norm: NormNode start: StartNode,
end: EndNode,
phase: PhaseNode,
norm: NormNode
}; };
const defaultEdgeOptions = { /**
type: 'default', * defines how the default edge looks inside the editor
markerEnd: { */
type: MarkerType.ArrowClosed, const DEFAULT_EDGE_OPTIONS = {
color: '#505050', type: 'default',
}, markerEnd: {
type: MarkerType.ArrowClosed,
color: '#505050',
},
}; };
const selector = (state: FlowState) => ({ const selector = (state: FlowState) => ({
nodes: state.nodes, nodes: state.nodes,
edges: state.edges, edges: state.edges,
onNodesChange: state.onNodesChange, onNodesChange: state.onNodesChange,
onEdgesChange: state.onEdgesChange, onEdgesChange: state.onEdgesChange,
onConnect: state.onConnect, onConnect: state.onConnect,
onReconnectStart: state.onReconnectStart, onReconnectStart: state.onReconnectStart,
onReconnectEnd: state.onReconnectEnd, onReconnectEnd: state.onReconnectEnd,
onReconnect: state.onReconnect onReconnect: state.onReconnect
}); });
// --| define ReactFlow editor |-- // --| define ReactFlow editor |--
const VisProgUI = ()=> { const VisProgUI = () => {
const {
nodes, edges,
onNodesChange,
onEdgesChange,
onConnect,
onReconnect,
onReconnectStart,
onReconnectEnd
} = useFlowStore(useShallow(selector)); // instructs the editor to use the corresponding functions from the FlowStore
const { nodes, edges, onNodesChange, onEdgesChange, onConnect, onReconnect, onReconnectStart, onReconnectEnd } = useFlowStore( return (
useShallow(selector), <div className={'outer-editor-container'}>
); <div className={'inner-editor-container'}>
<ReactFlow
return ( nodes={nodes}
<div style={{marginInline: 'auto',display: 'flex',justifySelf: 'center', padding:'10px', alignItems: 'center', width: '80vw', height: '80vh'}}> edges={edges}
<div style={{outlineStyle: 'solid', borderRadius: '10pt',width: '90%', height:'100%' }}> defaultEdgeOptions={DEFAULT_EDGE_OPTIONS}
<ReactFlow nodeTypes={NODE_TYPES}
nodes={nodes} onNodesChange={onNodesChange}
edges={edges} onEdgesChange={onEdgesChange}
defaultEdgeOptions={defaultEdgeOptions} onReconnect={onReconnect}
nodeTypes={nodeTypes} onReconnectStart={onReconnectStart}
onNodesChange={onNodesChange} onReconnectEnd={onReconnectEnd}
onEdgesChange={onEdgesChange} onConnect={onConnect}
onReconnect={onReconnect} snapToGrid
onReconnectStart={onReconnectStart} fitView
onReconnectEnd={onReconnectEnd} proOptions={{hideAttribution: true}}
onConnect={onConnect} >
snapToGrid <Panel position="top-center" className={'dnd-panel'}>
fitView <Sidebar/> {/* contains the drag and drop panel for nodes */}
proOptions={{hideAttribution: true }} </Panel>
> <Controls/>
<Panel position="top-center" style={{marginInlineStart: 'auto', marginInlineEnd:'auto',backgroundColor: 'canvas' ,marginBottom: '0.5rem', marginTop:'auto', width: '50%', height:'7%'}}> <Background/>
<Sidebar /> </ReactFlow>
</Panel> </div>
<Controls /> </div>
<Background /> );
</ReactFlow>
</div>
</div>
);
}; };
/* --| Places the VisProgUI component inside a ReactFlowProvider /**
* Places the VisProgUI component inside a ReactFlowProvider
* *
* Wrapping the editor component inside a ReactFlowProvider * Wrapping the editor component inside a ReactFlowProvider
* allows us to access and interact with the components inside the editor, outside the editor definition, * allows us to access and interact with the components inside the editor, outside the editor definition,
* thus facilitating the addition of node specific functions inside their node definitions * thus facilitating the addition of node specific functions inside their node definitions
*/ */
function VisualProgrammingUI(){ function VisualProgrammingUI() {
return ( return (
<ReactFlowProvider> <ReactFlowProvider>
<VisProgUI /> <VisProgUI/>
</ReactFlowProvider> </ReactFlowProvider>
); );
} }
export default VisualProgrammingUI; export default VisualProgrammingUI;