Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

working add and delete node functions #8

Merged
merged 3 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion actionFlow.editor/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion actionFlow.editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
"flowbite-react": "^0.10.1",
"next": "14.1.3",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"uuid": "^10.0.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.19",
"eslint": "^8",
"eslint-config-next": "14.1.3",
Expand Down
190 changes: 96 additions & 94 deletions actionFlow.editor/src/components/Flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,106 @@ import {
useEdgesState,
type OnConnect,
Panel,
Node,
Edge,
getIncomers,
getOutgoers,
getConnectedEdges,
} from "@xyflow/react";

import "@xyflow/react/dist/style.css";

import { initialNodes, nodeTypes, type CustomNodeType } from "./nodes";
import { initialEdges, edgeTypes, type CustomEdgeType } from "./edges";
import { Button, Drawer, Modal } from "flowbite-react";
import ActionDrawer from "./left-pane/ActionDrawer";
import AddActionModal from "./left-pane/AddActionModal";
import { generateNode } from "@/modules/nodes/node-generator";

export default function App() {
const [nodes, , onNodesChange] = useNodesState<CustomNodeType>(initialNodes);
const [nodes, setNodes, onNodesChange] =
useNodesState<CustomNodeType>(initialNodes);
const [edges, setEdges, onEdgesChange] =
useEdgesState<CustomEdgeType>(initialEdges);
const onConnect: OnConnect = useCallback(
(connection) => setEdges((edges) => addEdge(connection, edges)),
[setEdges]
);

const [openModal, setOpenModal] = useState(false);
const [showAddActionModal, setOpenAddActionModal] = useState(false);
const [selectedNodes, setSelectedNodes] = useState<Node[]>([]);

const actionIcon = (<svg className="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
<path fillRule="evenodd" d="M3 4a1 1 0 0 0-.822 1.57L6.632 12l-4.454 6.43A1 1 0 0 0 3 20h13.153a1 1 0 0 0 .822-.43l4.847-7a1 1 0 0 0 0-1.14l-4.847-7a1 1 0 0 0-.822-.43H3Z" clipRule="evenodd" />
</svg>)
const handleAddAction = () => {
setOpenAddActionModal(true);
};

const handleCloseAddActionModal = () => {
setOpenAddActionModal(false);
};

const handleFlowSelectionChange = (params: {
nodes: Node[];
edges: Edge[];
}) => {
setSelectedNodes(params.nodes);
};

const handleDeleteNodes = useCallback(() => {
selectedNodes.forEach((selectedNode) => {
setEdges((edges) => {
const incomers = getIncomers(selectedNode, nodes, edges);
const outgoers = getOutgoers(selectedNode, nodes, edges);
const connectedEdges = getConnectedEdges([selectedNode], edges);

const remainingEdges = edges.filter(
(edge) => !connectedEdges.includes(edge)
);

const createdEdges = incomers.flatMap(({ id: source }) =>
outgoers.map(({ id: target }) => ({
id: `${source}->${target}`,
source,
target,
}))
);

return [...remainingEdges, ...createdEdges];
});
});

setNodes((nodes) =>
nodes.filter(
(node) =>
selectedNodes.find((selected) => selected.id == node.id) == undefined
)
);
}, [selectedNodes, nodes, edges, setNodes, setEdges]);

const handleAddNode = useCallback((nodeType: string) => {
const parentNode = selectedNodes[0];
const nodeToAdd = generateNode(nodeType, parentNode);

setNodes((nds) => nds.concat(nodeToAdd));

setEdges((edges) => {
const connectedEdges = getConnectedEdges([parentNode], edges);

const remainingEdges = edges.map(
(edge) => connectedEdges.includes(edge) && edge.source === parentNode.id ? {
...edge,
id: `${nodeToAdd.id}->${edge.target}`,
source: nodeToAdd.id
} : edge
);

const createdEdges = [{ id: `${parentNode.id}->${nodeToAdd.id}`, source: parentNode.id, target: nodeToAdd.id, animated: false }]

return [...remainingEdges, ...createdEdges];
});

setSelectedNodes([nodeToAdd]);
setOpenAddActionModal(false);

}, [selectedNodes, nodes, edges, setNodes, setEdges, setSelectedNodes, setOpenAddActionModal])

return (
<ReactFlow<CustomNodeType, CustomEdgeType>
Expand All @@ -44,104 +122,28 @@ export default function App() {
nodesConnectable={true}
nodesDraggable={true}
elementsSelectable={true}
onSelectionChange={handleFlowSelectionChange}
fitView
className="bg-white dark:bg-gray-900 antialiased"
>
<Background />
<MiniMap />

<Panel position="top-left">

<Drawer open={true} onClose={() => { }} backdrop={false}>
<Drawer.Header title="Drawer" />
<Drawer.Items>
<p className="mb-6 text-sm text-gray-500 dark:text-gray-400">
Supercharge your hiring by taking advantage of our&nbsp;
<a href="#" className="text-cyan-600 underline hover:no-underline dark:text-cyan-500">
limited-time sale
</a>
&nbsp;for Flowbite Docs + Job Board. Unlimited access to over 190K top-ranked candidates and the #1 design
job board.
</p>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<a
href="#"
className="rounded-lg border border-gray-200 bg-white px-4 py-2 text-center text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-cyan-700 focus:z-10 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
>
Learn more
</a>
<a
href="#"
className="inline-flex items-center rounded-lg bg-cyan-700 px-4 py-2 text-center text-sm font-medium text-white hover:bg-cyan-800 focus:outline-none focus:ring-4 focus:ring-cyan-300 dark:bg-cyan-600 dark:hover:bg-cyan-700 dark:focus:ring-cyan-800"
>
Get access&nbsp;
<svg
className="ms-2 h-3.5 w-3.5 rtl:rotate-180"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 10"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M1 5h12m0 0L9 1m4 4L9 9"
/>
</svg>
</a>
</div>

<Button.Group>
<Button color="gray" onClick={() => setOpenModal(true)}>Add</Button>
<Button color="gray">Delete</Button>
</Button.Group>

</Drawer.Items>
</Drawer>
<ActionDrawer
onAddAction={handleAddAction}
selectedNodes={selectedNodes}
onDeleteAction={handleDeleteNodes}
/>
</Panel>

<Controls />

<Modal show={openModal} size="xl" onClose={() => setOpenModal(false)} popup>
<Modal.Header />
<Modal.Body>
<div className="grid grid-cols-3 gap-4 p-4 lg:grid-cols-4">
<div className="cursor-pointer rounded-lg bg-gray-50 p-4 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600">
<div className="mx-auto mb-2 flex h-[48px] max-h-[48px] w-[48px] max-w-[48px] items-center justify-center rounded-full bg-gray-200 p-2 dark:bg-gray-600">
{actionIcon}
</div>
<div className="text-center font-medium text-gray-500 dark:text-gray-400">Set Variable</div>
</div>
<div className="cursor-pointer rounded-lg bg-gray-50 p-4 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600">
<div className="mx-auto mb-2 flex h-[48px] max-h-[48px] w-[48px] max-w-[48px] items-center justify-center rounded-full bg-gray-200 p-2 dark:bg-gray-600">
{actionIcon}
</div>
<div className="text-center font-medium text-gray-500 dark:text-gray-400">Loop</div>
</div>
<div className="cursor-pointer rounded-lg bg-gray-50 p-4 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600">
<div className="mx-auto mb-2 flex h-[48px] max-h-[48px] w-[48px] max-w-[48px] items-center justify-center rounded-full bg-gray-200 p-2 dark:bg-gray-600">
{actionIcon}
</div>
<div className="text-center font-medium text-gray-500 dark:text-gray-400">Http Call</div>
</div>
<div className="cursor-pointer rounded-lg bg-gray-50 p-4 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600">
<div className="mx-auto mb-2 flex h-[48px] max-h-[48px] w-[48px] max-w-[48px] items-center justify-center rounded-full bg-gray-200 p-2 dark:bg-gray-600">
{actionIcon}
</div>
<div className="text-center font-medium text-gray-500 dark:text-gray-400">Control Flow</div>
</div>
<div className="cursor-pointer rounded-lg bg-gray-50 p-4 hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600">
<div className="mx-auto mb-2 flex h-[48px] max-h-[48px] w-[48px] max-w-[48px] items-center justify-center rounded-full bg-gray-200 p-2 dark:bg-gray-600">
{actionIcon}
</div>
<div className="text-center font-medium text-gray-500 dark:text-gray-400">Call Workflow</div>
</div>
</div>
</Modal.Body>
</Modal>
<Controls className="left-80" />

<AddActionModal
showModal={showAddActionModal}
onCloseModal={handleCloseAddActionModal}
onAddNode={handleAddNode}
/>
</ReactFlow>
);
}
39 changes: 39 additions & 0 deletions actionFlow.editor/src/components/left-pane/ActionDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Drawer, Button } from "flowbite-react"
import { Node } from "@xyflow/react";

export type ActionDrawerData = {
onAddAction?: () => void;
onDeleteAction?: () => void;
selectedNodes?: Node[];
}

export default function ActionDrawer({ onAddAction: addAction, onDeleteAction, selectedNodes }: ActionDrawerData) {

const validNodeTypesToDelete = [
"variable"
]

const validNodeTypesToAddTo = [
"input",
"variable"
]

const canDelete = selectedNodes && selectedNodes.length > 0 && selectedNodes?.every(x => {
return x.type && validNodeTypesToDelete.includes(x.type);
})

const canAdd = selectedNodes && selectedNodes.length == 1 && selectedNodes[0].type && validNodeTypesToAddTo.includes(selectedNodes[0].type);

return (
<Drawer open={true} onClose={() => { }} backdrop={false}>
<Drawer.Header title="Action Flow Editor" />
<Drawer.Items>
<Button.Group>
<Button color="gray" disabled={!canAdd} onClick={() => addAction && addAction()}>Add</Button>
<Button color="gray" disabled={!canDelete} onClick={() => onDeleteAction && onDeleteAction()}>Delete</Button>
</Button.Group>

</Drawer.Items>
</Drawer>
)
}
Loading
Loading