From b1c7424026a2787867aba553ec34e9b04a03f3f7 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Sat, 29 Mar 2025 21:23:50 -0600 Subject: [PATCH] Added functional ability to save and open workflows --- Data/WebUI/src/{App.js => App.jsx} | 125 +++++++++++++++++++---------- Data/WebUI/src/index.js | 17 ++++ Workflows/Test.json | 80 ++++++++++++++++++ 3 files changed, 178 insertions(+), 44 deletions(-) rename Data/WebUI/src/{App.js => App.jsx} (74%) create mode 100644 Data/WebUI/src/index.js create mode 100644 Workflows/Test.json diff --git a/Data/WebUI/src/App.js b/Data/WebUI/src/App.jsx similarity index 74% rename from Data/WebUI/src/App.js rename to Data/WebUI/src/App.jsx index 84733de..7f5f7db 100644 --- a/Data/WebUI/src/App.js +++ b/Data/WebUI/src/App.jsx @@ -77,7 +77,6 @@ const CustomNode = ({ data }) => { ); }; -// FlowEditor with drag-and-drop and updated behavior function FlowEditor({ nodes, edges, setNodes, setEdges }) { const reactFlowWrapper = useRef(null); const { project } = useReactFlow(); @@ -202,10 +201,10 @@ export default function App() { const [nodes, setNodes] = useState([]); const [edges, setEdges] = useState([]); const [confirmCloseOpen, setConfirmCloseOpen] = useState(false); + const fileInputRef = useRef(null); const handleAboutMenuOpen = (event) => setAboutAnchorEl(event.currentTarget); const handleAboutMenuClose = () => setAboutAnchorEl(null); - const handleOpenCloseDialog = () => setConfirmCloseOpen(true); const handleCloseDialog = () => setConfirmCloseOpen(false); const handleConfirmCloseWorkflow = () => { @@ -214,21 +213,59 @@ export default function App() { setConfirmCloseOpen(false); }; - const handleAddTestNode = () => { - const id = `test-node-${Date.now()}`; - const newNode = { - id, - type: "custom", - data: { - label: "Custom Node", - content: "Placeholder" - }, - position: { - x: 250 + Math.random() * 300, - y: 150 + Math.random() * 200 + const handleSaveWorkflow = async () => { + const data = JSON.stringify({ nodes, edges }, null, 2); + const blob = new Blob([data], { type: "application/json" }); + + if (window.showSaveFilePicker) { + try { + const fileHandle = await window.showSaveFilePicker({ + suggestedName: "workflow.json", + types: [{ + description: "Workflow JSON File", + accept: { "application/json": [".json"] } + }] + }); + const writable = await fileHandle.createWritable(); + await writable.write(blob); + await writable.close(); + } catch (error) { + console.error("Save cancelled or failed:", error); } - }; - setNodes((nds) => [...nds, newNode]); + } else { + const a = document.createElement("a"); + a.href = URL.createObjectURL(blob); + a.download = "workflow.json"; + a.style.display = "none"; + document.body.appendChild(a); + a.click(); + URL.revokeObjectURL(a.href); + document.body.removeChild(a); + } + }; + + const handleOpenWorkflow = async () => { + if (window.showOpenFilePicker) { + try { + const [fileHandle] = await window.showOpenFilePicker({ + types: [{ + description: "Workflow JSON File", + accept: { "application/json": [".json"] } + }] + }); + const file = await fileHandle.getFile(); + const text = await file.text(); + const { nodes: loadedNodes, edges: loadedEdges } = JSON.parse(text); + const confirm = window.confirm("Opening a workflow will overwrite your current one. Continue?"); + if (!confirm) return; + setNodes(loadedNodes); + setEdges(loadedEdges); + } catch (error) { + console.error("Open cancelled or failed:", error); + } + } else { + fileInputRef.current?.click(); + } }; return ( @@ -272,15 +309,9 @@ export default function App() { Workflows - - - + + + @@ -316,23 +347,12 @@ export default function App() { - + Nodes: 0 | Update Rate: 500ms - {/* Confirmation Dialog */} - + Close Workflow? @@ -340,14 +360,31 @@ export default function App() { - - + + + + { + const file = e.target.files[0]; + if (!file) return; + try { + const text = await file.text(); + const { nodes: loadedNodes, edges: loadedEdges } = JSON.parse(text); + const confirm = window.confirm("Opening a workflow will overwrite your current one. Continue?"); + if (!confirm) return; + setNodes(loadedNodes); + setEdges(loadedEdges); + } catch (err) { + console.error("Failed to read file:", err); + } + }} + /> ); } diff --git a/Data/WebUI/src/index.js b/Data/WebUI/src/index.js new file mode 100644 index 0000000..4ff38a4 --- /dev/null +++ b/Data/WebUI/src/index.js @@ -0,0 +1,17 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App.jsx'; +import reportWebVitals from './reportWebVitals'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/Workflows/Test.json b/Workflows/Test.json new file mode 100644 index 0000000..cfa8bc6 --- /dev/null +++ b/Workflows/Test.json @@ -0,0 +1,80 @@ +{ + "nodes": [ + { + "id": "node-1743304886846", + "type": "custom", + "position": { + "x": 214.63333129882812, + "y": 146.66666666666666 + }, + "data": { + "label": "Custom Node", + "content": "Placeholder" + }, + "width": 160, + "height": 72 + }, + { + "id": "node-1743304888268", + "type": "custom", + "position": { + "x": 621.2999979654948, + "y": 276.6666666666667 + }, + "data": { + "label": "Custom Node", + "content": "Placeholder" + }, + "width": 160, + "height": 72 + }, + { + "id": "node-1743304891251", + "type": "custom", + "position": { + "x": 814.6333312988281, + "y": 65.33333333333334 + }, + "data": { + "label": "Custom Node", + "content": "Placeholder" + }, + "width": 160, + "height": 72, + "selected": true, + "positionAbsolute": { + "x": 814.6333312988281, + "y": 65.33333333333334 + }, + "dragging": false + } + ], + "edges": [ + { + "source": "node-1743304886846", + "sourceHandle": null, + "target": "node-1743304888268", + "targetHandle": null, + "type": "smoothstep", + "animated": true, + "style": { + "strokeDasharray": "6 3", + "stroke": "#58a6ff" + }, + "id": "reactflow__edge-node-1743304886846-node-1743304888268" + }, + { + "source": "node-1743304886846", + "sourceHandle": null, + "target": "node-1743304891251", + "targetHandle": null, + "type": "smoothstep", + "animated": true, + "style": { + "strokeDasharray": "6 3", + "stroke": "#58a6ff" + }, + "id": "reactflow__edge-node-1743304886846-node-1743304891251" + } + ] +} \ No newline at end of file