Fixed logic to simplify transition to routes
This commit is contained in:
parent
ceb9b1d155
commit
9408442191
11 changed files with 397 additions and 493 deletions
BIN
langflow.db
BIN
langflow.db
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
||||||
import "reactflow/dist/style.css";
|
import "reactflow/dist/style.css";
|
||||||
import { useState, useEffect, useContext } from "react";
|
import { useState, useEffect, useContext } from "react";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { useLocation, useParams } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
import ErrorAlert from "./alerts/error";
|
import ErrorAlert from "./alerts/error";
|
||||||
|
|
@ -27,7 +27,7 @@ export default function App() {
|
||||||
setShowSideBar(true);
|
setShowSideBar(true);
|
||||||
setIsStackedOpen(true);
|
setIsStackedOpen(true);
|
||||||
}, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]);
|
}, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]);
|
||||||
const { hardReset, setTabIndex } = useContext(TabsContext);
|
const { hardReset } = useContext(TabsContext);
|
||||||
const {
|
const {
|
||||||
errorData,
|
errorData,
|
||||||
errorOpen,
|
errorOpen,
|
||||||
|
|
@ -46,11 +46,9 @@ export default function App() {
|
||||||
//create the first flow
|
//create the first flow
|
||||||
if (flows.length === 0) {
|
if (flows.length === 0) {
|
||||||
addFlow();
|
addFlow();
|
||||||
console.log("que");
|
|
||||||
}
|
}
|
||||||
}, [addFlow, flows.length]);
|
}, [addFlow, flows.length]);
|
||||||
|
|
||||||
|
|
||||||
// Initialize state variable for the list of alerts
|
// Initialize state variable for the list of alerts
|
||||||
const [alertsList, setAlertsList] = useState<
|
const [alertsList, setAlertsList] = useState<
|
||||||
Array<{
|
Array<{
|
||||||
|
|
@ -137,7 +135,7 @@ export default function App() {
|
||||||
>
|
>
|
||||||
{flows.length !== 0 && (
|
{flows.length !== 0 && (
|
||||||
<>
|
<>
|
||||||
<Header />
|
{/* <Header /> */}
|
||||||
<Router />
|
<Router />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ export default function ParameterComponent({
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
const { reactFlowInstance } = useContext(typesContext);
|
||||||
let disabled =
|
let disabled =
|
||||||
reactFlowInstance?.getEdges().some((e) => e.targetHandle === id) ?? false;
|
reactFlowInstance?.getEdges().some((e) => e.targetHandle === id) ?? false;
|
||||||
const { save } = useContext(TabsContext);
|
|
||||||
const [myData, setMyData] = useState(useContext(typesContext).data);
|
const [myData, setMyData] = useState(useContext(typesContext).data);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -160,7 +159,7 @@ export default function ParameterComponent({
|
||||||
}
|
}
|
||||||
onChange={(t: string[]) => {
|
onChange={(t: string[]) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : data.node.template[name].multiline ? (
|
) : data.node.template[name].multiline ? (
|
||||||
|
|
@ -169,7 +168,7 @@ export default function ParameterComponent({
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t: string) => {
|
onChange={(t: string) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -180,7 +179,7 @@ export default function ParameterComponent({
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t) => {
|
onChange={(t) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -193,7 +192,7 @@ export default function ParameterComponent({
|
||||||
setEnabled={(t) => {
|
setEnabled={(t) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
setEnabled(t);
|
setEnabled(t);
|
||||||
save();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -204,7 +203,6 @@ export default function ParameterComponent({
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t) => {
|
onChange={(t) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : left === true &&
|
) : left === true &&
|
||||||
|
|
@ -221,7 +219,7 @@ export default function ParameterComponent({
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t: string) => {
|
onChange={(t: string) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : left === true && type === "file" ? (
|
) : left === true && type === "file" ? (
|
||||||
|
|
@ -235,7 +233,7 @@ export default function ParameterComponent({
|
||||||
suffixes={data.node.template[name].suffixes}
|
suffixes={data.node.template[name].suffixes}
|
||||||
onFileChange={(t: string) => {
|
onFileChange={(t: string) => {
|
||||||
data.node.template[name].content = t;
|
data.node.template[name].content = t;
|
||||||
save();
|
|
||||||
}}
|
}}
|
||||||
></InputFileComponent>
|
></InputFileComponent>
|
||||||
) : left === true && type === "int" ? (
|
) : left === true && type === "int" ? (
|
||||||
|
|
@ -245,7 +243,7 @@ export default function ParameterComponent({
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t) => {
|
onChange={(t) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : left === true && type === "prompt" ? (
|
) : left === true && type === "prompt" ? (
|
||||||
|
|
@ -254,7 +252,7 @@ export default function ParameterComponent({
|
||||||
value={data.node.template[name].value ?? ""}
|
value={data.node.template[name].value ?? ""}
|
||||||
onChange={(t: string) => {
|
onChange={(t: string) => {
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
save();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,8 @@ import {
|
||||||
} from "../controllers/API";
|
} from "../controllers/API";
|
||||||
|
|
||||||
const TabsContextInitialValue: TabsContextType = {
|
const TabsContextInitialValue: TabsContextType = {
|
||||||
save: () => {},
|
tabId: "",
|
||||||
tabIndex: 0,
|
setTabId: (index: string) => {},
|
||||||
setTabIndex: (index: number) => {},
|
|
||||||
flows: [],
|
flows: [],
|
||||||
removeFlow: (id: string) => {},
|
removeFlow: (id: string) => {},
|
||||||
addFlow: (flowData?: any) => {},
|
addFlow: (flowData?: any) => {},
|
||||||
|
|
@ -56,7 +55,7 @@ export const TabsContext = createContext<TabsContextType>(
|
||||||
|
|
||||||
export function TabsProvider({ children }: { children: ReactNode }) {
|
export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
const { setErrorData, setNoticeData } = useContext(alertContext);
|
const { setErrorData, setNoticeData } = useContext(alertContext);
|
||||||
const [tabIndex, setTabIndex] = useState(0);
|
const [tabId, setTabId] = useState("");
|
||||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||||
const [id, setId] = useState(uuidv4());
|
const [id, setId] = useState(uuidv4());
|
||||||
const { templates, reactFlowInstance } = useContext(typesContext);
|
const { templates, reactFlowInstance } = useContext(typesContext);
|
||||||
|
|
@ -67,32 +66,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
newNodeId.current = uuidv4();
|
newNodeId.current = uuidv4();
|
||||||
return newNodeId.current;
|
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) {
|
// function loadCookie(cookie: string) {
|
||||||
// if (cookie && Object.keys(templates).length > 0) {
|
// 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) {
|
function updateStateWithDbData(tabsData) {
|
||||||
setFlows(tabsData);
|
setFlows(tabsData);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
//save tabs locally
|
|
||||||
console.log(id);
|
|
||||||
save();
|
|
||||||
}, [flows, id, tabIndex, newNodeId]);
|
|
||||||
|
|
||||||
function hardReset() {
|
function hardReset() {
|
||||||
newNodeId.current = uuidv4();
|
newNodeId.current = uuidv4();
|
||||||
setTabIndex(0);
|
setTabId("");
|
||||||
setFlows([]);
|
setFlows([]);
|
||||||
setId(uuidv4());
|
setId(uuidv4());
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +241,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
// create a link element and set its properties
|
// create a link element and set its properties
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = jsonString;
|
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
|
// simulate a click on the link element to trigger the download
|
||||||
link.click();
|
link.click();
|
||||||
|
|
@ -331,13 +292,14 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
const index = newFlows.findIndex((flow) => flow.id === id);
|
const index = newFlows.findIndex((flow) => flow.id === id);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
deleteFlowFromDatabase(id).then(() => {
|
deleteFlowFromDatabase(id).then(() => {
|
||||||
|
let tabIndex = flows.findIndex((flow) => flow.id === tabId);
|
||||||
if (index === tabIndex) {
|
if (index === tabIndex) {
|
||||||
setTabIndex(flows.length - 2);
|
setTabId(flows[flows.length - 2].id);
|
||||||
newFlows.splice(index, 1);
|
newFlows.splice(index, 1);
|
||||||
} else {
|
} else {
|
||||||
let flowId = flows[tabIndex].id;
|
let flowId = flows[tabIndex].id;
|
||||||
newFlows.splice(index, 1);
|
newFlows.splice(index, 1);
|
||||||
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
|
setTabId(flowId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -452,9 +414,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Add the new flow to the list of flows.
|
// Add the new flow to the list of flows.
|
||||||
addFlowToLocalState(newFlow);
|
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
|
* Updates an existing flow with new data
|
||||||
* @param newFlow - The new flow object containing the updated data
|
* @param newFlow - The new flow object containing the updated data
|
||||||
|
|
@ -552,10 +507,9 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
setLastCopiedSelection,
|
setLastCopiedSelection,
|
||||||
disableCopyPaste,
|
disableCopyPaste,
|
||||||
setDisableCopyPaste,
|
setDisableCopyPaste,
|
||||||
save,
|
|
||||||
hardReset,
|
hardReset,
|
||||||
tabIndex,
|
tabId,
|
||||||
setTabIndex,
|
setTabId,
|
||||||
flows,
|
flows,
|
||||||
incrementNodeId,
|
incrementNodeId,
|
||||||
removeFlow,
|
removeFlow,
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ export const useUndoRedo: UseUndoRedo = ({
|
||||||
enableShortcuts = defaultOptions.enableShortcuts,
|
enableShortcuts = defaultOptions.enableShortcuts,
|
||||||
} = defaultOptions) => {
|
} = defaultOptions) => {
|
||||||
// the past and future arrays store the states that we can jump to
|
// 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 [past, setPast] = useState<HistoryItem[][]>(flows.map(() => []));
|
||||||
const [future, setFuture] = useState<HistoryItem[][]>(flows.map(() => []));
|
const [future, setFuture] = useState<HistoryItem[][]>(flows.map(() => []));
|
||||||
|
|
@ -68,12 +68,14 @@ export const useUndoRedo: UseUndoRedo = ({
|
||||||
getEdges,
|
getEdges,
|
||||||
past,
|
past,
|
||||||
future,
|
future,
|
||||||
tabIndex,
|
tabId,
|
||||||
setPast,
|
setPast,
|
||||||
setFuture,
|
setFuture,
|
||||||
maxHistorySize,
|
maxHistorySize,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const tabIndex = flows.findIndex((f) => f.id === tabId);
|
||||||
|
|
||||||
const undo = useCallback(() => {
|
const undo = useCallback(() => {
|
||||||
// get the last state that we want to go back to
|
// get the last state that we want to go back to
|
||||||
const pastState = past[tabIndex][past[tabIndex].length - 1];
|
const pastState = past[tabIndex][past[tabIndex].length - 1];
|
||||||
|
|
|
||||||
|
|
@ -1,395 +1,16 @@
|
||||||
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
import { useContext, useEffect } from "react";
|
||||||
import ReactFlow, {
|
import Page from "./components/PageComponent";
|
||||||
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 { TabsContext } from "../../contexts/tabsContext";
|
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";
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
const nodeTypes = {
|
export default function FlowPage(){
|
||||||
genericNode: GenericNode,
|
const { flows, tabId, setTabId } = useContext(TabsContext);
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
const {id} = useParams();
|
const {id} = useParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(flows.length !== 0){
|
setTabId(id);
|
||||||
console.log(id, "o id é esse");
|
}, [id])
|
||||||
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);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full flex basis-auto flex-1 overflow-hidden">
|
flows.length > 0 && tabId !== "" && flows.findIndex(flow => flow.id === tabId) !== -1 &&
|
||||||
<ExtraSidebar />
|
<Page flow={flows.find(flow => flow.id === tabId)} />
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
@ -23,14 +23,14 @@ import { updateFlowInDatabase } from "../../../../controllers/API";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
export const CardComponent = ({
|
export const CardComponent = ({
|
||||||
flow,
|
flow,
|
||||||
idx,
|
id,
|
||||||
removeFlow,
|
removeFlow,
|
||||||
setTabIndex,
|
setTabId,
|
||||||
}: {
|
}: {
|
||||||
flow: FlowType;
|
flow: FlowType;
|
||||||
idx: number;
|
id: string;
|
||||||
removeFlow: (id: string) => void;
|
removeFlow: (id: string) => void;
|
||||||
setTabIndex: (idx: number) => void;
|
setTabId: (id: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
const { updateFlow } = useContext(TabsContext);
|
const { updateFlow } = useContext(TabsContext);
|
||||||
|
|
@ -68,7 +68,7 @@ export const CardComponent = ({
|
||||||
<Edit
|
<Edit
|
||||||
className="w-4"
|
className="w-4"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTabIndex(idx);
|
setTabId(id);
|
||||||
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -26,37 +26,21 @@ import { MenuBar } from "./components/menuBar";
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const {
|
const {
|
||||||
flows,
|
flows,
|
||||||
addFlow,
|
|
||||||
removeFlow,
|
removeFlow,
|
||||||
tabIndex,
|
setTabId,
|
||||||
setTabIndex,
|
|
||||||
} = useContext(TabsContext);
|
} = 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 (
|
return (
|
||||||
flows.length !== 0 && (
|
Object.keys(flows).length !== 0 && (
|
||||||
<div
|
<div
|
||||||
className="w-full h-full flex flex-col bg-muted"
|
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">
|
<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
|
<CardComponent
|
||||||
flow={flow}
|
flow={flows[flow]}
|
||||||
idx={idx}
|
id={flow}
|
||||||
removeFlow={removeFlow}
|
removeFlow={removeFlow}
|
||||||
setTabIndex={setTabIndex}
|
setTabId={setTabId}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { FlowType } from "../flow";
|
import { FlowType } from "../flow";
|
||||||
|
|
||||||
export type TabsContextType = {
|
export type TabsContextType = {
|
||||||
save: () => void;
|
tabId: string;
|
||||||
tabIndex: number;
|
setTabId: (index: string) => void;
|
||||||
setTabIndex: (index: number) => void;
|
|
||||||
flows: Array<FlowType>;
|
flows: Array<FlowType>;
|
||||||
removeFlow: (id: string) => void;
|
removeFlow: (id: string) => void;
|
||||||
addFlow: (flowData?: FlowType, newFlow?: boolean) => void;
|
addFlow: (flowData?: FlowType, newFlow?: boolean) => void;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"baseUrl": "."
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src"
|
"src"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue