Implemented animations on the nodes while executing
Some checks failed
Build / build (push) Has been cancelled

This commit is contained in:
Ankitkumar Satapara
2026-04-20 21:23:18 +05:30
parent 4252cc6912
commit 99b8e2c24d
8 changed files with 158 additions and 3 deletions

View File

@@ -103,6 +103,15 @@ namespace Nodify.Calculator
GraphSerializer.Load(firstEditor.Calculator, firstEditor.Calculator.OperationsMenu);
}
catch { }
// When the log panel is closed, clear any lingering execution animation state
LogPanel.PanelClosed += () =>
{
foreach (var editor in Editors)
{
editor.Calculator?.ClearExecutionState();
}
};
}
private void OnOpenInnerCalculator(EditorViewModel parentEditor, CalculatorViewModel calculator)

View File

@@ -149,6 +149,14 @@ namespace Nodify.Calculator
Operations.Add(knot);
}
public void ClearExecutionState()
{
foreach (var op in Operations)
op.ExecutionState = ExecutionState.None;
foreach (var con in Connections)
con.IsActiveInExecution = false;
}
public INodifyCommand StartConnectionCommand { get; }
public INodifyCommand CreateConnectionCommand { get; }
public INodifyCommand DisconnectConnectorCommand { get; }

View File

@@ -32,5 +32,12 @@
set { _outputNodeId = value; }
}
private bool _isActiveInExecution;
public bool IsActiveInExecution
{
get => _isActiveInExecution;
set => SetProperty(ref _isActiveInExecution, value);
}
}
}

View File

@@ -99,8 +99,27 @@
Target="{Binding Input.Anchor}"
Foreground="{Binding Input.Color}"
Stroke="{Binding Input.Color}"
StrokeThickness="2"
/>
StrokeThickness="2">
<nodify:CircuitConnection.Style>
<Style TargetType="{x:Type nodify:CircuitConnection}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsActiveInExecution}" Value="True">
<Setter Property="Stroke" Value="#FFFFEB3B" />
<Setter Property="StrokeThickness" Value="5" />
<Setter Property="StrokeDashArray" Value="4 3" />
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation Storyboard.TargetProperty="StrokeDashOffset"
From="20" To="0" Duration="0:0:0.6" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</nodify:CircuitConnection.Style>
</nodify:CircuitConnection>
</DataTemplate>
<DataTemplate x:Key="PendingConnectionTemplate"
@@ -126,6 +145,36 @@
Value="{Binding BorderBrush, Source={StaticResource AnimatedBorderPlaceholder}}" />
<Setter Property="BorderThickness"
Value="2" />
<Style.Triggers>
<DataTrigger Binding="{Binding ExecutionState}" Value="Running">
<Setter Property="BorderBrush" Value="#FFFFC107" />
<Setter Property="BorderThickness" Value="3" />
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever" AutoReverse="True">
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="1.0" To="0.55" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
To="1.0" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
<DataTrigger Binding="{Binding ExecutionState}" Value="Completed">
<Setter Property="BorderBrush" Value="#FF4CAF50" />
<Setter Property="BorderThickness" Value="3" />
</DataTrigger>
<DataTrigger Binding="{Binding ExecutionState}" Value="Error">
<Setter Property="BorderBrush" Value="#FFF44336" />
<Setter Property="BorderThickness" Value="3" />
</DataTrigger>
</Style.Triggers>
</Style>
<SolidColorBrush x:Key="SquareConnectorColor" Color="MediumSlateBlue"></SolidColorBrush>
<SolidColorBrush x:Key="TriangleConnectorColor" Color="White"></SolidColorBrush>

View File

@@ -0,0 +1,10 @@
namespace Nodify.Calculator
{
public enum ExecutionState
{
None,
Running,
Completed,
Error
}
}

View File

@@ -34,6 +34,9 @@ namespace Nodify.Calculator
static Dictionary<string, string> outputs = new Dictionary<string, string>();
static Dictionary<string, dynamic> variables = new Dictionary<string, dynamic>();
// Animation delay between node steps (milliseconds) while executing
private const int AnimationDelayMs = 600;
// Auth configuration populated from the Auth node
private string _authBaseUrl = string.Empty;
private string _authType = string.Empty;
@@ -76,6 +79,9 @@ namespace Nodify.Calculator
var allnodes = calcModel.Operations;
var allConnections = calcModel.Connections;
// Reset any previous execution animation state
ResetExecutionState(calcModel);
// Resolve Auth node configuration before execution
ResolveAuthNode(allnodes);
@@ -97,6 +103,36 @@ namespace Nodify.Calculator
return errorString;
}
private static void RunOnUI(Action action)
{
var dispatcher = System.Windows.Application.Current?.Dispatcher;
if (dispatcher == null) { action(); return; }
if (dispatcher.CheckAccess()) action();
else dispatcher.Invoke(action);
}
private void ResetExecutionState(CalculatorViewModel calc)
{
RunOnUI(() =>
{
foreach (var op in calc.Operations)
op.ExecutionState = ExecutionState.None;
foreach (var con in calc.Connections)
con.IsActiveInExecution = false;
});
}
private void SetNodeState(OperationViewModel node, ExecutionState state)
{
RunOnUI(() => node.ExecutionState = state);
}
private void SetConnectionActive(ConnectionViewModel conn, bool active)
{
if (conn == null) return;
RunOnUI(() => conn.IsActiveInExecution = active);
}
public static string GenerateClassFromJson(string json, string className)
{
// Parse the JSON to understand its structure
@@ -947,12 +983,30 @@ namespace Nodify.Calculator
if (isExecute)
{
// Mark current node as running and animate
SetNodeState(currentNode, ExecutionState.Running);
System.Threading.Thread.Sleep(AnimationDelayMs / 2);
StartExecution(currentNode, connections);
// Mark current node as completed
SetNodeState(currentNode, ExecutionState.Completed);
// Animate the flow connection leading to the next node
SetConnectionActive(connection, true);
System.Threading.Thread.Sleep(AnimationDelayMs);
SetConnectionActive(connection, false);
}
// Recursively check the next node; stop on the first failure
if (TraverseChain(nextNode, endNodeTitle, connections, visitedNodes, isExecute))
{
if (isExecute)
{
// Mark the terminal "end" node as completed too
if (nextNode.Title?.Equals(endNodeTitle, StringComparison.OrdinalIgnoreCase) == true)
SetNodeState(nextNode, ExecutionState.Completed);
}
return true; // If a valid path to the "end" is found, exit
}
}

View File

@@ -18,9 +18,18 @@ namespace Nodify.Calculator
public bool IsOpen
{
get => _isOpen;
set => SetProperty(ref _isOpen, value);
set
{
var wasOpen = _isOpen;
if (SetProperty(ref _isOpen, value) && wasOpen && !value)
{
PanelClosed?.Invoke();
}
}
}
public event Action PanelClosed;
private bool _isRunning;
public bool IsRunning
{

View File

@@ -72,6 +72,15 @@ namespace Nodify.Calculator
public bool IsReadOnly { get; set; }
private ExecutionState _executionState;
[BsonIgnore]
[Newtonsoft.Json.JsonIgnore]
public ExecutionState ExecutionState
{
get => _executionState;
set => SetProperty(ref _executionState, value);
}
[BsonIgnore]
private IOperation? _operation;
[BsonIgnore]