From 3b6f8bfbac5f19d4a7fbf7e8c83671bd60373090 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Sat, 9 Aug 2025 22:20:59 -0600 Subject: [PATCH] Enable workflow import/export and rename --- Data/Server/WebUI/src/App.jsx | 89 ++++++++++++++++- Data/Server/WebUI/src/Dialogs.jsx | 37 +++++++ Data/Server/WebUI/src/Node_Sidebar.jsx | 9 +- Data/Server/WebUI/src/Workflow_List.jsx | 124 ++++++++++++++++++------ Data/Server/server.py | 57 +++++++++++ 5 files changed, 281 insertions(+), 35 deletions(-) diff --git a/Data/Server/WebUI/src/App.jsx b/Data/Server/WebUI/src/App.jsx index 4a83eeb..9c99db6 100644 --- a/Data/Server/WebUI/src/App.jsx +++ b/Data/Server/WebUI/src/App.jsx @@ -175,6 +175,86 @@ export default function App() { setRenameDialogOpen(false); }; + const handleExportFlow = useCallback(() => { + const tab = tabs.find((t) => t.id === activeTabId); + if (!tab) return; + const payload = { + tab_name: tab.tab_name, + nodes: tab.nodes, + edges: tab.edges + }; + const fileName = `${tab.tab_name || "workflow"}.json`; + const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = fileName; + a.click(); + URL.revokeObjectURL(url); + }, [tabs, activeTabId]); + + const handleImportFlow = useCallback(() => { + if (fileInputRef.current) { + fileInputRef.current.value = null; + fileInputRef.current.click(); + } + }, []); + + const onFileInputChange = useCallback( + (e) => { + const file = e.target.files && e.target.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = () => { + try { + const data = JSON.parse(reader.result); + const newId = "flow_" + Date.now(); + setTabs((prev) => [ + ...prev, + { + id: newId, + tab_name: + data.tab_name || data.name || file.name.replace(/\.json$/i, ""), + nodes: data.nodes || [], + edges: data.edges || [] + } + ]); + setActiveTabId(newId); + setCurrentPage("workflow-editor"); + } catch (err) { + console.error("Failed to import workflow:", err); + } + }; + reader.readAsText(file); + e.target.value = ""; + }, + [setTabs] + ); + + const handleSaveFlow = useCallback(async () => { + const tab = tabs.find((t) => t.id === activeTabId); + if (!tab) return; + const name = window.prompt("Enter workflow name", tab.tab_name || "workflow"); + if (!name) return; + const payload = { + name, + workflow: { + tab_name: tab.tab_name, + nodes: tab.nodes, + edges: tab.edges + } + }; + try { + await fetch("/api/storage/save_workflow", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }); + } catch (err) { + console.error("Failed to save workflow:", err); + } + }, [tabs, activeTabId]); + const renderMainContent = () => { switch (currentPage) { case "devices": @@ -230,11 +310,12 @@ export default function App() { {}} - handleImportFlow={() => {}} - handleOpenCloseAllDialog={() => {}} + handleExportFlow={handleExportFlow} + handleImportFlow={handleImportFlow} + handleSaveFlow={handleSaveFlow} + handleOpenCloseAllDialog={() => setConfirmCloseOpen(true)} fileInputRef={fileInputRef} - onFileInputChange={() => {}} + onFileInputChange={onFileInputChange} /> + Rename Workflow + + onChange(e.target.value)} + sx={{ + "& .MuiOutlinedInput-root": { + backgroundColor: "#2a2a2a", + color: "#ccc", + "& fieldset": { + borderColor: "#444" + }, + "&:hover fieldset": { + borderColor: "#666" + } + }, + label: { color: "#aaa" }, + mt: 1 + }} + /> + + + + + + + ); +} + export function DeleteDeviceDialog({ open, onCancel, onConfirm }) { return ( diff --git a/Data/Server/WebUI/src/Node_Sidebar.jsx b/Data/Server/WebUI/src/Node_Sidebar.jsx index 38a7204..9b0d04d 100644 --- a/Data/Server/WebUI/src/Node_Sidebar.jsx +++ b/Data/Server/WebUI/src/Node_Sidebar.jsx @@ -12,6 +12,7 @@ import { } from "@mui/material"; import { ExpandMore as ExpandMoreIcon, + SaveAlt as SaveAltIcon, Save as SaveIcon, FileOpen as FileOpenIcon, DeleteForever as DeleteForeverIcon, @@ -25,6 +26,7 @@ export default function NodeSidebar({ categorizedNodes, handleExportFlow, handleImportFlow, + handleSaveFlow, handleOpenCloseAllDialog, fileInputRef, onFileInputChange @@ -72,10 +74,15 @@ export default function NodeSidebar({ - + + +