Added nodes related to the list and array operations
Some checks failed
Build / build (push) Has been cancelled
Some checks failed
Build / build (push) Has been cancelled
This commit is contained in:
@@ -49,6 +49,9 @@ namespace Nodify.Calculator
|
||||
|
||||
// Dynamic Parse Json node: adapt input to match source
|
||||
if (!IsLoading) HandleParseJsonNodeConnected(c);
|
||||
|
||||
// Dynamic List nodes: adapt connectors based on connected list type
|
||||
if (!IsLoading) HandleListNodeConnected(c);
|
||||
})
|
||||
.WhenRemoved(c =>
|
||||
{
|
||||
@@ -87,6 +90,9 @@ namespace Nodify.Calculator
|
||||
|
||||
// Dynamic Parse Json node: reset input on disconnect
|
||||
HandleParseJsonNodeDisconnected(c);
|
||||
|
||||
// Dynamic List nodes: reset on disconnect
|
||||
HandleListNodeDisconnected(c);
|
||||
});
|
||||
|
||||
Operations.WhenAdded(x =>
|
||||
@@ -211,7 +217,16 @@ namespace Nodify.Calculator
|
||||
// If either side has no DataType set, allow connection (generic/untyped connector)
|
||||
if (string.IsNullOrEmpty(sourceType) || string.IsNullOrEmpty(targetType))
|
||||
return true;
|
||||
return string.Equals(sourceType, targetType, System.StringComparison.OrdinalIgnoreCase);
|
||||
if (string.Equals(sourceType, targetType, System.StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
// Generic "list" is compatible with any List<T> or array type
|
||||
var s = sourceType.ToLower();
|
||||
var t = targetType.ToLower();
|
||||
if (s == "list" && (t.StartsWith("list<") || t.EndsWith("[]")))
|
||||
return true;
|
||||
if (t == "list" && (s.StartsWith("list<") || s.EndsWith("[]")))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsTakeListBlockedConnection(ConnectorViewModel source, ConnectorViewModel target)
|
||||
@@ -957,6 +972,178 @@ namespace Nodify.Calculator
|
||||
parseInput.ConnectorColor = System.Drawing.Color.White;
|
||||
parseInput.DataType = string.Empty;
|
||||
}
|
||||
|
||||
private static readonly SystemOperations[] _listOps = new[]
|
||||
{
|
||||
SystemOperations.LIST_ADD, SystemOperations.LIST_REMOVE, SystemOperations.LIST_UPDATE,
|
||||
SystemOperations.LIST_GET, SystemOperations.LIST_COUNT, SystemOperations.LIST_CONTAINS,
|
||||
SystemOperations.LIST_CLEAR
|
||||
};
|
||||
|
||||
private static bool IsListNode(OperationViewModel op, out SystemOperationViewModel sysVm)
|
||||
{
|
||||
sysVm = op as SystemOperationViewModel;
|
||||
return sysVm != null && _listOps.Contains(sysVm.SystemOperationType);
|
||||
}
|
||||
|
||||
private void HandleListNodeConnected(ConnectionViewModel c)
|
||||
{
|
||||
// Find the list node's Grid (List) input that was just connected
|
||||
ConnectorViewModel listInput = null;
|
||||
ConnectorViewModel sourceConnector = null;
|
||||
|
||||
if (c.Input.Shape == ConnectorShape.Grid && c.Input.IsInput && IsListNode(c.Input.Operation, out _))
|
||||
{
|
||||
listInput = c.Input;
|
||||
sourceConnector = c.Output;
|
||||
}
|
||||
else if (c.Output.Shape == ConnectorShape.Grid && c.Output.IsInput && IsListNode(c.Output.Operation, out _))
|
||||
{
|
||||
listInput = c.Output;
|
||||
sourceConnector = c.Input;
|
||||
}
|
||||
|
||||
if (listInput == null || sourceConnector == null) return;
|
||||
|
||||
if (!IsListNode(listInput.Operation, out var sysVm)) return;
|
||||
|
||||
// Determine the full list type and element type from the source
|
||||
var sourceDataType = sourceConnector.DataType ?? "";
|
||||
string elementType;
|
||||
string listType;
|
||||
if (sourceDataType.StartsWith("List<") && sourceDataType.EndsWith(">"))
|
||||
{
|
||||
elementType = sourceDataType.Substring(5, sourceDataType.Length - 6);
|
||||
listType = sourceDataType;
|
||||
}
|
||||
else
|
||||
{
|
||||
elementType = "object";
|
||||
listType = sourceDataType;
|
||||
}
|
||||
|
||||
// Adapt the List input connector
|
||||
listInput.Title = $"List ({listType})";
|
||||
listInput.ConnectorColor = sourceConnector.RawColor;
|
||||
listInput.DataType = sourceDataType;
|
||||
|
||||
// Adapt all List output connectors (Grid shape)
|
||||
foreach (var outConn in listInput.Operation.Output.Where(o => o.Shape == ConnectorShape.Grid))
|
||||
{
|
||||
outConn.Title = listType;
|
||||
outConn.ConnectorColor = sourceConnector.RawColor;
|
||||
outConn.DataType = sourceDataType;
|
||||
}
|
||||
|
||||
// Determine element connector properties
|
||||
var primitiveTypes = new[] { "string", "int", "double", "bool", "float", "decimal", "long", "object", "datetime" };
|
||||
bool isModel = !primitiveTypes.Contains(elementType.ToLower())
|
||||
&& File.Exists(Path.Combine(ProjectManager.ProjectDirectory, "CustomModels", $"{elementType}.cs"));
|
||||
|
||||
var elemShape = isModel ? ConnectorShape.Square : ConnectorShape.Circle;
|
||||
var elemColor = isModel ? System.Drawing.Color.FromArgb(255, 150, 80, 220) : ConnectorViewModel.GetColorForType(elementType);
|
||||
|
||||
// Adapt Item input connectors (IsCopyConnector ones) for Add/Update/Contains
|
||||
foreach (var itemConn in listInput.Operation.Input.Where(i => i.IsCopyConnector))
|
||||
{
|
||||
itemConn.Title = $"Item ({elementType})";
|
||||
itemConn.Shape = elemShape;
|
||||
itemConn.ConnectorColor = elemColor;
|
||||
itemConn.DataType = elementType;
|
||||
}
|
||||
|
||||
// Adapt Item output connector for List Get
|
||||
if (sysVm.SystemOperationType == SystemOperations.LIST_GET)
|
||||
{
|
||||
var itemOut = listInput.Operation.Output.FirstOrDefault(o => o.Shape != ConnectorShape.Triangle && o.Shape != ConnectorShape.Grid);
|
||||
if (itemOut != null)
|
||||
{
|
||||
itemOut.Title = elementType;
|
||||
itemOut.Shape = elemShape;
|
||||
itemOut.ConnectorColor = elemColor;
|
||||
itemOut.DataType = elementType;
|
||||
}
|
||||
}
|
||||
|
||||
// Update node title
|
||||
var baseTitle = sysVm.SystemOperationType switch
|
||||
{
|
||||
SystemOperations.LIST_ADD => "List Add",
|
||||
SystemOperations.LIST_REMOVE => "List Remove",
|
||||
SystemOperations.LIST_UPDATE => "List Update",
|
||||
SystemOperations.LIST_GET => "List Get",
|
||||
SystemOperations.LIST_COUNT => "List Count",
|
||||
SystemOperations.LIST_CONTAINS => "List Contains",
|
||||
SystemOperations.LIST_CLEAR => "List Clear",
|
||||
_ => "List"
|
||||
};
|
||||
sysVm.Title = $"{baseTitle} <{elementType}>";
|
||||
}
|
||||
|
||||
private void HandleListNodeDisconnected(ConnectionViewModel c)
|
||||
{
|
||||
ConnectorViewModel listInput = null;
|
||||
|
||||
if (c.Input.Shape == ConnectorShape.Grid && c.Input.IsInput && IsListNode(c.Input.Operation, out _))
|
||||
listInput = c.Input;
|
||||
else if (c.Output.Shape == ConnectorShape.Grid && c.Output.IsInput && IsListNode(c.Output.Operation, out _))
|
||||
listInput = c.Output;
|
||||
|
||||
if (listInput == null) return;
|
||||
|
||||
var stillConnected = Connections.Any(con => con.Input == listInput || con.Output == listInput);
|
||||
if (stillConnected) return;
|
||||
|
||||
if (!IsListNode(listInput.Operation, out var sysVm)) return;
|
||||
|
||||
// Reset List input
|
||||
listInput.Title = "List";
|
||||
listInput.ConnectorColor = NodeColors.List;
|
||||
listInput.DataType = "list";
|
||||
|
||||
// Reset List outputs
|
||||
foreach (var outConn in listInput.Operation.Output.Where(o => o.Shape == ConnectorShape.Grid))
|
||||
{
|
||||
outConn.Title = "List";
|
||||
outConn.ConnectorColor = NodeColors.List;
|
||||
outConn.DataType = "list";
|
||||
}
|
||||
|
||||
// Reset Item input connectors
|
||||
foreach (var itemConn in listInput.Operation.Input.Where(i => i.IsCopyConnector))
|
||||
{
|
||||
itemConn.Title = "Item";
|
||||
itemConn.Shape = ConnectorShape.Circle;
|
||||
itemConn.ConnectorColor = NodeColors.Universal;
|
||||
itemConn.DataType = string.Empty;
|
||||
}
|
||||
|
||||
// Reset Item output for List Get
|
||||
if (sysVm.SystemOperationType == SystemOperations.LIST_GET)
|
||||
{
|
||||
var itemOut = listInput.Operation.Output.FirstOrDefault(o => o.Shape != ConnectorShape.Triangle && o.Shape != ConnectorShape.Grid);
|
||||
if (itemOut != null)
|
||||
{
|
||||
itemOut.Title = "Item";
|
||||
itemOut.Shape = ConnectorShape.Circle;
|
||||
itemOut.ConnectorColor = NodeColors.Object;
|
||||
itemOut.DataType = "object";
|
||||
}
|
||||
}
|
||||
|
||||
// Reset title
|
||||
sysVm.Title = sysVm.SystemOperationType switch
|
||||
{
|
||||
SystemOperations.LIST_ADD => "List Add",
|
||||
SystemOperations.LIST_REMOVE => "List Remove",
|
||||
SystemOperations.LIST_UPDATE => "List Update",
|
||||
SystemOperations.LIST_GET => "List Get",
|
||||
SystemOperations.LIST_COUNT => "List Count",
|
||||
SystemOperations.LIST_CONTAINS => "List Contains",
|
||||
SystemOperations.LIST_CLEAR => "List Clear",
|
||||
_ => "List"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -839,6 +839,34 @@
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- List / Array Nodes -->
|
||||
<TextBlock Text="📋 List / Array" FontWeight="Bold" FontSize="14"
|
||||
Foreground="{DynamicResource ForegroundBrush}" Margin="5 10 0 4" />
|
||||
<ItemsControl ItemsSource="{Binding Calculator.OperationsMenu.ListNodes}"
|
||||
Focusable="False">
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style>
|
||||
<Setter Property="FrameworkElement.Margin" Value="5" />
|
||||
<Setter Property="FrameworkElement.HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="FrameworkElement.Cursor" Value="Hand" />
|
||||
<Setter Property="FrameworkElement.ToolTip" Value="Drag and drop into the editor" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:OperationViewModel}">
|
||||
<nodify:Node Content="{Binding Title}"
|
||||
Input="{Binding Input}"
|
||||
Output="{Binding Output}"
|
||||
BorderBrush="{StaticResource AnimatedBrush}"
|
||||
BorderThickness="2"
|
||||
MouseMove="OnNodeDrag"
|
||||
Focusable="True"
|
||||
KeyboardNavigation.TabNavigation="None">
|
||||
</nodify:Node>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- Grouped Swagger API operations -->
|
||||
<ItemsControl ItemsSource="{Binding Calculator.OperationsMenu.GroupedSwaggerOperations}"
|
||||
Focusable="False">
|
||||
|
||||
@@ -340,6 +340,108 @@ namespace Nodify.Calculator.Execution.Handlers
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// List operation execution handlers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
internal sealed class ListAddExecutionHandler : INodeExecutionHandler
|
||||
{
|
||||
public bool CanHandle(OperationViewModel node, ExecutionContext ctx)
|
||||
=> node is SystemOperationViewModel sys && sys.SystemOperationType == SystemOperations.LIST_ADD;
|
||||
|
||||
public void Execute(OperationViewModel node, ExecutionContext ctx)
|
||||
{
|
||||
var listJson = ListExecHelper.ReadInput(node, ctx, "List");
|
||||
var itemJson = ListExecHelper.ReadInput(node, ctx, "Item");
|
||||
var list = ListExecHelper.ParseList(listJson);
|
||||
list.Add(itemJson != null ? JToken.Parse(itemJson) : JValue.CreateNull());
|
||||
var result = list.ToString(Formatting.None);
|
||||
ctx.Outputs[node.NodeId] = result;
|
||||
ctx.Variables[node.NodeId] = result;
|
||||
ctx.Log($"[List Add] Added item. Count={list.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ListRemoveExecutionHandler : INodeExecutionHandler
|
||||
{
|
||||
public bool CanHandle(OperationViewModel node, ExecutionContext ctx)
|
||||
=> node is SystemOperationViewModel sys && sys.SystemOperationType == SystemOperations.LIST_REMOVE;
|
||||
|
||||
public void Execute(OperationViewModel node, ExecutionContext ctx)
|
||||
{
|
||||
var listJson = ListExecHelper.ReadInput(node, ctx, "List");
|
||||
var indexStr = ListExecHelper.ReadInput(node, ctx, "Index");
|
||||
var list = ListExecHelper.ParseList(listJson);
|
||||
int index = int.TryParse(indexStr, out var i) ? i : -1;
|
||||
if (index >= 0 && index < list.Count)
|
||||
list.RemoveAt(index);
|
||||
else
|
||||
ctx.Log($"[List Remove] Index {index} out of range (Count={list.Count})", logType.Warning);
|
||||
var result = list.ToString(Formatting.None);
|
||||
ctx.Outputs[node.NodeId] = result;
|
||||
ctx.Variables[node.NodeId] = result;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ListUpdateExecutionHandler : INodeExecutionHandler
|
||||
{
|
||||
public bool CanHandle(OperationViewModel node, ExecutionContext ctx)
|
||||
=> node is SystemOperationViewModel sys && sys.SystemOperationType == SystemOperations.LIST_UPDATE;
|
||||
|
||||
public void Execute(OperationViewModel node, ExecutionContext ctx)
|
||||
{
|
||||
var listJson = ListExecHelper.ReadInput(node, ctx, "List");
|
||||
var indexStr = ListExecHelper.ReadInput(node, ctx, "Index");
|
||||
var itemJson = ListExecHelper.ReadInput(node, ctx, "Item");
|
||||
var list = ListExecHelper.ParseList(listJson);
|
||||
int index = int.TryParse(indexStr, out var i) ? i : -1;
|
||||
if (index >= 0 && index < list.Count)
|
||||
list[index] = itemJson != null ? JToken.Parse(itemJson) : JValue.CreateNull();
|
||||
else
|
||||
ctx.Log($"[List Update] Index {index} out of range (Count={list.Count})", logType.Warning);
|
||||
var result = list.ToString(Formatting.None);
|
||||
ctx.Outputs[node.NodeId] = result;
|
||||
ctx.Variables[node.NodeId] = result;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ListClearExecutionHandler : INodeExecutionHandler
|
||||
{
|
||||
public bool CanHandle(OperationViewModel node, ExecutionContext ctx)
|
||||
=> node is SystemOperationViewModel sys && sys.SystemOperationType == SystemOperations.LIST_CLEAR;
|
||||
|
||||
public void Execute(OperationViewModel node, ExecutionContext ctx)
|
||||
{
|
||||
var result = "[]";
|
||||
ctx.Outputs[node.NodeId] = result;
|
||||
ctx.Variables[node.NodeId] = result;
|
||||
ctx.Log("[List Clear] Cleared list");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Helper for reading list node inputs and parsing JSON arrays.</summary>
|
||||
internal static class ListExecHelper
|
||||
{
|
||||
public static string ReadInput(OperationViewModel node, ExecutionContext ctx, string title)
|
||||
{
|
||||
var conn = ctx.Connections.FirstOrDefault(c =>
|
||||
c.Input.Operation == node
|
||||
&& (c.Input.Title ?? "").StartsWith(title, StringComparison.OrdinalIgnoreCase)
|
||||
&& c.Input.Shape != ConnectorShape.Triangle);
|
||||
if (conn == null) return null;
|
||||
if (ctx.Outputs.TryGetValue(conn.OutputNodeId, out var val))
|
||||
return val;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static JArray ParseList(string json)
|
||||
{
|
||||
if (string.IsNullOrEmpty(json)) return new JArray();
|
||||
try { return JArray.Parse(json); }
|
||||
catch { return new JArray(); }
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Default / fallback: API call
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -205,4 +205,88 @@ namespace Nodify.Calculator.Execution.Resolvers
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// LIST GET: pick an element by index from a JSON array (non-flow value node).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
internal sealed class ListGetValueResolver : INodeValueResolver
|
||||
{
|
||||
public bool CanResolve(OperationViewModel node, ConnectorViewModel c)
|
||||
=> node is SystemOperationViewModel sys && sys.SystemOperationType == SystemOperations.LIST_GET;
|
||||
|
||||
public string Resolve(OperationViewModel node, ConnectorViewModel outputConnector, IValueResolutionContext ctx)
|
||||
{
|
||||
var listInput = node.Input.FirstOrDefault(i => i.Shape == ConnectorShape.Grid);
|
||||
var indexInput = node.Input.FirstOrDefault(i => (i.Title ?? "").StartsWith("Index", StringComparison.OrdinalIgnoreCase));
|
||||
if (listInput == null) return null;
|
||||
|
||||
var json = ctx.Read(listInput);
|
||||
var indexStr = indexInput != null ? ctx.Read(indexInput) : "0";
|
||||
int index = int.TryParse(indexStr, out var i) ? i : 0;
|
||||
|
||||
try
|
||||
{
|
||||
var arr = JArray.Parse(json ?? "[]");
|
||||
if (index >= 0 && index < arr.Count)
|
||||
{
|
||||
var item = arr[index];
|
||||
return item.Type == JTokenType.String ? item.ToString() : item.ToString(Formatting.None);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// LIST COUNT: returns the length of a JSON array as a string integer.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
internal sealed class ListCountValueResolver : INodeValueResolver
|
||||
{
|
||||
public bool CanResolve(OperationViewModel node, ConnectorViewModel c)
|
||||
=> node is SystemOperationViewModel sys && sys.SystemOperationType == SystemOperations.LIST_COUNT;
|
||||
|
||||
public string Resolve(OperationViewModel node, ConnectorViewModel outputConnector, IValueResolutionContext ctx)
|
||||
{
|
||||
var listInput = node.Input.FirstOrDefault(i => i.Shape == ConnectorShape.Grid);
|
||||
if (listInput == null) return "0";
|
||||
|
||||
var json = ctx.Read(listInput);
|
||||
try
|
||||
{
|
||||
var arr = JArray.Parse(json ?? "[]");
|
||||
return arr.Count.ToString();
|
||||
}
|
||||
catch { return "0"; }
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// LIST CONTAINS: checks if item exists in array, returns "true"/"false".
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
internal sealed class ListContainsValueResolver : INodeValueResolver
|
||||
{
|
||||
public bool CanResolve(OperationViewModel node, ConnectorViewModel c)
|
||||
=> node is SystemOperationViewModel sys && sys.SystemOperationType == SystemOperations.LIST_CONTAINS;
|
||||
|
||||
public string Resolve(OperationViewModel node, ConnectorViewModel outputConnector, IValueResolutionContext ctx)
|
||||
{
|
||||
var listInput = node.Input.FirstOrDefault(i => i.Shape == ConnectorShape.Grid);
|
||||
var itemInput = node.Input.FirstOrDefault(i => i.IsCopyConnector);
|
||||
if (listInput == null) return "false";
|
||||
|
||||
var json = ctx.Read(listInput);
|
||||
var itemJson = itemInput != null ? ctx.Read(itemInput) : null;
|
||||
|
||||
try
|
||||
{
|
||||
var arr = JArray.Parse(json ?? "[]");
|
||||
if (string.IsNullOrEmpty(itemJson)) return "false";
|
||||
var searchToken = JToken.Parse(itemJson);
|
||||
bool found = arr.Any(t => JToken.DeepEquals(t, searchToken));
|
||||
return found.ToString().ToLower();
|
||||
}
|
||||
catch { return "false"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,10 @@ namespace Nodify.Calculator
|
||||
new AssertHandler(),
|
||||
new ForEachHandler(),
|
||||
new FunctionHandler(),
|
||||
new ListAddExecutionHandler(),
|
||||
new ListRemoveExecutionHandler(),
|
||||
new ListUpdateExecutionHandler(),
|
||||
new ListClearExecutionHandler(),
|
||||
new ApiRequestHandler(), // default / fallback
|
||||
};
|
||||
|
||||
@@ -71,6 +75,9 @@ namespace Nodify.Calculator
|
||||
new KnotNodeValueResolver(),
|
||||
new TakeNodeValueResolver(),
|
||||
new ParseJsonNodeValueResolver(),
|
||||
new ListGetValueResolver(),
|
||||
new ListCountValueResolver(),
|
||||
new ListContainsValueResolver(),
|
||||
new GetVariableNodeValueResolver(),
|
||||
};
|
||||
|
||||
|
||||
251
Examples/Nodify.Calculator/NodeHandlers/ListNodeHandlers.cs
Normal file
251
Examples/Nodify.Calculator/NodeHandlers/ListNodeHandlers.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
using Nodify.Calculator.Models;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nodify.Calculator.NodeHandlers
|
||||
{
|
||||
// ─── LIST ADD ───
|
||||
public class ListAddHandler : INodeHandler
|
||||
{
|
||||
public string NodeTypeKey => "System";
|
||||
|
||||
public bool CanCreate(OperationInfoViewModel info)
|
||||
=> info.Type == OperationType.System && info.sysOp == SystemOperations.LIST_ADD;
|
||||
|
||||
public bool CanRestore(NodeData data)
|
||||
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.LIST_ADD);
|
||||
|
||||
public OperationViewModel Create(OperationInfoViewModel info)
|
||||
{
|
||||
var op = new SystemOperationViewModel
|
||||
{
|
||||
Title = info.Title ?? "List Add",
|
||||
SystemOperationType = SystemOperations.LIST_ADD
|
||||
};
|
||||
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
|
||||
op.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
|
||||
op.Input.Add(new ConnectorViewModel { Title = "List", Shape = ConnectorShape.Grid, ConnectorColor = NodeColors.List, DataType = "list" });
|
||||
op.Input.Add(new ConnectorViewModel { Title = "Item", Shape = ConnectorShape.Circle, ConnectorColor = NodeColors.Universal, IsCopyConnector = true });
|
||||
op.Output.Add(new ConnectorViewModel { Title = "List", IsInput = false, Shape = ConnectorShape.Grid, ConnectorColor = NodeColors.List, DataType = "list" });
|
||||
return op;
|
||||
}
|
||||
|
||||
public OperationViewModel Restore(NodeData data)
|
||||
=> Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = SystemOperations.LIST_ADD });
|
||||
|
||||
public void Save(OperationViewModel vm, NodeData data)
|
||||
{
|
||||
data.NodeType = "System";
|
||||
data.SystemOp = nameof(SystemOperations.LIST_ADD);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── LIST REMOVE ───
|
||||
public class ListRemoveHandler : INodeHandler
|
||||
{
|
||||
public string NodeTypeKey => "System";
|
||||
|
||||
public bool CanCreate(OperationInfoViewModel info)
|
||||
=> info.Type == OperationType.System && info.sysOp == SystemOperations.LIST_REMOVE;
|
||||
|
||||
public bool CanRestore(NodeData data)
|
||||
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.LIST_REMOVE);
|
||||
|
||||
public OperationViewModel Create(OperationInfoViewModel info)
|
||||
{
|
||||
var op = new SystemOperationViewModel
|
||||
{
|
||||
Title = info.Title ?? "List Remove",
|
||||
SystemOperationType = SystemOperations.LIST_REMOVE
|
||||
};
|
||||
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
|
||||
op.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
|
||||
op.Input.Add(new ConnectorViewModel { Title = "List", Shape = ConnectorShape.Grid, ConnectorColor = NodeColors.List, DataType = "list" });
|
||||
op.Input.Add(new ConnectorViewModel { Title = "Index", Shape = ConnectorShape.Circle, ConnectorColor = NodeColors.Integer, DataType = "int" });
|
||||
op.Output.Add(new ConnectorViewModel { Title = "List", IsInput = false, Shape = ConnectorShape.Grid, ConnectorColor = NodeColors.List, DataType = "list" });
|
||||
return op;
|
||||
}
|
||||
|
||||
public OperationViewModel Restore(NodeData data)
|
||||
=> Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = SystemOperations.LIST_REMOVE });
|
||||
|
||||
public void Save(OperationViewModel vm, NodeData data)
|
||||
{
|
||||
data.NodeType = "System";
|
||||
data.SystemOp = nameof(SystemOperations.LIST_REMOVE);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── LIST UPDATE ───
|
||||
public class ListUpdateHandler : INodeHandler
|
||||
{
|
||||
public string NodeTypeKey => "System";
|
||||
|
||||
public bool CanCreate(OperationInfoViewModel info)
|
||||
=> info.Type == OperationType.System && info.sysOp == SystemOperations.LIST_UPDATE;
|
||||
|
||||
public bool CanRestore(NodeData data)
|
||||
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.LIST_UPDATE);
|
||||
|
||||
public OperationViewModel Create(OperationInfoViewModel info)
|
||||
{
|
||||
var op = new SystemOperationViewModel
|
||||
{
|
||||
Title = info.Title ?? "List Update",
|
||||
SystemOperationType = SystemOperations.LIST_UPDATE
|
||||
};
|
||||
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
|
||||
op.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
|
||||
op.Input.Add(new ConnectorViewModel { Title = "List", Shape = ConnectorShape.Grid, ConnectorColor = NodeColors.List, DataType = "list" });
|
||||
op.Input.Add(new ConnectorViewModel { Title = "Index", Shape = ConnectorShape.Circle, ConnectorColor = NodeColors.Integer, DataType = "int" });
|
||||
op.Input.Add(new ConnectorViewModel { Title = "Item", Shape = ConnectorShape.Circle, ConnectorColor = NodeColors.Universal, IsCopyConnector = true });
|
||||
op.Output.Add(new ConnectorViewModel { Title = "List", IsInput = false, Shape = ConnectorShape.Grid, ConnectorColor = NodeColors.List, DataType = "list" });
|
||||
return op;
|
||||
}
|
||||
|
||||
public OperationViewModel Restore(NodeData data)
|
||||
=> Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = SystemOperations.LIST_UPDATE });
|
||||
|
||||
public void Save(OperationViewModel vm, NodeData data)
|
||||
{
|
||||
data.NodeType = "System";
|
||||
data.SystemOp = nameof(SystemOperations.LIST_UPDATE);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── LIST GET (non-flow, value node) ───
|
||||
public class ListGetHandler : INodeHandler
|
||||
{
|
||||
public string NodeTypeKey => "System";
|
||||
|
||||
public bool CanCreate(OperationInfoViewModel info)
|
||||
=> info.Type == OperationType.System && info.sysOp == SystemOperations.LIST_GET;
|
||||
|
||||
public bool CanRestore(NodeData data)
|
||||
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.LIST_GET);
|
||||
|
||||
public OperationViewModel Create(OperationInfoViewModel info)
|
||||
{
|
||||
var op = new SystemOperationViewModel
|
||||
{
|
||||
Title = info.Title ?? "List Get",
|
||||
SystemOperationType = SystemOperations.LIST_GET
|
||||
};
|
||||
op.Input.Add(new ConnectorViewModel { Title = "List", Shape = ConnectorShape.Grid, ConnectorColor = NodeColors.List, DataType = "list" });
|
||||
op.Input.Add(new ConnectorViewModel { Title = "Index", Shape = ConnectorShape.Circle, ConnectorColor = NodeColors.Integer, DataType = "int" });
|
||||
op.Output.Add(new ConnectorViewModel { Title = "Item", IsInput = false, Shape = ConnectorShape.Circle, ConnectorColor = NodeColors.Object, DataType = "object" });
|
||||
return op;
|
||||
}
|
||||
|
||||
public OperationViewModel Restore(NodeData data)
|
||||
=> Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = SystemOperations.LIST_GET });
|
||||
|
||||
public void Save(OperationViewModel vm, NodeData data)
|
||||
{
|
||||
data.NodeType = "System";
|
||||
data.SystemOp = nameof(SystemOperations.LIST_GET);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── LIST COUNT (non-flow, value node) ───
|
||||
public class ListCountHandler : INodeHandler
|
||||
{
|
||||
public string NodeTypeKey => "System";
|
||||
|
||||
public bool CanCreate(OperationInfoViewModel info)
|
||||
=> info.Type == OperationType.System && info.sysOp == SystemOperations.LIST_COUNT;
|
||||
|
||||
public bool CanRestore(NodeData data)
|
||||
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.LIST_COUNT);
|
||||
|
||||
public OperationViewModel Create(OperationInfoViewModel info)
|
||||
{
|
||||
var op = new SystemOperationViewModel
|
||||
{
|
||||
Title = info.Title ?? "List Count",
|
||||
SystemOperationType = SystemOperations.LIST_COUNT
|
||||
};
|
||||
op.Input.Add(new ConnectorViewModel { Title = "List", Shape = ConnectorShape.Grid, ConnectorColor = NodeColors.List, DataType = "list" });
|
||||
op.Output.Add(new ConnectorViewModel { Title = "Count", IsInput = false, Shape = ConnectorShape.Circle, ConnectorColor = NodeColors.Integer, DataType = "int" });
|
||||
return op;
|
||||
}
|
||||
|
||||
public OperationViewModel Restore(NodeData data)
|
||||
=> Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = SystemOperations.LIST_COUNT });
|
||||
|
||||
public void Save(OperationViewModel vm, NodeData data)
|
||||
{
|
||||
data.NodeType = "System";
|
||||
data.SystemOp = nameof(SystemOperations.LIST_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── LIST CONTAINS (non-flow, value node) ───
|
||||
public class ListContainsHandler : INodeHandler
|
||||
{
|
||||
public string NodeTypeKey => "System";
|
||||
|
||||
public bool CanCreate(OperationInfoViewModel info)
|
||||
=> info.Type == OperationType.System && info.sysOp == SystemOperations.LIST_CONTAINS;
|
||||
|
||||
public bool CanRestore(NodeData data)
|
||||
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.LIST_CONTAINS);
|
||||
|
||||
public OperationViewModel Create(OperationInfoViewModel info)
|
||||
{
|
||||
var op = new SystemOperationViewModel
|
||||
{
|
||||
Title = info.Title ?? "List Contains",
|
||||
SystemOperationType = SystemOperations.LIST_CONTAINS
|
||||
};
|
||||
op.Input.Add(new ConnectorViewModel { Title = "List", Shape = ConnectorShape.Grid, ConnectorColor = NodeColors.List, DataType = "list" });
|
||||
op.Input.Add(new ConnectorViewModel { Title = "Item", Shape = ConnectorShape.Circle, ConnectorColor = NodeColors.Universal, IsCopyConnector = true });
|
||||
op.Output.Add(new ConnectorViewModel { Title = "Result", IsInput = false, Shape = ConnectorShape.Circle, ConnectorColor = NodeColors.Boolean, DataType = "bool" });
|
||||
return op;
|
||||
}
|
||||
|
||||
public OperationViewModel Restore(NodeData data)
|
||||
=> Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = SystemOperations.LIST_CONTAINS });
|
||||
|
||||
public void Save(OperationViewModel vm, NodeData data)
|
||||
{
|
||||
data.NodeType = "System";
|
||||
data.SystemOp = nameof(SystemOperations.LIST_CONTAINS);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── LIST CLEAR ───
|
||||
public class ListClearHandler : INodeHandler
|
||||
{
|
||||
public string NodeTypeKey => "System";
|
||||
|
||||
public bool CanCreate(OperationInfoViewModel info)
|
||||
=> info.Type == OperationType.System && info.sysOp == SystemOperations.LIST_CLEAR;
|
||||
|
||||
public bool CanRestore(NodeData data)
|
||||
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.LIST_CLEAR);
|
||||
|
||||
public OperationViewModel Create(OperationInfoViewModel info)
|
||||
{
|
||||
var op = new SystemOperationViewModel
|
||||
{
|
||||
Title = info.Title ?? "List Clear",
|
||||
SystemOperationType = SystemOperations.LIST_CLEAR
|
||||
};
|
||||
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
|
||||
op.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
|
||||
op.Input.Add(new ConnectorViewModel { Title = "List", Shape = ConnectorShape.Grid, ConnectorColor = NodeColors.List, DataType = "list" });
|
||||
op.Output.Add(new ConnectorViewModel { Title = "List", IsInput = false, Shape = ConnectorShape.Grid, ConnectorColor = NodeColors.List, DataType = "list" });
|
||||
return op;
|
||||
}
|
||||
|
||||
public OperationViewModel Restore(NodeData data)
|
||||
=> Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = SystemOperations.LIST_CLEAR });
|
||||
|
||||
public void Save(OperationViewModel vm, NodeData data)
|
||||
{
|
||||
data.NodeType = "System";
|
||||
data.SystemOp = nameof(SystemOperations.LIST_CLEAR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,13 @@ namespace Nodify.Calculator.NodeHandlers
|
||||
_handlers.Add(new CopyHandler());
|
||||
_handlers.Add(new SplitHandler());
|
||||
_handlers.Add(new ParseJsonHandler());
|
||||
_handlers.Add(new ListAddHandler());
|
||||
_handlers.Add(new ListRemoveHandler());
|
||||
_handlers.Add(new ListUpdateHandler());
|
||||
_handlers.Add(new ListGetHandler());
|
||||
_handlers.Add(new ListCountHandler());
|
||||
_handlers.Add(new ListContainsHandler());
|
||||
_handlers.Add(new ListClearHandler());
|
||||
_handlers.Add(new GetSetVariableHandler());
|
||||
_handlers.Add(new GetSetModelHandler());
|
||||
_handlers.Add(new BeginEndHandler());
|
||||
@@ -108,6 +115,13 @@ namespace Nodify.Calculator.NodeHandlers
|
||||
SystemOperations.END => _handlers.OfType<BeginEndHandler>().FirstOrDefault(),
|
||||
SystemOperations.GET_SET when sys.IsSimpleVariable => _handlers.OfType<GetSetVariableHandler>().FirstOrDefault(),
|
||||
SystemOperations.GET_SET => _handlers.OfType<GetSetModelHandler>().FirstOrDefault(),
|
||||
SystemOperations.LIST_ADD => _handlers.OfType<ListAddHandler>().FirstOrDefault(),
|
||||
SystemOperations.LIST_REMOVE => _handlers.OfType<ListRemoveHandler>().FirstOrDefault(),
|
||||
SystemOperations.LIST_UPDATE => _handlers.OfType<ListUpdateHandler>().FirstOrDefault(),
|
||||
SystemOperations.LIST_GET => _handlers.OfType<ListGetHandler>().FirstOrDefault(),
|
||||
SystemOperations.LIST_COUNT => _handlers.OfType<ListCountHandler>().FirstOrDefault(),
|
||||
SystemOperations.LIST_CONTAINS => _handlers.OfType<ListContainsHandler>().FirstOrDefault(),
|
||||
SystemOperations.LIST_CLEAR => _handlers.OfType<ListClearHandler>().FirstOrDefault(),
|
||||
_ => _handlers.OfType<GenericSystemHandler>().FirstOrDefault()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -153,13 +153,116 @@ namespace Nodify.Calculator
|
||||
systemNodes.Add(debugAndCreateModels);
|
||||
systemNodes.Add(jsonParseNode);
|
||||
systemNodes.Add(splitNode);
|
||||
systemNodes.Add(takeNode);
|
||||
systemNodes.Add(forEachNode);
|
||||
systemNodes.Add(assertNode);
|
||||
systemNodes.Add(stringConcatNode);
|
||||
return systemNodes;
|
||||
}
|
||||
|
||||
public static List<OperationInfoViewModel> GetListNodes()
|
||||
{
|
||||
var listNodes = new List<OperationInfoViewModel>();
|
||||
|
||||
var listAdd = new OperationInfoViewModel
|
||||
{
|
||||
Title = "List Add",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.LIST_ADD,
|
||||
IsFlowNode = true
|
||||
};
|
||||
listAdd.Input.Add("List");
|
||||
listAdd.Input.Add("Item");
|
||||
|
||||
var listRemove = new OperationInfoViewModel
|
||||
{
|
||||
Title = "List Remove",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.LIST_REMOVE,
|
||||
IsFlowNode = true
|
||||
};
|
||||
listRemove.Input.Add("List");
|
||||
listRemove.Input.Add("Index");
|
||||
|
||||
var listUpdate = new OperationInfoViewModel
|
||||
{
|
||||
Title = "List Update",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.LIST_UPDATE,
|
||||
IsFlowNode = true
|
||||
};
|
||||
listUpdate.Input.Add("List");
|
||||
listUpdate.Input.Add("Index");
|
||||
listUpdate.Input.Add("Item");
|
||||
|
||||
var listGet = new OperationInfoViewModel
|
||||
{
|
||||
Title = "List Get",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.LIST_GET,
|
||||
IsFlowNode = false
|
||||
};
|
||||
listGet.Input.Add("List");
|
||||
listGet.Input.Add("Index");
|
||||
|
||||
var listCount = new OperationInfoViewModel
|
||||
{
|
||||
Title = "List Count",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.LIST_COUNT,
|
||||
IsFlowNode = false
|
||||
};
|
||||
listCount.Input.Add("List");
|
||||
|
||||
var listContains = new OperationInfoViewModel
|
||||
{
|
||||
Title = "List Contains",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.LIST_CONTAINS,
|
||||
IsFlowNode = false
|
||||
};
|
||||
listContains.Input.Add("List");
|
||||
listContains.Input.Add("Item");
|
||||
|
||||
var listClear = new OperationInfoViewModel
|
||||
{
|
||||
Title = "List Clear",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.LIST_CLEAR,
|
||||
IsFlowNode = true
|
||||
};
|
||||
listClear.Input.Add("List");
|
||||
|
||||
// Also include existing list-related nodes
|
||||
var takeNode = new OperationInfoViewModel
|
||||
{
|
||||
Title = "TAKE",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.TAKE,
|
||||
IsFlowNode = false
|
||||
};
|
||||
takeNode.Input.Add("List");
|
||||
|
||||
var forEachNode = new OperationInfoViewModel
|
||||
{
|
||||
Title = "ForEach",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.FOREACH,
|
||||
IsFlowNode = true
|
||||
};
|
||||
forEachNode.Input.Add("List");
|
||||
|
||||
listNodes.Add(takeNode);
|
||||
listNodes.Add(forEachNode);
|
||||
listNodes.Add(listAdd);
|
||||
listNodes.Add(listRemove);
|
||||
listNodes.Add(listUpdate);
|
||||
listNodes.Add(listGet);
|
||||
listNodes.Add(listCount);
|
||||
listNodes.Add(listContains);
|
||||
listNodes.Add(listClear);
|
||||
|
||||
return listNodes;
|
||||
}
|
||||
|
||||
public static List<OperationInfoViewModel> GetOperationsInfo(Type container)
|
||||
{
|
||||
List<OperationInfoViewModel> result = new List<OperationInfoViewModel>();
|
||||
|
||||
@@ -120,6 +120,9 @@ namespace Nodify.Calculator
|
||||
|
||||
/// <summary>Non-API system nodes for the right-side panel.</summary>
|
||||
public NodifyObservableCollection<OperationInfoViewModel> SystemNodes { get; } = new NodifyObservableCollection<OperationInfoViewModel>();
|
||||
|
||||
/// <summary>List/Array operation nodes for the right-side panel.</summary>
|
||||
public NodifyObservableCollection<OperationInfoViewModel> ListNodes { get; } = new NodifyObservableCollection<OperationInfoViewModel>();
|
||||
public INodifyCommand CreateOperationCommand { get; }
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
@@ -160,6 +163,12 @@ namespace Nodify.Calculator
|
||||
foreach (var sysNode in operations)
|
||||
SystemNodes.Add(sysNode);
|
||||
|
||||
// Populate list/array nodes for the right panel
|
||||
var listOps = OperationFactory.GetListNodes();
|
||||
foreach (var listNode in listOps)
|
||||
ListNodes.Add(listNode);
|
||||
operations.AddRange(listOps);
|
||||
|
||||
SwaggerOperations = new NodifyObservableCollection<OperationInfoViewModel>();
|
||||
LoadSwaggerNodesFromDb();
|
||||
RebuildGroupedSwaggerOperations();
|
||||
|
||||
@@ -24,7 +24,14 @@ namespace Nodify.Calculator
|
||||
FOREACH,
|
||||
ASSERT,
|
||||
KNOT,
|
||||
STRING_CONCAT
|
||||
STRING_CONCAT,
|
||||
LIST_ADD,
|
||||
LIST_REMOVE,
|
||||
LIST_UPDATE,
|
||||
LIST_COUNT,
|
||||
LIST_CONTAINS,
|
||||
LIST_CLEAR,
|
||||
LIST_GET
|
||||
}
|
||||
|
||||
public class SystemOperationViewModel : OperationViewModel
|
||||
|
||||
Reference in New Issue
Block a user