Fixed logic to simplify transition to routes

This commit is contained in:
Lucas Oliveira 2023-06-12 19:21:48 -03:00
commit 9408442191
11 changed files with 397 additions and 493 deletions

Binary file not shown.

View file

@ -1,7 +1,7 @@
import "reactflow/dist/style.css";
import { useState, useEffect, useContext } from "react";
import "./App.css";
import { useLocation, useParams } from "react-router-dom";
import { useLocation } from "react-router-dom";
import _ from "lodash";
import ErrorAlert from "./alerts/error";
@ -27,7 +27,7 @@ export default function App() {
setShowSideBar(true);
setIsStackedOpen(true);
}, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]);
const { hardReset, setTabIndex } = useContext(TabsContext);
const { hardReset } = useContext(TabsContext);
const {
errorData,
errorOpen,
@ -46,10 +46,8 @@ export default function App() {
//create the first flow
if (flows.length === 0) {
addFlow();
console.log("que");
}
}, [addFlow, flows.length]);
// Initialize state variable for the list of alerts
const [alertsList, setAlertsList] = useState<
@ -137,7 +135,7 @@ export default function App() {
>
{flows.length !== 0 && (
<>
<Header />
{/* <Header /> */}
<Router />
</>
)}

View file

@ -57,7 +57,6 @@ export default function ParameterComponent({
const { reactFlowInstance } = useContext(typesContext);
let disabled =
reactFlowInstance?.getEdges().some((e) => e.targetHandle === id) ?? false;
const { save } = useContext(TabsContext);
const [myData, setMyData] = useState(useContext(typesContext).data);
useEffect(() => {
@ -160,7 +159,7 @@ export default function ParameterComponent({
}
onChange={(t: string[]) => {
data.node.template[name].value = t;
save();
}}
/>
) : data.node.template[name].multiline ? (
@ -169,7 +168,7 @@ export default function ParameterComponent({
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
save();
}}
/>
) : (
@ -180,7 +179,7 @@ export default function ParameterComponent({
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
save();
}}
/>
)}
@ -193,7 +192,7 @@ export default function ParameterComponent({
setEnabled={(t) => {
data.node.template[name].value = t;
setEnabled(t);
save();
}}
/>
</div>
@ -204,7 +203,6 @@ export default function ParameterComponent({
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
save();
}}
/>
) : left === true &&
@ -221,7 +219,7 @@ export default function ParameterComponent({
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
save();
}}
/>
) : left === true && type === "file" ? (
@ -235,7 +233,7 @@ export default function ParameterComponent({
suffixes={data.node.template[name].suffixes}
onFileChange={(t: string) => {
data.node.template[name].content = t;
save();
}}
></InputFileComponent>
) : left === true && type === "int" ? (
@ -245,7 +243,7 @@ export default function ParameterComponent({
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
save();
}}
/>
) : left === true && type === "prompt" ? (
@ -254,7 +252,7 @@ export default function ParameterComponent({
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
save();
}}
/>
) : (

View file

@ -27,9 +27,8 @@ import {
} from "../controllers/API";
const TabsContextInitialValue: TabsContextType = {
save: () => {},
tabIndex: 0,
setTabIndex: (index: number) => {},
tabId: "",
setTabId: (index: string) => {},
flows: [],
removeFlow: (id: string) => {},
addFlow: (flowData?: any) => {},
@ -56,7 +55,7 @@ export const TabsContext = createContext<TabsContextType>(
export function TabsProvider({ children }: { children: ReactNode }) {
const { setErrorData, setNoticeData } = useContext(alertContext);
const [tabIndex, setTabIndex] = useState(0);
const [tabId, setTabId] = useState("");
const [flows, setFlows] = useState<Array<FlowType>>([]);
const [id, setId] = useState(uuidv4());
const { templates, reactFlowInstance } = useContext(typesContext);
@ -67,32 +66,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
newNodeId.current = uuidv4();
return newNodeId.current;
}
function save() {
// added clone deep to avoid mutating the original object
let Saveflows = _.cloneDeep(flows);
if (Saveflows.length !== 0) {
Saveflows.forEach((flow) => {
if (flow.data && flow.data?.nodes) {
flow.data?.nodes.forEach((node) => {
console.log(node.data.type);
//looking for file fields to prevent saving the content and breaking the flow for exceeding the the data limite for local storage
Object.keys(node.data.node.template).forEach((key) => {
console.log(node.data.node.template[key].type);
if (node.data.node.template[key].type === "file") {
console.log(node.data.node.template[key]);
node.data.node.template[key].content = null;
node.data.node.template[key].value = "";
}
});
});
}
});
window.localStorage.setItem(
"tabsData",
JSON.stringify({ tabIndex, flows: Saveflows, id })
);
}
}
// function loadCookie(cookie: string) {
// if (cookie && Object.keys(templates).length > 0) {
@ -245,25 +218,13 @@ export function TabsProvider({ children }: { children: ReactNode }) {
);
}
function updateStateWithTabsData(tabsData) {
setTabIndex(tabsData.tabIndex);
setFlows(tabsData.flows);
setId(tabsData.id);
}
function updateStateWithDbData(tabsData) {
setFlows(tabsData);
}
useEffect(() => {
//save tabs locally
console.log(id);
save();
}, [flows, id, tabIndex, newNodeId]);
function hardReset() {
newNodeId.current = uuidv4();
setTabIndex(0);
setTabId("");
setFlows([]);
setId(uuidv4());
}
@ -280,7 +241,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
// create a link element and set its properties
const link = document.createElement("a");
link.href = jsonString;
link.download = `${flows[tabIndex].name}.json`;
link.download = `${flows.find((f) => f.id === tabId).name}.json`;
// simulate a click on the link element to trigger the download
link.click();
@ -331,13 +292,14 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const index = newFlows.findIndex((flow) => flow.id === id);
if (index >= 0) {
deleteFlowFromDatabase(id).then(() => {
let tabIndex = flows.findIndex((flow) => flow.id === tabId);
if (index === tabIndex) {
setTabIndex(flows.length - 2);
setTabId(flows[flows.length - 2].id);
newFlows.splice(index, 1);
} else {
let flowId = flows[tabIndex].id;
newFlows.splice(index, 1);
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
setTabId(flowId);
}
});
}
@ -452,9 +414,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
.then(() => {
// Add the new flow to the list of flows.
addFlowToLocalState(newFlow);
// Set the tab index to the new flow.
setTabIndexToLocalState();
});
};
@ -522,10 +481,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
};
const setTabIndexToLocalState = () => {
setTabIndex(flows.length);
};
/**
* Updates an existing flow with new data
* @param newFlow - The new flow object containing the updated data
@ -552,10 +507,9 @@ export function TabsProvider({ children }: { children: ReactNode }) {
setLastCopiedSelection,
disableCopyPaste,
setDisableCopyPaste,
save,
hardReset,
tabIndex,
setTabIndex,
tabId,
setTabId,
flows,
incrementNodeId,
removeFlow,

View file

@ -0,0 +1,349 @@
import _ from "lodash";
import { useContext, useRef, useState, useEffect, useCallback } from "react";
import ReactFlow, { OnSelectionChangeParams, useNodesState, useEdgesState, useReactFlow, EdgeChange, Connection, addEdge, NodeDragHandler, SelectionDragHandler, OnEdgesDelete, Edge, updateEdge, Background, Controls } from "reactflow";
import GenericNode from "../../../../CustomNodes/GenericNode";
import Chat from "../../../../components/chatComponent";
import { alertContext } from "../../../../contexts/alertContext";
import { locationContext } from "../../../../contexts/locationContext";
import { TabsContext } from "../../../../contexts/tabsContext";
import { typesContext } from "../../../../contexts/typesContext";
import { APIClassType } from "../../../../types/api";
import { FlowType, NodeType } from "../../../../types/flow";
import { isValidConnection } from "../../../../utils";
import useUndoRedo from "../../hooks/useUndoRedo";
import ConnectionLineComponent from "../ConnectionLineComponent";
import ExtraSidebar from "../extraSidebarComponent";
const nodeTypes = {
genericNode: GenericNode,
};
export default function Page({ flow }: { flow: FlowType }) {
let {
updateFlow,
disableCopyPaste,
addFlow,
getNodeId,
paste,
lastCopiedSelection,
setLastCopiedSelection,
} = useContext(TabsContext);
const { types, reactFlowInstance, setReactFlowInstance, templates } =
useContext(typesContext);
const reactFlowWrapper = useRef(null);
const { takeSnapshot } = useUndoRedo();
const [position, setPosition] = useState({ x: 0, y: 0 });
const [lastSelection, setLastSelection] =
useState<OnSelectionChangeParams>(null);
useEffect(() => {
// this effect is used to attach the global event handlers
const onKeyDown = (event: KeyboardEvent) => {
if (
(event.ctrlKey || event.metaKey) &&
event.key === "c" &&
lastSelection &&
!disableCopyPaste
) {
event.preventDefault();
console.log(_.cloneDeep(lastSelection));
setLastCopiedSelection(_.cloneDeep(lastSelection));
}
if (
(event.ctrlKey || event.metaKey) &&
event.key === "v" &&
lastCopiedSelection &&
!disableCopyPaste
) {
event.preventDefault();
let bounds = reactFlowWrapper.current.getBoundingClientRect();
paste(lastCopiedSelection, {
x: position.x - bounds.left,
y: position.y - bounds.top,
});
}
if (
(event.ctrlKey || event.metaKey) &&
event.key === "g" &&
lastSelection
) {
event.preventDefault();
// addFlow(newFlow, false);
}
};
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
document.addEventListener("keydown", onKeyDown);
document.addEventListener("mousemove", handleMouseMove);
return () => {
document.removeEventListener("keydown", onKeyDown);
document.removeEventListener("mousemove", handleMouseMove);
};
}, [position, lastCopiedSelection, lastSelection]);
const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
const { setErrorData } = useContext(alertContext);
const [nodes, setNodes, onNodesChange] = useNodesState(
flow.data?.nodes ?? []
);
const [edges, setEdges, onEdgesChange] = useEdgesState(
flow.data?.edges ?? []
);
const { setViewport } = useReactFlow();
const edgeUpdateSuccessful = useRef(true);
useEffect(() => {
if (reactFlowInstance && flow) {
flow.data = reactFlowInstance.toObject();
updateFlow(flow);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodes, edges]);
//update flow when tabs change
useEffect(() => {
setNodes(flow?.data?.nodes ?? []);
setEdges(flow?.data?.edges ?? []);
if (reactFlowInstance) {
setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 });
}
}, [flow, reactFlowInstance, setEdges, setNodes, setViewport]);
//set extra sidebar
useEffect(() => {
setExtraComponent(<ExtraSidebar />);
setExtraNavigation({ title: "Components" });
}, [setExtraComponent, setExtraNavigation]);
const onEdgesChangeMod = useCallback(
(s: EdgeChange[]) => {
onEdgesChange(s);
setNodes((x) => {
let newX = _.cloneDeep(x);
return newX;
});
},
[onEdgesChange, setNodes]
);
const onConnect = useCallback(
(params: Connection) => {
takeSnapshot();
setEdges((eds) =>
addEdge(
{
...params,
style: { stroke: "inherit" },
className:
params.targetHandle.split("|")[0] === "Text"
? "stroke-gray-800 dark:stroke-gray-300"
: "stroke-gray-900 dark:stroke-gray-200",
animated: params.targetHandle.split("|")[0] === "Text",
},
eds
)
);
setNodes((x) => {
let newX = _.cloneDeep(x);
return newX;
});
},
[setEdges, setNodes, takeSnapshot]
);
const onNodeDragStart: NodeDragHandler = useCallback(() => {
// 👇 make dragging a node undoable
takeSnapshot();
// 👉 you can place your event handlers here
}, [takeSnapshot]);
const onSelectionDragStart: SelectionDragHandler = useCallback(() => {
// 👇 make dragging a selection undoable
takeSnapshot();
}, [takeSnapshot]);
const onEdgesDelete: OnEdgesDelete = useCallback(() => {
// 👇 make deleting edges undoable
takeSnapshot();
}, [takeSnapshot]);
const onDragOver = useCallback((event: React.DragEvent) => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
}, []);
const onDrop = useCallback(
(event: React.DragEvent) => {
event.preventDefault();
takeSnapshot();
// Get the current bounds of the ReactFlow wrapper element
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
// Extract the data from the drag event and parse it as a JSON object
let data: { type: string; node?: APIClassType } = JSON.parse(
event.dataTransfer.getData("json")
);
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
// Calculate the position where the node should be created
const position = reactFlowInstance.project({
x: event.clientX - reactflowBounds.left,
y: event.clientY - reactflowBounds.top,
});
// Generate a unique node ID
let newId = getNodeId();
let newNode: NodeType;
if (data.type !== "groupNode") {
// Create a new node object
newNode = {
id: newId,
type: "genericNode",
position,
data: {
...data,
id: newId,
value: null,
},
};
} else {
// Create a new node object
newNode = {
id: newId,
type: "genericNode",
position,
data: {
...data,
id: newId,
value: null,
},
};
// Add the new node to the list of nodes in state
}
setNodes((nds) => nds.concat(newNode));
},
// Specify dependencies for useCallback
[getNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]
);
const onDelete = useCallback(
(mynodes) => {
takeSnapshot();
setEdges(
edges.filter(
(ns) => !mynodes.some((n) => ns.source === n.id || ns.target === n.id)
)
);
},
[takeSnapshot, edges, setEdges]
);
const onEdgeUpdateStart = useCallback(() => {
edgeUpdateSuccessful.current = false;
}, []);
const onEdgeUpdate = useCallback(
(oldEdge: Edge, newConnection: Connection) => {
if (isValidConnection(newConnection, reactFlowInstance)) {
edgeUpdateSuccessful.current = true;
setEdges((els) => updateEdge(oldEdge, newConnection, els));
}
},
[]
);
const onEdgeUpdateEnd = useCallback((_, edge) => {
if (!edgeUpdateSuccessful.current) {
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
}
edgeUpdateSuccessful.current = true;
}, []);
const [selectionEnded, setSelectionEnded] = useState(false);
const onSelectionEnd = useCallback(() => {
setSelectionEnded(true);
}, []);
const onSelectionStart = useCallback(() => {
setSelectionEnded(false);
}, []);
// Workaround to show the menu only after the selection has ended.
useEffect(() => {
if (selectionEnded && lastSelection && lastSelection.nodes.length > 1) {
setSelectionMenuVisible(true);
} else {
setSelectionMenuVisible(false);
}
}, [selectionEnded, lastSelection]);
const onSelectionChange = useCallback((flow) => {
setLastSelection(flow);
}, []);
const { setDisableCopyPaste } = useContext(TabsContext);
return (
<div className="w-full h-full" ref={reactFlowWrapper}>
{Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? (
<>
<ReactFlow
nodes={nodes}
onMove={() => {
updateFlow({ ...flow, data: reactFlowInstance.toObject() });
}}
edges={edges}
onPaneClick={() => {
setDisableCopyPaste(false);
}}
onPaneMouseLeave={() => {
setDisableCopyPaste(true);
}}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChangeMod}
onConnect={onConnect}
disableKeyboardA11y={true}
onLoad={setReactFlowInstance}
onInit={setReactFlowInstance}
nodeTypes={nodeTypes}
onEdgeUpdate={onEdgeUpdate}
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
onNodeDragStart={onNodeDragStart}
onSelectionDragStart={onSelectionDragStart}
onSelectionEnd={onSelectionEnd}
onSelectionStart={onSelectionStart}
onEdgesDelete={onEdgesDelete}
connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver}
onDrop={onDrop}
onNodesDelete={onDelete}
onSelectionChange={onSelectionChange}
nodesDraggable={!disableCopyPaste}
panOnDrag={!disableCopyPaste}
zoomOnDoubleClick={!disableCopyPaste}
selectNodesOnDrag={false}
className="theme-attribution"
>
<Background className="dark:bg-gray-900" />
<Controls className="[&>button]:text-black [&>button]:dark:bg-gray-800 hover:[&>button]:dark:bg-gray-700 [&>button]:dark:text-gray-400 [&>button]:dark:fill-gray-400 [&>button]:dark:border-gray-600"></Controls>
</ReactFlow>
<Chat flow={flow} reactFlowInstance={reactFlowInstance} />
</>
) : (
<></>
)}
</div>
);
}

View file

@ -32,7 +32,7 @@ export const useUndoRedo: UseUndoRedo = ({
enableShortcuts = defaultOptions.enableShortcuts,
} = defaultOptions) => {
// the past and future arrays store the states that we can jump to
const { tabIndex, flows } = useContext(TabsContext);
const { tabId, flows } = useContext(TabsContext);
const [past, setPast] = useState<HistoryItem[][]>(flows.map(() => []));
const [future, setFuture] = useState<HistoryItem[][]>(flows.map(() => []));
@ -68,12 +68,14 @@ export const useUndoRedo: UseUndoRedo = ({
getEdges,
past,
future,
tabIndex,
tabId,
setPast,
setFuture,
maxHistorySize,
]);
const tabIndex = flows.findIndex((f) => f.id === tabId);
const undo = useCallback(() => {
// get the last state that we want to go back to
const pastState = past[tabIndex][past[tabIndex].length - 1];

View file

@ -1,395 +1,16 @@
import { useCallback, useContext, useEffect, useRef, useState } from "react";
import ReactFlow, {
Background,
Controls,
addEdge,
useEdgesState,
useNodesState,
useReactFlow,
updateEdge,
EdgeChange,
Connection,
Edge,
useKeyPress,
NodeDragHandler,
OnEdgesDelete,
OnNodesDelete,
SelectionDragHandler,
useOnViewportChange,
OnSelectionChangeParams,
OnNodesChange,
ReactFlowProvider,
} from "reactflow";
import _ from "lodash";
import { locationContext } from "../../contexts/locationContext";
import ExtraSidebar from "./components/extraSidebarComponent";
import Chat from "../../components/chatComponent";
import GenericNode from "../../CustomNodes/GenericNode";
import { alertContext } from "../../contexts/alertContext";
import { useContext, useEffect } from "react";
import Page from "./components/PageComponent";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
import ConnectionLineComponent from "./components/ConnectionLineComponent";
import { FlowType, NodeType } from "../../types/flow";
import { APIClassType } from "../../types/api";
import { isValidConnection } from "../../utils";
import useUndoRedo from "./hooks/useUndoRedo";
import { useParams } from "react-router-dom";
const nodeTypes = {
genericNode: GenericNode,
};
export default function FlowPage() {
let {
updateFlow,
disableCopyPaste,
addFlow,
getNodeId,
paste,
lastCopiedSelection,
setLastCopiedSelection,
flows,
tabIndex,
setTabIndex
} = useContext(TabsContext);
const { types, reactFlowInstance, setReactFlowInstance, templates } =
useContext(typesContext);
const reactFlowWrapper = useRef(null);
const { takeSnapshot } = useUndoRedo();
export default function FlowPage(){
const { flows, tabId, setTabId } = useContext(TabsContext);
const {id} = useParams();
useEffect(() => {
if(flows.length !== 0){
console.log(id, "o id é esse");
setTabIndex(flows.findIndex((flow) => flow.id === id) == -1 ? 0 : flows.findIndex((flow) => flow.id === id));
console.log("setou", flows.findIndex((flow) => flow.id === id))
console.log(flows[tabIndex])
}
}, [flows, id])
const [position, setPosition] = useState({ x: 0, y: 0 });
const [lastSelection, setLastSelection] =
useState<OnSelectionChangeParams>(null);
useEffect(() => {
// this effect is used to attach the global event handlers
const onKeyDown = (event: KeyboardEvent) => {
if (
(event.ctrlKey || event.metaKey) &&
event.key === "c" &&
lastSelection &&
!disableCopyPaste
) {
event.preventDefault();
console.log(_.cloneDeep(lastSelection));
setLastCopiedSelection(_.cloneDeep(lastSelection));
}
if (
(event.ctrlKey || event.metaKey) &&
event.key === "v" &&
lastCopiedSelection &&
!disableCopyPaste
) {
event.preventDefault();
let bounds = reactFlowWrapper.current.getBoundingClientRect();
paste(lastCopiedSelection, {
x: position.x - bounds.left,
y: position.y - bounds.top,
});
}
if (
(event.ctrlKey || event.metaKey) &&
event.key === "g" &&
lastSelection
) {
event.preventDefault();
// addFlow(newFlow, false);
}
};
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
document.addEventListener("keydown", onKeyDown);
document.addEventListener("mousemove", handleMouseMove);
return () => {
document.removeEventListener("keydown", onKeyDown);
document.removeEventListener("mousemove", handleMouseMove);
};
}, [position, lastCopiedSelection, lastSelection]);
const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
const { setErrorData } = useContext(alertContext);
const [nodes, setNodes, onNodesChange] = useNodesState(
flows[tabIndex].data?.nodes ?? []
);
const [edges, setEdges, onEdgesChange] = useEdgesState(
flows[tabIndex].data?.edges ?? []
);
const { setViewport } = useReactFlow();
const edgeUpdateSuccessful = useRef(true);
useEffect(() => {
if (reactFlowInstance && flows[tabIndex]) {
flows[tabIndex].data = reactFlowInstance.toObject();
updateFlow(flows[tabIndex]);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodes, edges]);
//update flow when tabs change
useEffect(() => {
setNodes(flows[tabIndex]?.data?.nodes ?? []);
setEdges(flows[tabIndex]?.data?.edges ?? []);
if (reactFlowInstance) {
setViewport(flows[tabIndex]?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 });
}
}, [flows[tabIndex], reactFlowInstance, setEdges, setNodes, setViewport]);
//set extra sidebar
useEffect(() => {
setExtraComponent(<ExtraSidebar />);
setExtraNavigation({ title: "Components" });
}, [setExtraComponent, setExtraNavigation]);
const onEdgesChangeMod = useCallback(
(s: EdgeChange[]) => {
onEdgesChange(s);
setNodes((x) => {
let newX = _.cloneDeep(x);
return newX;
});
},
[onEdgesChange, setNodes]
);
const onConnect = useCallback(
(params: Connection) => {
takeSnapshot();
setEdges((eds) =>
addEdge(
{
...params,
style: { stroke: "inherit" },
className:
params.targetHandle.split("|")[0] === "Text"
? "stroke-gray-800 dark:stroke-gray-300"
: "stroke-gray-900 dark:stroke-gray-200",
animated: params.targetHandle.split("|")[0] === "Text",
},
eds
)
);
setNodes((x) => {
let newX = _.cloneDeep(x);
return newX;
});
},
[setEdges, setNodes, takeSnapshot]
);
const onNodeDragStart: NodeDragHandler = useCallback(() => {
// 👇 make dragging a node undoable
takeSnapshot();
// 👉 you can place your event handlers here
}, [takeSnapshot]);
const onSelectionDragStart: SelectionDragHandler = useCallback(() => {
// 👇 make dragging a selection undoable
takeSnapshot();
}, [takeSnapshot]);
const onEdgesDelete: OnEdgesDelete = useCallback(() => {
// 👇 make deleting edges undoable
takeSnapshot();
}, [takeSnapshot]);
const onDragOver = useCallback((event: React.DragEvent) => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
}, []);
const onDrop = useCallback(
(event: React.DragEvent) => {
event.preventDefault();
takeSnapshot();
// Get the current bounds of the ReactFlow wrapper element
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
// Extract the data from the drag event and parse it as a JSON object
let data: { type: string; node?: APIClassType } = JSON.parse(
event.dataTransfer.getData("json")
);
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
// Calculate the position where the node should be created
const position = reactFlowInstance.project({
x: event.clientX - reactflowBounds.left,
y: event.clientY - reactflowBounds.top,
});
// Generate a unique node ID
let newId = getNodeId();
let newNode: NodeType;
if (data.type !== "groupNode") {
// Create a new node object
newNode = {
id: newId,
type: "genericNode",
position,
data: {
...data,
id: newId,
value: null,
},
};
} else {
// Create a new node object
newNode = {
id: newId,
type: "genericNode",
position,
data: {
...data,
id: newId,
value: null,
},
};
// Add the new node to the list of nodes in state
}
setNodes((nds) => nds.concat(newNode));
},
// Specify dependencies for useCallback
[getNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]
);
const onDelete = useCallback(
(mynodes) => {
takeSnapshot();
setEdges(
edges.filter(
(ns) => !mynodes.some((n) => ns.source === n.id || ns.target === n.id)
)
);
},
[takeSnapshot, edges, setEdges]
);
const onEdgeUpdateStart = useCallback(() => {
edgeUpdateSuccessful.current = false;
}, []);
const onEdgeUpdate = useCallback(
(oldEdge: Edge, newConnection: Connection) => {
if (isValidConnection(newConnection, reactFlowInstance)) {
edgeUpdateSuccessful.current = true;
setEdges((els) => updateEdge(oldEdge, newConnection, els));
}
},
[]
);
const onEdgeUpdateEnd = useCallback((_, edge) => {
if (!edgeUpdateSuccessful.current) {
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
}
edgeUpdateSuccessful.current = true;
}, []);
const [selectionEnded, setSelectionEnded] = useState(false);
const onSelectionEnd = useCallback(() => {
setSelectionEnded(true);
}, []);
const onSelectionStart = useCallback(() => {
setSelectionEnded(false);
}, []);
// Workaround to show the menu only after the selection has ended.
useEffect(() => {
if (selectionEnded && lastSelection && lastSelection.nodes.length > 1) {
setSelectionMenuVisible(true);
} else {
setSelectionMenuVisible(false);
}
}, [selectionEnded, lastSelection]);
const onSelectionChange = useCallback((flow) => {
setLastSelection(flow);
}, []);
const { setDisableCopyPaste } = useContext(TabsContext);
setTabId(id);
}, [id])
return (
<div className="h-full w-full flex basis-auto flex-1 overflow-hidden">
<ExtraSidebar />
<main className="h-full w-full flex-1 border-t border-gray-200 dark:border-gray-700 flex">
{/* Primary column */}
<div className="w-full h-full" ref={reactFlowWrapper}>
{Object.keys(templates).length > 0 &&
Object.keys(types).length > 0 ? (
<>
<ReactFlow
nodes={nodes}
onMove={() => {
updateFlow({
...flows[tabIndex],
data: reactFlowInstance.toObject(),
});
}}
edges={edges}
onPaneClick={() => {
setDisableCopyPaste(false);
}}
onPaneMouseLeave={() => {
setDisableCopyPaste(true);
}}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChangeMod}
onConnect={onConnect}
disableKeyboardA11y={true}
onLoad={setReactFlowInstance}
onInit={setReactFlowInstance}
nodeTypes={nodeTypes}
onEdgeUpdate={onEdgeUpdate}
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
onNodeDragStart={onNodeDragStart}
onSelectionDragStart={onSelectionDragStart}
onSelectionEnd={onSelectionEnd}
onSelectionStart={onSelectionStart}
onEdgesDelete={onEdgesDelete}
connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver}
onDrop={onDrop}
onNodesDelete={onDelete}
onSelectionChange={onSelectionChange}
nodesDraggable={!disableCopyPaste}
panOnDrag={!disableCopyPaste}
zoomOnDoubleClick={!disableCopyPaste}
selectNodesOnDrag={false}
className="theme-attribution"
>
<Background className="dark:bg-gray-900" />
<Controls className="[&>button]:text-black [&>button]:dark:bg-gray-800 hover:[&>button]:dark:bg-gray-700 [&>button]:dark:text-gray-400 [&>button]:dark:fill-gray-400 [&>button]:dark:border-gray-600"></Controls>
</ReactFlow>
<Chat flow={flows[tabIndex]} reactFlowInstance={reactFlowInstance} />
</>
) : (
<></>
)}
</div>
</main>
</div>
);
}
flows.length > 0 && tabId !== "" && flows.findIndex(flow => flow.id === tabId) !== -1 &&
<Page flow={flows.find(flow => flow.id === tabId)} />
)
}

View file

@ -23,14 +23,14 @@ import { updateFlowInDatabase } from "../../../../controllers/API";
import { Link } from "react-router-dom";
export const CardComponent = ({
flow,
idx,
id,
removeFlow,
setTabIndex,
setTabId,
}: {
flow: FlowType;
idx: number;
id: string;
removeFlow: (id: string) => void;
setTabIndex: (idx: number) => void;
setTabId: (id: string) => void;
}) => {
const { setErrorData } = useContext(alertContext);
const { updateFlow } = useContext(TabsContext);
@ -68,7 +68,7 @@ export const CardComponent = ({
<Edit
className="w-4"
onClick={() => {
setTabIndex(idx);
setTabId(id);
}}
/>

View file

@ -26,37 +26,21 @@ import { MenuBar } from "./components/menuBar";
export default function HomePage() {
const {
flows,
addFlow,
removeFlow,
tabIndex,
setTabIndex,
setTabId,
} = useContext(TabsContext);
const { openPopUp } = useContext(PopUpContext);
const { templates } = useContext(typesContext);
const AlertWidth = 384;
const { dark, setDark } = useContext(darkContext);
const [rename, setRename] = useState(false);
const { notificationCenter, setNotificationCenter, setErrorData } =
useContext(alertContext);
useEffect(() => {
//create the first flow
if (flows.length === 0 && Object.keys(templates).length > 0) {
addFlow();
}
}, [addFlow, flows.length, templates]);
return (
flows.length !== 0 && (
Object.keys(flows).length !== 0 && (
<div
className="w-full h-full flex flex-col bg-muted"
>
<div className="w-full p-4 grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{flows.map((flow, idx) => (
{Object.keys(flows).map((flow, idx) => (
<CardComponent
flow={flow}
idx={idx}
flow={flows[flow]}
id={flow}
removeFlow={removeFlow}
setTabIndex={setTabIndex}
setTabId={setTabId}
/>
))}
</div>

View file

@ -1,9 +1,8 @@
import { FlowType } from "../flow";
export type TabsContextType = {
save: () => void;
tabIndex: number;
setTabIndex: (index: number) => void;
tabId: string;
setTabId: (index: string) => void;
flows: Array<FlowType>;
removeFlow: (id: string) => void;
addFlow: (flowData?: FlowType, newFlow?: boolean) => void;

View file

@ -20,7 +20,6 @@
"noEmit": true,
"jsx": "react-jsx",
"noImplicitAny": false,
"baseUrl": "."
},
"include": [
"src"