Skip to content

Commit f43c2ff

Browse files
authored
Merge pull request #83 from festim-dev/local-file-system
Local file system
2 parents 697bb0c + 9478376 commit f43c2ff

1 file changed

Lines changed: 195 additions & 64 deletions

File tree

src/App.jsx

Lines changed: 195 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)