Implemented animations on the nodes while executing
Some checks failed
Build / build (push) Has been cancelled
Some checks failed
Build / build (push) Has been cancelled
This commit is contained in:
@@ -103,6 +103,15 @@ namespace Nodify.Calculator
|
|||||||
GraphSerializer.Load(firstEditor.Calculator, firstEditor.Calculator.OperationsMenu);
|
GraphSerializer.Load(firstEditor.Calculator, firstEditor.Calculator.OperationsMenu);
|
||||||
}
|
}
|
||||||
catch { }
|
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)
|
private void OnOpenInnerCalculator(EditorViewModel parentEditor, CalculatorViewModel calculator)
|
||||||
|
|||||||
@@ -149,6 +149,14 @@ namespace Nodify.Calculator
|
|||||||
Operations.Add(knot);
|
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 StartConnectionCommand { get; }
|
||||||
public INodifyCommand CreateConnectionCommand { get; }
|
public INodifyCommand CreateConnectionCommand { get; }
|
||||||
public INodifyCommand DisconnectConnectorCommand { get; }
|
public INodifyCommand DisconnectConnectorCommand { get; }
|
||||||
|
|||||||
@@ -32,5 +32,12 @@
|
|||||||
set { _outputNodeId = value; }
|
set { _outputNodeId = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _isActiveInExecution;
|
||||||
|
public bool IsActiveInExecution
|
||||||
|
{
|
||||||
|
get => _isActiveInExecution;
|
||||||
|
set => SetProperty(ref _isActiveInExecution, value);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,8 +99,27 @@
|
|||||||
Target="{Binding Input.Anchor}"
|
Target="{Binding Input.Anchor}"
|
||||||
Foreground="{Binding Input.Color}"
|
Foreground="{Binding Input.Color}"
|
||||||
Stroke="{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>
|
||||||
|
|
||||||
<DataTemplate x:Key="PendingConnectionTemplate"
|
<DataTemplate x:Key="PendingConnectionTemplate"
|
||||||
@@ -126,6 +145,36 @@
|
|||||||
Value="{Binding BorderBrush, Source={StaticResource AnimatedBorderPlaceholder}}" />
|
Value="{Binding BorderBrush, Source={StaticResource AnimatedBorderPlaceholder}}" />
|
||||||
<Setter Property="BorderThickness"
|
<Setter Property="BorderThickness"
|
||||||
Value="2" />
|
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>
|
</Style>
|
||||||
<SolidColorBrush x:Key="SquareConnectorColor" Color="MediumSlateBlue"></SolidColorBrush>
|
<SolidColorBrush x:Key="SquareConnectorColor" Color="MediumSlateBlue"></SolidColorBrush>
|
||||||
<SolidColorBrush x:Key="TriangleConnectorColor" Color="White"></SolidColorBrush>
|
<SolidColorBrush x:Key="TriangleConnectorColor" Color="White"></SolidColorBrush>
|
||||||
|
|||||||
10
Examples/Nodify.Calculator/ExecutionState.cs
Normal file
10
Examples/Nodify.Calculator/ExecutionState.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public enum ExecutionState
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Running,
|
||||||
|
Completed,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,6 +34,9 @@ namespace Nodify.Calculator
|
|||||||
static Dictionary<string, string> outputs = new Dictionary<string, string>();
|
static Dictionary<string, string> outputs = new Dictionary<string, string>();
|
||||||
static Dictionary<string, dynamic> variables = new Dictionary<string, dynamic>();
|
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
|
// Auth configuration populated from the Auth node
|
||||||
private string _authBaseUrl = string.Empty;
|
private string _authBaseUrl = string.Empty;
|
||||||
private string _authType = string.Empty;
|
private string _authType = string.Empty;
|
||||||
@@ -76,6 +79,9 @@ namespace Nodify.Calculator
|
|||||||
var allnodes = calcModel.Operations;
|
var allnodes = calcModel.Operations;
|
||||||
var allConnections = calcModel.Connections;
|
var allConnections = calcModel.Connections;
|
||||||
|
|
||||||
|
// Reset any previous execution animation state
|
||||||
|
ResetExecutionState(calcModel);
|
||||||
|
|
||||||
// Resolve Auth node configuration before execution
|
// Resolve Auth node configuration before execution
|
||||||
ResolveAuthNode(allnodes);
|
ResolveAuthNode(allnodes);
|
||||||
|
|
||||||
@@ -97,6 +103,36 @@ namespace Nodify.Calculator
|
|||||||
return errorString;
|
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)
|
public static string GenerateClassFromJson(string json, string className)
|
||||||
{
|
{
|
||||||
// Parse the JSON to understand its structure
|
// Parse the JSON to understand its structure
|
||||||
@@ -947,12 +983,30 @@ namespace Nodify.Calculator
|
|||||||
|
|
||||||
if (isExecute)
|
if (isExecute)
|
||||||
{
|
{
|
||||||
|
// Mark current node as running and animate
|
||||||
|
SetNodeState(currentNode, ExecutionState.Running);
|
||||||
|
System.Threading.Thread.Sleep(AnimationDelayMs / 2);
|
||||||
|
|
||||||
StartExecution(currentNode, connections);
|
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
|
// Recursively check the next node; stop on the first failure
|
||||||
if (TraverseChain(nextNode, endNodeTitle, connections, visitedNodes, isExecute))
|
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
|
return true; // If a valid path to the "end" is found, exit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,18 @@ namespace Nodify.Calculator
|
|||||||
public bool IsOpen
|
public bool IsOpen
|
||||||
{
|
{
|
||||||
get => _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;
|
private bool _isRunning;
|
||||||
public bool IsRunning
|
public bool IsRunning
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -72,6 +72,15 @@ namespace Nodify.Calculator
|
|||||||
|
|
||||||
public bool IsReadOnly { get; set; }
|
public bool IsReadOnly { get; set; }
|
||||||
|
|
||||||
|
private ExecutionState _executionState;
|
||||||
|
[BsonIgnore]
|
||||||
|
[Newtonsoft.Json.JsonIgnore]
|
||||||
|
public ExecutionState ExecutionState
|
||||||
|
{
|
||||||
|
get => _executionState;
|
||||||
|
set => SetProperty(ref _executionState, value);
|
||||||
|
}
|
||||||
|
|
||||||
[BsonIgnore]
|
[BsonIgnore]
|
||||||
private IOperation? _operation;
|
private IOperation? _operation;
|
||||||
[BsonIgnore]
|
[BsonIgnore]
|
||||||
|
|||||||
Reference in New Issue
Block a user