diff --git a/src/App.jsx b/src/App.jsx index 6f657110..2cdbaecf 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -82,14 +82,8 @@ export default function App() { // Global variables state const [globalVariables, setGlobalVariables] = useState([]); - // Function to save a graph + // Function to save a graph to computer with "Save As" dialog const saveGraph = async () => { - const filename = prompt("Your file will be saved in the saved_graphs folder. Enter a name for your file:"); - // if user cancels the prompt, filename will be null - if (!filename) { - alert("Save cancelled."); - return; - } const graphData = { nodes, edges, @@ -98,60 +92,168 @@ export default function App() { globalVariables }; - try { - const response = await fetch('http://localhost:8000/save', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - filename, - graph: graphData, - }), - }); + // Check if File System Access API is supported + if ('showSaveFilePicker' in window) { + try { + // Modern approach: Use File System Access API for proper "Save As" dialog + const fileHandle = await window.showSaveFilePicker({ + suggestedName: 'fuel-cycle-graph.json', + types: [{ + description: 'JSON files', + accept: { + 'application/json': ['.json'] + } + }] + }); - const result = await response.json(); - alert(result.message); - } catch (error) { - console.error('Error saving graph:', error); + // Create a writable stream and write the data + const writable = await fileHandle.createWritable(); + await writable.write(JSON.stringify(graphData, null, 2)); + await writable.close(); + + // Success message + alert('Graph saved successfully!'); + } catch (error) { + if (error.name !== 'AbortError') { + console.error('Error saving file:', error); + alert('Failed to save file.'); + } + // User cancelled the dialog - no error message needed + } + } else { + // Fallback for browsers (like Firefox and Safari) that don't support File System Access API + const blob = new Blob([JSON.stringify(graphData, null, 2)], { + type: 'application/json' + }); + + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'fuel-cycle-graph.json'; + + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + alert('Graph downloaded successfully!'); } }; - // Function to load a saved graph - const loadGraph = async () => { - const filename = prompt("Enter the name of a file from the saved_graphs folder to load:"); - if (!filename) return; - try { - const response = await fetch('http://localhost:8000/load', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ filename }), - }); + // Function to load a saved graph from computer + const loadGraph = async () => { + // Check if File System Access API is supported + if ('showOpenFilePicker' in window) { + try { + // Modern approach: Use File System Access API + const [fileHandle] = await window.showOpenFilePicker({ + types: [{ + description: 'JSON files', + accept: { + 'application/json': ['.json'] + } + }], + multiple: false + }); - if (!response.ok) { - alert("Failed to load file."); - return; + const file = await fileHandle.getFile(); + const text = await file.text(); + + try { + const graphData = JSON.parse(text); + + // Validate that it's a valid graph file + if (!graphData.nodes || !Array.isArray(graphData.nodes)) { + alert("Invalid file format. Please select a valid graph JSON file."); + return; + } + + // Load the graph data + const { nodes: loadedNodes, edges: loadedEdges, nodeCounter: loadedNodeCounter, solverParams: loadedSolverParams, globalVariables: loadedGlobalVariables } = graphData; + setNodes(loadedNodes || []); + setEdges(loadedEdges || []); + setSelectedNode(null); + setNodeCounter(loadedNodeCounter ?? loadedNodes.length); + setSolverParams(loadedSolverParams ?? { + dt: '0.01', + dt_min: '1e-6', + dt_max: '1.0', + Solver: 'SSPRK22', + tolerance_fpi: '1e-6', + iterations_max: '100', + log: 'true', + simulation_duration: '50.0', + extra_params: '{}' + }); + setGlobalVariables(loadedGlobalVariables ?? []); + + alert('Graph loaded successfully!'); + } catch (error) { + console.error('Error parsing file:', error); + alert('Error reading file. Please make sure it\'s a valid JSON file.'); + } + } catch (error) { + if (error.name !== 'AbortError') { + console.error('Error opening file:', error); + alert('Failed to open file.'); + } + // User cancelled the dialog - no error message needed } - - const { nodes: loadedNodes, edges: loadedEdges, nodeCounter: loadedNodeCounter, solverParams: loadedSolverParams, globalVariables: loadedGlobalVariables } = await response.json(); - setNodes(loadedNodes); - setEdges(loadedEdges); - setSelectedNode(null); - setNodeCounter(loadedNodeCounter ?? loadedNodes.length); - setSolverParams(loadedSolverParams ?? { - dt: '0.01', - dt_min: '1e-6', - dt_max: '1.0', - Solver: 'SSPRK22', - tolerance_fpi: '1e-6', - iterations_max: '100', - log: 'true', - simulation_duration: '50.0', - extra_params: '{}' - }); - setGlobalVariables(loadedGlobalVariables ?? []); - } catch (error) { - console.error('Error loading graph:', error); + } else { + // Fallback for browsers that don't support File System Access API + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = '.json'; + fileInput.style.display = 'none'; + + fileInput.onchange = (event) => { + const file = event.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (e) => { + try { + const graphData = JSON.parse(e.target.result); + + if (!graphData.nodes || !Array.isArray(graphData.nodes)) { + alert("Invalid file format. Please select a valid graph JSON file."); + return; + } + + const { nodes: loadedNodes, edges: loadedEdges, nodeCounter: loadedNodeCounter, solverParams: loadedSolverParams, globalVariables: loadedGlobalVariables } = graphData; + setNodes(loadedNodes || []); + setEdges(loadedEdges || []); + setSelectedNode(null); + setNodeCounter(loadedNodeCounter ?? loadedNodes.length); + setSolverParams(loadedSolverParams ?? { + dt: '0.01', + dt_min: '1e-6', + dt_max: '1.0', + Solver: 'SSPRK22', + tolerance_fpi: '1e-6', + iterations_max: '100', + log: 'true', + simulation_duration: '50.0', + extra_params: '{}' + }); + setGlobalVariables(loadedGlobalVariables ?? []); + + alert('Graph loaded successfully!'); + } catch (error) { + console.error('Error parsing file:', error); + alert('Error reading file. Please make sure it\'s a valid JSON file.'); + } + }; + + reader.readAsText(file); + document.body.removeChild(fileInput); + }; + + document.body.appendChild(fileInput); + fileInput.click(); } }; + // Allows user to clear user inputs and go back to default settings const resetGraph = () => { setNodes(initialNodes); @@ -193,18 +295,47 @@ export default function App() { const result = await response.json(); if (result.success) { - // Create a downloadable file with the generated Python script - const blob = new Blob([result.script], { type: 'text/python' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'generated_fuel_cycle_script.py'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); + // Check if File System Access API is supported + if ('showSaveFilePicker' in window) { + try { + // Modern approach: Use File System Access API for proper "Save As" dialog + const fileHandle = await window.showSaveFilePicker({ + suggestedName: 'fuel_cycle_script.py', + types: [{ + description: 'Python files', + accept: { + 'text/x-python': ['.py'] + } + }] + }); + + // Create a writable stream and write the Python script + const writable = await fileHandle.createWritable(); + await writable.write(result.script); + await writable.close(); - alert('Python script generated and downloaded successfully!'); + alert('Python script generated and saved successfully!'); + } catch (error) { + if (error.name !== 'AbortError') { + console.error('Error saving Python file:', error); + alert('Failed to save Python script.'); + } + // User cancelled the dialog - no error message needed + } + } else { + // Fallback for browsers (Firefox, Safari) that don't support File System Access API + const blob = new Blob([result.script], { type: 'text/x-python' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'fuel_cycle_script.py'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + alert('Python script generated and downloaded to your default downloads folder!'); + } } else { alert(`Error generating Python script: ${result.error}`); }