Added nodes related to the list and array operations
Some checks failed
Build / build (push) Has been cancelled

This commit is contained in:
Ankitkumar Satapara
2026-04-23 19:15:08 +05:30
parent 1cb9838942
commit baa7ae5579
10 changed files with 796 additions and 4 deletions

View File

@@ -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"
};
}
}
}

View File

@@ -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">

View File

@@ -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
// ─────────────────────────────────────────────────────────────────────────────

View File

@@ -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"; }
}
}
}

View File

@@ -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(),
};

View 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);
}
}
}

View File

@@ -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()
};
}

View File

@@ -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>();

View File

@@ -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();

View File

@@ -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