Upload and Download Flows done, fixed deleting flow bug
This commit is contained in:
parent
74192c205c
commit
d76d0b2d51
9 changed files with 142 additions and 82 deletions
BIN
langflow.db
BIN
langflow.db
Binary file not shown.
|
|
@ -21,12 +21,6 @@ export default function Header() {
|
||||||
const [rename, setRename] = useState(false);
|
const [rename, setRename] = useState(false);
|
||||||
const { notificationCenter, setNotificationCenter, setErrorData } =
|
const { notificationCenter, setNotificationCenter, setErrorData } =
|
||||||
useContext(alertContext);
|
useContext(alertContext);
|
||||||
useEffect(() => {
|
|
||||||
//create the first flow
|
|
||||||
if (flows.length === 0 && Object.keys(templates).length > 0) {
|
|
||||||
addFlow();
|
|
||||||
}
|
|
||||||
}, [addFlow, flows.length, templates]);
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-12 flex justify-between items-center border-b bg-muted">
|
<div className="w-full h-12 flex justify-between items-center border-b bg-muted">
|
||||||
<div className="flex gap-2 justify-start items-center w-96">
|
<div className="flex gap-2 justify-start items-center w-96">
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,13 @@ 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";
|
import { addEdge } from "reactflow";
|
||||||
import _ from "lodash";
|
import _, { flow } from "lodash";
|
||||||
import {
|
import {
|
||||||
readFlowsFromDatabase,
|
readFlowsFromDatabase,
|
||||||
deleteFlowFromDatabase,
|
deleteFlowFromDatabase,
|
||||||
saveFlowToDatabase,
|
saveFlowToDatabase,
|
||||||
|
downloadFlowsFromDatabase,
|
||||||
|
uploadFlowsToDatabase,
|
||||||
} from "../controllers/API";
|
} from "../controllers/API";
|
||||||
|
|
||||||
const TabsContextInitialValue: TabsContextType = {
|
const TabsContextInitialValue: TabsContextType = {
|
||||||
|
|
@ -35,6 +37,8 @@ const TabsContextInitialValue: TabsContextType = {
|
||||||
updateFlow: (newFlow: FlowType) => {},
|
updateFlow: (newFlow: FlowType) => {},
|
||||||
incrementNodeId: () => uuidv4(),
|
incrementNodeId: () => uuidv4(),
|
||||||
downloadFlow: (flow: FlowType) => {},
|
downloadFlow: (flow: FlowType) => {},
|
||||||
|
downloadFlows: () => {},
|
||||||
|
uploadFlows: () => {},
|
||||||
uploadFlow: () => {},
|
uploadFlow: () => {},
|
||||||
hardReset: () => {},
|
hardReset: () => {},
|
||||||
disableCopyPaste: false,
|
disableCopyPaste: false,
|
||||||
|
|
@ -56,7 +60,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 [tabId, setTabId] = useState("");
|
const [tabId, setTabId] = useState("");
|
||||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
const [flows, setFlows] = useState([]);
|
||||||
const [id, setId] = useState(uuidv4());
|
const [id, setId] = useState(uuidv4());
|
||||||
const { templates, reactFlowInstance } = useContext(typesContext);
|
const { templates, reactFlowInstance } = useContext(typesContext);
|
||||||
const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
|
const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
|
||||||
|
|
@ -114,10 +118,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
useEffect(() => {
|
function refreshFlows() {
|
||||||
// get data from db
|
|
||||||
//get tabs locally saved
|
|
||||||
// let tabsData = getLocalStorageTabsData();
|
|
||||||
getTabsDataFromDB().then((DbData) => {
|
getTabsDataFromDB().then((DbData) => {
|
||||||
if (DbData && Object.keys(templates).length > 0) {
|
if (DbData && Object.keys(templates).length > 0) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -128,6 +129,13 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// get data from db
|
||||||
|
//get tabs locally saved
|
||||||
|
// let tabsData = getLocalStorageTabsData();
|
||||||
|
refreshFlows();
|
||||||
}, [templates]);
|
}, [templates]);
|
||||||
|
|
||||||
function getTabsDataFromDB() {
|
function getTabsDataFromDB() {
|
||||||
|
|
@ -148,20 +156,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function processTabsData(tabsData) {
|
|
||||||
tabsData.flows.forEach((flow) => {
|
|
||||||
try {
|
|
||||||
if (!flow.data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
processFlowEdges(flow);
|
|
||||||
processFlowNodes(flow);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function processFlowEdges(flow) {
|
function processFlowEdges(flow) {
|
||||||
flow.data.edges.forEach((edge) => {
|
flow.data.edges.forEach((edge) => {
|
||||||
edge.className = "";
|
edge.className = "";
|
||||||
|
|
@ -244,6 +238,22 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function downloadFlows() {
|
||||||
|
downloadFlowsFromDatabase().then((flows) => {
|
||||||
|
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
||||||
|
JSON.stringify(flows)
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
// create a link element and set its properties
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = jsonString;
|
||||||
|
link.download = `flows.json`;
|
||||||
|
|
||||||
|
// simulate a click on the link element to trigger the download
|
||||||
|
link.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getNodeId() {
|
function getNodeId() {
|
||||||
return `dndnode_` + incrementNodeId();
|
return `dndnode_` + incrementNodeId();
|
||||||
}
|
}
|
||||||
|
|
@ -275,30 +285,41 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
// trigger the file input click event to open the file dialog
|
// trigger the file input click event to open the file dialog
|
||||||
input.click();
|
input.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function uploadFlows() {
|
||||||
|
// 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
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
uploadFlowsToDatabase(formData).then(() => {
|
||||||
|
refreshFlows();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 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.
|
* Removes a flow from an array of flows based on its id.
|
||||||
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
|
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
|
||||||
* @param {string} id - The id of the flow to remove.
|
* @param {string} id - The id of the flow to remove.
|
||||||
*/
|
*/
|
||||||
function removeFlow(id: string) {
|
function removeFlow(id: string) {
|
||||||
setFlows((prevState) => {
|
const index = flows.findIndex((flow) => flow.id === id);
|
||||||
const newFlows = [...prevState];
|
console.log(index);
|
||||||
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);
|
setFlows(flows.filter((flow) => flow.id !== id));
|
||||||
if (index === tabIndex) {
|
|
||||||
setTabId(flows[flows.length - 2].id);
|
|
||||||
newFlows.splice(index, 1);
|
|
||||||
} else {
|
|
||||||
let flowId = flows[tabIndex].id;
|
|
||||||
newFlows.splice(index, 1);
|
|
||||||
setTabId(flowId);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return newFlows;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Add a new flow to the list of flows.
|
* Add a new flow to the list of flows.
|
||||||
|
|
@ -510,6 +531,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
addFlow,
|
addFlow,
|
||||||
updateFlow,
|
updateFlow,
|
||||||
downloadFlow,
|
downloadFlow,
|
||||||
|
downloadFlows,
|
||||||
|
uploadFlows,
|
||||||
uploadFlow,
|
uploadFlow,
|
||||||
getNodeId,
|
getNodeId,
|
||||||
paste,
|
paste,
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,35 @@ export async function readFlowsFromDatabase() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function downloadFlowsFromDatabase() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/flows/download/");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function uploadFlowsToDatabase(flows) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/flows/upload/`, {
|
||||||
|
method: "POST", // Or "PATCH" depending on your backend API
|
||||||
|
body: flows,
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a flow from the database.
|
* Deletes a flow from the database.
|
||||||
*
|
*
|
||||||
|
|
@ -198,7 +227,7 @@ export async function getFlowFromDatabase(flowId: number) {
|
||||||
*/
|
*/
|
||||||
export async function getFlowStylesFromDatabase() {
|
export async function getFlowStylesFromDatabase() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/flows_styles/");
|
const response = await fetch("/flow_styles/");
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
@ -218,7 +247,7 @@ export async function getFlowStylesFromDatabase() {
|
||||||
*/
|
*/
|
||||||
export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
|
export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/flows_styles/", {
|
const response = await fetch("/flow_styles/", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
accept: "application/json",
|
accept: "application/json",
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export default function ExportModal() {
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext);
|
const { flows, tabId, updateFlow, downloadFlow } = useContext(TabsContext);
|
||||||
function setModalOpen(x: boolean) {
|
function setModalOpen(x: boolean) {
|
||||||
setOpen(x);
|
setOpen(x);
|
||||||
if (x === false) {
|
if (x === false) {
|
||||||
|
|
@ -40,7 +40,7 @@ export default function ExportModal() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const [checked, setChecked] = useState(true);
|
const [checked, setChecked] = useState(true);
|
||||||
const [name, setName] = useState(flows[tabIndex].name);
|
const [name, setName] = useState(flows.find((f) => f.id === tabId).name);
|
||||||
return (
|
return (
|
||||||
<Dialog open={true} onOpenChange={setModalOpen}>
|
<Dialog open={true} onOpenChange={setModalOpen}>
|
||||||
<DialogTrigger asChild></DialogTrigger>
|
<DialogTrigger asChild></DialogTrigger>
|
||||||
|
|
@ -63,7 +63,7 @@ export default function ExportModal() {
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (event.target.value != "") {
|
if (event.target.value != "") {
|
||||||
let newFlow = flows[tabIndex];
|
let newFlow = flows.find((f) => f.id === tabId);
|
||||||
newFlow.name = event.target.value;
|
newFlow.name = event.target.value;
|
||||||
setName(event.target.value);
|
setName(event.target.value);
|
||||||
updateFlow(newFlow);
|
updateFlow(newFlow);
|
||||||
|
|
@ -84,11 +84,11 @@ export default function ExportModal() {
|
||||||
name="description"
|
name="description"
|
||||||
id="description"
|
id="description"
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
let newFlow = flows[tabIndex];
|
let newFlow = flows.find((f) => f.id === tabId);
|
||||||
newFlow.description = event.target.value;
|
newFlow.description = event.target.value;
|
||||||
updateFlow(newFlow);
|
updateFlow(newFlow);
|
||||||
}}
|
}}
|
||||||
value={flows[tabIndex].description ?? null}
|
value={flows.find((f) => f.id === tabId).description ?? null}
|
||||||
placeholder="Flow description"
|
placeholder="Flow description"
|
||||||
className="max-h-[100px] mt-2"
|
className="max-h-[100px] mt-2"
|
||||||
rows={3}
|
rows={3}
|
||||||
|
|
@ -112,8 +112,8 @@ export default function ExportModal() {
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (checked) downloadFlow(flows[tabIndex]);
|
if (checked) downloadFlow(flows.find((f) => f.id === tabId));
|
||||||
else downloadFlow(removeApiKeys(flows[tabIndex]));
|
else downloadFlow(removeApiKeys(flows.find((f) => f.id === tabId)));
|
||||||
}}
|
}}
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -51,23 +51,7 @@ export default function ExtraSidebar() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col overflow-auto scrollbar-hide h-full border-r">
|
<div className="flex flex-col overflow-auto scrollbar-hide h-full border-r">
|
||||||
<div className="relative mt-2 flex items-center mb-2 mx-2">
|
<div className="mt-2 w-full flex gap-2 justify-between px-2 items-center">
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="search"
|
|
||||||
id="search"
|
|
||||||
placeholder="Search nodes"
|
|
||||||
className="dark:text-white focus:outline-none block w-full rounded-md py-1.5 ps-3 pr-9 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 sm:text-sm sm:leading-6 dark:ring-0 dark:bg-[#2d3747] dark:focus:outline-none"
|
|
||||||
onChange={(e) => {
|
|
||||||
handleSearchInput(e.target.value);
|
|
||||||
setSearch(e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-y-0 right-0 flex py-1.5 pr-3 items-center">
|
|
||||||
<MagnifyingGlassIcon className="h-5 w-5 dark:text-white"></MagnifyingGlassIcon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mb-2 w-full flex gap-2 justify-between px-2 items-center">
|
|
||||||
<ShadTooltip delayDuration={1000} content="Import" side="top">
|
<ShadTooltip delayDuration={1000} content="Import" side="top">
|
||||||
<button
|
<button
|
||||||
className="hover:dark:hover:bg-[#242f47] text-gray-700 w-full justify-center shadow-sm transition-all duration-500 ease-in-out dark:bg-gray-800 dark:text-gray-300 relative inline-flex items-center rounded-md bg-white px-2 py-2 ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
|
className="hover:dark:hover:bg-[#242f47] text-gray-700 w-full justify-center shadow-sm transition-all duration-500 ease-in-out dark:bg-gray-800 dark:text-gray-300 relative inline-flex items-center rounded-md bg-white px-2 py-2 ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
|
||||||
|
|
@ -110,6 +94,23 @@ export default function ExtraSidebar() {
|
||||||
</button>
|
</button>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="relative mt-2 flex items-center mb-2 mx-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="search"
|
||||||
|
id="search"
|
||||||
|
placeholder="Search nodes"
|
||||||
|
className="dark:text-white focus:outline-none block w-full rounded-md py-1.5 ps-3 pr-9 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 sm:text-sm sm:leading-6 dark:ring-0 dark:bg-[#2d3747] dark:focus:outline-none"
|
||||||
|
onChange={(e) => {
|
||||||
|
handleSearchInput(e.target.value);
|
||||||
|
setSearch(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-y-0 right-0 flex py-1.5 pr-3 items-center">
|
||||||
|
<MagnifyingGlassIcon className="h-5 w-5 dark:text-white"></MagnifyingGlassIcon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
|
|
||||||
|
|
@ -24,16 +24,12 @@ import { Link } from "react-router-dom";
|
||||||
export const CardComponent = ({
|
export const CardComponent = ({
|
||||||
flow,
|
flow,
|
||||||
id,
|
id,
|
||||||
removeFlow,
|
|
||||||
setTabId,
|
|
||||||
}: {
|
}: {
|
||||||
flow: FlowType;
|
flow: FlowType;
|
||||||
id: string;
|
id: string;
|
||||||
removeFlow: (id: string) => void;
|
|
||||||
setTabId: (id: string) => void;
|
|
||||||
}) => {
|
}) => {
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
const { updateFlow } = useContext(TabsContext);
|
const { updateFlow, removeFlow, setTabId } = useContext(TabsContext);
|
||||||
function handleSaveFlow(flow) {
|
function handleSaveFlow(flow) {
|
||||||
try {
|
try {
|
||||||
updateFlowInDatabase(flow);
|
updateFlowInDatabase(flow);
|
||||||
|
|
@ -73,12 +69,11 @@ export const CardComponent = ({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
<button onClick={() => {removeFlow(flow.id)}}>
|
||||||
<Trash
|
<Trash
|
||||||
className="w-4"
|
className="w-4"
|
||||||
onClick={() => {
|
|
||||||
removeFlow(flow.id);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="pt-2 pb-2 h-10">
|
<CardDescription className="pt-2 pb-2 h-10">
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import ExtraSidebar from "../../components/ExtraSidebarComponent";
|
||||||
import { ReactFlowProvider } from "reactflow";
|
import { ReactFlowProvider } from "reactflow";
|
||||||
import FlowPage from "../FlowPage";
|
import FlowPage from "../FlowPage";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { SunIcon, MoonIcon, BellIcon, GithubIcon } from "lucide-react";
|
import { SunIcon, MoonIcon, BellIcon, GithubIcon, Download, Upload } from "lucide-react";
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
import AlertDropdown from "../../alerts/alertDropDown";
|
import AlertDropdown from "../../alerts/alertDropDown";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
|
|
@ -20,14 +20,15 @@ import { FaGithub } from "react-icons/fa";
|
||||||
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
import { updateFlowInDatabase } from "../../controllers/API";
|
import { updateFlowInDatabase, uploadFlowsToDatabase } from "../../controllers/API";
|
||||||
import { CardComponent } from "./components/cardComponent";
|
import { CardComponent } from "./components/cardComponent";
|
||||||
import { MenuBar } from "../../components/headerComponent/components/menuBar";
|
import { MenuBar } from "../../components/headerComponent/components/menuBar";
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const {
|
const {
|
||||||
flows,
|
flows,
|
||||||
removeFlow,
|
|
||||||
setTabId,
|
setTabId,
|
||||||
|
downloadFlows,
|
||||||
|
uploadFlows,
|
||||||
} = useContext(TabsContext);
|
} = useContext(TabsContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTabId("");
|
setTabId("");
|
||||||
|
|
@ -36,13 +37,28 @@ export default function HomePage() {
|
||||||
<div
|
<div
|
||||||
className="w-full h-full flex overflow-auto flex-col bg-muted"
|
className="w-full h-full flex overflow-auto flex-col bg-muted"
|
||||||
>
|
>
|
||||||
|
<div className="w-full flex justify-between py-12 px-8">
|
||||||
|
<span className="text-xl font-semibold">
|
||||||
|
Flows
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button variant="outline" onClick={() => {
|
||||||
|
downloadFlows();
|
||||||
|
}}>
|
||||||
|
<Download className="w-4 mr-2" />
|
||||||
|
Download Database
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" onClick={() => {uploadFlows();}}>
|
||||||
|
<Upload className="w-4 mr-2" />
|
||||||
|
Upload Database
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<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">
|
||||||
{Object.keys(flows).map((flow, idx) => (
|
{flows.map((flow, idx) => (
|
||||||
<CardComponent
|
<CardComponent
|
||||||
flow={flows[flow]}
|
flow={flow}
|
||||||
id={flow}
|
id={flow.id}
|
||||||
removeFlow={removeFlow}
|
|
||||||
setTabId={setTabId}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ export type TabsContextType = {
|
||||||
updateFlow: (newFlow: FlowType) => void;
|
updateFlow: (newFlow: FlowType) => void;
|
||||||
incrementNodeId: () => string;
|
incrementNodeId: () => string;
|
||||||
downloadFlow: (flow: FlowType) => void;
|
downloadFlow: (flow: FlowType) => void;
|
||||||
|
downloadFlows: () => void;
|
||||||
|
uploadFlows: () => void;
|
||||||
uploadFlow: (newFlow?: boolean) => void;
|
uploadFlow: (newFlow?: boolean) => void;
|
||||||
hardReset: () => void;
|
hardReset: () => void;
|
||||||
//disable CopyPaste
|
//disable CopyPaste
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue