Skip to content
Closed
Show file tree
Hide file tree
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
316 changes: 289 additions & 27 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@
"dependencies": {
"@xyflow/react": "^12.8.1",
"plotly.js": "^3.0.3",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-plotly.js": "^2.6.0"
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-plotly.js": "^2.6.0",
"@uiw/react-codemirror": "^4.24.2",
"@uiw/codemirror-theme-vscode": "^4.24.2",
"@codemirror/lang-python": "^6.2.1"
},
"devDependencies": {
"@eslint/js": "^9.25.0",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@vitejs/plugin-react": "^4.4.1",
"concurrently": "^9.2.0",
"eslint": "^9.25.0",
Expand Down
283 changes: 36 additions & 247 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
import { DnDProvider, useDnD } from './DnDContext.jsx';
import ContextMenu from './ContextMenu.jsx';
import EventsTab from './EventsTab.jsx';
import GlobalVariablesTab from './GlobalVariablesTab.jsx';
import { isValidPythonIdentifier } from './utils.js';

Check failure on line 23 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

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

Check failure on line 23 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

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

Check failure on line 23 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

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

Check failure on line 23 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

'isValidPythonIdentifier' is defined but never used. Allowed unused vars must match /^[A-Z_]/u
import { makeEdge } from './CustomEdge';
import { nodeTypes } from './nodeConfig.js';

Expand Down Expand Up @@ -58,7 +59,7 @@
}, []);

const onDragStart = (event, nodeType) => {
setType(nodeType);

Check failure on line 62 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

'setType' is not defined

Check failure on line 62 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

'setType' is not defined

Check failure on line 62 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

'setType' is not defined

Check failure on line 62 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

'setType' is not defined
event.dataTransfer.setData('text/plain', nodeType);
event.dataTransfer.effectAllowed = 'move';
};
Expand All @@ -80,6 +81,10 @@
// Global variables state
const [globalVariables, setGlobalVariables] = useState([]);
const [events, setEvents] = useState([]);

// Python code editor state
const [pythonCode, setPythonCode] = useState("# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n");

const [defaultValues, setDefaultValues] = useState({});
const [isEditingLabel, setIsEditingLabel] = useState(false);
const [tempLabel, setTempLabel] = useState('');
Expand Down Expand Up @@ -211,7 +216,7 @@
setNodes((nds) => [...nds, newNode]);
setNodeCounter((count) => count + 1);
},
[screenToFlowPosition, type, nodeCounter, fetchDefaultValues, setDefaultValues, setNodes, setNodeCounter],

Check warning on line 219 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

React Hook useCallback has an unnecessary dependency: 'setDefaultValues'. Either exclude it or remove the dependency array

Check warning on line 219 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

React Hook useCallback has an unnecessary dependency: 'setDefaultValues'. Either exclude it or remove the dependency array

Check warning on line 219 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.10)

React Hook useCallback has an unnecessary dependency: 'setDefaultValues'. Either exclude it or remove the dependency array

Check warning on line 219 in src/App.jsx

View workflow job for this annotation

GitHub Actions / test (20.x, 3.11)

React Hook useCallback has an unnecessary dependency: 'setDefaultValues'. Either exclude it or remove the dependency array
);

// Function to save a graph to computer with "Save As" dialog
Expand All @@ -222,7 +227,8 @@
nodeCounter,
solverParams,
globalVariables,
events
events,
pythonCode
};

// Check if File System Access API is supported
Expand Down Expand Up @@ -302,7 +308,15 @@
}

// Load the graph data
const { nodes: loadedNodes, edges: loadedEdges, nodeCounter: loadedNodeCounter, solverParams: loadedSolverParams, globalVariables: loadedGlobalVariables, events: loadedEvents } = graphData;
const {
nodes: loadedNodes,
edges: loadedEdges,
nodeCounter: loadedNodeCounter,
solverParams: loadedSolverParams,
globalVariables: loadedGlobalVariables,
events: loadedEvents,
pythonCode: loadedPythonCode
} = graphData;
setNodes(loadedNodes || []);
setEdges(loadedEdges || []);
setSelectedNode(null);
Expand All @@ -320,6 +334,7 @@
});
setGlobalVariables(loadedGlobalVariables ?? []);
setEvents(loadedEvents ?? []);
setPythonCode(loadedPythonCode ?? "# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n");

alert('Graph loaded successfully!');
} catch (error) {
Expand Down Expand Up @@ -354,7 +369,15 @@
return;
}

const { nodes: loadedNodes, edges: loadedEdges, nodeCounter: loadedNodeCounter, solverParams: loadedSolverParams, globalVariables: loadedGlobalVariables, events: loadedEvents } = graphData;
const {
nodes: loadedNodes,
edges: loadedEdges,
nodeCounter: loadedNodeCounter,
solverParams: loadedSolverParams,
globalVariables: loadedGlobalVariables,
events: loadedEvents,
pythonCode: loadedPythonCode
} = graphData;
setNodes(loadedNodes || []);
setEdges(loadedEdges || []);
setSelectedNode(null);
Expand All @@ -372,6 +395,7 @@
});
setGlobalVariables(loadedGlobalVariables ?? []);
setEvents(loadedEvents ?? []);
setPythonCode(loadedPythonCode ?? "# Define your Python variables and functions here\n# Example:\n# my_variable = 42\n# def my_function(x):\n# return x * 2\n");

alert('Graph loaded successfully!');
} catch (error) {
Expand Down Expand Up @@ -540,7 +564,8 @@
edges,
solverParams,
globalVariables,
events
events,
pythonCode
};

const response = await fetch(getApiEndpoint('/run-pathsim'), {
Expand Down Expand Up @@ -568,36 +593,6 @@
}
};

const addGlobalVariable = () => {
const newVariable = {
id: Date.now().toString(),
name: '',
value: '',
nameError: false
};
setGlobalVariables([...globalVariables, newVariable]);
};

const removeGlobalVariable = (id) => {
setGlobalVariables(globalVariables.filter(variable => variable.id !== id));
};

const updateGlobalVariable = (id, field, value) => {
setGlobalVariables(globalVariables.map(variable => {
if (variable.id === id) {
const updatedVariable = { ...variable, [field]: value };

// Validate name field
if (field === 'name') {
updatedVariable.nameError = value !== '' && !isValidPythonIdentifier(value);
}

return updatedVariable;
}
return variable;
}));
};

//When user connects two nodes by dragging, creates an edge according to the styles in our makeEdge function
const onConnect = useCallback(
(params) => {
Expand Down Expand Up @@ -903,7 +898,7 @@
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [selectedEdge, selectedNode, copiedNode, duplicateNode, setCopyFeedback]);

Check warning on line 901 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 901 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

Check warning on line 901 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

Check warning on line 901 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

return (
<div style={{ width: '100vw', height: '100vh', display: 'flex', flexDirection: 'column' }}>
Expand Down Expand Up @@ -1645,219 +1640,13 @@

{/* Global Variables Tab */}
{activeTab === 'globals' && (
<div style={{
width: '100%',
height: 'calc(100vh - 50px)',
paddingTop: '50px',
backgroundColor: '#1e1e2f',
overflow: 'auto',
}}>
<div style={{
padding: '40px',
maxWidth: '800px',
margin: '0 auto',
}}>
<h1 style={{ color: '#ffffff', marginBottom: '30px', textAlign: 'center' }}>
Global Variables
</h1>
<div style={{
backgroundColor: '#2c2c54',
padding: '30px',
borderRadius: '10px',
boxShadow: '0 4px 8px rgba(0,0,0,0.3)',
}}>
<p style={{
color: '#cccccc',
marginBottom: '20px',
textAlign: 'center',
fontSize: '14px'
}}>
Define global variables that can be used in node definitions throughout your model.
</p>

{globalVariables.length === 0 ? (
<div style={{
textAlign: 'center',
color: '#888',
padding: '40px 20px',
fontStyle: 'italic'
}}>
No global variables defined. Click "Add Variable" to create one.
</div>
) : (
<div style={{ marginBottom: '20px' }}>
{globalVariables.map((variable) => (
<div key={variable.id} style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr auto',
gap: '15px',
alignItems: 'start',
marginBottom: '15px',
padding: '15px',
backgroundColor: '#1e1e2f',
borderRadius: '8px',
border: '1px solid #555'
}}>
<div>
<label style={{
color: '#ffffff',
display: 'block',
marginBottom: '5px',
fontSize: '12px',
fontWeight: 'bold'
}}>
Variable Name:
</label>
<input
type="text"
value={variable.name}
onChange={(e) => updateGlobalVariable(variable.id, 'name', e.target.value)}
placeholder="variable_name"
style={{
width: '95%',
padding: '8px',
borderRadius: '4px',
border: variable.nameError ? '2px solid #e74c3c' : '1px solid #666',
backgroundColor: '#2c2c54',
color: '#ffffff',
fontSize: '14px'
}}
/>
{variable.nameError && (
<div style={{
color: '#e74c3c',
fontSize: '11px',
marginTop: '3px',
fontStyle: 'italic'
}}>
Invalid Python variable name
</div>
)}
</div>
<div>
<label style={{
color: '#ffffff',
display: 'block',
marginBottom: '5px',
fontSize: '12px',
fontWeight: 'bold'
}}>
Value:
</label>
<input
type="text"
value={variable.value}
onChange={(e) => updateGlobalVariable(variable.id, 'value', e.target.value)}
placeholder="0.5"
style={{
width: '95%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #666',
backgroundColor: '#2c2c54',
color: '#ffffff',
fontSize: '14px'
}}
/>
{/* Placeholder div to maintain alignment */}
<div style={{
height: variable.nameError ? '20px' : '0px',
fontSize: '11px',
marginTop: '3px'
}}>
{/* Empty space to match error message height */}
</div>
</div>
<div style={{
display: 'flex',
alignItems: 'center',
height: '100%',
paddingTop: '10px' // Align with input field (label height + margin)
}}>
<button
onClick={() => removeGlobalVariable(variable.id)}
style={{
padding: '8px 12px',
backgroundColor: '#e74c3c',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '16px',
fontWeight: 'bold',
minWidth: '40px'
}}
title="Remove variable"
>
</button>
</div>
</div>
))}
</div>
)}

<div style={{
display: 'flex',
justifyContent: 'center',
gap: '15px',
marginTop: '20px'
}}>
<button
onClick={addGlobalVariable}
style={{
padding: '12px 24px',
backgroundColor: '#78A083',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
fontSize: '16px',
fontWeight: 'bold',
display: 'flex',
alignItems: 'center',
gap: '8px'
}}
>
+ Add Variable
</button>
<button
style={{
padding: '12px 24px',
backgroundColor: '#3498db',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
fontSize: '16px',
fontWeight: 'bold'
}}
onClick={() => setActiveTab('graph')}
>
Back to Graph Editor
</button>
</div>
</div>

<div style={{
marginTop: '30px',
padding: '20px',
backgroundColor: '#2c2c54',
borderRadius: '8px',
border: '1px solid #555'
}}>
<h3 style={{ color: '#ffffff', marginBottom: '15px' }}>Usage Instructions:</h3>
<ul style={{ color: '#cccccc', lineHeight: '1.6' }}>
<li><strong>Variable names</strong> must be valid Python identifiers (start with letter/underscore, contain only letters/digits/underscores)</li>
<li><strong>Cannot use Python keywords</strong> like "if", "for", "class", "def", etc.</li>
<li>Use meaningful names (e.g., "flow_rate", "temperature", "my_constant")</li>
<li>Use numeric values, expressions, or references to other variables</li>
<li>Variables can be referenced in node parameters using their exact names</li>
<li>Variables are saved and loaded with your graph files</li>
</ul>
</div>
</div>
</div>
<GlobalVariablesTab
globalVariables={globalVariables}
setGlobalVariables={setGlobalVariables}
setActiveTab={setActiveTab}
pythonCode={pythonCode}
setPythonCode={setPythonCode}
/>
)}

{/* Results Tab */}
Expand Down
Loading
Loading