handleNodeToggle method
- String nodeId
Handle node toggle (expand/collapse).
Uses either Dart-first or JS-first toggle based on useDartFirstParsing.
Implementation
Future<SchematicLayoutResult?> handleNodeToggle(String nodeId) async {
if (isToggling) {
return null;
}
final engine = _layoutEngine;
if (engine == null) {
return null;
}
// Determine label before the toggle mutates state.
final wasExpanded = schematicAdapter?.isExpanded(nodeId) ?? false;
setState(() {
isToggling = true;
togglingLabel = wasExpanded ? 'Collapsing...' : 'Expanding...';
pendingToggleNodeId = nodeId;
});
// Wait until the frame is painted and spinner animation starts
// before beginning heavy processing
final completer = Completer<void>();
WidgetsBinding.instance.addPostFrameCallback((_) {
completer.complete();
});
await completer.future;
// Request visual updates during processing to keep spinner animating
SchedulerBinding.instance.ensureVisualUpdate();
// Ensure connectivity is available for this module before toggling.
await ensureConnectivity(nodeId);
try {
SchematicLayoutResult newLayout;
if (schematicAdapter == null) {
if (!mounted) {
return null;
}
setState(() {
isToggling = false;
pendingToggleNodeId = null;
});
return null;
}
// Toggle in Dart, then re-serialize for layout
// Snapshot expansion state before mutating, so we can restore
// exactly if ELK rejects the resulting graph (e.g. partial → full
// fails, but a blind re-toggle would go full → collapsed instead).
final preToggleSnapshot = schematicAdapter!.schematic.expansionSnapshot();
// Toggle in the Dart-owned graph
final toggled = schematicAdapter!.toggleNode(nodeId);
if (!toggled) {
if (!mounted) {
return null;
}
setState(() {
isToggling = false;
pendingToggleNodeId = null;
});
return null;
}
// Re-serialize and compute layout
final elkGraph = schematicAdapter!.schematic.toJsGraph();
newLayout = await engine.computeLayoutFromElkGraph(
elkGraph,
sessionId: _sessionId,
);
if (newLayout.hasError) {
if (!mounted) {
return null;
}
// Restore to pre-toggle state so the adapter stays consistent
// with the visible layout (which was not updated due to the error).
schematicAdapter!.schematic.restoreExpansionSnapshot(preToggleSnapshot);
setState(() {
isToggling = false;
pendingToggleNodeId = null;
});
return null;
}
if (!mounted) {
return null;
}
setState(() {
layout = newLayout;
isToggling = false;
pendingToggleNodeId = null;
recentlyToggledNodeId = nodeId;
});
// Clear the recently toggled node after the next frame
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
recentlyToggledNodeId = null;
});
}
});
return newLayout;
} on Exception {
if (!mounted) {
return null;
}
setState(() {
isToggling = false;
pendingToggleNodeId = null;
});
return null;
}
}