CommandManager
Undo/redo system using the command pattern. Provides unlimited history for graph operations.
Overview
CommandManager automatically tracks graph operations and provides undo/redo functionality. All node and edge operations are recorded.
Properties
| Property | Type | Description |
|---|---|---|
| Graph | FlowGraph | Associated graph |
| UndoCount | int | Number of operations in undo stack |
| RedoCount | int | Number of operations in redo stack |
Methods
UndoAsync
Undoes the last operation.
Signature: ValueTask UndoAsync()
await graph.CommandManager.UndoAsync();
RedoAsync
Redoes the last undone operation.
Signature: ValueTask RedoAsync()
await graph.CommandManager.RedoAsync();
ClearStacks
Clears the undo and redo stacks.
Signature: void ClearStacks()
graph.CommandManager.ClearStacks();
Events
UndoRedoStackChanged
Fired when the undo/redo stacks change.
Type: EventHandler<EventArgs>
graph.CommandManager.UndoRedoStackChanged += (sender, e) =>
{
UpdateUI();
};
Automatic Tracking
Commands are automatically tracked for:
- Node creation
- Node removal
- Edge creation
- Edge removal
// These operations are automatically tracked:
await graph.CreateNodeAsync<MyNode>(100, 100, []); // Tracked
await graph.RemoveNodeAsync(nodeId); // Tracked
await graph.ConnectAsync(from, to, "Out", "In"); // Tracked
await graph.RemoveEdgeAsync(edgeId); // Tracked
UI Integration
Undo/Redo Buttons
<button @onclick="Undo"
disabled="@(graph.CommandManager.UndoCount == 0)">
↶ Undo (@graph.CommandManager.UndoCount)
</button>
<button @onclick="Redo"
disabled="@(graph.CommandManager.RedoCount == 0)">
↷ Redo (@graph.CommandManager.RedoCount)
</button>
@code {
async Task Undo()
{
await graph.CommandManager.UndoAsync();
}
async Task Redo()
{
await graph.CommandManager.RedoAsync();
}
}
Keyboard Shortcuts
FlowCanvas automatically handles Ctrl+Z / Ctrl+Y:
// Built-in keyboard shortcuts:
// Ctrl+Z or Cmd+Z → Undo
// Ctrl+Y or Cmd+Shift+Z → Redo
Custom keyboard handling:
<FlowCanvas Graph="graph" OnKeyDown="HandleKeyDown">
...
</FlowCanvas>
@code {
async Task HandleKeyDown(KeyboardEventArgs e)
{
if (e.CtrlKey || e.MetaKey)
{
if (e.Key == "z" && !e.ShiftKey)
{
await graph.CommandManager.UndoAsync();
}
else if (e.Key == "y" || (e.Key == "z" && e.ShiftKey))
{
await graph.CommandManager.RedoAsync();
}
}
}
}
Complete Example
@using FlowState.Components
@using FlowState.Models
<div class="toolbar">
<button class="toolbar-btn"
@onclick="Undo"
disabled="@(!CanUndo)">
↶ Undo
</button>
<button class="toolbar-btn"
@onclick="Redo"
disabled="@(!CanRedo)">
↷ Redo
</button>
<div class="history-info">
History: @graph.CommandManager.UndoCount operations
</div>
</div>
<FlowCanvas Graph="graph" Height="calc(100vh - 60px)" Width="100vw">
<BackgroundContent>
<FlowBackground class="grid-bg"/>
</BackgroundContent>
</FlowCanvas>
@code {
FlowGraph graph = new();
bool CanUndo => graph.CommandManager.UndoCount > 0;
bool CanRedo => graph.CommandManager.RedoCount > 0;
protected override void OnInitialized()
{
// Subscribe to stack changes to update UI
graph.CommandManager.UndoRedoStackChanged += (s, e) =>
{
StateHasChanged();
};
graph.RegisterNode<MyNode>();
}
async Task Undo()
{
if (CanUndo)
{
await graph.CommandManager.UndoAsync();
}
}
async Task Redo()
{
if (CanRedo)
{
await graph.CommandManager.RedoAsync();
}
}
}
Read-Only Mode
Commands are not tracked in read-only mode:
await canvas.SetReadOnlyAsync(true);
// Operations in read-only mode are not tracked
// Undo/redo are disabled
Clearing History
Clear the command history when appropriate:
// After loading a new graph
await graph.DeserializeAsync(json);
graph.CommandManager.ClearStacks();
// After clearing the canvas
await graph.ClearAsync();
// Stacks are automatically cleared
Stack Limits
There is no built-in limit to the undo/redo stacks. For memory-constrained scenarios, you might want to implement a custom limit.
Suppressing Commands
Operations can suppress command tracking:
// Internal use - normally not needed in user code
await graph.CreateNodeAsync<MyNode>(
100, 100,
[],
suppressEvent: false,
suppressAddingToCommandStack: true // Don't track
);
Command Types
The following command types are automatically tracked:
- NodeAddedCommand - Node creation
- NodeRemovedCommand - Node deletion (includes connected edges)
- EdgeAddedCommand - Edge creation
- EdgeRemovedCommand - Edge deletion
See Also
- FlowGraph - Graph operations
- FlowCanvas - Keyboard events
- Getting Started - Complete examples with undo/redo