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

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