Major changes in the flow of code, implemented modularization of the code for maintainability
Some checks failed
Build / build (push) Has been cancelled

This commit is contained in:
Ankitkumar Satapara
2026-04-22 15:03:20 +05:30
parent f9118a563c
commit b2fed489e2
9 changed files with 1224 additions and 873 deletions

View File

@@ -1,4 +1,5 @@
using Nodify.Calculator.Models; using Nodify.Calculator.Models;
using Nodify.Calculator.NodeHandlers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
@@ -34,54 +35,9 @@ namespace Nodify.Calculator
LocationY = op.Location.Y LocationY = op.Location.Y
}; };
switch (op) // Delegate to the handler for VM-specific properties
{ var handler = NodeHandlerRegistry.FindForSave(op);
case TakeOperationViewModel take: handler?.Save(op, nodeData);
nodeData.NodeType = "System";
nodeData.SystemOp = take.SystemOperationType.ToString();
nodeData.NthIndex = take.NthIndex;
nodeData.IsRandom = take.IsRandom;
break;
case AuthOperationViewModel auth:
nodeData.NodeType = "System";
nodeData.SystemOp = auth.SystemOperationType.ToString();
nodeData.AuthBaseUrl = auth.BaseUrl ?? string.Empty;
nodeData.AuthType = auth.AuthType ?? string.Empty;
break;
case FunctionOperationViewModel func:
nodeData.NodeType = "Function";
nodeData.FunctionName = func.FunctionName;
foreach (var p in func.InputParameters)
nodeData.FunctionInputs.Add(new FunctionParamData { Name = p.Name, Type = p.Type });
foreach (var p in func.OutputParameters)
nodeData.FunctionOutputs.Add(new FunctionParamData { Name = p.Name, Type = p.Type });
break;
case SystemOperationViewModel sys:
nodeData.NodeType = "System";
nodeData.SystemOp = sys.SystemOperationType.ToString();
// For GET_SET nodes, save ClassName and variable metadata
if (sys.SystemOperationType == SystemOperations.GET_SET)
{
nodeData.ClassName = sys.ClassName ?? string.Empty;
nodeData.IsSimpleVariable = sys.IsSimpleVariable;
nodeData.VariableType = sys.VariableType ?? string.Empty;
}
break;
case APIOperationViewModel api:
nodeData.NodeType = "API";
nodeData.OPType = api.OperationType;
nodeData.ResponseModelClassName = api.ResponseModelClassName;
break;
case ExpandoOperationViewModel:
nodeData.NodeType = "Expando";
break;
case CalculatorOperationViewModel:
nodeData.NodeType = "Calculator";
break;
default:
nodeData.NodeType = "Normal";
break;
}
// Save connector metadata for all nodes // Save connector metadata for all nodes
foreach (var c in op.Input) foreach (var c in op.Input)
@@ -179,47 +135,48 @@ namespace Nodify.Calculator
calculator.IsLoading = true; calculator.IsLoading = true;
try try
{ {
var nodeMap = new Dictionary<string, OperationViewModel>();
var nodeMap = new Dictionary<string, OperationViewModel>(); // Recreate nodes — each handler knows how to restore its own type
foreach (var nd in graph.Nodes)
{
OperationViewModel op = null;
try
{
var handler = NodeHandlerRegistry.FindForRestore(nd);
if (handler != null)
op = handler.Restore(nd);
}
catch
{
continue;
}
// Recreate nodes if (op == null) continue;
foreach (var nd in graph.Nodes)
{ op.NodeId = nd.NodeId;
OperationViewModel op = null; op.Location = new System.Windows.Point(nd.LocationX, nd.LocationY);
try nodeMap[nd.NodeId] = op;
{ calculator.Operations.Add(op);
op = RecreateNode(nd, opsMenu);
}
catch
{
continue;
} }
if (op == null) continue; // Recreate connections
foreach (var cd in graph.Connections)
op.NodeId = nd.NodeId;
op.Location = new System.Windows.Point(nd.LocationX, nd.LocationY);
nodeMap[nd.NodeId] = op;
calculator.Operations.Add(op);
}
// Recreate connections
foreach (var cd in graph.Connections)
{
if (!nodeMap.TryGetValue(cd.SourceNodeId, out var sourceOp)) continue;
if (!nodeMap.TryGetValue(cd.TargetNodeId, out var targetOp)) continue;
if (cd.SourceConnectorIndex >= sourceOp.Output.Count) continue;
if (cd.TargetConnectorIndex >= targetOp.Input.Count) continue;
var sourceConn = sourceOp.Output[cd.SourceConnectorIndex];
var targetConn = targetOp.Input[cd.TargetConnectorIndex];
calculator.Connections.Add(new ConnectionViewModel
{ {
Input = targetConn, if (!nodeMap.TryGetValue(cd.SourceNodeId, out var sourceOp)) continue;
Output = sourceConn if (!nodeMap.TryGetValue(cd.TargetNodeId, out var targetOp)) continue;
}); if (cd.SourceConnectorIndex >= sourceOp.Output.Count) continue;
} if (cd.TargetConnectorIndex >= targetOp.Input.Count) continue;
var sourceConn = sourceOp.Output[cd.SourceConnectorIndex];
var targetConn = targetOp.Input[cd.TargetConnectorIndex];
calculator.Connections.Add(new ConnectionViewModel
{
Input = targetConn,
Output = sourceConn
});
}
} }
finally finally
{ {
@@ -227,292 +184,6 @@ namespace Nodify.Calculator
} }
} }
private static OperationViewModel RecreateNode(NodeData nd, OperationsMenuViewModel opsMenu)
{
switch (nd.NodeType)
{
case "API":
{
// Find matching swagger operation or create from saved data
var info = new OperationInfoViewModel
{
Title = nd.Title,
OPType = nd.OPType?.ToLower() ?? "get",
Type = OperationType.API,
ResponseModelClassName = nd.ResponseModelClassName ?? string.Empty
};
// Restore inputs from saved connector data (skip flow triangle at index 0)
foreach (var ic in nd.InputConnectors)
{
if (ic.Shape != "Triangle")
info.Input.Add(ic.Title);
}
info.Output.Add("");
return OperationFactory.GetOperation(info);
}
case "System":
{
if (Enum.TryParse<SystemOperations>(nd.SystemOp, out var sysOp))
{
var info = new OperationInfoViewModel
{
Title = nd.Title,
Type = OperationType.System,
sysOp = sysOp
};
if (sysOp == SystemOperations.GET_SET && !string.IsNullOrEmpty(nd.ClassName))
{
info.ClassName = nd.ClassName;
info.IsSimpleVariable = nd.IsSimpleVariable;
info.VariableType = nd.VariableType ?? string.Empty;
info.IsModelNode = true;
// Extract just "GET" or "SET" prefix so the factory doesn't re-append ClassName
var savedTitle = nd.Title ?? "";
if (savedTitle.StartsWith("GET ")) info.Title = "GET";
else if (savedTitle.StartsWith("SET ")) info.Title = "SET";
}
// Set up inputs/outputs based on system operation type
switch (sysOp)
{
case SystemOperations.BEGIN:
info.Output.Add(""); // flow output only
break;
case SystemOperations.END:
info.Input.Add(""); // flow input only
break;
case SystemOperations.DEBUG:
info.Input.Add("Value");
info.IsFlowNode = true;
break;
case SystemOperations.TAKE:
info.Input.Add("List");
break;
case SystemOperations.COPY:
info.Input.Add("");
break;
case SystemOperations.SPLIT:
info.Input.Add("");
break;
case SystemOperations.NEW_OBJECT:
info.Input.Add("");
info.IsFlowNode = true;
break;
case SystemOperations.IF:
info.Output.Add("");
info.IsFlowNode = true;
break;
case SystemOperations.KNOT:
// Knot nodes manage their own connectors; don't add any here
break;
case SystemOperations.GET_SET:
// GET_SET nodes build their own connectors in OperationFactory
break;
default:
info.Output.Add("");
info.IsFlowNode = true;
break;
}
var op = OperationFactory.GetOperation(info);
// Restore Take properties
if (op is TakeOperationViewModel takeOp)
{
takeOp.NthIndex = nd.NthIndex;
takeOp.IsRandom = nd.IsRandom;
}
// Restore dynamic connectors for SPLIT/COPY/TAKE from saved data
RestoreDynamicConnectors(op, nd);
return op;
}
return null;
}
case "Function":
{
var info = new OperationInfoViewModel
{
Title = nd.FunctionName,
Type = OperationType.System,
sysOp = SystemOperations.FUNCTION,
IsFunction = true
};
foreach (var inp in nd.FunctionInputs)
info.FunctionInputs.Add(new FunctionParameterInfo { Name = inp.Name, Type = inp.Type });
foreach (var outp in nd.FunctionOutputs)
info.FunctionOutputs.Add(new FunctionParameterInfo { Name = outp.Name, Type = outp.Type });
info.Output.Add("");
return OperationFactory.GetOperation(info);
}
case "Expando":
{
var info = new OperationInfoViewModel
{
Title = nd.Title,
Type = OperationType.Expando
};
// Restore input count from saved connectors
foreach (var ic in nd.InputConnectors)
info.Input.Add(ic.Title);
info.Output.Add("");
return OperationFactory.GetOperation(info);
}
default:
{
var info = new OperationInfoViewModel
{
Title = nd.Title,
Type = OperationType.Normal
};
foreach (var ic in nd.InputConnectors)
info.Input.Add(ic.Title);
info.Output.Add("");
return OperationFactory.GetOperation(info);
}
}
}
private static void RestoreDynamicConnectors(OperationViewModel op, NodeData nd)
{
// For SPLIT nodes: if saved outputs have more than just the flow triangle,
// restore the dynamic property outputs
if (op is SystemOperationViewModel sysVm)
{
if (sysVm.SystemOperationType == SystemOperations.SPLIT)
{
// Remove default non-triangle outputs, then add saved ones
var defaultNonTriangle = op.Output.Where(o => o.Shape != ConnectorShape.Triangle).ToList();
foreach (var d in defaultNonTriangle) op.Output.Remove(d);
foreach (var sc in nd.OutputConnectors)
{
if (sc.Shape == "Triangle") continue;
op.Output.Add(DeserializeConnector(sc, false));
}
// Update title from saved
sysVm.Title = nd.Title;
}
else if (sysVm.SystemOperationType == SystemOperations.NEW_OBJECT)
{
// Restore dynamic property inputs (keep triangle + square, replace the rest)
var dynamicInputs = op.Input.Where(i => i.Shape != ConnectorShape.Triangle && i.Shape != ConnectorShape.Square).ToList();
foreach (var d in dynamicInputs) op.Input.Remove(d);
foreach (var ic in nd.InputConnectors)
{
if (ic.Shape == "Triangle" || ic.Shape == "Square") continue;
op.Input.Add(DeserializeConnector(ic, true));
}
// Restore output connector metadata (Square model output)
var savedModelOutput = nd.OutputConnectors.FirstOrDefault(o => o.Shape == "Square");
var modelOutput = op.Output.FirstOrDefault(o => o.Shape == ConnectorShape.Square);
if (savedModelOutput != null && modelOutput != null)
{
modelOutput.Title = savedModelOutput.Title;
modelOutput.DataType = savedModelOutput.DataType;
modelOutput.ConnectorColor = Color.FromArgb(savedModelOutput.ColorArgb);
}
sysVm.Title = nd.Title;
}
else if (sysVm.SystemOperationType == SystemOperations.COPY)
{
// Restore adapted connectors
if (nd.InputConnectors.Count > 0 && nd.InputConnectors[0].Shape != "Circle")
{
// Input was adapted — restore
var savedInput = nd.InputConnectors[0];
if (op.Input.Count > 0)
{
var inp = op.Input[0];
inp.Shape = ParseShape(savedInput.Shape);
inp.ConnectorColor = Color.FromArgb(savedInput.ColorArgb);
inp.DataType = savedInput.DataType;
}
}
// Restore output connectors
var existingOutputs = op.Output.ToList();
foreach (var eo in existingOutputs) op.Output.Remove(eo);
foreach (var sc in nd.OutputConnectors)
{
op.Output.Add(DeserializeConnector(sc, false));
}
}
else if (sysVm.SystemOperationType == SystemOperations.TAKE)
{
// Restore adapted list input
foreach (var inp in op.Input)
{
if (!inp.IsTakeListConnector) continue;
var savedInp = nd.InputConnectors.FirstOrDefault(c => c.IsTakeListConnector);
if (savedInp != null)
{
inp.Shape = ParseShape(savedInp.Shape);
inp.ConnectorColor = Color.FromArgb(savedInp.ColorArgb);
inp.DataType = savedInp.DataType;
}
}
// Restore output
var existingOutputs = op.Output.ToList();
foreach (var eo in existingOutputs) op.Output.Remove(eo);
foreach (var sc in nd.OutputConnectors)
{
op.Output.Add(DeserializeConnector(sc, false));
}
}
else if (sysVm.SystemOperationType == SystemOperations.AUTH)
{
if (op is AuthOperationViewModel authOp)
{
authOp.BaseUrl = nd.AuthBaseUrl ?? string.Empty;
if (!string.IsNullOrEmpty(nd.AuthType))
authOp.AuthType = nd.AuthType;
// Restore data input connectors (preserve the leading triangle flow connector).
var dataInputs = op.Input.Where(i => i.Shape != ConnectorShape.Triangle).ToList();
foreach (var d in dataInputs) op.Input.Remove(d);
foreach (var ic in nd.InputConnectors)
{
if (ic.Shape == "Triangle") continue;
op.Input.Add(DeserializeConnector(ic, true));
}
}
}
else if (sysVm.SystemOperationType == SystemOperations.KNOT)
{
// Restore knot connector shape/color/datatype from saved data
if (op is KnotOperationViewModel knotOp)
{
knotOp.Input.Clear();
knotOp.Output.Clear();
foreach (var ic in nd.InputConnectors)
{
var conn = DeserializeConnector(ic, true);
conn.IsKnotConnector = true;
knotOp.Input.Add(conn);
}
foreach (var oc in nd.OutputConnectors)
{
var conn = DeserializeConnector(oc, false);
conn.IsKnotConnector = true;
knotOp.Output.Add(conn);
}
}
}
}
}
private static ConnectorData SerializeConnector(ConnectorViewModel c) private static ConnectorData SerializeConnector(ConnectorViewModel c)
{ {
return new ConnectorData return new ConnectorData
@@ -526,25 +197,5 @@ namespace Nodify.Calculator
IsKnotConnector = c.IsKnotConnector IsKnotConnector = c.IsKnotConnector
}; };
} }
private static ConnectorViewModel DeserializeConnector(ConnectorData cd, bool isInput)
{
return new ConnectorViewModel
{
Title = cd.Title,
Shape = ParseShape(cd.Shape),
ConnectorColor = Color.FromArgb(cd.ColorArgb),
DataType = cd.DataType,
IsCopyConnector = cd.IsCopyConnector,
IsTakeListConnector = cd.IsTakeListConnector,
IsKnotConnector = cd.IsKnotConnector,
IsInput = isInput
};
}
private static ConnectorShape ParseShape(string shape)
{
return Enum.TryParse<ConnectorShape>(shape, out var s) ? s : ConnectorShape.Circle;
}
} }
} }

View File

@@ -0,0 +1,145 @@
using Nodify.Calculator.Models;
using System.Drawing;
namespace Nodify.Calculator.NodeHandlers
{
// ─── BEGIN / END ───
public class BeginEndHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System
&& (info.sysOp == SystemOperations.BEGIN || info.sysOp == SystemOperations.END);
public bool CanRestore(NodeData data)
=> data.NodeType == "System"
&& (data.SystemOp == nameof(SystemOperations.BEGIN) || data.SystemOp == nameof(SystemOperations.END));
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new SystemOperationViewModel
{
Title = info.Title,
SystemOperationType = info.sysOp
};
if (info.sysOp == SystemOperations.BEGIN)
op.Output.Add(new ConnectorViewModel { Title = "", IsInput = false, Shape = ConnectorShape.Triangle, ConnectorColor = Color.DarkRed });
else
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, ConnectorColor = Color.DarkRed });
return op;
}
public OperationViewModel Restore(NodeData data)
{
var sysOp = data.SystemOp == nameof(SystemOperations.BEGIN) ? SystemOperations.BEGIN : SystemOperations.END;
var info = new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = sysOp };
return Create(info);
}
public void Save(OperationViewModel vm, NodeData data)
{
if (vm is SystemOperationViewModel sys)
{
data.NodeType = "System";
data.SystemOp = sys.SystemOperationType.ToString();
}
}
}
// ─── COPY ───
public class CopyHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System && info.sysOp == SystemOperations.COPY;
public bool CanRestore(NodeData data)
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.COPY);
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new SystemOperationViewModel
{
Title = info.Title ?? "COPY",
SystemOperationType = SystemOperations.COPY
};
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Circle, ConnectorColor = Color.White, IsCopyConnector = true });
return op;
}
public OperationViewModel Restore(NodeData data)
{
var op = Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = SystemOperations.COPY });
// Restore adapted input connector
if (data.InputConnectors.Count > 0 && data.InputConnectors[0].Shape != "Circle")
{
var saved = data.InputConnectors[0];
if (op.Input.Count > 0)
{
var inp = op.Input[0];
inp.Shape = NodeHandlerRegistry.ParseShape(saved.Shape);
inp.ConnectorColor = Color.FromArgb(saved.ColorArgb);
inp.DataType = saved.DataType;
}
}
// Restore output connectors
foreach (var sc in data.OutputConnectors)
op.Output.Add(NodeHandlerRegistry.DeserializeConnector(sc, false));
return op;
}
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "System";
data.SystemOp = nameof(SystemOperations.COPY);
}
}
// ─── SPLIT ───
public class SplitHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System && info.sysOp == SystemOperations.SPLIT;
public bool CanRestore(NodeData data)
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.SPLIT);
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new SystemOperationViewModel
{
Title = info.Title ?? "Split",
SystemOperationType = SystemOperations.SPLIT
};
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Square, ConnectorColor = Color.MediumPurple });
return op;
}
public OperationViewModel Restore(NodeData data)
{
var op = (SystemOperationViewModel)Create(new OperationInfoViewModel { Title = "Split", Type = OperationType.System, sysOp = SystemOperations.SPLIT });
// Restore dynamic outputs from saved data
foreach (var sc in data.OutputConnectors)
{
if (sc.Shape == "Triangle") continue;
op.Output.Add(NodeHandlerRegistry.DeserializeConnector(sc, false));
}
op.Title = data.Title;
return op;
}
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "System";
data.SystemOp = nameof(SystemOperations.SPLIT);
}
}
}

View File

@@ -0,0 +1,170 @@
using Nodify.Calculator.Models;
using System.Drawing;
using System.Linq;
namespace Nodify.Calculator.NodeHandlers
{
// ─── DEBUG ───
public class DebugHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System && info.sysOp == SystemOperations.DEBUG;
public bool CanRestore(NodeData data)
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.DEBUG);
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new SystemOperationViewModel
{
Title = info.Title ?? "Debug",
SystemOperationType = SystemOperations.DEBUG
};
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 = "Value", ConnectorColor = Color.LimeGreen });
return op;
}
public OperationViewModel Restore(NodeData data)
=> Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = SystemOperations.DEBUG });
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "System";
data.SystemOp = nameof(SystemOperations.DEBUG);
}
}
// ─── AUTH ───
public class AuthHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System && info.sysOp == SystemOperations.AUTH;
public bool CanRestore(NodeData data)
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.AUTH);
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new AuthOperationViewModel();
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
op.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
// Data inputs
var labels = new[] { "Base URL", "Auth Type", "Token", "API Key", "Username", "Password" };
foreach (var label in labels)
op.Input.Add(new ConnectorViewModel { Title = label, ConnectorColor = Color.Orange });
return op;
}
public OperationViewModel Restore(NodeData data)
{
var op = new AuthOperationViewModel
{
BaseUrl = data.AuthBaseUrl ?? string.Empty,
AuthType = !string.IsNullOrEmpty(data.AuthType) ? data.AuthType : "Bearer Token"
};
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
op.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
// Restore data inputs from saved connectors (skip triangles)
foreach (var ic in data.InputConnectors)
{
if (ic.Shape == "Triangle") continue;
op.Input.Add(NodeHandlerRegistry.DeserializeConnector(ic, true));
}
return op;
}
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "System";
data.SystemOp = nameof(SystemOperations.AUTH);
if (vm is AuthOperationViewModel auth)
{
data.AuthBaseUrl = auth.BaseUrl ?? string.Empty;
data.AuthType = auth.AuthType ?? string.Empty;
}
}
}
// ─── TAKE ───
public class TakeHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System && info.sysOp == SystemOperations.TAKE;
public bool CanRestore(NodeData data)
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.TAKE);
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new TakeOperationViewModel
{
Title = info.Title ?? "TAKE",
SystemOperationType = SystemOperations.TAKE
};
op.Input.Add(new ConnectorViewModel
{
Title = "List",
Shape = ConnectorShape.Circle,
ConnectorColor = Color.White,
IsCopyConnector = true,
IsTakeListConnector = true
});
var nthConnector = new ConnectorViewModel
{
Title = "Nth",
Shape = ConnectorShape.Circle,
ConnectorColor = ConnectorViewModel.GetColorForType("int"),
DataType = "int"
};
op.NthConnector = nthConnector;
op.Input.Add(nthConnector);
return op;
}
public OperationViewModel Restore(NodeData data)
{
var op = (TakeOperationViewModel)Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = SystemOperations.TAKE });
op.NthIndex = data.NthIndex;
op.IsRandom = data.IsRandom;
// Restore adapted list input
var savedListInp = data.InputConnectors.FirstOrDefault(c => c.IsTakeListConnector);
if (savedListInp != null)
{
var listInp = op.Input.FirstOrDefault(i => i.IsTakeListConnector);
if (listInp != null)
{
listInp.Shape = NodeHandlerRegistry.ParseShape(savedListInp.Shape);
listInp.ConnectorColor = Color.FromArgb(savedListInp.ColorArgb);
listInp.DataType = savedListInp.DataType;
}
}
// Restore outputs
foreach (var sc in data.OutputConnectors)
op.Output.Add(NodeHandlerRegistry.DeserializeConnector(sc, false));
return op;
}
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "System";
data.SystemOp = nameof(SystemOperations.TAKE);
if (vm is TakeOperationViewModel take)
{
data.NthIndex = take.NthIndex;
data.IsRandom = take.IsRandom;
}
}
}
}

View File

@@ -0,0 +1,209 @@
using Nodify.Calculator.Models;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
namespace Nodify.Calculator.NodeHandlers
{
// ─── GET/SET for simple variables (IsSimpleVariable = true) ───
public class GetSetVariableHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System
&& info.sysOp == SystemOperations.GET_SET
&& info.IsSimpleVariable;
public bool CanRestore(NodeData data)
=> data.NodeType == "System"
&& data.SystemOp == nameof(SystemOperations.GET_SET)
&& data.IsSimpleVariable;
public OperationViewModel Create(OperationInfoViewModel info)
{
var varType = (info.VariableType ?? "").ToLower();
var varColor = ConnectorViewModel.GetColorForType(varType);
var varLabel = $"{info.Title} {info.ClassName} ({info.VariableType})";
var op = new SystemOperationViewModel
{
Title = varLabel,
SystemOperationType = SystemOperations.GET_SET,
IsSimpleVariable = true,
VariableType = info.VariableType ?? string.Empty,
ClassName = info.ClassName ?? string.Empty
};
if (info.Title == "GET")
{
op.Output.Add(new ConnectorViewModel
{
Title = "Value",
IsInput = false,
Shape = ConnectorShape.Circle,
ConnectorColor = varColor,
DataType = varType
});
}
else // SET
{
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 = "Value",
Shape = ConnectorShape.Circle,
ConnectorColor = varColor,
DataType = varType
});
}
return op;
}
public OperationViewModel Restore(NodeData data)
{
var savedTitle = data.Title ?? "";
var prefix = savedTitle.StartsWith("SET ") ? "SET" : "GET";
var info = new OperationInfoViewModel
{
Title = prefix,
Type = OperationType.System,
sysOp = SystemOperations.GET_SET,
IsSimpleVariable = true,
VariableType = data.VariableType ?? string.Empty,
ClassName = data.ClassName ?? string.Empty
};
return Create(info);
}
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "System";
data.SystemOp = nameof(SystemOperations.GET_SET);
if (vm is SystemOperationViewModel sys)
{
data.ClassName = sys.ClassName ?? string.Empty;
data.IsSimpleVariable = true;
data.VariableType = sys.VariableType ?? string.Empty;
}
}
}
// ─── GET/SET for model types (IsModelNode = true, IsSimpleVariable = false) ───
public class GetSetModelHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System
&& info.sysOp == SystemOperations.GET_SET
&& info.IsModelNode
&& !info.IsSimpleVariable;
public bool CanRestore(NodeData data)
=> data.NodeType == "System"
&& data.SystemOp == nameof(SystemOperations.GET_SET)
&& !data.IsSimpleVariable
&& !string.IsNullOrEmpty(data.ClassName);
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new SystemOperationViewModel
{
Title = $"{info.Title} {info.ClassName}",
SystemOperationType = SystemOperations.GET_SET,
IsSimpleVariable = false,
ClassName = info.ClassName ?? string.Empty
};
if (info.Title == "GET")
{
// GET model: output a Square connector for the model
op.Output.Add(new ConnectorViewModel
{
Title = info.ClassName ?? "",
IsInput = false,
Shape = ConnectorShape.Square,
ConnectorColor = Color.MediumPurple,
DataType = info.ClassName ?? "object"
});
}
else // SET
{
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
op.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
// Try to read class properties for detailed connectors
var customModelDir = Path.Combine(ProjectManager.ProjectDirectory, "CustomModels");
Directory.CreateDirectory(customModelDir);
var filePath = Path.Combine(customModelDir, info.ClassName + ".cs");
if (File.Exists(filePath))
{
var fileContent = File.ReadAllText(filePath);
var properties = OperationFactory.GetPropertiesFromClassPublic(fileContent);
// Square input for the class object
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Square, ConnectorColor = Color.MediumPurple });
// Circle output per property, colored by type
foreach (var property in properties)
{
var propColor = ConnectorViewModel.GetColorForType(property.Type);
op.Output.Add(new ConnectorViewModel
{
Title = $"{property.Name} ({property.Type})",
IsInput = false,
Shape = ConnectorShape.Circle,
ConnectorColor = propColor,
DataType = property.Type.ToLower()
});
}
}
else
{
// Fallback: generic square I/O
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Square, ConnectorColor = Color.MediumPurple });
op.Output.Add(new ConnectorViewModel
{
Title = info.ClassName ?? "",
IsInput = false,
Shape = ConnectorShape.Square,
ConnectorColor = Color.MediumPurple,
DataType = info.ClassName ?? "object"
});
}
}
return op;
}
public OperationViewModel Restore(NodeData data)
{
var savedTitle = data.Title ?? "";
var prefix = savedTitle.StartsWith("SET ") ? "SET" : "GET";
var info = new OperationInfoViewModel
{
Title = prefix,
Type = OperationType.System,
sysOp = SystemOperations.GET_SET,
IsModelNode = true,
IsSimpleVariable = false,
ClassName = data.ClassName ?? string.Empty
};
return Create(info);
}
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "System";
data.SystemOp = nameof(SystemOperations.GET_SET);
if (vm is SystemOperationViewModel sys)
{
data.ClassName = sys.ClassName ?? string.Empty;
data.IsSimpleVariable = false;
data.VariableType = sys.VariableType ?? string.Empty;
}
}
}
}

View File

@@ -0,0 +1,45 @@
using Nodify.Calculator.Models;
namespace Nodify.Calculator.NodeHandlers
{
/// <summary>
/// Each node type implements this interface to encapsulate its own
/// creation, save and load logic — eliminating giant switch/if chains.
/// </summary>
public interface INodeHandler
{
/// <summary>
/// The NodeType discriminator stored in <see cref="NodeData.NodeType"/> (e.g. "System", "API", "Function").
/// </summary>
string NodeTypeKey { get; }
/// <summary>
/// Returns true if this handler can create a node from the given <see cref="OperationInfoViewModel"/>.
/// Used when building a node from the toolbox / left-panel.
/// </summary>
bool CanCreate(OperationInfoViewModel info);
/// <summary>
/// Returns true if this handler can restore a node from the given <see cref="NodeData"/>.
/// Used when loading a saved graph.
/// </summary>
bool CanRestore(NodeData data);
/// <summary>
/// Creates the <see cref="OperationViewModel"/> from toolbox info.
/// </summary>
OperationViewModel Create(OperationInfoViewModel info);
/// <summary>
/// Creates the <see cref="OperationViewModel"/> from saved <see cref="NodeData"/>.
/// The base properties (NodeId, Location) are set by the caller.
/// </summary>
OperationViewModel Restore(NodeData data);
/// <summary>
/// Writes ViewModel-specific properties into <see cref="NodeData"/> during save.
/// Common properties (NodeId, Title, Location, Connectors) are handled by the caller.
/// </summary>
void Save(OperationViewModel vm, NodeData data);
}
}

View File

@@ -0,0 +1,133 @@
using Nodify.Calculator.Models;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
namespace Nodify.Calculator.NodeHandlers
{
/// <summary>
/// Registry that discovers and dispatches to the correct <see cref="INodeHandler"/>.
/// Replaces all the switch/if chains in OperationFactory and GraphSerializer.
/// </summary>
public static class NodeHandlerRegistry
{
private static readonly List<INodeHandler> _handlers = new();
private static bool _initialized;
public static IReadOnlyList<INodeHandler> Handlers
{
get
{
EnsureInitialized();
return _handlers;
}
}
private static void EnsureInitialized()
{
if (_initialized) return;
_initialized = true;
// Register all handlers — order matters for CanCreate/CanRestore matching.
// More specific handlers first (e.g. TakeHandler before generic SystemHandler).
_handlers.Add(new KnotHandler());
_handlers.Add(new FunctionHandler());
_handlers.Add(new AuthHandler());
_handlers.Add(new TakeHandler());
_handlers.Add(new ForEachHandler());
_handlers.Add(new AssertHandler());
_handlers.Add(new DebugHandler());
_handlers.Add(new NewObjectHandler());
_handlers.Add(new CopyHandler());
_handlers.Add(new SplitHandler());
_handlers.Add(new GetSetVariableHandler());
_handlers.Add(new GetSetModelHandler());
_handlers.Add(new BeginEndHandler());
_handlers.Add(new GenericSystemHandler());
_handlers.Add(new ApiHandler());
_handlers.Add(new ExpandoHandler());
_handlers.Add(new NormalHandler());
}
/// <summary>
/// Find the handler that can create a node from toolbox info.
/// </summary>
public static INodeHandler? FindForCreate(OperationInfoViewModel info)
{
EnsureInitialized();
return _handlers.FirstOrDefault(h => h.CanCreate(info));
}
/// <summary>
/// Find the handler that can restore a node from saved data.
/// </summary>
public static INodeHandler? FindForRestore(NodeData data)
{
EnsureInitialized();
return _handlers.FirstOrDefault(h => h.CanRestore(data));
}
/// <summary>
/// Find the handler that can save a given ViewModel.
/// </summary>
public static INodeHandler? FindForSave(OperationViewModel vm)
{
EnsureInitialized();
// Build a temporary info to test CanCreate? No — instead match by VM type.
// We use a simple type check cascade, but kept in one place.
return vm switch
{
KnotOperationViewModel => _handlers.OfType<KnotHandler>().FirstOrDefault(),
FunctionOperationViewModel => _handlers.OfType<FunctionHandler>().FirstOrDefault(),
AuthOperationViewModel => _handlers.OfType<AuthHandler>().FirstOrDefault(),
TakeOperationViewModel => _handlers.OfType<TakeHandler>().FirstOrDefault(),
ForEachOperationViewModel => _handlers.OfType<ForEachHandler>().FirstOrDefault(),
AssertOperationViewModel => _handlers.OfType<AssertHandler>().FirstOrDefault(),
APIOperationViewModel => _handlers.OfType<ApiHandler>().FirstOrDefault(),
ExpandoOperationViewModel => _handlers.OfType<ExpandoHandler>().FirstOrDefault(),
CalculatorOperationViewModel => _handlers.OfType<NormalHandler>().FirstOrDefault(),
SystemOperationViewModel sys => FindSystemSaveHandler(sys),
_ => _handlers.OfType<NormalHandler>().FirstOrDefault()
};
}
private static INodeHandler? FindSystemSaveHandler(SystemOperationViewModel sys)
{
return sys.SystemOperationType switch
{
SystemOperations.COPY => _handlers.OfType<CopyHandler>().FirstOrDefault(),
SystemOperations.SPLIT => _handlers.OfType<SplitHandler>().FirstOrDefault(),
SystemOperations.DEBUG => _handlers.OfType<DebugHandler>().FirstOrDefault(),
SystemOperations.NEW_OBJECT => _handlers.OfType<NewObjectHandler>().FirstOrDefault(),
SystemOperations.BEGIN => _handlers.OfType<BeginEndHandler>().FirstOrDefault(),
SystemOperations.END => _handlers.OfType<BeginEndHandler>().FirstOrDefault(),
SystemOperations.GET_SET when sys.IsSimpleVariable => _handlers.OfType<GetSetVariableHandler>().FirstOrDefault(),
SystemOperations.GET_SET => _handlers.OfType<GetSetModelHandler>().FirstOrDefault(),
_ => _handlers.OfType<GenericSystemHandler>().FirstOrDefault()
};
}
// ─── Shared helpers used by multiple handlers ───
public static ConnectorViewModel DeserializeConnector(ConnectorData cd, bool isInput)
{
return new ConnectorViewModel
{
Title = cd.Title,
Shape = ParseShape(cd.Shape),
ConnectorColor = Color.FromArgb(cd.ColorArgb),
DataType = cd.DataType,
IsCopyConnector = cd.IsCopyConnector,
IsTakeListConnector = cd.IsTakeListConnector,
IsKnotConnector = cd.IsKnotConnector,
IsInput = isInput
};
}
public static ConnectorShape ParseShape(string shape)
{
return Enum.TryParse<ConnectorShape>(shape, out var s) ? s : ConnectorShape.Circle;
}
}
}

View File

@@ -0,0 +1,293 @@
using Nodify.Calculator.Models;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
namespace Nodify.Calculator.NodeHandlers
{
// ─── FUNCTION ───
public class FunctionHandler : INodeHandler
{
public string NodeTypeKey => "Function";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System && info.sysOp == SystemOperations.FUNCTION;
public bool CanRestore(NodeData data)
=> data.NodeType == "Function";
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new FunctionOperationViewModel
{
Title = info.Title,
FunctionName = info.Title ?? "Function"
};
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
op.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
op.ConfigureParameters(info.FunctionInputs, info.FunctionOutputs);
return op;
}
public OperationViewModel Restore(NodeData data)
{
var info = new OperationInfoViewModel
{
Title = data.FunctionName,
Type = OperationType.System,
sysOp = SystemOperations.FUNCTION,
IsFunction = true
};
foreach (var inp in data.FunctionInputs)
info.FunctionInputs.Add(new FunctionParameterInfo { Name = inp.Name, Type = inp.Type });
foreach (var outp in data.FunctionOutputs)
info.FunctionOutputs.Add(new FunctionParameterInfo { Name = outp.Name, Type = outp.Type });
return Create(info);
}
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "Function";
if (vm is FunctionOperationViewModel func)
{
data.FunctionName = func.FunctionName;
foreach (var p in func.InputParameters)
data.FunctionInputs.Add(new FunctionParamData { Name = p.Name, Type = p.Type });
foreach (var p in func.OutputParameters)
data.FunctionOutputs.Add(new FunctionParamData { Name = p.Name, Type = p.Type });
}
}
}
// ─── API ───
public class ApiHandler : INodeHandler
{
public string NodeTypeKey => "API";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.API;
public bool CanRestore(NodeData data)
=> data.NodeType == "API";
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new APIOperationViewModel
{
Title = info.Title,
OperationType = (info.OPType ?? "GET").ToUpper(),
ResponseModelClassName = info.ResponseModelClassName ?? string.Empty
};
// Flow connectors
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
op.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
// Response output
if (!string.IsNullOrEmpty(info.ResponseModelClassName))
{
var rc = info.ResponseModelClassName;
bool isList = rc.StartsWith("List<") && rc.EndsWith(">");
op.Output.Add(new ConnectorViewModel
{
Title = rc,
IsInput = false,
Shape = isList ? ConnectorShape.Grid : ConnectorShape.Square,
ConnectorColor = isList ? Color.MediumSpringGreen : Color.MediumPurple,
DataType = rc
});
}
else
{
op.Output.Add(new ConnectorViewModel
{
Title = "Response",
IsInput = false,
Shape = ConnectorShape.Circle,
ConnectorColor = Color.LimeGreen,
DataType = "object"
});
}
// Data inputs
foreach (var label in info.Input)
{
op.Input.Add(new ConnectorViewModel { Title = label, ConnectorColor = Color.LimeGreen });
}
return op;
}
public OperationViewModel Restore(NodeData data)
{
var info = new OperationInfoViewModel
{
Title = data.Title,
OPType = data.OPType?.ToLower() ?? "get",
Type = OperationType.API,
ResponseModelClassName = data.ResponseModelClassName ?? string.Empty
};
foreach (var ic in data.InputConnectors)
{
if (ic.Shape != "Triangle")
info.Input.Add(ic.Title);
}
return Create(info);
}
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "API";
if (vm is APIOperationViewModel api)
{
data.OPType = api.OperationType;
data.ResponseModelClassName = api.ResponseModelClassName;
}
}
}
// ─── EXPANDO ───
public class ExpandoHandler : INodeHandler
{
public string NodeTypeKey => "Expando";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.Expando;
public bool CanRestore(NodeData data)
=> data.NodeType == "Expando";
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new ExpandoOperationViewModel
{
MaxInput = info.MaxInput,
MinInput = info.MinInput,
Title = info.Title,
Operation = info.Operation
};
op.Output.Add(new ConnectorViewModel { ConnectorColor = Color.DodgerBlue });
foreach (var label in info.Input)
op.Input.Add(new ConnectorViewModel { Title = label, ConnectorColor = Color.DodgerBlue });
return op;
}
public OperationViewModel Restore(NodeData data)
{
var info = new OperationInfoViewModel
{
Title = data.Title,
Type = OperationType.Expando
};
foreach (var ic in data.InputConnectors)
info.Input.Add(ic.Title);
return Create(info);
}
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "Expando";
}
}
// ─── NORMAL (default fallback) ───
public class NormalHandler : INodeHandler
{
public string NodeTypeKey => "Normal";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.Normal
|| info.Type == OperationType.Calculator
|| info.Type == OperationType.Expression
|| info.Type == OperationType.Group
|| info.Type == OperationType.Graph;
public bool CanRestore(NodeData data)
=> data.NodeType == "Normal" || data.NodeType == "Calculator";
public OperationViewModel Create(OperationInfoViewModel info)
{
// Delegate to old factory for Expression/Calculator/Group/Graph — these are rarely saved
return OperationFactory.GetOperation(info);
}
public OperationViewModel Restore(NodeData data)
{
var info = new OperationInfoViewModel
{
Title = data.Title,
Type = data.NodeType == "Calculator" ? OperationType.Calculator : OperationType.Normal
};
foreach (var ic in data.InputConnectors)
info.Input.Add(ic.Title);
info.Output.Add("");
return OperationFactory.GetOperation(info);
}
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = vm is CalculatorOperationViewModel ? "Calculator" : "Normal";
}
}
// ─── Catch-all for system nodes that don't have a specific handler ───
public class GenericSystemHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System;
public bool CanRestore(NodeData data)
=> data.NodeType == "System";
public OperationViewModel Create(OperationInfoViewModel info)
{
// Fallback: flow node with generic connectors
var op = new SystemOperationViewModel
{
Title = info.Title,
SystemOperationType = info.sysOp
};
if (info.IsFlowNode)
{
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
op.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
}
foreach (var label in info.Input)
op.Input.Add(new ConnectorViewModel { Title = label, ConnectorColor = Color.Cyan });
foreach (var label in info.Output)
op.Output.Add(new ConnectorViewModel { Title = label, IsInput = false, ConnectorColor = Color.Cyan });
return op;
}
public OperationViewModel Restore(NodeData data)
{
if (!System.Enum.TryParse<SystemOperations>(data.SystemOp, out var sysOp))
return Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System });
var info = new OperationInfoViewModel
{
Title = data.Title,
Type = OperationType.System,
sysOp = sysOp,
IsFlowNode = true
};
foreach (var ic in data.InputConnectors)
{
if (ic.Shape != "Triangle") info.Input.Add(ic.Title);
}
foreach (var oc in data.OutputConnectors)
{
if (oc.Shape != "Triangle") info.Output.Add(oc.Title);
}
return Create(info);
}
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "System";
if (vm is SystemOperationViewModel sys)
data.SystemOp = sys.SystemOperationType.ToString();
}
}
}

View File

@@ -0,0 +1,172 @@
using Nodify.Calculator.Models;
using System.Drawing;
using System.Linq;
namespace Nodify.Calculator.NodeHandlers
{
// ─── FOREACH ───
public class ForEachHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System && info.sysOp == SystemOperations.FOREACH;
public bool CanRestore(NodeData data)
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.FOREACH);
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new ForEachOperationViewModel { Title = info.Title ?? "ForEach" };
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 = Color.MediumSpringGreen });
op.Output.Add(new ConnectorViewModel { Title = "Loop Body", IsInput = false, Shape = ConnectorShape.Triangle, ConnectorColor = Color.MediumSpringGreen });
op.Output.Add(new ConnectorViewModel { Title = "Current Item", IsInput = false, Shape = ConnectorShape.Circle, ConnectorColor = Color.MediumSpringGreen, DataType = "object" });
op.Output.Add(new ConnectorViewModel { Title = "Index", IsInput = false, Shape = ConnectorShape.Circle, ConnectorColor = Color.LightSkyBlue, DataType = "int" });
return op;
}
public OperationViewModel Restore(NodeData data)
=> Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = SystemOperations.FOREACH });
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "System";
data.SystemOp = nameof(SystemOperations.FOREACH);
}
}
// ─── ASSERT ───
public class AssertHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System && info.sysOp == SystemOperations.ASSERT;
public bool CanRestore(NodeData data)
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.ASSERT);
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new AssertOperationViewModel { Title = info.Title ?? "Assert" };
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 = "Actual", ConnectorColor = Color.Gold, Shape = ConnectorShape.Circle });
op.Input.Add(new ConnectorViewModel { Title = "Expected", ConnectorColor = Color.Gold, Shape = ConnectorShape.Circle });
op.Output.Add(new ConnectorViewModel { Title = "Result", IsInput = false, Shape = ConnectorShape.Circle, ConnectorColor = Color.Gold, DataType = "bool" });
return op;
}
public OperationViewModel Restore(NodeData data)
=> Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = SystemOperations.ASSERT });
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "System";
data.SystemOp = nameof(SystemOperations.ASSERT);
}
}
// ─── NEW_OBJECT ───
public class NewObjectHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System && info.sysOp == SystemOperations.NEW_OBJECT;
public bool CanRestore(NodeData data)
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.NEW_OBJECT);
public OperationViewModel Create(OperationInfoViewModel info)
{
var op = new SystemOperationViewModel
{
Title = info.Title ?? "New Object",
SystemOperationType = SystemOperations.NEW_OBJECT
};
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 = "", Shape = ConnectorShape.Square, ConnectorColor = Color.MediumPurple });
op.Output.Add(new ConnectorViewModel { Title = "Object", IsInput = false, Shape = ConnectorShape.Square, ConnectorColor = Color.MediumPurple, DataType = "object" });
return op;
}
public OperationViewModel Restore(NodeData data)
{
var op = (SystemOperationViewModel)Create(new OperationInfoViewModel { Title = data.Title, Type = OperationType.System, sysOp = SystemOperations.NEW_OBJECT });
// Restore dynamic property inputs (keep triangle + square, replace the rest)
var dynamicInputs = op.Input.Where(i => i.Shape != ConnectorShape.Triangle && i.Shape != ConnectorShape.Square).ToList();
foreach (var d in dynamicInputs) op.Input.Remove(d);
foreach (var ic in data.InputConnectors)
{
if (ic.Shape == "Triangle" || ic.Shape == "Square") continue;
op.Input.Add(NodeHandlerRegistry.DeserializeConnector(ic, true));
}
// Restore output connector metadata (Square model output)
var savedModelOutput = data.OutputConnectors.FirstOrDefault(o => o.Shape == "Square");
var modelOutput = op.Output.FirstOrDefault(o => o.Shape == ConnectorShape.Square);
if (savedModelOutput != null && modelOutput != null)
{
modelOutput.Title = savedModelOutput.Title;
modelOutput.DataType = savedModelOutput.DataType;
modelOutput.ConnectorColor = Color.FromArgb(savedModelOutput.ColorArgb);
}
op.Title = data.Title;
return op;
}
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "System";
data.SystemOp = nameof(SystemOperations.NEW_OBJECT);
}
}
// ─── KNOT ───
public class KnotHandler : INodeHandler
{
public string NodeTypeKey => "System";
public bool CanCreate(OperationInfoViewModel info)
=> info.Type == OperationType.System && info.sysOp == SystemOperations.KNOT;
public bool CanRestore(NodeData data)
=> data.NodeType == "System" && data.SystemOp == nameof(SystemOperations.KNOT);
public OperationViewModel Create(OperationInfoViewModel info)
=> new KnotOperationViewModel();
public OperationViewModel Restore(NodeData data)
{
var op = new KnotOperationViewModel();
op.Input.Clear();
op.Output.Clear();
foreach (var ic in data.InputConnectors)
{
var conn = NodeHandlerRegistry.DeserializeConnector(ic, true);
conn.IsKnotConnector = true;
op.Input.Add(conn);
}
foreach (var oc in data.OutputConnectors)
{
var conn = NodeHandlerRegistry.DeserializeConnector(oc, false);
conn.IsKnotConnector = true;
op.Output.Add(conn);
}
return op;
}
public void Save(OperationViewModel vm, NodeData data)
{
data.NodeType = "System";
data.SystemOp = nameof(SystemOperations.KNOT);
}
}
}

View File

@@ -236,6 +236,22 @@ namespace Nodify.Calculator
} }
public static OperationViewModel GetOperation(OperationInfoViewModel info) public static OperationViewModel GetOperation(OperationInfoViewModel info)
{
// Route through the handler registry for all supported types
var handler = NodeHandlers.NodeHandlerRegistry.FindForCreate(info);
if (handler != null)
{
return handler.Create(info);
}
// Fallback for legacy types not yet migrated
return GetOperationLegacy(info);
}
/// <summary>
/// Legacy creation path — only used for Expression, Calculator, Group, Graph, and generic Normal types.
/// </summary>
internal static OperationViewModel GetOperationLegacy(OperationInfoViewModel info)
{ {
var input = info.Input.Select(i => new ConnectorViewModel var input = info.Input.Select(i => new ConnectorViewModel
{ {
@@ -260,19 +276,6 @@ namespace Nodify.Calculator
Operation = info.Operation, Operation = info.Operation,
}; };
case OperationType.Expando:
var o = new ExpandoOperationViewModel
{
MaxInput = info.MaxInput,
MinInput = info.MinInput,
Title = info.Title,
Operation = info.Operation
};
o.Output.Add(new ConnectorViewModel { ConnectorColor = Color.DodgerBlue });
foreach (var ei in input) { ei.ConnectorColor = Color.DodgerBlue; }
o.Input.AddRange(input);
return o;
case OperationType.Group: case OperationType.Group:
return new OperationGroupViewModel return new OperationGroupViewModel
{ {
@@ -286,476 +289,6 @@ namespace Nodify.Calculator
DesiredSize = new Size(420, 250) DesiredSize = new Size(420, 250)
}; };
case OperationType.API:
var _o = new APIOperationViewModel
{
Title = info.Title,
OperationType = info.OPType.ToUpper(),
ResponseModelClassName = info.ResponseModelClassName ?? string.Empty
};
var connectorViewModel = new ConnectorViewModel()
{
Title = "",
Shape = ConnectorShape.Triangle
};
var connectorViewModel2 = new ConnectorViewModel()
{
Title = "",
Shape = ConnectorShape.Triangle,
IsInput = false
};
_o.Output.Add(connectorViewModel2);
// Add typed response output based on ResponseModelClassName
if (!string.IsNullOrEmpty(info.ResponseModelClassName))
{
var responseClassName = info.ResponseModelClassName;
bool isList = responseClassName.StartsWith("List<") && responseClassName.EndsWith(">");
var innerType = isList ? responseClassName.Substring(5, responseClassName.Length - 6) : responseClassName;
// Use Grid shape for list/array types, Square for single model
_o.Output.Add(new ConnectorViewModel
{
Title = responseClassName,
IsInput = false,
Shape = isList ? ConnectorShape.Grid : ConnectorShape.Square,
ConnectorColor = isList ? Color.MediumSpringGreen : Color.MediumPurple,
DataType = responseClassName
});
}
else
{
// Default: generic object output
_o.Output.Add(new ConnectorViewModel
{
Title = "Response",
IsInput = false,
Shape = ConnectorShape.Circle,
ConnectorColor = Color.LimeGreen,
DataType = "object"
});
}
_o.Input.Add(connectorViewModel);
foreach (var item in input)
{
item.ConnectorColor = Color.LimeGreen;
_o.Input.Add(item);
}
return _o;
case OperationType.System:
if (info.sysOp == SystemOperations.FUNCTION)
{
var funcOp = new FunctionOperationViewModel
{
Title = info.Title,
FunctionName = info.Title
};
// Add flow connectors (triangle)
var funcFlowIn = new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle };
var funcFlowOut = new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false };
funcOp.Input.Add(funcFlowIn);
funcOp.Output.Add(funcFlowOut);
// Configure typed input/output parameters
funcOp.ConfigureParameters(info.FunctionInputs, info.FunctionOutputs);
return funcOp;
}
if (info.sysOp == SystemOperations.NEW_OBJECT)
{
var newObjOp = new SystemOperationViewModel
{
Title = info.Title,
SystemOperationType = SystemOperations.NEW_OBJECT
};
// Flow connectors
newObjOp.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
newObjOp.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
// Model class input (Square — user connects a model source)
foreach (var inp in input)
{
inp.Shape = ConnectorShape.Square;
inp.ConnectorColor = Color.MediumPurple;
newObjOp.Input.Add(inp);
}
// Model output (Square — returns the constructed object)
newObjOp.Output.Add(new ConnectorViewModel
{
Title = "Object",
IsInput = false,
Shape = ConnectorShape.Square,
ConnectorColor = Color.MediumPurple,
DataType = "object"
});
return newObjOp;
}
if (info.sysOp == SystemOperations.FOREACH)
{
var forEachOp = new ForEachOperationViewModel
{
Title = info.Title
};
// Flow connectors
forEachOp.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
forEachOp.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
// List data input (Grid shape for array/list types)
forEachOp.Input.Add(new ConnectorViewModel
{
Title = "List",
Shape = ConnectorShape.Grid,
ConnectorColor = Color.MediumSpringGreen
});
// "Loop Body" flow output — connects to nodes executed per iteration
forEachOp.Output.Add(new ConnectorViewModel
{
Title = "Loop Body",
IsInput = false,
Shape = ConnectorShape.Triangle,
ConnectorColor = Color.MediumSpringGreen
});
// "Current Item" data output — the element of the current iteration
forEachOp.Output.Add(new ConnectorViewModel
{
Title = "Current Item",
IsInput = false,
Shape = ConnectorShape.Circle,
ConnectorColor = Color.MediumSpringGreen,
DataType = "object"
});
// "Index" data output — current iteration index
forEachOp.Output.Add(new ConnectorViewModel
{
Title = "Index",
IsInput = false,
Shape = ConnectorShape.Circle,
ConnectorColor = Color.LightSkyBlue,
DataType = "int"
});
return forEachOp;
}
if (info.sysOp == SystemOperations.ASSERT)
{
var assertOp = new AssertOperationViewModel
{
Title = info.Title
};
// Flow connectors
assertOp.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
assertOp.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
// Data inputs
foreach (var inp in input)
{
inp.ConnectorColor = Color.Gold;
inp.Shape = ConnectorShape.Circle;
assertOp.Input.Add(inp);
}
// "Result" output — pass/fail boolean
assertOp.Output.Add(new ConnectorViewModel
{
Title = "Result",
IsInput = false,
Shape = ConnectorShape.Circle,
ConnectorColor = Color.Gold,
DataType = "bool"
});
return assertOp;
}
if (info.sysOp == SystemOperations.KNOT)
{
return new KnotOperationViewModel();
}
if (info.sysOp == SystemOperations.DEBUG)
{
var debugOp = new SystemOperationViewModel
{
Title = info.Title,
SystemOperationType = SystemOperations.DEBUG
};
// Flow connectors
debugOp.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
debugOp.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
// Data input
foreach (var inp in input)
{
inp.ConnectorColor = Color.LimeGreen;
debugOp.Input.Add(inp);
}
return debugOp;
}
if (info.sysOp == SystemOperations.AUTH)
{
var authOp = new AuthOperationViewModel
{
Title = info.Title,
SystemOperationType = SystemOperations.AUTH
};
// Add flow connectors (triangle)
var flowIn = new ConnectorViewModel()
{
Title = "",
Shape = ConnectorShape.Triangle
};
var flowOut = new ConnectorViewModel()
{
Title = "",
Shape = ConnectorShape.Triangle,
IsInput = false
};
authOp.Input.Add(flowIn);
authOp.Output.Add(flowOut);
// Add data input connectors
foreach (var inp in input)
{
inp.ConnectorColor = Color.Orange;
authOp.Input.Add(inp);
}
return authOp;
}
if (info.sysOp == SystemOperations.TAKE)
{
var takeOp = new TakeOperationViewModel
{
Title = info.Title,
SystemOperationType = SystemOperations.TAKE
};
// List input — accepts any shape but only list/array DataTypes
foreach (var item in input)
{
item.Shape = ConnectorShape.Circle;
item.ConnectorColor = Color.White;
item.IsCopyConnector = true;
item.IsTakeListConnector = true;
item.Title = "List";
takeOp.Input.Add(item);
}
// Nth index input (int type)
var nthConnector = new ConnectorViewModel
{
Title = "Nth",
Shape = ConnectorShape.Circle,
ConnectorColor = ConnectorViewModel.GetColorForType("int"),
DataType = "int"
};
takeOp.NthConnector = nthConnector;
takeOp.Input.Add(nthConnector);
return takeOp;
}
var sysOp = new SystemOperationViewModel
{
Title = info.Title,
SystemOperationType = info.sysOp,
IsSimpleVariable = info.IsSimpleVariable,
VariableType = info.VariableType ?? string.Empty,
ClassName = info.ClassName ?? string.Empty
};
if (info.sysOp == SystemOperations.COPY)
{
sysOp.Title = info.Title;
// Universal input — accepts any shape. Starts with no outputs.
foreach (var item in input)
{
item.Shape = ConnectorShape.Circle;
item.ConnectorColor = Color.White;
item.IsCopyConnector = true;
sysOp.Input.Add(item);
}
return sysOp;
}
if (info.sysOp == SystemOperations.SPLIT)
{
sysOp.Title = info.Title;
// Square input to accept model/class objects
foreach (var item in input)
{
item.Shape = ConnectorShape.Square;
item.ConnectorColor = Color.MediumPurple;
sysOp.Input.Add(item);
}
return sysOp;
}
if (info.sysOp == SystemOperations.GET_SET && info.IsModelNode && !info.IsSimpleVariable)
{
if (info.Title == "GET")
{
info.Output.Add("");
info.IsFlowNode = false;
}
else if (info.Title == "SET")
{
info.Input.Add("");
var customModelDir = System.IO.Path.Combine(ProjectManager.ProjectDirectory, "CustomModels");
Directory.CreateDirectory(customModelDir);
var flpath = System.IO.Path.Combine(customModelDir, info.ClassName + ".cs");
if (File.Exists(flpath))
{
var fileContent = File.ReadAllText(flpath);
var properties = GetPropertiesFromClass(fileContent);
Console.WriteLine("Properties found:");
foreach (var property in properties)
{
Console.WriteLine($"Property Name: {property.Name}, Type: {property.Type}");
}
info.IsFlowNode = true;
// Build the node with flow connectors first
var flTitle = $"{info.Title} {info.ClassName}";
sysOp.Title = flTitle;
// Flow connectors
sysOp.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
sysOp.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
// Input: Square connector for the class object
foreach (var item in input)
{
item.Shape = ConnectorShape.Square;
item.ConnectorColor = Color.MediumPurple;
sysOp.Input.Add(item);
}
// Outputs: Circle connectors per property, colored by type
foreach (var property in properties)
{
var propColor = ConnectorViewModel.GetColorForType(property.Type);
var propType = property.Type.ToLower();
sysOp.Output.Add(new ConnectorViewModel
{
Title = $"{property.Name} ({property.Type})",
IsInput = false,
Shape = ConnectorShape.Circle,
ConnectorColor = propColor,
DataType = propType
});
}
return sysOp;
}
info.IsFlowNode = true;
}
var flTitle2 = $"{info.Title} {info.ClassName}";
sysOp.Title = flTitle2;
}
if (info.sysOp == SystemOperations.GET_SET && info.IsSimpleVariable)
{
var varLabel = $"{info.Title} {info.ClassName} ({info.VariableType})";
sysOp.Title = varLabel;
var varType = info.VariableType.ToLower();
var varColor = ConnectorViewModel.GetColorForType(varType);
if (info.Title == "GET")
{
// GET variable: output the value, no flow needed
info.IsFlowNode = false;
// Flow connectors not needed, output connector with type color
sysOp.Output.Add(new ConnectorViewModel
{
Title = "Value",
IsInput = false,
Shape = ConnectorShape.Circle,
ConnectorColor = varColor,
DataType = varType
});
return sysOp;
}
else if (info.Title == "SET")
{
// SET variable: input connector for the value, flow node
info.IsFlowNode = true;
// Flow connectors
sysOp.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
sysOp.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
// Data input with type color
sysOp.Input.Add(new ConnectorViewModel
{
Title = "Value",
Shape = ConnectorShape.Circle,
ConnectorColor = varColor,
DataType = varType
});
return sysOp;
}
}
if (info.sysOp != SystemOperations.BEGIN &&
info.sysOp != SystemOperations.END &&
info.IsFlowNode)
{
var flowinputnode = new ConnectorViewModel()
{
Title = "",
Shape = ConnectorShape.Triangle
};
var flowoutputnode = new ConnectorViewModel()
{
Title = "",
Shape = ConnectorShape.Triangle,
IsInput = false
};
sysOp.Input.Add(flowinputnode);
sysOp.Output.Add(flowoutputnode);
}
// Determine connector shape & color based on system operation type
ConnectorShape dataShape;
System.Drawing.Color dataColor;
if (info.IsModelNode && !info.IsSimpleVariable)
{
dataShape = ConnectorShape.Square;
dataColor = Color.MediumPurple;
}
else if (info.IsSimpleVariable)
{
dataShape = ConnectorShape.Circle;
dataColor = Color.LightGreen;
}
else
{
dataShape = ConnectorShape.Circle;
dataColor = Color.Cyan;
}
foreach (var item in info.Output)
{
var isBeginEnd = info.sysOp == SystemOperations.BEGIN || info.sysOp == SystemOperations.END;
var out1 = new ConnectorViewModel()
{
Title = string.IsNullOrEmpty(item) ? "" : item,
IsInput = false,
Shape = isBeginEnd ? ConnectorShape.Triangle : dataShape,
ConnectorColor = isBeginEnd ? Color.DarkRed : dataColor,
};
sysOp.Output.Add(out1);
}
foreach (var item in input)
{
var isBeginEnd = info.sysOp == SystemOperations.BEGIN || info.sysOp == SystemOperations.END;
item.Shape = isBeginEnd ? ConnectorShape.Triangle : dataShape;
item.ConnectorColor = isBeginEnd ? Color.DarkRed : dataColor;
sysOp.Input.Add(item);
}
return sysOp;
default: default:
{ {
var op = new OperationViewModel var op = new OperationViewModel