Implemented save load functionality to save and load the editor
This commit is contained in:
@@ -38,46 +38,27 @@ namespace Nodify.Calculator
|
||||
}
|
||||
});
|
||||
SaveFileCommand = new DelegateCommand(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var firstEditor = Editors.First();
|
||||
var allNodes = firstEditor.Calculator.Operations;
|
||||
|
||||
SaveGraphModel svm = new SaveGraphModel();
|
||||
foreach (var item in allNodes)
|
||||
{
|
||||
SaveNodes svn = new SaveNodes()
|
||||
{
|
||||
Location = item.Location,
|
||||
};
|
||||
svm.Nodes.Add(svn);
|
||||
GraphSerializer.Save(firstEditor.Calculator);
|
||||
}
|
||||
|
||||
|
||||
var jsonEditors = JsonConvert.SerializeObject(svm, new JsonSerializerSettings
|
||||
catch (Exception ex)
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
});
|
||||
//var jsonEditors = JsonConvert.SerializeObject(Editors);
|
||||
File.WriteAllText("SaveFile.AEXN", jsonEditors);
|
||||
MessageBox.Show($"Failed to save: {ex.Message}", "Save Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
});
|
||||
OpenFileCommand = new DelegateCommand(() =>
|
||||
{
|
||||
if (File.Exists("SaveFile.AEXN"))
|
||||
try
|
||||
{
|
||||
var allText = File.ReadAllText("SaveFile.AEXN");
|
||||
var edts = JsonConvert.DeserializeObject<SaveGraphModel>(allText);
|
||||
var firstEditor = Editors.First();
|
||||
var nodesToAdd = edts.Nodes;
|
||||
foreach (var item in nodesToAdd)
|
||||
{
|
||||
firstEditor.Calculator.Operations.Add(new()
|
||||
{
|
||||
Location = item.Location,
|
||||
NodeId = "H",
|
||||
Title = "HHA"
|
||||
});
|
||||
GraphSerializer.Load(firstEditor.Calculator, firstEditor.Calculator.OperationsMenu);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Failed to load: {ex.Message}", "Load Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
});
|
||||
Editors.WhenAdded((editor) =>
|
||||
@@ -98,6 +79,14 @@ namespace Nodify.Calculator
|
||||
{
|
||||
Name = $"Editor {Editors.Count + 1}"
|
||||
});
|
||||
|
||||
// Auto-load saved graph from project DB
|
||||
try
|
||||
{
|
||||
var firstEditor = Editors.First();
|
||||
GraphSerializer.Load(firstEditor.Calculator, firstEditor.Calculator.OperationsMenu);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void OnOpenInnerCalculator(EditorViewModel parentEditor, CalculatorViewModel calculator)
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace Nodify.Calculator
|
||||
{
|
||||
public class CalculatorViewModel : ObservableObject
|
||||
{
|
||||
public bool IsLoading { get; set; }
|
||||
|
||||
public CalculatorViewModel()
|
||||
{
|
||||
CreateConnectionCommand = new DelegateCommand<ConnectorViewModel>(
|
||||
@@ -27,13 +29,13 @@ namespace Nodify.Calculator
|
||||
c.Output.ValueObservers.Add(c.Input);
|
||||
|
||||
// Dynamic Split node: populate outputs when a model connects to the Square input
|
||||
HandleSplitNodeConnected(c);
|
||||
if (!IsLoading) HandleSplitNodeConnected(c);
|
||||
|
||||
// Dynamic Copy node: adapt connectors when something connects
|
||||
HandleCopyNodeConnected(c);
|
||||
if (!IsLoading) HandleCopyNodeConnected(c);
|
||||
|
||||
// Dynamic Take node: adapt connectors when a list connects
|
||||
HandleTakeNodeConnected(c);
|
||||
if (!IsLoading) HandleTakeNodeConnected(c);
|
||||
})
|
||||
.WhenRemoved(c =>
|
||||
{
|
||||
@@ -65,7 +67,7 @@ namespace Nodify.Calculator
|
||||
Operations.WhenAdded(x =>
|
||||
{
|
||||
x.Input.WhenRemoved(RemoveConnection);
|
||||
x.NodeId = (Operations.Count + 1).ToString();
|
||||
if (!IsLoading) x.NodeId = (Operations.Count + 1).ToString();
|
||||
Debug.WriteLine($"Currently adding the node with node id : {x.NodeId} , Title : {x.Title}");
|
||||
if (x is CalculatorInputOperationViewModel ci)
|
||||
{
|
||||
|
||||
420
Examples/Nodify.Calculator/GraphSerializer.cs
Normal file
420
Examples/Nodify.Calculator/GraphSerializer.cs
Normal file
@@ -0,0 +1,420 @@
|
||||
using Nodify.Calculator.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public static class GraphSerializer
|
||||
{
|
||||
public static void Save(CalculatorViewModel calculator)
|
||||
{
|
||||
var graph = new SaveGraphModel();
|
||||
var operations = calculator.Operations;
|
||||
var connections = calculator.Connections;
|
||||
|
||||
// Ensure all nodes have IDs
|
||||
int idCounter = 1;
|
||||
foreach (var op in operations)
|
||||
{
|
||||
if (string.IsNullOrEmpty(op.NodeId))
|
||||
op.NodeId = $"node_{idCounter++}";
|
||||
}
|
||||
|
||||
// Serialize nodes
|
||||
foreach (var op in operations)
|
||||
{
|
||||
var nodeData = new NodeData
|
||||
{
|
||||
NodeId = op.NodeId,
|
||||
Title = op.Title ?? string.Empty,
|
||||
LocationX = op.Location.X,
|
||||
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 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, derive ClassName from title
|
||||
if (sys.SystemOperationType == SystemOperations.GET_SET)
|
||||
{
|
||||
var title = sys.Title ?? "";
|
||||
if (title.StartsWith("GET ")) nodeData.ClassName = title.Substring(4).Trim();
|
||||
else if (title.StartsWith("SET ")) nodeData.ClassName = title.Substring(4).Trim();
|
||||
}
|
||||
break;
|
||||
case APIOperationViewModel api:
|
||||
nodeData.NodeType = "API";
|
||||
nodeData.OPType = api.OperationType;
|
||||
nodeData.ResponseModelClassName = api.ResponseModelClassName;
|
||||
break;
|
||||
case ExpandoOperationViewModel:
|
||||
nodeData.NodeType = "Expando";
|
||||
break;
|
||||
case CalculatorOperationViewModel:
|
||||
nodeData.NodeType = "Calculator";
|
||||
break;
|
||||
default:
|
||||
nodeData.NodeType = "Normal";
|
||||
break;
|
||||
}
|
||||
|
||||
// Save connector metadata for all nodes
|
||||
foreach (var c in op.Input)
|
||||
nodeData.InputConnectors.Add(SerializeConnector(c));
|
||||
foreach (var c in op.Output)
|
||||
nodeData.OutputConnectors.Add(SerializeConnector(c));
|
||||
|
||||
graph.Nodes.Add(nodeData);
|
||||
}
|
||||
|
||||
// Serialize connections
|
||||
foreach (var conn in connections)
|
||||
{
|
||||
var inputOp = conn.Input?.Operation;
|
||||
var outputOp = conn.Output?.Operation;
|
||||
if (inputOp == null || outputOp == null) continue;
|
||||
|
||||
var inputIndex = inputOp.Input.IndexOf(conn.Input);
|
||||
var outputIndex = outputOp.Output.IndexOf(conn.Output);
|
||||
|
||||
if (inputIndex < 0 || outputIndex < 0) continue;
|
||||
|
||||
graph.Connections.Add(new ConnectionData
|
||||
{
|
||||
TargetNodeId = inputOp.NodeId,
|
||||
TargetConnectorIndex = inputIndex,
|
||||
SourceNodeId = outputOp.NodeId,
|
||||
SourceConnectorIndex = outputIndex
|
||||
});
|
||||
}
|
||||
|
||||
// Save to LiteDB
|
||||
using var db = new LiteDbHelper<SaveGraphModel>("Graph");
|
||||
db.DeleteMany(_ => true);
|
||||
db.Insert(graph);
|
||||
}
|
||||
|
||||
public static void Load(CalculatorViewModel calculator, OperationsMenuViewModel opsMenu)
|
||||
{
|
||||
SaveGraphModel graph;
|
||||
try
|
||||
{
|
||||
using var db = new LiteDbHelper<SaveGraphModel>("Graph");
|
||||
graph = db.FindAll().FirstOrDefault();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (graph == null || graph.Nodes.Count == 0) return;
|
||||
|
||||
// Clear existing
|
||||
calculator.Connections.Clear();
|
||||
calculator.Operations.Clear();
|
||||
|
||||
calculator.IsLoading = true;
|
||||
try
|
||||
{
|
||||
|
||||
var nodeMap = new Dictionary<string, OperationViewModel>();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Recreate connections
|
||||
foreach (var cd in graph.Connections)
|
||||
{
|
||||
if (!nodeMap.TryGetValue(cd.SourceNodeId, out var sourceOp)) continue;
|
||||
if (!nodeMap.TryGetValue(cd.TargetNodeId, out var targetOp)) continue;
|
||||
if (cd.SourceConnectorIndex >= sourceOp.Output.Count) continue;
|
||||
if (cd.TargetConnectorIndex >= targetOp.Input.Count) continue;
|
||||
|
||||
var sourceConn = sourceOp.Output[cd.SourceConnectorIndex];
|
||||
var targetConn = targetOp.Input[cd.TargetConnectorIndex];
|
||||
|
||||
calculator.Connections.Add(new ConnectionViewModel
|
||||
{
|
||||
Input = targetConn,
|
||||
Output = sourceConn
|
||||
});
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
calculator.IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static OperationViewModel RecreateNode(NodeData nd, OperationsMenuViewModel opsMenu)
|
||||
{
|
||||
switch (nd.NodeType)
|
||||
{
|
||||
case "API":
|
||||
{
|
||||
// Find matching swagger operation or create from saved data
|
||||
var info = new OperationInfoViewModel
|
||||
{
|
||||
Title = nd.Title,
|
||||
OPType = nd.OPType?.ToLower() ?? "get",
|
||||
Type = OperationType.API,
|
||||
ResponseModelClassName = nd.ResponseModelClassName ?? string.Empty
|
||||
};
|
||||
// Restore inputs from saved connector data (skip flow triangle at index 0)
|
||||
foreach (var ic in nd.InputConnectors)
|
||||
{
|
||||
if (ic.Shape != "Triangle")
|
||||
info.Input.Add(ic.Title);
|
||||
}
|
||||
info.Output.Add("");
|
||||
return OperationFactory.GetOperation(info);
|
||||
}
|
||||
|
||||
case "System":
|
||||
{
|
||||
if (Enum.TryParse<SystemOperations>(nd.SystemOp, out var sysOp))
|
||||
{
|
||||
var info = new OperationInfoViewModel
|
||||
{
|
||||
Title = nd.Title,
|
||||
Type = OperationType.System,
|
||||
sysOp = sysOp
|
||||
};
|
||||
|
||||
if (sysOp == SystemOperations.GET_SET && !string.IsNullOrEmpty(nd.ClassName))
|
||||
{
|
||||
info.IsModelNode = true;
|
||||
info.ClassName = nd.ClassName;
|
||||
}
|
||||
|
||||
// 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.TAKE:
|
||||
info.Input.Add("List");
|
||||
break;
|
||||
case SystemOperations.COPY:
|
||||
info.Input.Add("");
|
||||
break;
|
||||
case SystemOperations.SPLIT:
|
||||
info.Input.Add("");
|
||||
break;
|
||||
case SystemOperations.IF:
|
||||
info.Output.Add("");
|
||||
info.IsFlowNode = true;
|
||||
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.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ConnectorData SerializeConnector(ConnectorViewModel c)
|
||||
{
|
||||
return new ConnectorData
|
||||
{
|
||||
Title = c.Title ?? string.Empty,
|
||||
Shape = c.Shape.ToString(),
|
||||
ColorArgb = c.RawColor.ToArgb(),
|
||||
DataType = c.DataType ?? string.Empty,
|
||||
IsCopyConnector = c.IsCopyConnector,
|
||||
IsTakeListConnector = c.IsTakeListConnector
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
IsInput = isInput
|
||||
};
|
||||
}
|
||||
|
||||
private static ConnectorShape ParseShape(string shape)
|
||||
{
|
||||
return Enum.TryParse<ConnectorShape>(shape, out var s) ? s : ConnectorShape.Circle;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using Nodify.Interactivity;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
@@ -16,6 +18,39 @@ namespace Nodify.Calculator
|
||||
typeof(UIElement),
|
||||
Keyboard.PreviewGotKeyboardFocusEvent,
|
||||
(KeyboardFocusChangedEventHandler)OnPreviewGotKeyboardFocus);
|
||||
|
||||
Closing += MainWindow_Closing;
|
||||
}
|
||||
|
||||
private void MainWindow_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"Do you want to save the project before closing?",
|
||||
"Save Project",
|
||||
MessageBoxButton.YesNoCancel,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
if (result == MessageBoxResult.Cancel)
|
||||
{
|
||||
e.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (DataContext is ApplicationViewModel appVm && appVm.Editors.Count > 0)
|
||||
{
|
||||
var firstEditor = appVm.Editors.First();
|
||||
GraphSerializer.Save(firstEditor.Calculator);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Failed to save: {ex.Message}", "Save Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
||||
|
||||
@@ -1,20 +1,75 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Calculator.Models
|
||||
{
|
||||
public class SaveGraphModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public List<SaveNodes> Nodes { get; set; } = new List<SaveNodes>();
|
||||
public int Id { get; set; } = 1;
|
||||
public List<NodeData> Nodes { get; set; } = new List<NodeData>();
|
||||
public List<ConnectionData> Connections { get; set; } = new List<ConnectionData>();
|
||||
}
|
||||
|
||||
public class NodeData
|
||||
{
|
||||
/// <summary>Unique ID matching OperationViewModel.NodeId</summary>
|
||||
public string NodeId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Discriminator: "API", "System", "Expando", "Calculator", "Function"</summary>
|
||||
public string NodeType { get; set; } = string.Empty;
|
||||
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public double LocationX { get; set; }
|
||||
public double LocationY { get; set; }
|
||||
|
||||
// API node properties
|
||||
public string OPType { get; set; } = string.Empty;
|
||||
public string ResponseModelClassName { get; set; } = string.Empty;
|
||||
|
||||
// System node properties
|
||||
public string SystemOp { get; set; } = string.Empty;
|
||||
public string ClassName { get; set; } = string.Empty;
|
||||
|
||||
// Take node properties
|
||||
public int NthIndex { get; set; }
|
||||
public bool IsRandom { get; set; }
|
||||
|
||||
// Function node properties
|
||||
public string FunctionName { get; set; } = string.Empty;
|
||||
public List<FunctionParamData> FunctionInputs { get; set; } = new List<FunctionParamData>();
|
||||
public List<FunctionParamData> FunctionOutputs { get; set; } = new List<FunctionParamData>();
|
||||
|
||||
/// <summary>Saved input connector metadata (for dynamic nodes like SPLIT/COPY/TAKE)</summary>
|
||||
public List<ConnectorData> InputConnectors { get; set; } = new List<ConnectorData>();
|
||||
/// <summary>Saved output connector metadata</summary>
|
||||
public List<ConnectorData> OutputConnectors { get; set; } = new List<ConnectorData>();
|
||||
}
|
||||
|
||||
public class ConnectorData
|
||||
{
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Shape { get; set; } = "Circle";
|
||||
public int ColorArgb { get; set; }
|
||||
public string DataType { get; set; } = string.Empty;
|
||||
public bool IsCopyConnector { get; set; }
|
||||
public bool IsTakeListConnector { get; set; }
|
||||
}
|
||||
|
||||
public class ConnectionData
|
||||
{
|
||||
public string SourceNodeId { get; set; } = string.Empty;
|
||||
public int SourceConnectorIndex { get; set; }
|
||||
public string TargetNodeId { get; set; } = string.Empty;
|
||||
public int TargetConnectorIndex { get; set; }
|
||||
}
|
||||
|
||||
public class FunctionParamData
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Type { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
// Keep legacy for compatibility
|
||||
public class SaveNodes
|
||||
{
|
||||
public Point Location { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user