Dynamic Node Importing from Folders Implemented

This commit is contained in:
Nicole Rappe 2025-03-30 05:01:12 -06:00
parent 6bfce8ba54
commit 417dc4d940
7 changed files with 400 additions and 87 deletions

View File

@ -17,7 +17,8 @@ import {
DialogTitle,
DialogContent,
DialogContentText,
DialogActions
DialogActions,
Divider
} from "@mui/material";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
@ -29,59 +30,34 @@ import ReactFlow, {
applyNodeChanges,
applyEdgeChanges,
ReactFlowProvider,
useReactFlow,
Handle,
Position
useReactFlow
} from "reactflow";
import "reactflow/dist/style.css";
import "./Borealis.css";
// Custom styled node with handles
const CustomNode = ({ data }) => {
return (
<div style={{
background: "#2c2c2c",
border: "1px solid #3a3a3a",
borderRadius: "6px",
color: "#ccc",
fontSize: "12px",
minWidth: "160px",
maxWidth: "260px",
boxShadow: `
0 0 5px rgba(88, 166, 255, 0.1),
0 0 10px rgba(88, 166, 255, 0.1)
`,
position: "relative",
transition: "box-shadow 0.3s ease-in-out"
}}>
<Handle
type="target"
position={Position.Left}
style={{ background: "#58a6ff", width: 10, height: 10 }}
/>
<Handle
type="source"
position={Position.Right}
style={{ background: "#58a6ff", width: 10, height: 10 }}
/>
<div style={{
background: "#232323",
padding: "6px 10px",
borderTopLeftRadius: "6px",
borderTopRightRadius: "6px",
fontWeight: "bold",
fontSize: "13px"
}}>
{data.label || "Custom Node"}
</div>
<div style={{ padding: "10px" }}>
{data.content || "Placeholder"}
</div>
</div>
);
};
const nodeContext = require.context("./nodes", true, /\.jsx$/);
const nodeTypes = {};
const categorizedNodes = {};
function FlowEditor({ nodes, edges, setNodes, setEdges }) {
nodeContext.keys().forEach((path) => {
const mod = nodeContext(path);
if (!mod.default) return;
const { type, label, component } = mod.default;
if (!type || !component) return;
const pathParts = path.replace("./", "").split("/");
if (pathParts.length < 2) return;
const category = pathParts[0];
if (!categorizedNodes[category]) {
categorizedNodes[category] = [];
}
categorizedNodes[category].push({ type, label });
nodeTypes[type] = component;
});
function FlowEditor({ nodes, edges, setNodes, setEdges, nodeTypes }) {
const reactFlowWrapper = useRef(null);
const { project } = useReactFlow();
@ -98,13 +74,18 @@ function FlowEditor({ nodes, edges, setNodes, setEdges }) {
});
const id = `node-${Date.now()}`;
const nodeMeta = Object.values(categorizedNodes)
.flat()
.find((n) => n.type === type);
const newNode = {
id,
type: "custom",
type,
position,
data: {
label: "Custom Node",
content: "Placeholder"
label: nodeMeta?.label || type,
content: nodeMeta?.label || "Node"
}
};
@ -160,7 +141,7 @@ function FlowEditor({ nodes, edges, setNodes, setEdges }) {
proOptions={{ hideAttribution: true }}
nodes={nodes}
edges={edges}
nodeTypes={{ custom: CustomNode }}
nodeTypes={nodeTypes}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
@ -300,14 +281,7 @@ export default function App() {
<Box display="flex" flexDirection="column" height="calc(100vh - 64px)">
<Box display="flex" flexGrow={1} minHeight={0}>
<Box
sx={{
width: 240,
bgcolor: "#121212",
borderRight: "1px solid #333",
overflowY: "auto"
}}
>
<Box sx={{ width: 240, bgcolor: "#121212", borderRight: "1px solid #333", overflowY: "auto" }}>
<Accordion defaultExpanded square disableGutters sx={{ "&:before": { display: "none" }, margin: 0, border: 0 }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />} sx={accordionHeaderStyle}>
<Typography align="left" sx={{ fontSize: "0.9rem", color: "#0475c2" }}><b>Workflows</b></Typography>
@ -319,22 +293,49 @@ export default function App() {
</AccordionDetails>
</Accordion>
<Accordion square disableGutters sx={{ "&:before": { display: "none" }, margin: 0, border: 0 }}>
<Accordion defaultExpanded square disableGutters sx={{ "&:before": { display: "none" }, margin: 0, border: 0 }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />} sx={accordionHeaderStyle}>
<Typography align="left" sx={{ fontSize: "0.9rem", color: "#0475c2" }}><b>Nodes</b></Typography>
</AccordionSummary>
<AccordionDetails sx={{ p: 0 }}>
<Button
fullWidth
sx={sidebarBtnStyle}
draggable
onDragStart={(event) => {
event.dataTransfer.setData("application/reactflow", "testNode");
event.dataTransfer.effectAllowed = "move";
}}
>
TEST NODE
</Button>
{Object.entries(categorizedNodes).map(([category, items]) => (
<Box key={category} sx={{ mb: 2 }}>
<Divider
sx={{
bgcolor: "#2c2c2c",
mb: 1,
px: 1,
py: 0.5,
display: "flex",
justifyContent: "center"
}}
>
<Typography
variant="caption"
sx={{
color: "#888",
fontSize: "0.75rem"
}}
>
{category}
</Typography>
</Divider>
{items.map(({ type, label }) => (
<Button
key={`${category}-${type}`}
fullWidth
sx={sidebarBtnStyle}
draggable
onDragStart={(event) => {
event.dataTransfer.setData("application/reactflow", type);
event.dataTransfer.effectAllowed = "move";
}}
>
{label}
</Button>
))}
</Box>
))}
</AccordionDetails>
</Accordion>
</Box>
@ -346,6 +347,7 @@ export default function App() {
edges={edges}
setNodes={setNodes}
setEdges={setEdges}
nodeTypes={nodeTypes}
/>
</ReactFlowProvider>
</Box>

View File

@ -0,0 +1,65 @@
// src/nodes/General Purpose/CustomNode.jsx
import React from "react";
import { Handle, Position } from "reactflow";
const experimentalNode = ({ data }) => {
return (
<div
style={{
background: "#2c2c2c",
border: "1px solid #3a3a3a",
borderRadius: "6px",
color: "#ccc",
fontSize: "12px",
minWidth: "160px",
maxWidth: "260px",
boxShadow: `
0 0 5px rgba(88, 166, 255, 0.1),
0 0 10px rgba(88, 166, 255, 0.1)
`,
position: "relative",
transition: "box-shadow 0.3s ease-in-out"
}}
>
<Handle
type="target"
position={Position.Left}
style={{
background: "#58a6ff",
width: 10,
height: 10
}}
/>
<Handle
type="source"
position={Position.Right}
style={{
background: "#58a6ff",
width: 10,
height: 10
}}
/>
<div
style={{
background: "#232323",
padding: "6px 10px",
borderTopLeftRadius: "6px",
borderTopRightRadius: "6px",
fontWeight: "bold",
fontSize: "13px"
}}
>
{data.label || "Experimental Node"}
</div>
<div style={{ padding: "10px" }}>
{data.content || "Experimental Node"}
</div>
</div>
);
};
export default {
type: "experimentalNode", // Must match the type used in nodeTypes
label: "Experimental Node",
component: experimentalNode
};

View File

@ -0,0 +1,65 @@
// src/nodes/General Purpose/CustomNode.jsx
import React from "react";
import { Handle, Position } from "reactflow";
const flyffNode = ({ data }) => {
return (
<div
style={{
background: "#2c2c2c",
border: "1px solid #3a3a3a",
borderRadius: "6px",
color: "#ccc",
fontSize: "12px",
minWidth: "160px",
maxWidth: "260px",
boxShadow: `
0 0 5px rgba(88, 166, 255, 0.1),
0 0 10px rgba(88, 166, 255, 0.1)
`,
position: "relative",
transition: "box-shadow 0.3s ease-in-out"
}}
>
<Handle
type="target"
position={Position.Left}
style={{
background: "#58a6ff",
width: 10,
height: 10
}}
/>
<Handle
type="source"
position={Position.Right}
style={{
background: "#58a6ff",
width: 10,
height: 10
}}
/>
<div
style={{
background: "#232323",
padding: "6px 10px",
borderTopLeftRadius: "6px",
borderTopRightRadius: "6px",
fontWeight: "bold",
fontSize: "13px"
}}
>
{data.label || "Flyff Node"}
</div>
<div style={{ padding: "10px" }}>
{data.content || "Flyff Node"}
</div>
</div>
);
};
export default {
type: "flyffNode", // Must match the type used in nodeTypes
label: "Flyff Node",
component: flyffNode
};

View File

@ -0,0 +1,65 @@
// src/nodes/General Purpose/CustomNode.jsx
import React from "react";
import { Handle, Position } from "reactflow";
const dataNode = ({ data }) => {
return (
<div
style={{
background: "#2c2c2c",
border: "1px solid #3a3a3a",
borderRadius: "6px",
color: "#ccc",
fontSize: "12px",
minWidth: "160px",
maxWidth: "260px",
boxShadow: `
0 0 5px rgba(88, 166, 255, 0.1),
0 0 10px rgba(88, 166, 255, 0.1)
`,
position: "relative",
transition: "box-shadow 0.3s ease-in-out"
}}
>
<Handle
type="target"
position={Position.Left}
style={{
background: "#58a6ff",
width: 10,
height: 10
}}
/>
<Handle
type="source"
position={Position.Right}
style={{
background: "#58a6ff",
width: 10,
height: 10
}}
/>
<div
style={{
background: "#232323",
padding: "6px 10px",
borderTopLeftRadius: "6px",
borderTopRightRadius: "6px",
fontWeight: "bold",
fontSize: "13px"
}}
>
{data.label || "Data Node"}
</div>
<div style={{ padding: "10px" }}>
{data.content || "Data Node"}
</div>
</div>
);
};
export default {
type: "dataNode", // Must match the type used in nodeTypes
label: "Data Node",
component: dataNode
};

View File

@ -0,0 +1,65 @@
// src/nodes/General Purpose/CustomNode.jsx
import React from "react";
import { Handle, Position } from "reactflow";
const nodeGroupBox = ({ data }) => {
return (
<div
style={{
background: "#2c2c2c",
border: "1px solid #3a3a3a",
borderRadius: "6px",
color: "#ccc",
fontSize: "12px",
minWidth: "160px",
maxWidth: "260px",
boxShadow: `
0 0 5px rgba(88, 166, 255, 0.1),
0 0 10px rgba(88, 166, 255, 0.1)
`,
position: "relative",
transition: "box-shadow 0.3s ease-in-out"
}}
>
<Handle
type="target"
position={Position.Left}
style={{
background: "#58a6ff",
width: 10,
height: 10
}}
/>
<Handle
type="source"
position={Position.Right}
style={{
background: "#58a6ff",
width: 10,
height: 10
}}
/>
<div
style={{
background: "#232323",
padding: "6px 10px",
borderTopLeftRadius: "6px",
borderTopRightRadius: "6px",
fontWeight: "bold",
fontSize: "13px"
}}
>
{data.label || "Node Group Box"}
</div>
<div style={{ padding: "10px" }}>
{data.content || "Node Group Box"}
</div>
</div>
);
};
export default {
type: "nodeGroupBox", // Must match the type used in nodeTypes
label: "Node Group Box",
component: nodeGroupBox
};

View File

@ -0,0 +1,65 @@
// src/nodes/General Purpose/CustomNode.jsx
import React from "react";
import { Handle, Position } from "reactflow";
const ExportToCSVNode = ({ data }) => {
return (
<div
style={{
background: "#2c2c2c",
border: "1px solid #3a3a3a",
borderRadius: "6px",
color: "#ccc",
fontSize: "12px",
minWidth: "160px",
maxWidth: "260px",
boxShadow: `
0 0 5px rgba(88, 166, 255, 0.1),
0 0 10px rgba(88, 166, 255, 0.1)
`,
position: "relative",
transition: "box-shadow 0.3s ease-in-out"
}}
>
<Handle
type="target"
position={Position.Left}
style={{
background: "#58a6ff",
width: 10,
height: 10
}}
/>
<Handle
type="source"
position={Position.Right}
style={{
background: "#58a6ff",
width: 10,
height: 10
}}
/>
<div
style={{
background: "#232323",
padding: "6px 10px",
borderTopLeftRadius: "6px",
borderTopRightRadius: "6px",
fontWeight: "bold",
fontSize: "13px"
}}
>
{data.label || "Export to CSV"}
</div>
<div style={{ padding: "10px" }}>
{data.content || "Export to CSV"}
</div>
</div>
);
};
export default {
type: "ExportToCSVNode", // Must match the type used in nodeTypes
label: "Export to CSV",
component: ExportToCSVNode
};

View File

@ -1,14 +0,0 @@
// Data/WebUI/src/nodes/TestNode.jsx
import React from 'react';
import { Handle, Position } from 'reactflow';
export default function TestNode({ data }) {
return (
<div style={{ background: '#fff', padding: '8px', border: '1px solid #999' }}>
<b>Test Node</b>
<div>{data.label}</div>
<Handle type="target" position={Position.Top} />
<Handle type="source" position={Position.Bottom} />
</div>
);
}