Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 195 additions & 64 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Background,
useNodesState,
useEdgesState,
addEdge,

Check failure on line 9 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

'addEdge' is defined but never used. Allowed unused vars must match /^[A-Z_]/u

Check failure on line 9 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

'addEdge' is defined but never used. Allowed unused vars must match /^[A-Z_]/u
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import './App.css';
Expand Down Expand Up @@ -82,14 +82,8 @@
// 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,
Expand All @@ -98,60 +92,168 @@
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);
Expand Down Expand Up @@ -193,18 +295,47 @@
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}`);
}
Expand Down Expand Up @@ -471,7 +602,7 @@
break;
case 'bubbler':
nodeData = { ...nodeData, conversion_efficiency: '0.95', vial_efficiency: '0.9', replacement_time: '' };
default:

Check failure on line 605 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

Expected a 'break' statement before 'default'

Check failure on line 605 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

Expected a 'break' statement before 'default'
// For any other types, just use basic data
break;
}
Expand Down Expand Up @@ -539,7 +670,7 @@
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [selectedEdge, selectedNode]);

Check warning on line 673 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

React Hook useEffect has missing dependencies: 'deleteSelectedEdge' and 'deleteSelectedNode'. Either include them or remove the dependency array

Check warning on line 673 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

React Hook useEffect has missing dependencies: 'deleteSelectedEdge' and 'deleteSelectedNode'. Either include them or remove the dependency array

return (
<div style={{ width: '100vw', height: '100vh', position: 'relative' }}>
Expand Down
Loading