From 23913c2ce1a7577ff181b55e410efec81510f9de Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Sat, 9 Aug 2025 21:11:20 -0600 Subject: [PATCH] Load workflow JSON on open and enable tab closing --- Data/Server/WebUI/src/App.jsx | 107 +++++++++++++++++++++--------- Data/Server/WebUI/src/Dialogs.jsx | 2 +- Data/Server/server.py | 16 +++++ 3 files changed, 93 insertions(+), 32 deletions(-) diff --git a/Data/Server/WebUI/src/App.jsx b/Data/Server/WebUI/src/App.jsx index 948d9c8..d3734f2 100644 --- a/Data/Server/WebUI/src/App.jsx +++ b/Data/Server/WebUI/src/App.jsx @@ -136,6 +136,45 @@ export default function App() { const handleAboutMenuClose = () => setAboutAnchorEl(null); const openCreditsDialog = () => { handleAboutMenuClose(); setCreditsDialogOpen(true); }; + const handleTabRightClick = (evt, tabId) => { + evt.preventDefault(); + setTabMenuAnchor({ x: evt.clientX, y: evt.clientY }); + setTabMenuTabId(tabId); + }; + + const handleCloseTab = () => { + setTabs((prev) => { + const filtered = prev.filter((t) => t.id !== tabMenuTabId); + if (filtered.length === 0) { + const newTab = { id: "flow_1", tab_name: "Flow 1", nodes: [], edges: [] }; + setActiveTabId(newTab.id); + return [newTab]; + } + if (activeTabId === tabMenuTabId) { + setActiveTabId(filtered[0].id); + } + return filtered; + }); + setTabMenuAnchor(null); + }; + + const handleRenameTab = () => { + const tab = tabs.find((t) => t.id === tabMenuTabId); + if (tab) { + setRenameTabId(tabMenuTabId); + setRenameValue(tab.tab_name); + setRenameDialogOpen(true); + } + setTabMenuAnchor(null); + }; + + const handleSaveRename = () => { + setTabs((prev) => + prev.map((t) => (t.id === renameTabId ? { ...t, tab_name: renameValue } : t)) + ); + setRenameDialogOpen(false); + }; + const renderMainContent = () => { switch (currentPage) { case "devices": @@ -147,29 +186,35 @@ export default function App() { case "workflows": return ( { - // If workflow name exists in tabs, just switch to it - if (workflow?.name) { - const existing = tabs.find( - (t) => t.tab_name.toLowerCase() === workflow.name.toLowerCase() - ); - if (existing) { - setActiveTabId(existing.id); - setCurrentPage("workflow-editor"); - return; + onOpenWorkflow={async (workflow) => { + const newId = "flow_" + Date.now(); + if (workflow && workflow.rel_path) { + try { + const resp = await fetch( + `/api/storage/load_workflow?path=${encodeURIComponent( + workflow.rel_path + )}` + ); + if (!resp.ok) throw new Error(`HTTP ${resp.status}`); + const data = await resp.json(); + setTabs([ + { + id: newId, + tab_name: + data.tab_name || workflow.name || workflow.file_name || "Workflow", + nodes: data.nodes || [], + edges: data.edges || [] + } + ]); + } catch (err) { + console.error("Failed to load workflow:", err); + setTabs([ + { id: newId, tab_name: workflow?.name || "Workflow", nodes: [], edges: [] } + ]); } + } else { + setTabs([{ id: newId, tab_name: `Flow`, nodes: [], edges: [] }]); } - // Otherwise, create a new workflow tab - const newId = "flow_" + (tabs.length + 1); - setTabs((prev) => [ - ...prev, - { - id: newId, - tab_name: workflow?.name || `Flow ${tabs.length + 1}`, - nodes: [], - edges: [] - } - ]); setActiveTabId(newId); setCurrentPage("workflow-editor"); }} @@ -191,13 +236,13 @@ export default function App() { onFileInputChange={() => {}} /> - {}} - onTabRightClick={() => {}} - /> + {}} + onTabRightClick={handleTabRightClick} + /> {tabs.map((tab) => ( setRenameDialogOpen(false)} - onSave={() => {}} + onSave={handleSaveRename} /> setTabMenuAnchor(null)} - onRename={() => {}} - onCloseTab={() => {}} + onRename={handleRenameTab} + onCloseTab={handleCloseTab} /> ); diff --git a/Data/Server/WebUI/src/Dialogs.jsx b/Data/Server/WebUI/src/Dialogs.jsx index 9da3489..a77267c 100644 --- a/Data/Server/WebUI/src/Dialogs.jsx +++ b/Data/Server/WebUI/src/Dialogs.jsx @@ -133,7 +133,7 @@ export function TabContextMenu({ anchor, onClose, onRename, onCloseTab }) { }} > Rename - Close + Close Workflow ); } \ No newline at end of file diff --git a/Data/Server/server.py b/Data/Server/server.py index 730b5dc..ffb4f96 100644 --- a/Data/Server/server.py +++ b/Data/Server/server.py @@ -170,6 +170,22 @@ def load_workflows(): "workflows": results }) + +@app.route("/api/storage/load_workflow", methods=["GET"]) +def load_workflow(): + """Load a single workflow JSON by its relative path.""" + rel_path = request.args.get("path", "") + workflows_root = os.path.abspath( + os.path.join(os.path.dirname(__file__), "..", "..", "Workflows") + ) + abs_path = os.path.abspath(os.path.join(workflows_root, rel_path)) + + if not abs_path.startswith(workflows_root) or not os.path.isfile(abs_path): + return jsonify({"error": "Workflow not found"}), 404 + + obj = _safe_read_json(abs_path) + return jsonify(obj) + # --------------------------------------------- # Borealis Agent API Endpoints # ---------------------------------------------