@@ -82,14 +82,8 @@ export default function App() {
8282 // Global variables state
8383 const [ globalVariables , setGlobalVariables ] = useState ( [ ] ) ;
8484
85- // Function to save a graph
85+ // Function to save a graph to computer with "Save As" dialog
8686 const saveGraph = async ( ) => {
87- const filename = prompt ( "Your file will be saved in the saved_graphs folder. Enter a name for your file:" ) ;
88- // if user cancels the prompt, filename will be null
89- if ( ! filename ) {
90- alert ( "Save cancelled." ) ;
91- return ;
92- }
9387 const graphData = {
9488 nodes,
9589 edges,
@@ -98,60 +92,168 @@ export default function App() {
9892 globalVariables
9993 } ;
10094
101- try {
102- const response = await fetch ( 'http://localhost:8000/save' , {
103- method : 'POST' ,
104- headers : { 'Content-Type' : 'application/json' } ,
105- body : JSON . stringify ( {
106- filename,
107- graph : graphData ,
108- } ) ,
109- } ) ;
95+ // Check if File System Access API is supported
96+ if ( 'showSaveFilePicker' in window ) {
97+ try {
98+ // Modern approach: Use File System Access API for proper "Save As" dialog
99+ const fileHandle = await window . showSaveFilePicker ( {
100+ suggestedName : 'fuel-cycle-graph.json' ,
101+ types : [ {
102+ description : 'JSON files' ,
103+ accept : {
104+ 'application/json' : [ '.json' ]
105+ }
106+ } ]
107+ } ) ;
110108
111- const result = await response . json ( ) ;
112- alert ( result . message ) ;
113- } catch ( error ) {
114- console . error ( 'Error saving graph:' , error ) ;
109+ // Create a writable stream and write the data
110+ const writable = await fileHandle . createWritable ( ) ;
111+ await writable . write ( JSON . stringify ( graphData , null , 2 ) ) ;
112+ await writable . close ( ) ;
113+
114+ // Success message
115+ alert ( 'Graph saved successfully!' ) ;
116+ } catch ( error ) {
117+ if ( error . name !== 'AbortError' ) {
118+ console . error ( 'Error saving file:' , error ) ;
119+ alert ( 'Failed to save file.' ) ;
120+ }
121+ // User cancelled the dialog - no error message needed
122+ }
123+ } else {
124+ // Fallback for browsers (like Firefox and Safari) that don't support File System Access API
125+ const blob = new Blob ( [ JSON . stringify ( graphData , null , 2 ) ] , {
126+ type : 'application/json'
127+ } ) ;
128+
129+ const url = URL . createObjectURL ( blob ) ;
130+ const a = document . createElement ( 'a' ) ;
131+ a . href = url ;
132+ a . download = 'fuel-cycle-graph.json' ;
133+
134+ document . body . appendChild ( a ) ;
135+ a . click ( ) ;
136+ document . body . removeChild ( a ) ;
137+ URL . revokeObjectURL ( url ) ;
138+
139+ alert ( 'Graph downloaded successfully!' ) ;
115140 }
116141 } ;
117- // Function to load a saved graph
118- const loadGraph = async ( ) => {
119- const filename = prompt ( "Enter the name of a file from the saved_graphs folder to load:" ) ;
120- if ( ! filename ) return ;
121142
122- try {
123- const response = await fetch ( 'http://localhost:8000/load' , {
124- method : 'POST' ,
125- headers : { 'Content-Type' : 'application/json' } ,
126- body : JSON . stringify ( { filename } ) ,
127- } ) ;
143+ // Function to load a saved graph from computer
144+ const loadGraph = async ( ) => {
145+ // Check if File System Access API is supported
146+ if ( 'showOpenFilePicker' in window ) {
147+ try {
148+ // Modern approach: Use File System Access API
149+ const [ fileHandle ] = await window . showOpenFilePicker ( {
150+ types : [ {
151+ description : 'JSON files' ,
152+ accept : {
153+ 'application/json' : [ '.json' ]
154+ }
155+ } ] ,
156+ multiple : false
157+ } ) ;
128158
129- if ( ! response . ok ) {
130- alert ( "Failed to load file." ) ;
131- return ;
159+ const file = await fileHandle . getFile ( ) ;
160+ const text = await file . text ( ) ;
161+
162+ try {
163+ const graphData = JSON . parse ( text ) ;
164+
165+ // Validate that it's a valid graph file
166+ if ( ! graphData . nodes || ! Array . isArray ( graphData . nodes ) ) {
167+ alert ( "Invalid file format. Please select a valid graph JSON file." ) ;
168+ return ;
169+ }
170+
171+ // Load the graph data
172+ const { nodes : loadedNodes , edges : loadedEdges , nodeCounter : loadedNodeCounter , solverParams : loadedSolverParams , globalVariables : loadedGlobalVariables } = graphData ;
173+ setNodes ( loadedNodes || [ ] ) ;
174+ setEdges ( loadedEdges || [ ] ) ;
175+ setSelectedNode ( null ) ;
176+ setNodeCounter ( loadedNodeCounter ?? loadedNodes . length ) ;
177+ setSolverParams ( loadedSolverParams ?? {
178+ dt : '0.01' ,
179+ dt_min : '1e-6' ,
180+ dt_max : '1.0' ,
181+ Solver : 'SSPRK22' ,
182+ tolerance_fpi : '1e-6' ,
183+ iterations_max : '100' ,
184+ log : 'true' ,
185+ simulation_duration : '50.0' ,
186+ extra_params : '{}'
187+ } ) ;
188+ setGlobalVariables ( loadedGlobalVariables ?? [ ] ) ;
189+
190+ alert ( 'Graph loaded successfully!' ) ;
191+ } catch ( error ) {
192+ console . error ( 'Error parsing file:' , error ) ;
193+ alert ( 'Error reading file. Please make sure it\'s a valid JSON file.' ) ;
194+ }
195+ } catch ( error ) {
196+ if ( error . name !== 'AbortError' ) {
197+ console . error ( 'Error opening file:' , error ) ;
198+ alert ( 'Failed to open file.' ) ;
199+ }
200+ // User cancelled the dialog - no error message needed
132201 }
133-
134- const { nodes : loadedNodes , edges : loadedEdges , nodeCounter : loadedNodeCounter , solverParams : loadedSolverParams , globalVariables : loadedGlobalVariables } = await response . json ( ) ;
135- setNodes ( loadedNodes ) ;
136- setEdges ( loadedEdges ) ;
137- setSelectedNode ( null ) ;
138- setNodeCounter ( loadedNodeCounter ?? loadedNodes . length ) ;
139- setSolverParams ( loadedSolverParams ?? {
140- dt : '0.01' ,
141- dt_min : '1e-6' ,
142- dt_max : '1.0' ,
143- Solver : 'SSPRK22' ,
144- tolerance_fpi : '1e-6' ,
145- iterations_max : '100' ,
146- log : 'true' ,
147- simulation_duration : '50.0' ,
148- extra_params : '{}'
149- } ) ;
150- setGlobalVariables ( loadedGlobalVariables ?? [ ] ) ;
151- } catch ( error ) {
152- console . error ( 'Error loading graph:' , error ) ;
202+ } else {
203+ // Fallback for browsers that don't support File System Access API
204+ const fileInput = document . createElement ( 'input' ) ;
205+ fileInput . type = 'file' ;
206+ fileInput . accept = '.json' ;
207+ fileInput . style . display = 'none' ;
208+
209+ fileInput . onchange = ( event ) => {
210+ const file = event . target . files [ 0 ] ;
211+ if ( ! file ) return ;
212+
213+ const reader = new FileReader ( ) ;
214+ reader . onload = ( e ) => {
215+ try {
216+ const graphData = JSON . parse ( e . target . result ) ;
217+
218+ if ( ! graphData . nodes || ! Array . isArray ( graphData . nodes ) ) {
219+ alert ( "Invalid file format. Please select a valid graph JSON file." ) ;
220+ return ;
221+ }
222+
223+ const { nodes : loadedNodes , edges : loadedEdges , nodeCounter : loadedNodeCounter , solverParams : loadedSolverParams , globalVariables : loadedGlobalVariables } = graphData ;
224+ setNodes ( loadedNodes || [ ] ) ;
225+ setEdges ( loadedEdges || [ ] ) ;
226+ setSelectedNode ( null ) ;
227+ setNodeCounter ( loadedNodeCounter ?? loadedNodes . length ) ;
228+ setSolverParams ( loadedSolverParams ?? {
229+ dt : '0.01' ,
230+ dt_min : '1e-6' ,
231+ dt_max : '1.0' ,
232+ Solver : 'SSPRK22' ,
233+ tolerance_fpi : '1e-6' ,
234+ iterations_max : '100' ,
235+ log : 'true' ,
236+ simulation_duration : '50.0' ,
237+ extra_params : '{}'
238+ } ) ;
239+ setGlobalVariables ( loadedGlobalVariables ?? [ ] ) ;
240+
241+ alert ( 'Graph loaded successfully!' ) ;
242+ } catch ( error ) {
243+ console . error ( 'Error parsing file:' , error ) ;
244+ alert ( 'Error reading file. Please make sure it\'s a valid JSON file.' ) ;
245+ }
246+ } ;
247+
248+ reader . readAsText ( file ) ;
249+ document . body . removeChild ( fileInput ) ;
250+ } ;
251+
252+ document . body . appendChild ( fileInput ) ;
253+ fileInput . click ( ) ;
153254 }
154255 } ;
256+
155257 // Allows user to clear user inputs and go back to default settings
156258 const resetGraph = ( ) => {
157259 setNodes ( initialNodes ) ;
@@ -193,18 +295,47 @@ export default function App() {
193295 const result = await response . json ( ) ;
194296
195297 if ( result . success ) {
196- // Create a downloadable file with the generated Python script
197- const blob = new Blob ( [ result . script ] , { type : 'text/python' } ) ;
198- const url = URL . createObjectURL ( blob ) ;
199- const a = document . createElement ( 'a' ) ;
200- a . href = url ;
201- a . download = 'generated_fuel_cycle_script.py' ;
202- document . body . appendChild ( a ) ;
203- a . click ( ) ;
204- document . body . removeChild ( a ) ;
205- URL . revokeObjectURL ( url ) ;
298+ // Check if File System Access API is supported
299+ if ( 'showSaveFilePicker' in window ) {
300+ try {
301+ // Modern approach: Use File System Access API for proper "Save As" dialog
302+ const fileHandle = await window . showSaveFilePicker ( {
303+ suggestedName : 'fuel_cycle_script.py' ,
304+ types : [ {
305+ description : 'Python files' ,
306+ accept : {
307+ 'text/x-python' : [ '.py' ]
308+ }
309+ } ]
310+ } ) ;
311+
312+ // Create a writable stream and write the Python script
313+ const writable = await fileHandle . createWritable ( ) ;
314+ await writable . write ( result . script ) ;
315+ await writable . close ( ) ;
206316
207- alert ( 'Python script generated and downloaded successfully!' ) ;
317+ alert ( 'Python script generated and saved successfully!' ) ;
318+ } catch ( error ) {
319+ if ( error . name !== 'AbortError' ) {
320+ console . error ( 'Error saving Python file:' , error ) ;
321+ alert ( 'Failed to save Python script.' ) ;
322+ }
323+ // User cancelled the dialog - no error message needed
324+ }
325+ } else {
326+ // Fallback for browsers (Firefox, Safari) that don't support File System Access API
327+ const blob = new Blob ( [ result . script ] , { type : 'text/x-python' } ) ;
328+ const url = URL . createObjectURL ( blob ) ;
329+ const a = document . createElement ( 'a' ) ;
330+ a . href = url ;
331+ a . download = 'fuel_cycle_script.py' ;
332+ document . body . appendChild ( a ) ;
333+ a . click ( ) ;
334+ document . body . removeChild ( a ) ;
335+ URL . revokeObjectURL ( url ) ;
336+
337+ alert ( 'Python script generated and downloaded to your default downloads folder!' ) ;
338+ }
208339 } else {
209340 alert ( `Error generating Python script: ${ result . error } ` ) ;
210341 }
0 commit comments