From b2fed489e21632e4de6d9dde63b36ee00c2deb86 Mon Sep 17 00:00:00 2001 From: Ankitkumar Satapara Date: Wed, 22 Apr 2026 15:03:20 +0530 Subject: [PATCH] Major changes in the flow of code, implemented modularization of the code for maintainability --- Examples/Nodify.Calculator/GraphSerializer.cs | 431 ++------------- .../NodeHandlers/BasicSystemHandlers.cs | 145 +++++ .../NodeHandlers/FlowNodeHandlers.cs | 170 ++++++ .../NodeHandlers/GetSetHandlers.cs | 209 ++++++++ .../NodeHandlers/INodeHandler.cs | 45 ++ .../NodeHandlers/NodeHandlerRegistry.cs | 133 +++++ .../NodeHandlers/OtherNodeHandlers.cs | 293 ++++++++++ .../NodeHandlers/SpecializedNodeHandlers.cs | 172 ++++++ .../Operations/OperationFactory.cs | 499 +----------------- 9 files changed, 1224 insertions(+), 873 deletions(-) create mode 100644 Examples/Nodify.Calculator/NodeHandlers/BasicSystemHandlers.cs create mode 100644 Examples/Nodify.Calculator/NodeHandlers/FlowNodeHandlers.cs create mode 100644 Examples/Nodify.Calculator/NodeHandlers/GetSetHandlers.cs create mode 100644 Examples/Nodify.Calculator/NodeHandlers/INodeHandler.cs create mode 100644 Examples/Nodify.Calculator/NodeHandlers/NodeHandlerRegistry.cs create mode 100644 Examples/Nodify.Calculator/NodeHandlers/OtherNodeHandlers.cs create mode 100644 Examples/Nodify.Calculator/NodeHandlers/SpecializedNodeHandlers.cs diff --git a/Examples/Nodify.Calculator/GraphSerializer.cs b/Examples/Nodify.Calculator/GraphSerializer.cs index 15b44be..d2e434c 100644 --- a/Examples/Nodify.Calculator/GraphSerializer.cs +++ b/Examples/Nodify.Calculator/GraphSerializer.cs @@ -1,4 +1,5 @@ using Nodify.Calculator.Models; +using Nodify.Calculator.NodeHandlers; using System; using System.Collections.Generic; using System.Drawing; @@ -34,54 +35,9 @@ namespace Nodify.Calculator LocationY = op.Location.Y }; - switch (op) - { - case TakeOperationViewModel take: - 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; - } + // Delegate to the handler for VM-specific properties + var handler = NodeHandlerRegistry.FindForSave(op); + handler?.Save(op, nodeData); // Save connector metadata for all nodes foreach (var c in op.Input) @@ -179,47 +135,48 @@ namespace Nodify.Calculator calculator.IsLoading = true; try { + var nodeMap = new Dictionary(); - var nodeMap = new Dictionary(); + // 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 - foreach (var nd in graph.Nodes) - { - OperationViewModel op = null; - try - { - op = RecreateNode(nd, opsMenu); - } - catch - { - continue; + if (op == null) continue; + + op.NodeId = nd.NodeId; + op.Location = new System.Windows.Point(nd.LocationX, nd.LocationY); + nodeMap[nd.NodeId] = op; + calculator.Operations.Add(op); } - if (op == null) continue; - - 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 + // Recreate connections + foreach (var cd in graph.Connections) { - Input = targetConn, - Output = sourceConn - }); - } + 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, + Output = sourceConn + }); + } } 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(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) { return new ConnectorData @@ -526,25 +197,5 @@ namespace Nodify.Calculator 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(shape, out var s) ? s : ConnectorShape.Circle; - } } } diff --git a/Examples/Nodify.Calculator/NodeHandlers/BasicSystemHandlers.cs b/Examples/Nodify.Calculator/NodeHandlers/BasicSystemHandlers.cs new file mode 100644 index 0000000..ec00f13 --- /dev/null +++ b/Examples/Nodify.Calculator/NodeHandlers/BasicSystemHandlers.cs @@ -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); + } + } +} diff --git a/Examples/Nodify.Calculator/NodeHandlers/FlowNodeHandlers.cs b/Examples/Nodify.Calculator/NodeHandlers/FlowNodeHandlers.cs new file mode 100644 index 0000000..20e1a76 --- /dev/null +++ b/Examples/Nodify.Calculator/NodeHandlers/FlowNodeHandlers.cs @@ -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; + } + } + } +} diff --git a/Examples/Nodify.Calculator/NodeHandlers/GetSetHandlers.cs b/Examples/Nodify.Calculator/NodeHandlers/GetSetHandlers.cs new file mode 100644 index 0000000..e2be9af --- /dev/null +++ b/Examples/Nodify.Calculator/NodeHandlers/GetSetHandlers.cs @@ -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; + } + } + } +} diff --git a/Examples/Nodify.Calculator/NodeHandlers/INodeHandler.cs b/Examples/Nodify.Calculator/NodeHandlers/INodeHandler.cs new file mode 100644 index 0000000..279bc32 --- /dev/null +++ b/Examples/Nodify.Calculator/NodeHandlers/INodeHandler.cs @@ -0,0 +1,45 @@ +using Nodify.Calculator.Models; + +namespace Nodify.Calculator.NodeHandlers +{ + /// + /// Each node type implements this interface to encapsulate its own + /// creation, save and load logic — eliminating giant switch/if chains. + /// + public interface INodeHandler + { + /// + /// The NodeType discriminator stored in (e.g. "System", "API", "Function"). + /// + string NodeTypeKey { get; } + + /// + /// Returns true if this handler can create a node from the given . + /// Used when building a node from the toolbox / left-panel. + /// + bool CanCreate(OperationInfoViewModel info); + + /// + /// Returns true if this handler can restore a node from the given . + /// Used when loading a saved graph. + /// + bool CanRestore(NodeData data); + + /// + /// Creates the from toolbox info. + /// + OperationViewModel Create(OperationInfoViewModel info); + + /// + /// Creates the from saved . + /// The base properties (NodeId, Location) are set by the caller. + /// + OperationViewModel Restore(NodeData data); + + /// + /// Writes ViewModel-specific properties into during save. + /// Common properties (NodeId, Title, Location, Connectors) are handled by the caller. + /// + void Save(OperationViewModel vm, NodeData data); + } +} diff --git a/Examples/Nodify.Calculator/NodeHandlers/NodeHandlerRegistry.cs b/Examples/Nodify.Calculator/NodeHandlers/NodeHandlerRegistry.cs new file mode 100644 index 0000000..14b8e6c --- /dev/null +++ b/Examples/Nodify.Calculator/NodeHandlers/NodeHandlerRegistry.cs @@ -0,0 +1,133 @@ +using Nodify.Calculator.Models; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; + +namespace Nodify.Calculator.NodeHandlers +{ + /// + /// Registry that discovers and dispatches to the correct . + /// Replaces all the switch/if chains in OperationFactory and GraphSerializer. + /// + public static class NodeHandlerRegistry + { + private static readonly List _handlers = new(); + private static bool _initialized; + + public static IReadOnlyList 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()); + } + + /// + /// Find the handler that can create a node from toolbox info. + /// + public static INodeHandler? FindForCreate(OperationInfoViewModel info) + { + EnsureInitialized(); + return _handlers.FirstOrDefault(h => h.CanCreate(info)); + } + + /// + /// Find the handler that can restore a node from saved data. + /// + public static INodeHandler? FindForRestore(NodeData data) + { + EnsureInitialized(); + return _handlers.FirstOrDefault(h => h.CanRestore(data)); + } + + /// + /// Find the handler that can save a given ViewModel. + /// + 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().FirstOrDefault(), + FunctionOperationViewModel => _handlers.OfType().FirstOrDefault(), + AuthOperationViewModel => _handlers.OfType().FirstOrDefault(), + TakeOperationViewModel => _handlers.OfType().FirstOrDefault(), + ForEachOperationViewModel => _handlers.OfType().FirstOrDefault(), + AssertOperationViewModel => _handlers.OfType().FirstOrDefault(), + APIOperationViewModel => _handlers.OfType().FirstOrDefault(), + ExpandoOperationViewModel => _handlers.OfType().FirstOrDefault(), + CalculatorOperationViewModel => _handlers.OfType().FirstOrDefault(), + SystemOperationViewModel sys => FindSystemSaveHandler(sys), + _ => _handlers.OfType().FirstOrDefault() + }; + } + + private static INodeHandler? FindSystemSaveHandler(SystemOperationViewModel sys) + { + return sys.SystemOperationType switch + { + SystemOperations.COPY => _handlers.OfType().FirstOrDefault(), + SystemOperations.SPLIT => _handlers.OfType().FirstOrDefault(), + SystemOperations.DEBUG => _handlers.OfType().FirstOrDefault(), + SystemOperations.NEW_OBJECT => _handlers.OfType().FirstOrDefault(), + SystemOperations.BEGIN => _handlers.OfType().FirstOrDefault(), + SystemOperations.END => _handlers.OfType().FirstOrDefault(), + SystemOperations.GET_SET when sys.IsSimpleVariable => _handlers.OfType().FirstOrDefault(), + SystemOperations.GET_SET => _handlers.OfType().FirstOrDefault(), + _ => _handlers.OfType().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(shape, out var s) ? s : ConnectorShape.Circle; + } + } +} diff --git a/Examples/Nodify.Calculator/NodeHandlers/OtherNodeHandlers.cs b/Examples/Nodify.Calculator/NodeHandlers/OtherNodeHandlers.cs new file mode 100644 index 0000000..f9c5022 --- /dev/null +++ b/Examples/Nodify.Calculator/NodeHandlers/OtherNodeHandlers.cs @@ -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(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(); + } + } +} diff --git a/Examples/Nodify.Calculator/NodeHandlers/SpecializedNodeHandlers.cs b/Examples/Nodify.Calculator/NodeHandlers/SpecializedNodeHandlers.cs new file mode 100644 index 0000000..4e1d71d --- /dev/null +++ b/Examples/Nodify.Calculator/NodeHandlers/SpecializedNodeHandlers.cs @@ -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); + } + } +} diff --git a/Examples/Nodify.Calculator/Operations/OperationFactory.cs b/Examples/Nodify.Calculator/Operations/OperationFactory.cs index bf20aa2..7d5339a 100644 --- a/Examples/Nodify.Calculator/Operations/OperationFactory.cs +++ b/Examples/Nodify.Calculator/Operations/OperationFactory.cs @@ -236,6 +236,22 @@ namespace Nodify.Calculator } 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); + } + + /// + /// Legacy creation path — only used for Expression, Calculator, Group, Graph, and generic Normal types. + /// + internal static OperationViewModel GetOperationLegacy(OperationInfoViewModel info) { var input = info.Input.Select(i => new ConnectorViewModel { @@ -260,19 +276,6 @@ namespace Nodify.Calculator 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: return new OperationGroupViewModel { @@ -286,476 +289,6 @@ namespace Nodify.Calculator 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: { var op = new OperationViewModel