diff --git a/requirements.txt b/requirements.txt index d9e63044..7bf93ba3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,6 @@ pathsim==0.7.0 matplotlib==3.7.0 numpy==1.24.0 plotly~=6.0 -pytest \ No newline at end of file +pytest +sphinx>=4.0.0 +docutils>=0.17.0 \ No newline at end of file diff --git a/src/App.css b/src/App.css index bc1eee66..a88dada8 100644 --- a/src/App.css +++ b/src/App.css @@ -21,9 +21,7 @@ /* This is the code for customizing the controls icons */ .react-flow__controls { - background-color: #1e1e2f !important; padding: 6px; - min-height: 150px; display: flex; } @@ -92,3 +90,193 @@ font-size: 12px; color: #666; } + +/* Documentation HTML rendering styles for dark theme */ +.documentation-content { + color: #e8e8e8; +} + +.documentation-content p { + margin: 0.5em 0; + line-height: 1.4; +} + +.documentation-content h1, +.documentation-content h2, +.documentation-content h3, +.documentation-content h4, +.documentation-content h5, +.documentation-content h6 { + color: #ffffff; + margin: 1em 0 0.5em 0; + font-weight: bold; +} + +.documentation-content h2 { + font-size: 1.1em; + border-bottom: 1px solid #555; + padding-bottom: 0.2em; +} + +.documentation-content h3 { + font-size: 1.05em; +} + +.documentation-content pre { + background-color: #1a1a2e; + border: 1px solid #444; + border-radius: 3px; + padding: 0.8em; + overflow-x: auto; + font-family: 'Courier New', Consolas, monospace; + font-size: 0.9em; + margin: 0.5em 0; +} + +.documentation-content code { + background-color: #1a1a2e; + padding: 0.1em 0.3em; + border-radius: 2px; + font-family: 'Courier New', Consolas, monospace; + font-size: 0.9em; + color: #ffd700; +} + +.documentation-content pre code { + background-color: transparent; + padding: 0; + color: #e8e8e8; +} + +.documentation-content ul, +.documentation-content ol { + margin: 0.5em 0; + padding-left: 1.5em; +} + +.documentation-content li { + margin: 0.2em 0; +} + +.documentation-content blockquote { + border-left: 3px solid #555; + padding-left: 1em; + margin: 0.5em 0; + font-style: italic; + color: #ccc; +} + +.documentation-content table { + border-collapse: collapse; + width: 100%; + margin: 0.5em 0; +} + +.documentation-content th, +.documentation-content td { + border: 1px solid #555; + padding: 0.4em; + text-align: left; +} + +.documentation-content th { + background-color: #333; + font-weight: bold; +} + +.documentation-content em { + font-style: italic; + color: #ddd; +} + +.documentation-content strong { + font-weight: bold; + color: #ffffff; +} + +.documentation-content a { + color: #78A083; + text-decoration: underline; +} + +.documentation-content a:hover { + color: #9bc49f; +} + +/* Docutils-specific classes */ +.documentation-content .field-list { + margin: 0.5em 0; +} + +.documentation-content .field-name { + font-weight: bold; + color: #ffffff; +} + +.documentation-content .field-body { + margin-left: 1em; +} + +.documentation-content .literal { + background-color: #1a1a2e; + padding: 0.1em 0.3em; + border-radius: 2px; + font-family: 'Courier New', Consolas, monospace; + color: #ffd700; +} + +.documentation-content .note, +.documentation-content .warning, +.documentation-content .tip { + background-color: #2a2a3e; + border-left: 4px solid #78A083; + padding: 0.8em; + margin: 0.5em 0; + border-radius: 0 3px 3px 0; +} + +.documentation-content .warning { + border-left-color: #e74c3c; +} + +.documentation-content .note .first, +.documentation-content .warning .first, +.documentation-content .tip .first { + margin-top: 0; +} + +.documentation-content .note .last, +.documentation-content .warning .last, +.documentation-content .tip .last { + margin-bottom: 0; +} + +/* Custom scrollbar styles for sidebar */ +.sidebar-scrollable { + scrollbar-width: thin; + scrollbar-color: #555 #1e1e2f; +} + +.sidebar-scrollable::-webkit-scrollbar { + width: 8px; +} + +.sidebar-scrollable::-webkit-scrollbar-track { + background: #1e1e2f; + border-radius: 4px; +} + +.sidebar-scrollable::-webkit-scrollbar-thumb { + background: #555; + border-radius: 4px; + border: 1px solid #1e1e2f; +} + +.sidebar-scrollable::-webkit-scrollbar-thumb:hover { + background: #666; +} + +/* Smooth scrolling for the sidebar */ +.sidebar-scrollable { + scroll-behavior: smooth; +} diff --git a/src/App.jsx b/src/App.jsx index 34f85eeb..e9780966 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -96,6 +96,8 @@ export default function App() { // Global variables state const [globalVariables, setGlobalVariables] = useState([]); const [defaultValues, setDefaultValues] = useState({}); + const [nodeDocumentation, setNodeDocumentation] = useState({}); + const [isDocumentationExpanded, setIsDocumentationExpanded] = useState(false); // Function to fetch default values for a node type const fetchDefaultValues = async (nodeType) => { @@ -114,6 +116,32 @@ export default function App() { } }; + // Function to fetch documentation for a node type + const fetchNodeDocumentation = async (nodeType) => { + try { + const response = await fetch(getApiEndpoint(`/get-docs/${nodeType}`)); + if (response.ok) { + const result = await response.json(); + return { + html: result.html || result.docstring || 'No documentation available for this node type.', + text: result.docstring || 'No documentation available for this node type.' + }; + } else { + console.error('Failed to fetch documentation'); + return { + html: '
Failed to load documentation.
', + text: 'Failed to load documentation.' + }; + } + } catch (error) { + console.error('Error fetching documentation:', error); + return { + html: 'Error loading documentation.
', + text: 'Error loading documentation.' + }; + } + }; + // Function to save a graph to computer with "Save As" dialog const saveGraph = async () => { const graphData = { @@ -543,7 +571,7 @@ export default function App() { [edges, setEdges] ); // Function that when we click on a node, sets that node as the selected node - const onNodeClick = (event, node) => { + const onNodeClick = async (event, node) => { setSelectedNode(node); setSelectedEdge(null); // Clear selected edge when selecting a node // Reset all edge styles when selecting a node @@ -561,6 +589,17 @@ export default function App() { }, })) ); + + // Fetch default values and documentation for this node type + if (node.type && !defaultValues[node.type]) { + const defaults = await fetchDefaultValues(node.type); + setDefaultValues(prev => ({ ...prev, [node.type]: defaults })); + } + + if (node.type && !nodeDocumentation[node.type]) { + const docs = await fetchNodeDocumentation(node.type); + setNodeDocumentation(prev => ({ ...prev, [node.type]: docs })); + } }; // Function that when we click on an edge, sets that edge as the selected edge const onEdgeClick = (event, edge) => { @@ -632,15 +671,21 @@ export default function App() { const selectedType = availableTypes[choiceIndex]; const newNodeId = nodeCounter.toString(); - // Fetch default values for this node type + // Fetch default values and documentation for this node type const defaults = await fetchDefaultValues(selectedType); + const docs = await fetchNodeDocumentation(selectedType); - // Store default values for this node type + // Store default values and documentation for this node type setDefaultValues(prev => ({ ...prev, [selectedType]: defaults })); + setNodeDocumentation(prev => ({ + ...prev, + [selectedType]: docs + })); + // Create node data with label and initialize all expected fields as empty strings let nodeData = { label: `${selectedType} ${newNodeId}` }; @@ -882,7 +927,12 @@ export default function App() { {/* Graph Editor Tab */} {activeTab === 'graph' && ( -Error parsing docstring: {str(e)}\n\n{escaped}"
+
+
# Configure Flask app for Cloud Run
app = Flask(__name__, static_folder="../dist", static_url_path="")
@@ -97,6 +135,32 @@ def get_default_values(node_type):
), 400
+@app.route("/get-docs/