diff --git a/langflow.db b/langflow.db index f47b46f37..17fd1d662 100644 Binary files a/langflow.db and b/langflow.db differ diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 7f0b14be6..466cab8b2 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -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 && ( <> -
+ {/*
*/} )} diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index d6ca1fe90..dcf45b3d9 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -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(); + }} /> @@ -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(); + }} > ) : 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(); + }} /> ) : ( diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index d38067ee4..38c59f9f8 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -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( export function TabsProvider({ children }: { children: ReactNode }) { const { setErrorData, setNoticeData } = useContext(alertContext); - const [tabIndex, setTabIndex] = useState(0); + const [tabId, setTabId] = useState(""); const [flows, setFlows] = useState>([]); 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, diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx new file mode 100644 index 000000000..a45e650c7 --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -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(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(); + 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 ( +
+ {Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? ( + <> + { + 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" + > + + + + + + ) : ( + <> + )} +
+ ); +} diff --git a/src/frontend/src/pages/FlowPage/hooks/useUndoRedo.ts b/src/frontend/src/pages/FlowPage/hooks/useUndoRedo.ts index a78ec7580..4d2a2f529 100644 --- a/src/frontend/src/pages/FlowPage/hooks/useUndoRedo.ts +++ b/src/frontend/src/pages/FlowPage/hooks/useUndoRedo.ts @@ -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(flows.map(() => [])); const [future, setFuture] = useState(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]; diff --git a/src/frontend/src/pages/FlowPage/index.tsx b/src/frontend/src/pages/FlowPage/index.tsx index e4d6cbeba..961a27164 100644 --- a/src/frontend/src/pages/FlowPage/index.tsx +++ b/src/frontend/src/pages/FlowPage/index.tsx @@ -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(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(); - 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 ( -
- -
- {/* Primary column */} - -
- {Object.keys(templates).length > 0 && - Object.keys(types).length > 0 ? ( - <> - { - 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" - > - - - - - - ) : ( - <> - )} -
-
-
- ); -} + flows.length > 0 && tabId !== "" && flows.findIndex(flow => flow.id === tabId) !== -1 && + flow.id === tabId)} /> + ) +} \ No newline at end of file diff --git a/src/frontend/src/pages/MainPage/components/cardComponent/index.tsx b/src/frontend/src/pages/MainPage/components/cardComponent/index.tsx index ad5214a38..8f40da563 100644 --- a/src/frontend/src/pages/MainPage/components/cardComponent/index.tsx +++ b/src/frontend/src/pages/MainPage/components/cardComponent/index.tsx @@ -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 = ({ { - setTabIndex(idx); + setTabId(id); }} /> diff --git a/src/frontend/src/pages/MainPage/index.tsx b/src/frontend/src/pages/MainPage/index.tsx index 621a51cdd..1844ed61f 100644 --- a/src/frontend/src/pages/MainPage/index.tsx +++ b/src/frontend/src/pages/MainPage/index.tsx @@ -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 && (
- {flows.map((flow, idx) => ( + {Object.keys(flows).map((flow, idx) => ( ))}
diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index ba61b5ff2..6023663d0 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -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; removeFlow: (id: string) => void; addFlow: (flowData?: FlowType, newFlow?: boolean) => void; diff --git a/src/frontend/tsconfig.json b/src/frontend/tsconfig.json index 2ed005aff..aa0d9fc7a 100644 --- a/src/frontend/tsconfig.json +++ b/src/frontend/tsconfig.json @@ -20,7 +20,6 @@ "noEmit": true, "jsx": "react-jsx", "noImplicitAny": false, - "baseUrl": "." }, "include": [ "src"