Fixed Copy and Paste, refactored tabsContext code
This commit is contained in:
parent
f465d9712d
commit
610beb18dd
4 changed files with 629 additions and 543 deletions
|
|
@ -1,273 +1,335 @@
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
useRef,
|
useRef,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
useContext,
|
useContext,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { FlowType } from "../types/flow";
|
import { FlowType, NodeType } from "../types/flow";
|
||||||
import { LangFlowState, TabsContextType } from "../types/tabs";
|
import { LangFlowState, TabsContextType } from "../types/tabs";
|
||||||
import { normalCaseToSnakeCase, updateObject, updateTemplate } from "../utils";
|
import { normalCaseToSnakeCase, updateObject, updateTemplate } from "../utils";
|
||||||
import { alertContext } from "./alertContext";
|
import { alertContext } from "./alertContext";
|
||||||
import { typesContext } from "./typesContext";
|
import { typesContext } from "./typesContext";
|
||||||
import { APITemplateType, TemplateVariableType } from "../types/api";
|
import { APITemplateType, TemplateVariableType } from "../types/api";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { addEdge } from "reactflow";
|
||||||
|
|
||||||
const TabsContextInitialValue: TabsContextType = {
|
const TabsContextInitialValue: TabsContextType = {
|
||||||
save: () => {},
|
save: () => {},
|
||||||
tabIndex: 0,
|
tabIndex: 0,
|
||||||
setTabIndex: (index: number) => {},
|
setTabIndex: (index: number) => {},
|
||||||
flows: [],
|
flows: [],
|
||||||
removeFlow: (id: string) => {},
|
removeFlow: (id: string) => {},
|
||||||
addFlow: (flowData?: any) => {},
|
addFlow: (flowData?: any) => {},
|
||||||
updateFlow: (newFlow: FlowType) => {},
|
updateFlow: (newFlow: FlowType) => {},
|
||||||
incrementNodeId: () => 0,
|
incrementNodeId: () => uuidv4(),
|
||||||
downloadFlow: (flow: FlowType) => {},
|
downloadFlow: (flow: FlowType) => {},
|
||||||
uploadFlow: () => {},
|
uploadFlow: () => {},
|
||||||
hardReset: () => {},
|
hardReset: () => {},
|
||||||
disableCP: false,
|
disableCP:false,
|
||||||
setDisableCP: (state: boolean) => {},
|
setDisableCP:(state:boolean)=>{},
|
||||||
|
getNodeId: () => "",
|
||||||
|
paste: (selection: {nodes: any, edges: any}, position: {x: number, y: number}) => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TabsContext = createContext<TabsContextType>(
|
export const TabsContext = createContext<TabsContextType>(
|
||||||
TabsContextInitialValue
|
TabsContextInitialValue
|
||||||
);
|
);
|
||||||
|
|
||||||
export function TabsProvider({ children }: { children: ReactNode }) {
|
export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
const { setNoticeData } = useContext(alertContext);
|
const { setNoticeData } = useContext(alertContext);
|
||||||
const [tabIndex, setTabIndex] = useState(0);
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||||
const [id, setId] = useState(uuidv4());
|
const [id, setId] = useState(uuidv4());
|
||||||
const { templates } = useContext(typesContext);
|
const { templates, reactFlowInstance } = useContext(typesContext);
|
||||||
|
|
||||||
const newNodeId = useRef(0);
|
const newNodeId = useRef(uuidv4());
|
||||||
function incrementNodeId() {
|
function incrementNodeId() {
|
||||||
newNodeId.current = newNodeId.current + 1;
|
newNodeId.current = uuidv4();
|
||||||
return newNodeId.current;
|
return newNodeId.current;
|
||||||
}
|
}
|
||||||
function save() {
|
function save() {
|
||||||
if (flows.length !== 0)
|
if (flows.length !== 0)
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
"tabsData",
|
"tabsData",
|
||||||
JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current })
|
JSON.stringify({ tabIndex, flows, id})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//save tabs locally
|
//save tabs locally
|
||||||
save();
|
// console.log(id)
|
||||||
}, [flows, id, tabIndex, newNodeId]);
|
save();
|
||||||
|
}, [flows, id, tabIndex, newNodeId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//get tabs locally saved
|
//get tabs locally saved
|
||||||
let cookie = window.localStorage.getItem("tabsData");
|
let cookie = window.localStorage.getItem("tabsData");
|
||||||
if (cookie && Object.keys(templates).length > 0) {
|
if (cookie && Object.keys(templates).length > 0) {
|
||||||
let cookieObject: LangFlowState = JSON.parse(cookie);
|
let cookieObject: LangFlowState = JSON.parse(cookie);
|
||||||
cookieObject.flows.forEach((flow) => {
|
cookieObject.flows.forEach((flow) => {
|
||||||
// check if flow.data is null
|
flow.data.nodes.forEach((node) => {
|
||||||
if (flow.data) {
|
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
|
||||||
flow.data.nodes.forEach((node) => {
|
node.data.node.template = updateTemplate(
|
||||||
// check if node.data.type is in templates
|
templates[node.data.type][
|
||||||
if (
|
"template"
|
||||||
Object.keys(templates).includes(node.data.type) &&
|
] as unknown as APITemplateType,
|
||||||
Object.keys(templates[node.data.type]["template"]).length > 0
|
|
||||||
) {
|
|
||||||
node.data.node.base_classes =
|
|
||||||
templates[node.data.type]["base_classes"];
|
|
||||||
flow.data.edges.forEach((edge) => {
|
|
||||||
if (edge.source === node.id) {
|
|
||||||
edge.sourceHandle = edge.sourceHandle
|
|
||||||
.split("|")
|
|
||||||
.slice(0, 2)
|
|
||||||
.concat(templates[node.data.type]["base_classes"])
|
|
||||||
.join("|");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
node.data.node.description =
|
|
||||||
templates[node.data.type]["description"];
|
|
||||||
node.data.node.template = updateTemplate(
|
|
||||||
templates[node.data.type][
|
|
||||||
"template"
|
|
||||||
] as unknown as APITemplateType,
|
|
||||||
node.data.node.template as APITemplateType
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setTabIndex(cookieObject.tabIndex);
|
|
||||||
setFlows(cookieObject.flows);
|
|
||||||
setId(cookieObject.id);
|
|
||||||
newNodeId.current = cookieObject.nodeId;
|
|
||||||
}
|
|
||||||
}, [templates]);
|
|
||||||
|
|
||||||
function hardReset() {
|
node.data.node.template as APITemplateType
|
||||||
newNodeId.current = 0;
|
);
|
||||||
setTabIndex(0);
|
}
|
||||||
setFlows([]);
|
});
|
||||||
setId(uuidv4());
|
});
|
||||||
}
|
setTabIndex(cookieObject.tabIndex);
|
||||||
|
setFlows(cookieObject.flows);
|
||||||
|
setId(cookieObject.id);
|
||||||
|
}
|
||||||
|
}, [templates]);
|
||||||
|
|
||||||
/**
|
function hardReset() {
|
||||||
* Downloads the current flow as a JSON file
|
newNodeId.current = uuidv4();
|
||||||
*/
|
setTabIndex(0);
|
||||||
function downloadFlow(flow: FlowType) {
|
setFlows([]);
|
||||||
// create a data URI with the current flow data
|
setId(uuidv4());
|
||||||
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
}
|
||||||
JSON.stringify(flow)
|
|
||||||
)}`;
|
|
||||||
|
|
||||||
// create a link element and set its properties
|
/**
|
||||||
const link = document.createElement("a");
|
* Downloads the current flow as a JSON file
|
||||||
link.href = jsonString;
|
*/
|
||||||
link.download = `${flows[tabIndex].name}.json`;
|
function downloadFlow(flow: FlowType) {
|
||||||
|
// create a data URI with the current flow data
|
||||||
|
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
||||||
|
JSON.stringify(flow)
|
||||||
|
)}`;
|
||||||
|
|
||||||
// simulate a click on the link element to trigger the download
|
// create a link element and set its properties
|
||||||
link.click();
|
const link = document.createElement("a");
|
||||||
setNoticeData({
|
link.href = jsonString;
|
||||||
title: "Warning: Critical data,JSON file may including API keys.",
|
link.download = `${flows[tabIndex].name}.json`;
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// simulate a click on the link element to trigger the download
|
||||||
* Creates a file input and listens to a change event to upload a JSON flow file.
|
link.click();
|
||||||
* If the file type is application/json, the file is read and parsed into a JSON object.
|
setNoticeData({
|
||||||
* The resulting JSON object is passed to the addFlow function.
|
title: "Warning: Critical data,JSON file may including API keys.",
|
||||||
*/
|
});
|
||||||
function uploadFlow() {
|
}
|
||||||
// create a file input
|
|
||||||
const input = document.createElement("input");
|
|
||||||
input.type = "file";
|
|
||||||
// add a change event listener to the file input
|
|
||||||
input.onchange = (e: Event) => {
|
|
||||||
// check if the file type is application/json
|
|
||||||
if ((e.target as HTMLInputElement).files[0].type === "application/json") {
|
|
||||||
// get the file from the file input
|
|
||||||
const file = (e.target as HTMLInputElement).files[0];
|
|
||||||
// read the file as text
|
|
||||||
file.text().then((text) => {
|
|
||||||
// parse the text into a JSON object
|
|
||||||
let flow: FlowType = JSON.parse(text);
|
|
||||||
|
|
||||||
addFlow(flow);
|
function getNodeId() {
|
||||||
});
|
return `dndnode_` + incrementNodeId();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
// trigger the file input click event to open the file dialog
|
|
||||||
input.click();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Removes a flow from an array of flows based on its id.
|
|
||||||
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
|
|
||||||
* @param {string} id - The id of the flow to remove.
|
|
||||||
*/
|
|
||||||
function removeFlow(id: string) {
|
|
||||||
setFlows((prevState) => {
|
|
||||||
const newFlows = [...prevState];
|
|
||||||
const index = newFlows.findIndex((flow) => flow.id === id);
|
|
||||||
if (index >= 0) {
|
|
||||||
if (index === tabIndex) {
|
|
||||||
setTabIndex(flows.length - 2);
|
|
||||||
newFlows.splice(index, 1);
|
|
||||||
} else {
|
|
||||||
let flowId = flows[tabIndex].id;
|
|
||||||
newFlows.splice(index, 1);
|
|
||||||
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newFlows;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Add a new flow to the list of flows.
|
|
||||||
* @param flow Optional flow to add.
|
|
||||||
*/
|
|
||||||
function addFlow(flow?: FlowType) {
|
|
||||||
// Get data from the flow or set it to null if there's no flow provided.
|
|
||||||
const data = flow?.data ? flow.data : null;
|
|
||||||
const description = flow?.description ? flow.description : "";
|
|
||||||
|
|
||||||
if (data) {
|
/**
|
||||||
data.nodes.forEach((node) => {
|
* Creates a file input and listens to a change event to upload a JSON flow file.
|
||||||
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
|
* If the file type is application/json, the file is read and parsed into a JSON object.
|
||||||
node.data.node.base_classes =
|
* The resulting JSON object is passed to the addFlow function.
|
||||||
templates[node.data.type]["base_classes"];
|
*/
|
||||||
data.edges.forEach((edge) => {
|
function uploadFlow() {
|
||||||
if (edge.source === node.id) {
|
// create a file input
|
||||||
edge.sourceHandle = edge.sourceHandle
|
const input = document.createElement("input");
|
||||||
.split("|")
|
input.type = "file";
|
||||||
.slice(0, 2)
|
// add a change event listener to the file input
|
||||||
.concat(templates[node.data.type]["base_classes"])
|
input.onchange = (e: Event) => {
|
||||||
.join("|");
|
// check if the file type is application/json
|
||||||
}
|
if ((e.target as HTMLInputElement).files[0].type === "application/json") {
|
||||||
});
|
// get the file from the file input
|
||||||
node.data.node.description = templates[node.data.type]["description"];
|
const file = (e.target as HTMLInputElement).files[0];
|
||||||
node.data.node.template = updateTemplate(
|
// read the file as text
|
||||||
templates[node.data.type]["template"] as unknown as APITemplateType,
|
file.text().then((text) => {
|
||||||
node.data.node.template as APITemplateType
|
// parse the text into a JSON object
|
||||||
);
|
let flow: FlowType = JSON.parse(text);
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log(data);
|
|
||||||
}
|
|
||||||
// Create a new flow with a default name if no flow is provided.
|
|
||||||
let newFlow: FlowType = {
|
|
||||||
description,
|
|
||||||
name: flow?.name ?? "New Flow",
|
|
||||||
id: uuidv4(),
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Increment the ID counter.
|
addFlow(flow);
|
||||||
setId(uuidv4());
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// trigger the file input click event to open the file dialog
|
||||||
|
input.click();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Removes a flow from an array of flows based on its id.
|
||||||
|
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
|
||||||
|
* @param {string} id - The id of the flow to remove.
|
||||||
|
*/
|
||||||
|
function removeFlow(id: string) {
|
||||||
|
setFlows((prevState) => {
|
||||||
|
const newFlows = [...prevState];
|
||||||
|
const index = newFlows.findIndex((flow) => flow.id === id);
|
||||||
|
if (index >= 0) {
|
||||||
|
if (index === tabIndex) {
|
||||||
|
setTabIndex(flows.length - 2);
|
||||||
|
newFlows.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
let flowId = flows[tabIndex].id;
|
||||||
|
newFlows.splice(index, 1);
|
||||||
|
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newFlows;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Add a new flow to the list of flows.
|
||||||
|
* @param flow Optional flow to add.
|
||||||
|
*/
|
||||||
|
|
||||||
// Add the new flow to the list of flows.
|
function paste(selectionInstance, position){
|
||||||
setFlows((prevState) => {
|
console.log(position);
|
||||||
const newFlows = [...prevState, newFlow];
|
console.log(selectionInstance)
|
||||||
return newFlows;
|
let minimumX = Infinity;
|
||||||
});
|
let minimumY = Infinity;
|
||||||
|
let idsMap = {};
|
||||||
|
let nodes = reactFlowInstance.getNodes();
|
||||||
|
let edges = reactFlowInstance.getEdges();
|
||||||
|
selectionInstance.nodes.forEach((n) => {
|
||||||
|
if (n.position.y < minimumY) {
|
||||||
|
minimumY = n.position.y;
|
||||||
|
}
|
||||||
|
if (n.position.x < minimumX) {
|
||||||
|
minimumX = n.position.x;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Set the tab index to the new flow.
|
const insidePosition = reactFlowInstance.project(position);
|
||||||
setTabIndex(flows.length);
|
|
||||||
}
|
selectionInstance.nodes.forEach((n) => {
|
||||||
/**
|
// Generate a unique node ID
|
||||||
* Updates an existing flow with new data
|
let newId = getNodeId();
|
||||||
* @param newFlow - The new flow object containing the updated data
|
idsMap[n.id] = newId;
|
||||||
*/
|
|
||||||
function updateFlow(newFlow: FlowType) {
|
// Create a new node object
|
||||||
setFlows((prevState) => {
|
const newNode: NodeType = {
|
||||||
const newFlows = [...prevState];
|
id: newId,
|
||||||
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
|
type: "genericNode",
|
||||||
if (index !== -1) {
|
position: {
|
||||||
newFlows[index].description = newFlow.description ?? "";
|
x: insidePosition.x + n.position.x - minimumX,
|
||||||
newFlows[index].data = newFlow.data;
|
y: insidePosition.y + n.position.y - minimumY,
|
||||||
newFlows[index].name = newFlow.name;
|
},
|
||||||
}
|
data: {
|
||||||
return newFlows;
|
...n.data,
|
||||||
});
|
id: newId,
|
||||||
}
|
},
|
||||||
const [disableCP, setDisableCP] = useState(false);
|
};
|
||||||
|
|
||||||
|
// Add the new node to the list of nodes in state
|
||||||
|
nodes = nodes
|
||||||
|
.map((e) => ({ ...e, selected: false }))
|
||||||
|
.concat({ ...newNode, selected: false })
|
||||||
|
console.log(nodes);
|
||||||
|
});
|
||||||
|
reactFlowInstance.setNodes(nodes);
|
||||||
|
|
||||||
|
selectionInstance.edges.forEach((e) => {
|
||||||
|
let source = idsMap[e.source];
|
||||||
|
let target = idsMap[e.target];
|
||||||
|
let sourceHandleSplitted = e.sourceHandle.split("|");
|
||||||
|
let sourceHandle =
|
||||||
|
sourceHandleSplitted[0] +
|
||||||
|
"|" +
|
||||||
|
source +
|
||||||
|
"|" +
|
||||||
|
sourceHandleSplitted.slice(2).join("|");
|
||||||
|
let targetHandleSplitted = e.targetHandle.split("|");
|
||||||
|
let targetHandle =
|
||||||
|
targetHandleSplitted.slice(0, -1).join("|") + "|" + target;
|
||||||
|
let id =
|
||||||
|
"reactflow__edge-" +
|
||||||
|
source +
|
||||||
|
sourceHandle +
|
||||||
|
"-" +
|
||||||
|
target +
|
||||||
|
targetHandle;
|
||||||
|
edges = addEdge(
|
||||||
|
{
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
sourceHandle,
|
||||||
|
targetHandle,
|
||||||
|
id,
|
||||||
|
className: "animate-pulse",
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
edges.map((e) => ({ ...e, selected: false }))
|
||||||
|
);
|
||||||
|
console.log(edges);
|
||||||
|
});
|
||||||
|
reactFlowInstance.setEdges(edges);
|
||||||
|
};
|
||||||
|
|
||||||
|
function addFlow(flow?: FlowType) {
|
||||||
|
// Get data from the flow or set it to null if there's no flow provided.
|
||||||
|
const data = flow?.data ? flow.data : null;
|
||||||
|
const description = flow?.description ? flow.description : "";
|
||||||
|
|
||||||
return (
|
if (data) {
|
||||||
<TabsContext.Provider
|
data.nodes.forEach((node) => {
|
||||||
value={{
|
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
|
||||||
disableCP,
|
node.data.node.template = updateTemplate(
|
||||||
setDisableCP,
|
templates[node.data.type]["template"] as unknown as APITemplateType,
|
||||||
save,
|
node.data.node.template as APITemplateType
|
||||||
hardReset,
|
);
|
||||||
tabIndex,
|
}
|
||||||
setTabIndex,
|
});
|
||||||
flows,
|
}
|
||||||
incrementNodeId,
|
// Create a new flow with a default name if no flow is provided.
|
||||||
removeFlow,
|
let newFlow: FlowType = {
|
||||||
addFlow,
|
description,
|
||||||
updateFlow,
|
name: flow?.name ?? "New Flow",
|
||||||
downloadFlow,
|
id: uuidv4(),
|
||||||
uploadFlow,
|
data,
|
||||||
}}
|
};
|
||||||
>
|
|
||||||
{children}
|
// Increment the ID counter.
|
||||||
</TabsContext.Provider>
|
setId(uuidv4());
|
||||||
);
|
|
||||||
}
|
// Add the new flow to the list of flows.
|
||||||
|
|
||||||
|
setFlows((prevState) => {
|
||||||
|
const newFlows = [...prevState, newFlow];
|
||||||
|
return newFlows;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the tab index to the new flow.
|
||||||
|
setTabIndex(flows.length);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Updates an existing flow with new data
|
||||||
|
* @param newFlow - The new flow object containing the updated data
|
||||||
|
*/
|
||||||
|
function updateFlow(newFlow: FlowType) {
|
||||||
|
setFlows((prevState) => {
|
||||||
|
const newFlows = [...prevState];
|
||||||
|
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
newFlows[index].description = newFlow.description ?? "";
|
||||||
|
newFlows[index].data = newFlow.data;
|
||||||
|
newFlows[index].name = newFlow.name;
|
||||||
|
}
|
||||||
|
return newFlows;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const [disableCP, setDisableCP] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TabsContext.Provider
|
||||||
|
value={{
|
||||||
|
disableCP,
|
||||||
|
setDisableCP,
|
||||||
|
save,
|
||||||
|
hardReset,
|
||||||
|
tabIndex,
|
||||||
|
setTabIndex,
|
||||||
|
flows,
|
||||||
|
incrementNodeId,
|
||||||
|
removeFlow,
|
||||||
|
addFlow,
|
||||||
|
updateFlow,
|
||||||
|
downloadFlow,
|
||||||
|
uploadFlow,
|
||||||
|
getNodeId,
|
||||||
|
paste,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TabsContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -47,8 +47,8 @@ export default function TabComponent({
|
||||||
className="bg-transparent focus:border-none active:outline hover:outline focus:outline outline-gray-300 rounded-md w-28"
|
className="bg-transparent focus:border-none active:outline hover:outline focus:outline outline-gray-300 rounded-md w-28"
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
setIsRename(false);
|
setIsRename(false);
|
||||||
|
setDisableCP(false);
|
||||||
if (value !== "") {
|
if (value !== "") {
|
||||||
setDisableCP(false);
|
|
||||||
let newFlow = _.cloneDeep(flow);
|
let newFlow = _.cloneDeep(flow);
|
||||||
newFlow.name = value;
|
newFlow.name = value;
|
||||||
updateFlow(newFlow);
|
updateFlow(newFlow);
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,23 @@
|
||||||
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
Background,
|
Background,
|
||||||
Controls,
|
Controls,
|
||||||
addEdge,
|
addEdge,
|
||||||
useEdgesState,
|
useEdgesState,
|
||||||
useNodesState,
|
useNodesState,
|
||||||
useReactFlow,
|
useReactFlow,
|
||||||
updateEdge,
|
updateEdge,
|
||||||
EdgeChange,
|
EdgeChange,
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
useKeyPress,
|
useKeyPress,
|
||||||
useOnSelectionChange,
|
NodeDragHandler,
|
||||||
NodeDragHandler,
|
OnEdgesDelete,
|
||||||
OnEdgesDelete,
|
OnNodesDelete,
|
||||||
OnNodesDelete,
|
SelectionDragHandler,
|
||||||
SelectionDragHandler,
|
useOnViewportChange,
|
||||||
|
OnSelectionChangeParams,
|
||||||
|
OnNodesChange,
|
||||||
} from "reactflow";
|
} from "reactflow";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { locationContext } from "../../contexts/locationContext";
|
import { locationContext } from "../../contexts/locationContext";
|
||||||
|
|
@ -28,311 +30,331 @@ import { typesContext } from "../../contexts/typesContext";
|
||||||
import ConnectionLineComponent from "./components/ConnectionLineComponent";
|
import ConnectionLineComponent from "./components/ConnectionLineComponent";
|
||||||
import { FlowType, NodeType } from "../../types/flow";
|
import { FlowType, NodeType } from "../../types/flow";
|
||||||
import { APIClassType } from "../../types/api";
|
import { APIClassType } from "../../types/api";
|
||||||
import { isValidConnection } from "../../utils";
|
import {
|
||||||
|
isValidConnection,
|
||||||
|
} from "../../utils";
|
||||||
import useUndoRedo from "./hooks/useUndoRedo";
|
import useUndoRedo from "./hooks/useUndoRedo";
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
genericNode: GenericNode,
|
genericNode: GenericNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default function FlowPage({ flow }: { flow: FlowType }) {
|
export default function FlowPage({ flow }: { flow: FlowType }) {
|
||||||
let { updateFlow, incrementNodeId, disableCP} =
|
let { updateFlow, disableCP, addFlow, getNodeId, paste } =
|
||||||
useContext(TabsContext);
|
useContext(TabsContext);
|
||||||
const { types, reactFlowInstance, setReactFlowInstance, templates } =
|
const { types, reactFlowInstance, setReactFlowInstance, templates } =
|
||||||
useContext(typesContext);
|
useContext(typesContext);
|
||||||
const reactFlowWrapper = useRef(null);
|
const reactFlowWrapper = useRef(null);
|
||||||
|
|
||||||
const { undo, redo, canUndo, canRedo, takeSnapshot } = useUndoRedo();
|
const { undo, redo, canUndo, canRedo, takeSnapshot } = useUndoRedo();
|
||||||
|
|
||||||
const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
||||||
if ((event.ctrlKey || event.metaKey) && (event.key === 'c') && lastSelection && !disableCP) {
|
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||||
event.preventDefault();
|
const [lastSelection, setLastSelection] =
|
||||||
setLastCopiedSelection(lastSelection);
|
useState<OnSelectionChangeParams>(null);
|
||||||
}
|
|
||||||
if ((event.ctrlKey || event.metaKey) && (event.key === 'v') && lastCopiedSelection && !disableCP) {
|
const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
|
||||||
event.preventDefault();
|
|
||||||
paste();
|
useEffect(() => {
|
||||||
}
|
// this effect is used to attach the global event handlers
|
||||||
}
|
|
||||||
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
const [lastSelection, setLastSelection] = useState(null);
|
console.log("keydownou", lastCopiedSelection, position)
|
||||||
const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
|
if (
|
||||||
|
(event.ctrlKey || event.metaKey) &&
|
||||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
event.key === "c" &&
|
||||||
|
lastSelection &&
|
||||||
const handleMouseMove = (event) => {
|
!disableCP
|
||||||
setPosition({ x: event.clientX, y: event.clientY });
|
) {
|
||||||
};
|
event.preventDefault();
|
||||||
|
setLastCopiedSelection(_.cloneDeep(lastSelection));
|
||||||
useOnSelectionChange({
|
}
|
||||||
onChange: (flow) => { setLastSelection(flow); },
|
if (
|
||||||
})
|
(event.ctrlKey || event.metaKey) &&
|
||||||
|
event.key === "v" &&
|
||||||
let paste = () => {
|
lastCopiedSelection &&
|
||||||
let minimumX = Infinity;
|
!disableCP
|
||||||
let minimumY = Infinity;
|
) {
|
||||||
let idsMap = {};
|
event.preventDefault();
|
||||||
lastCopiedSelection.nodes.forEach((n) => {
|
let bounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||||
if (n.position.y < minimumY) {
|
paste(lastCopiedSelection, {
|
||||||
minimumY = n.position.y
|
x: position.x - bounds.left,
|
||||||
}
|
y: position.y - bounds.top,
|
||||||
if (n.position.x < minimumX) {
|
});
|
||||||
minimumX = n.position.x;
|
}
|
||||||
}
|
if (
|
||||||
});
|
(event.ctrlKey || event.metaKey) &&
|
||||||
|
event.key === "g" &&
|
||||||
const bounds = reactFlowWrapper.current.getBoundingClientRect();
|
lastSelection
|
||||||
const insidePosition = reactFlowInstance.project({
|
) {
|
||||||
x: position.x - bounds.left,
|
event.preventDefault();
|
||||||
y: position.y - bounds.top
|
// addFlow(newFlow, false);
|
||||||
});
|
}
|
||||||
|
};
|
||||||
lastCopiedSelection.nodes.forEach((n) => {
|
const handleMouseMove = (event) => {
|
||||||
|
setPosition({ x: event.clientX, y: event.clientY });
|
||||||
// Generate a unique node ID
|
};
|
||||||
let newId = getId();
|
|
||||||
idsMap[n.id] = newId;
|
document.addEventListener('keydown', onKeyDown);
|
||||||
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
// Create a new node object
|
|
||||||
const newNode: NodeType = {
|
return () => {
|
||||||
id: newId,
|
document.removeEventListener('keydown', onKeyDown);
|
||||||
type: "genericNode",
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
position: {
|
};
|
||||||
x: insidePosition.x + n.position.x - minimumX,
|
}, [position, lastCopiedSelection, lastSelection]);
|
||||||
y: insidePosition.y + n.position.y - minimumY,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
...n.data,
|
|
||||||
id: newId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the new node to the list of nodes in state
|
|
||||||
setNodes((nds) => nds.map((e) => ({ ...e, selected: false })).concat({ ...newNode, selected: false }));
|
|
||||||
})
|
|
||||||
|
|
||||||
lastCopiedSelection.edges.forEach((e) => {
|
|
||||||
let source = idsMap[e.source];
|
|
||||||
let target = idsMap[e.target];
|
|
||||||
let sourceHandleSplitted = e.sourceHandle.split('|');
|
|
||||||
let sourceHandle = sourceHandleSplitted[0] + '|' + source + '|' + sourceHandleSplitted.slice(2).join('|');
|
|
||||||
let targetHandleSplitted = e.targetHandle.split('|');
|
|
||||||
let targetHandle = targetHandleSplitted.slice(0, -1).join('|') + '|' + target;
|
|
||||||
let id = "reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle;
|
|
||||||
setEdges((eds) =>
|
|
||||||
addEdge({ source, target, sourceHandle, targetHandle, id, className: "animate-pulse", selected: false }, eds.map((e) => ({ ...e, selected: false })))
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
|
const [selectionMenuVisible, setSelectionMenuVisible] = useState(false);
|
||||||
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);
|
|
||||||
|
|
||||||
function getId() {
|
|
||||||
return `dndnode_` + incrementNodeId();
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
|
||||||
if (reactFlowInstance && flow) {
|
const { setErrorData } = useContext(alertContext);
|
||||||
flow.data = reactFlowInstance.toObject();
|
const [nodes, setNodes, onNodesChange] = useNodesState(
|
||||||
updateFlow(flow);
|
flow.data?.nodes ?? []
|
||||||
}
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
const [edges, setEdges, onEdgesChange] = useEdgesState(
|
||||||
}, [nodes, edges]);
|
flow.data?.edges ?? []
|
||||||
//update flow when tabs change
|
);
|
||||||
useEffect(() => {
|
const { setViewport } = useReactFlow();
|
||||||
setNodes(flow?.data?.nodes ?? []);
|
const edgeUpdateSuccessful = useRef(true);
|
||||||
setEdges(flow?.data?.edges ?? []);
|
useEffect(() => {
|
||||||
if (reactFlowInstance) {
|
if (reactFlowInstance && flow) {
|
||||||
setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 });
|
flow.data = reactFlowInstance.toObject();
|
||||||
}
|
updateFlow(flow);
|
||||||
}, [flow, reactFlowInstance, setEdges, setNodes, setViewport]);
|
}
|
||||||
//set extra sidebar
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
useEffect(() => {
|
}, [nodes, edges]);
|
||||||
setExtraComponent(<ExtraSidebar />);
|
//update flow when tabs change
|
||||||
setExtraNavigation({ title: "Components" });
|
useEffect(() => {
|
||||||
}, [setExtraComponent, setExtraNavigation]);
|
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(
|
const onEdgesChangeMod = useCallback(
|
||||||
(s: EdgeChange[]) => {
|
(s: EdgeChange[]) => {
|
||||||
onEdgesChange(s);
|
onEdgesChange(s);
|
||||||
setNodes((x) => {
|
setNodes((x) => {
|
||||||
let newX = _.cloneDeep(x);
|
let newX = _.cloneDeep(x);
|
||||||
return newX;
|
return newX;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[onEdgesChange, setNodes]
|
[onEdgesChange, setNodes]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onConnect = useCallback(
|
const onConnect = useCallback(
|
||||||
(params: Connection) => {
|
(params: Connection) => {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
setEdges((eds) =>
|
setEdges((eds) =>
|
||||||
addEdge({ ...params, className: "animate-pulse" }, eds)
|
addEdge(
|
||||||
);
|
{
|
||||||
setNodes((x) => {
|
...params,
|
||||||
let newX = _.cloneDeep(x);
|
style:
|
||||||
return newX;
|
params.targetHandle.split("|")[0] === "Text"
|
||||||
});
|
? { stroke: "#333333", strokeWidth: 2 }
|
||||||
},
|
: { stroke: "#222222" },
|
||||||
[setEdges, setNodes, takeSnapshot]
|
className:
|
||||||
);
|
params.targetHandle.split("|")[0] === "Text"
|
||||||
|
? ""
|
||||||
|
: "animate-pulse",
|
||||||
|
animated: params.targetHandle.split("|")[0] === "Text",
|
||||||
|
},
|
||||||
|
eds
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setNodes((x) => {
|
||||||
|
let newX = _.cloneDeep(x);
|
||||||
|
return newX;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setEdges, setNodes, takeSnapshot]
|
||||||
|
);
|
||||||
|
|
||||||
const onNodeDragStart: NodeDragHandler = useCallback(() => {
|
const onNodeDragStart: NodeDragHandler = useCallback(() => {
|
||||||
// 👇 make dragging a node undoable
|
// 👇 make dragging a node undoable
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
// 👉 you can place your event handlers here
|
// 👉 you can place your event handlers here
|
||||||
}, [takeSnapshot]);
|
}, [takeSnapshot]);
|
||||||
|
|
||||||
const onSelectionDragStart: SelectionDragHandler = useCallback(() => {
|
const onSelectionDragStart: SelectionDragHandler = useCallback(() => {
|
||||||
// 👇 make dragging a selection undoable
|
// 👇 make dragging a selection undoable
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
}, [takeSnapshot]);
|
}, [takeSnapshot]);
|
||||||
|
|
||||||
|
const onEdgesDelete: OnEdgesDelete = useCallback(() => {
|
||||||
|
// 👇 make deleting edges undoable
|
||||||
|
takeSnapshot();
|
||||||
|
}, [takeSnapshot]);
|
||||||
|
|
||||||
const onEdgesDelete: OnEdgesDelete = useCallback(() => {
|
const onDragOver = useCallback((event: React.DragEvent) => {
|
||||||
// 👇 make deleting edges undoable
|
event.preventDefault();
|
||||||
takeSnapshot();
|
event.dataTransfer.dropEffect = "move";
|
||||||
}, [takeSnapshot]);
|
}, []);
|
||||||
|
|
||||||
const onDragOver = useCallback((event: React.DragEvent) => {
|
const onDrop = useCallback(
|
||||||
event.preventDefault();
|
(event: React.DragEvent) => {
|
||||||
event.dataTransfer.dropEffect = "move";
|
event.preventDefault();
|
||||||
}, []);
|
takeSnapshot();
|
||||||
|
|
||||||
const onDrop = useCallback(
|
// Get the current bounds of the ReactFlow wrapper element
|
||||||
(event: React.DragEvent) => {
|
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||||
event.preventDefault();
|
|
||||||
takeSnapshot();
|
|
||||||
|
|
||||||
// Get the current bounds of the ReactFlow wrapper element
|
// Extract the data from the drag event and parse it as a JSON object
|
||||||
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
let data: { type: string; node?: APIClassType } = JSON.parse(
|
||||||
|
event.dataTransfer.getData("json")
|
||||||
|
);
|
||||||
|
|
||||||
// Extract the data from the drag event and parse it as a JSON object
|
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
|
||||||
let data: { type: string; node?: APIClassType } = JSON.parse(
|
// Calculate the position where the node should be created
|
||||||
event.dataTransfer.getData("json")
|
const position = reactFlowInstance.project({
|
||||||
);
|
x: event.clientX - reactflowBounds.left,
|
||||||
|
y: event.clientY - reactflowBounds.top,
|
||||||
|
});
|
||||||
|
|
||||||
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
|
// Generate a unique node ID
|
||||||
if (
|
let newId = getNodeId();
|
||||||
data.type !== "chatInput" ||
|
let newNode: NodeType;
|
||||||
(data.type === "chatInput" &&
|
|
||||||
!reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode"))
|
|
||||||
) {
|
|
||||||
// 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
|
if (data.type !== "groupNode") {
|
||||||
let newId = getId();
|
// 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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Create a new node object
|
// Add the new node to the list of nodes in state
|
||||||
const newNode: NodeType = {
|
}
|
||||||
id: newId,
|
setNodes((nds) => nds.concat(newNode));
|
||||||
type: "genericNode",
|
},
|
||||||
position,
|
// Specify dependencies for useCallback
|
||||||
data: {
|
[getNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]
|
||||||
...data,
|
);
|
||||||
id: newId,
|
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the new node to the list of nodes in state
|
const onDelete = useCallback(
|
||||||
setNodes((nds) => nds.concat(newNode));
|
(mynodes) => {
|
||||||
} else {
|
takeSnapshot();
|
||||||
// If a chat input node already exists, set an error message
|
setEdges(
|
||||||
setErrorData({
|
edges.filter(
|
||||||
title: "Error creating node",
|
(ns) => !mynodes.some((n) => ns.source === n.id || ns.target === n.id)
|
||||||
list: ["There can't be more than one chat input."],
|
)
|
||||||
});
|
);
|
||||||
}
|
},
|
||||||
},
|
[takeSnapshot, edges, setEdges]
|
||||||
// Specify dependencies for useCallback
|
);
|
||||||
[incrementNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onDelete = useCallback((mynodes) => {
|
const onEdgeUpdateStart = useCallback(() => {
|
||||||
takeSnapshot();
|
edgeUpdateSuccessful.current = false;
|
||||||
setEdges(
|
}, []);
|
||||||
edges.filter(
|
|
||||||
(ns) => !mynodes.some((n) => ns.source === n.id || ns.target === n.id)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}, [takeSnapshot, edges, setEdges]);
|
|
||||||
|
|
||||||
const onEdgeUpdateStart = useCallback(() => {
|
const onEdgeUpdate = useCallback(
|
||||||
edgeUpdateSuccessful.current = false;
|
(oldEdge: Edge, newConnection: Connection) => {
|
||||||
}, []);
|
if (isValidConnection(newConnection, reactFlowInstance)) {
|
||||||
|
edgeUpdateSuccessful.current = true;
|
||||||
|
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const onEdgeUpdate = useCallback(
|
const onEdgeUpdateEnd = useCallback((_, edge) => {
|
||||||
(oldEdge: Edge, newConnection: Connection) => {
|
if (!edgeUpdateSuccessful.current) {
|
||||||
if (isValidConnection(newConnection, reactFlowInstance)) {
|
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
|
||||||
edgeUpdateSuccessful.current = true;
|
}
|
||||||
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onEdgeUpdateEnd = useCallback((_, edge) => {
|
edgeUpdateSuccessful.current = true;
|
||||||
if (!edgeUpdateSuccessful.current) {
|
}, []);
|
||||||
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeUpdateSuccessful.current = true;
|
const [selectionEnded, setSelectionEnded] = useState(false);
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
const onSelectionEnd = useCallback(() => {
|
||||||
<div className="w-full h-full" onMouseMove={handleMouseMove} ref={reactFlowWrapper}>
|
setSelectionEnded(true);
|
||||||
{Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? (
|
}, []);
|
||||||
<>
|
const onSelectionStart = useCallback(() => {
|
||||||
<ReactFlow
|
setSelectionEnded(false);
|
||||||
nodes={nodes}
|
}, []);
|
||||||
onMove={() =>
|
|
||||||
updateFlow({ ...flow, data: reactFlowInstance.toObject() })
|
// Workaround to show the menu only after the selection has ended.
|
||||||
}
|
useEffect(() => {
|
||||||
edges={edges}
|
if (selectionEnded && lastSelection && lastSelection.nodes.length > 1) {
|
||||||
onNodesChange={onNodesChange}
|
setSelectionMenuVisible(true);
|
||||||
onEdgesChange={onEdgesChangeMod}
|
} else {
|
||||||
onKeyDown={(e) => onKeyDown(e)}
|
setSelectionMenuVisible(false);
|
||||||
onConnect={onConnect}
|
}
|
||||||
onLoad={setReactFlowInstance}
|
}, [selectionEnded, lastSelection]);
|
||||||
onInit={setReactFlowInstance}
|
|
||||||
nodeTypes={nodeTypes}
|
const onSelectionChange = useCallback((flow) => {
|
||||||
onEdgeUpdate={onEdgeUpdate}
|
setLastSelection(flow);
|
||||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
}, []);
|
||||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
|
||||||
onNodeDragStart={onNodeDragStart}
|
return (
|
||||||
onSelectionDragStart={onSelectionDragStart}
|
<div
|
||||||
onEdgesDelete={onEdgesDelete}
|
className="w-full h-full"
|
||||||
connectionLineComponent={ConnectionLineComponent}
|
ref={reactFlowWrapper}
|
||||||
onDragOver={onDragOver}
|
>
|
||||||
onDrop={onDrop}
|
{Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? (
|
||||||
onNodesDelete={onDelete}
|
<>
|
||||||
selectNodesOnDrag={false}
|
<ReactFlow
|
||||||
>
|
nodes={nodes}
|
||||||
<Background className="dark:bg-gray-900" />
|
onMove={() => {
|
||||||
<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">
|
updateFlow({ ...flow, data: reactFlowInstance.toObject() });
|
||||||
</Controls>
|
}}
|
||||||
</ReactFlow>
|
edges={edges}
|
||||||
<Chat flow={flow} reactFlowInstance={reactFlowInstance} />
|
onNodesChange={onNodesChange}
|
||||||
</>
|
onEdgesChange={onEdgesChangeMod}
|
||||||
) : (
|
onConnect={onConnect}
|
||||||
<></>
|
onLoad={setReactFlowInstance}
|
||||||
)}
|
onInit={setReactFlowInstance}
|
||||||
</div>
|
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}
|
||||||
|
selectNodesOnDrag={false}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -6,15 +6,17 @@ export type TabsContextType = {
|
||||||
setTabIndex: (index: number) => void;
|
setTabIndex: (index: number) => void;
|
||||||
flows: Array<FlowType>;
|
flows: Array<FlowType>;
|
||||||
removeFlow: (id: string) => void;
|
removeFlow: (id: string) => void;
|
||||||
addFlow: (flowData?: FlowType) => void;
|
addFlow: (flowData?: FlowType,newFlow?:boolean) => void;
|
||||||
updateFlow: (newFlow: FlowType) => void;
|
updateFlow: (newFlow: FlowType) => void;
|
||||||
incrementNodeId: () => number;
|
incrementNodeId: () => string;
|
||||||
downloadFlow: (flow: FlowType) => void;
|
downloadFlow: (flow: FlowType) => void;
|
||||||
uploadFlow: () => void;
|
uploadFlow: (newFlow?:boolean) => void;
|
||||||
hardReset: () => void;
|
hardReset: () => void;
|
||||||
//disable CopyPaste
|
//disable CopyPaste
|
||||||
disableCP: boolean;
|
disableCP: boolean;
|
||||||
setDisableCP: (value: boolean) => void;
|
setDisableCP: (value: boolean) => void;
|
||||||
|
getNodeId: () => string;
|
||||||
|
paste: (selection: {nodes: any, edges: any}, position: {x: number, y: number}) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LangFlowState = {
|
export type LangFlowState = {
|
||||||
|
|
@ -22,4 +24,4 @@ export type LangFlowState = {
|
||||||
flows: FlowType[];
|
flows: FlowType[];
|
||||||
id: string;
|
id: string;
|
||||||
nodeId: number;
|
nodeId: number;
|
||||||
};
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue