diff --git a/src/renderer/components/Experiment/Workflows/CustomNode.tsx b/src/renderer/components/Experiment/Workflows/CustomNode.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b55ff77c75a943c988f6df9b47fd67b141c68b15 --- /dev/null +++ b/src/renderer/components/Experiment/Workflows/CustomNode.tsx @@ -0,0 +1,37 @@ +import { BuiltInNode, Handle } from '@xyflow/react'; +import { NodeProps, Position } from '@xyflow/system'; +import { CircleXIcon, SquareXIcon } from 'lucide-react'; + +export default function CustomNode({ + data, + isConnectable, + targetPosition = Position.Top, + sourcePosition = Position.Bottom, +}: NodeProps<BuiltInNode>) { + return ( + <div className="custom-node"> + <div + className="custom-node-delete-button" + style={{ + position: 'absolute', + top: -8, + right: -8, + cursor: 'pointer', + }} + > + <SquareXIcon size="16px" /> + </div> + <Handle + type="target" + position={targetPosition} + isConnectable={isConnectable} + /> + {data?.label} + <Handle + type="source" + position={sourcePosition} + isConnectable={isConnectable} + /> + </div> + ); +} diff --git a/src/renderer/components/Experiment/Workflows/WorkflowCanvas.tsx b/src/renderer/components/Experiment/Workflows/WorkflowCanvas.tsx index d3fff78671e484be88821fc577345dec96236593..f688234b6b8676c1d9a1d4d7e829032aff3fe49a 100644 --- a/src/renderer/components/Experiment/Workflows/WorkflowCanvas.tsx +++ b/src/renderer/components/Experiment/Workflows/WorkflowCanvas.tsx @@ -8,7 +8,10 @@ import { useReactFlow, } from '@xyflow/react'; import { PlusCircleIcon } from 'lucide-react'; -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; +import CustomNode from './CustomNode'; + +const nodeTypes = { customNode: CustomNode }; function generateNodes(workflow: any) { let out: any[] = []; @@ -21,10 +24,11 @@ function generateNodes(workflow: any) { while (currentTask < workflowConfig.nodes.length) { out.push({ id: currentTask, + type: 'customNode', position: { x: 0, y: position }, data: { label: workflowConfig.nodes[currentTask].name }, }); - position += 100; + position += 60; currentTask = workflowConfig.nodes[currentTask].out; } @@ -75,6 +79,7 @@ const Flow = ({ selectedWorkflow, setNewNodeModalOpen = (x) => {} }) => { <ReactFlow nodes={generateNodes(selectedWorkflow)} edges={generateEdges(selectedWorkflow)} + nodeTypes={nodeTypes} fitView zoomOnScroll={false} zoomOnPinch={false} @@ -86,7 +91,7 @@ const Flow = ({ selectedWorkflow, setNewNodeModalOpen = (x) => {} }) => { onClick={() => { setNewNodeModalOpen(true); }} - variant="plain" + variant="soft" sx={{ zIndex: '1000', position: 'absolute', diff --git a/src/renderer/styles.css b/src/renderer/styles.css index 37e0cdb16288045ac7db7516d58d59ecebbe53ac..c69edfa10e41c539b357281511dbd923c2b19c8d 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -222,4 +222,35 @@ MIT licensed so we have the right to modify the source code if we want */ .react-flow__attribution a { display: none; +} + +.react-flow__node-customNode { + padding: 10px; + border-radius: var(--xy-node-border-radius, var(--xy-node-border-radius-default)); + width: 150px; + font-size: 12px; + color: var(--xy-node-color, var(--xy-node-color-default)); + text-align: center; + border: var(--xy-node-border, var(--xy-node-border-default)); + background-color: var(--xy-node-background-color, var(--xy-node-background-color-default)); +} + +.react-flow__node-customNode:hover { + /* add a light shadow effect */ + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + transition: box-shadow 0.2s ease-in-out; +} + +.custom-node-delete-button { + opacity: 0.0; +} + +.react-flow__node-customNode:hover .custom-node-delete-button { + opacity: 1.0; + transition: opacity 0.2s ease-in-out; + color: rgb(156, 144, 144); + background-color: rgba(255, 255, 255, 0.8); + height: 15px; + width: 15px; + border-radius: 30%; } \ No newline at end of file