Fixed new object request node and allowed multiple classes with dot notation
All checks were successful
Build / build (push) Successful in 39s
All checks were successful
Build / build (push) Successful in 39s
This commit is contained in:
@@ -32,9 +32,6 @@ namespace Nodify.Calculator
|
||||
// Dynamic Split node: populate outputs when a model connects to the Square input
|
||||
if (!IsLoading) HandleSplitNodeConnected(c);
|
||||
|
||||
// Dynamic New Object node: populate inputs when a model connects
|
||||
if (!IsLoading) HandleNewObjectNodeConnected(c);
|
||||
|
||||
// Dynamic Copy node: adapt connectors when something connects
|
||||
if (!IsLoading) HandleCopyNodeConnected(c);
|
||||
|
||||
@@ -73,9 +70,6 @@ namespace Nodify.Calculator
|
||||
// Dynamic Split node: clear outputs when model disconnects
|
||||
HandleSplitNodeDisconnected(c);
|
||||
|
||||
// Dynamic New Object node: clear inputs when model disconnects
|
||||
HandleNewObjectNodeDisconnected(c);
|
||||
|
||||
// Dynamic Copy node: reset on disconnect
|
||||
HandleCopyNodeDisconnected(c);
|
||||
|
||||
@@ -437,144 +431,6 @@ namespace Nodify.Calculator
|
||||
sysVm.Title = "Split";
|
||||
}
|
||||
|
||||
private void HandleNewObjectNodeConnected(ConnectionViewModel c)
|
||||
{
|
||||
// Find the New Object node's Square input connector
|
||||
var squareInput = c.Input.Shape == ConnectorShape.Square ? c.Input : null;
|
||||
if (squareInput == null) squareInput = c.Output.Shape == ConnectorShape.Square ? c.Output : null;
|
||||
if (squareInput == null) return;
|
||||
|
||||
var newObjOp = squareInput.Operation;
|
||||
if (newObjOp is not SystemOperationViewModel sysVm || sysVm.SystemOperationType != SystemOperations.NEW_OBJECT)
|
||||
return;
|
||||
|
||||
// The other end is the source — find which model class it comes from
|
||||
var sourceConnector = (c.Input == squareInput) ? c.Output : c.Input;
|
||||
var sourceOp = sourceConnector.Operation;
|
||||
|
||||
string className = null;
|
||||
if (sourceOp is SystemOperationViewModel srcSys)
|
||||
{
|
||||
var title = srcSys.Title ?? "";
|
||||
if (title.StartsWith("GET ")) className = title.Substring(4).Trim();
|
||||
else if (title.StartsWith("SET ")) className = title.Split(' ').ElementAtOrDefault(1);
|
||||
}
|
||||
else if (sourceOp is APIOperationViewModel apiVm)
|
||||
{
|
||||
className = apiVm.ResponseModelClassName;
|
||||
if (!string.IsNullOrEmpty(className) && className.StartsWith("List<") && className.EndsWith(">"))
|
||||
className = className.Substring(5, className.Length - 6);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(className) && !string.IsNullOrEmpty(sourceConnector.DataType))
|
||||
{
|
||||
var dt = sourceConnector.DataType;
|
||||
if (dt.StartsWith("List<") && dt.EndsWith(">"))
|
||||
dt = dt.Substring(5, dt.Length - 6);
|
||||
if (!new[] { "string", "int", "double", "bool", "object" }.Contains(dt.ToLower()))
|
||||
className = dt;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(className)) return;
|
||||
|
||||
var customModelDir = Path.Combine(ProjectManager.ProjectDirectory, "CustomModels");
|
||||
var filePath = Path.Combine(customModelDir, $"{className}.cs");
|
||||
if (!File.Exists(filePath)) return;
|
||||
|
||||
var fileContent = File.ReadAllText(filePath);
|
||||
var properties = OperationFactory.GetPropertiesFromClassPublic(fileContent);
|
||||
if (properties == null || properties.Count == 0) return;
|
||||
|
||||
// Remove existing dynamic inputs (non-triangle, non-square)
|
||||
var toRemove = newObjOp.Input.Where(i => i.Shape != ConnectorShape.Triangle && i.Shape != ConnectorShape.Square).ToList();
|
||||
toRemove.ForEach(i =>
|
||||
{
|
||||
DisconnectConnector(i);
|
||||
newObjOp.Input.Remove(i);
|
||||
});
|
||||
|
||||
// Add property inputs
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
var propType = prop.Type;
|
||||
bool isList = propType.StartsWith("List<") && propType.EndsWith(">");
|
||||
var innerType = isList ? propType.Substring(5, propType.Length - 6) : propType;
|
||||
|
||||
var primTypes = new[] { "string", "int", "double", "bool", "float", "decimal", "long", "object", "datetime" };
|
||||
bool isModel = !primTypes.Contains(innerType.ToLower())
|
||||
&& File.Exists(Path.Combine(customModelDir, $"{innerType}.cs"));
|
||||
|
||||
ConnectorShape shape;
|
||||
System.Drawing.Color color;
|
||||
|
||||
if (isList)
|
||||
{
|
||||
shape = ConnectorShape.Grid;
|
||||
color = System.Drawing.Color.MediumSpringGreen;
|
||||
}
|
||||
else if (isModel)
|
||||
{
|
||||
shape = ConnectorShape.Square;
|
||||
color = System.Drawing.Color.MediumPurple;
|
||||
}
|
||||
else
|
||||
{
|
||||
shape = ConnectorShape.Circle;
|
||||
color = ConnectorViewModel.GetColorForType(propType);
|
||||
}
|
||||
|
||||
newObjOp.Input.Add(new ConnectorViewModel
|
||||
{
|
||||
Title = $"{prop.Name} ({propType})",
|
||||
Shape = shape,
|
||||
ConnectorColor = color,
|
||||
DataType = isModel || isList ? propType : propType.ToLower()
|
||||
});
|
||||
}
|
||||
|
||||
// Update the output connector to reflect the model type
|
||||
var modelOutput = newObjOp.Output.FirstOrDefault(o => o.Shape == ConnectorShape.Square);
|
||||
if (modelOutput != null)
|
||||
{
|
||||
modelOutput.Title = className;
|
||||
modelOutput.DataType = className;
|
||||
}
|
||||
|
||||
sysVm.Title = $"New {className}";
|
||||
}
|
||||
|
||||
private void HandleNewObjectNodeDisconnected(ConnectionViewModel c)
|
||||
{
|
||||
var squareInput = c.Input.Shape == ConnectorShape.Square ? c.Input : null;
|
||||
if (squareInput == null) squareInput = c.Output.Shape == ConnectorShape.Square ? c.Output : null;
|
||||
if (squareInput == null) return;
|
||||
|
||||
var newObjOp = squareInput.Operation;
|
||||
if (newObjOp is not SystemOperationViewModel sysVm || sysVm.SystemOperationType != SystemOperations.NEW_OBJECT)
|
||||
return;
|
||||
|
||||
var stillConnected = Connections.Any(con => con.Input == squareInput || con.Output == squareInput);
|
||||
if (stillConnected) return;
|
||||
|
||||
// Remove dynamic property inputs (keep triangle and square connectors)
|
||||
var toRemove = newObjOp.Input.Where(i => i.Shape != ConnectorShape.Triangle && i.Shape != ConnectorShape.Square).ToList();
|
||||
toRemove.ForEach(i =>
|
||||
{
|
||||
DisconnectConnector(i);
|
||||
newObjOp.Input.Remove(i);
|
||||
});
|
||||
|
||||
// Reset output
|
||||
var modelOutput = newObjOp.Output.FirstOrDefault(o => o.Shape == ConnectorShape.Square);
|
||||
if (modelOutput != null)
|
||||
{
|
||||
modelOutput.Title = "Object";
|
||||
modelOutput.DataType = "object";
|
||||
}
|
||||
|
||||
sysVm.Title = "New Object";
|
||||
}
|
||||
|
||||
private void HandleCopyNodeConnected(ConnectionViewModel c)
|
||||
{
|
||||
// Find the COPY node's input connector
|
||||
|
||||
@@ -521,6 +521,25 @@
|
||||
</nodify:Node>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:NewObjectOperationViewModel}">
|
||||
<nodify:Node Header="{Binding Title}"
|
||||
Content="{Binding}"
|
||||
Input="{Binding Input}"
|
||||
Output="{Binding Output}">
|
||||
<nodify:Node.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type local:NewObjectOperationViewModel}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Model" FontSize="10" Foreground="Gray" Margin="0,0,0,2" />
|
||||
<ComboBox ItemsSource="{Binding AvailableModels}"
|
||||
SelectedItem="{Binding SelectedModel, Mode=TwoWay}"
|
||||
MinWidth="140"
|
||||
Margin="0,0,0,2" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</nodify:Node.ContentTemplate>
|
||||
</nodify:Node>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:StringConcatOperationViewModel}">
|
||||
<nodify:Node Header="{Binding Title}"
|
||||
Content="{Binding}"
|
||||
|
||||
@@ -75,41 +75,70 @@ namespace Nodify.Calculator.Execution.Handlers
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class NewObjectHandler : INodeExecutionHandler
|
||||
internal sealed class NewObjectExecHandler : INodeExecutionHandler
|
||||
{
|
||||
public bool CanHandle(OperationViewModel node, ExecutionContext ctx)
|
||||
=> node is SystemOperationViewModel sys && sys.SystemOperationType == SystemOperations.NEW_OBJECT;
|
||||
|
||||
public void Execute(OperationViewModel node, ExecutionContext ctx)
|
||||
{
|
||||
ctx.Log($"Constructing new object: {node.Title}");
|
||||
var jObj = new JObject();
|
||||
ctx.Log($"[NEW OBJECT] Constructing: {node.Title}");
|
||||
var root = new JObject();
|
||||
|
||||
foreach (var inp in node.Input)
|
||||
{
|
||||
if (inp.Shape == ConnectorShape.Triangle || inp.Shape == ConnectorShape.Square) continue;
|
||||
if (inp.Shape == ConnectorShape.Triangle) continue;
|
||||
|
||||
var propName = inp.Title ?? "";
|
||||
var parenIdx = propName.IndexOf(" (", StringComparison.Ordinal);
|
||||
if (parenIdx > 0) propName = propName.Substring(0, parenIdx);
|
||||
// Extract property path from title: "Address.Street (string)" → "Address.Street"
|
||||
var title = inp.Title ?? "";
|
||||
var parenIdx = title.IndexOf(" (", StringComparison.Ordinal);
|
||||
var propPath = parenIdx > 0 ? title.Substring(0, parenIdx).Trim() : title.Trim();
|
||||
if (string.IsNullOrEmpty(propPath)) continue;
|
||||
|
||||
var val = ctx.ReadInput(inp);
|
||||
ctx.Log($"[NEW OBJECT] {propPath} = {(val?.Length > 80 ? val.Substring(0, 80) + "..." : val ?? "null")}");
|
||||
|
||||
SetNestedValue(root, propPath, val);
|
||||
}
|
||||
|
||||
var json = root.ToString(Formatting.None);
|
||||
ctx.Outputs[node.NodeId] = json;
|
||||
ctx.Variables[node.NodeId] = json;
|
||||
ctx.Log($"[NEW OBJECT] Result: {(json.Length > 200 ? json.Substring(0, 200) + "..." : json)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value at a dot-notation path in a JObject, creating intermediate objects as needed.
|
||||
/// e.g., "Address.Street" with value "Main St" → { "Address": { "Street": "Main St" } }
|
||||
/// </summary>
|
||||
private static void SetNestedValue(JObject root, string path, string val)
|
||||
{
|
||||
var parts = path.Split('.');
|
||||
var current = root;
|
||||
|
||||
for (int i = 0; i < parts.Length - 1; i++)
|
||||
{
|
||||
if (current[parts[i]] is JObject existing)
|
||||
current = existing;
|
||||
else
|
||||
{
|
||||
var child = new JObject();
|
||||
current[parts[i]] = child;
|
||||
current = child;
|
||||
}
|
||||
}
|
||||
|
||||
var leaf = parts[parts.Length - 1];
|
||||
if (val != null)
|
||||
{
|
||||
try { jObj[propName] = JToken.Parse(val); }
|
||||
catch { jObj[propName] = val; }
|
||||
try { current[leaf] = JToken.Parse(val); }
|
||||
catch { current[leaf] = val; }
|
||||
}
|
||||
else
|
||||
{
|
||||
jObj[propName] = null;
|
||||
current[leaf] = null;
|
||||
}
|
||||
}
|
||||
|
||||
var json = jObj.ToString(Formatting.None);
|
||||
ctx.Outputs[node.NodeId] = json;
|
||||
ctx.Variables[node.NodeId] = json;
|
||||
ctx.Log($"Object constructed: {json}");
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class AssertHandler : INodeExecutionHandler
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Nodify.Calculator
|
||||
new AuthHandler(),
|
||||
new KnotHandler(),
|
||||
new DebugHandler(),
|
||||
new NewObjectHandler(),
|
||||
new NewObjectExecHandler(),
|
||||
new AssertHandler(),
|
||||
new ForEachHandler(),
|
||||
new Execution.Handlers.DeserializeHandler(),
|
||||
|
||||
176
Examples/Nodify.Calculator/NewObjectOperationViewModel.cs
Normal file
176
Examples/Nodify.Calculator/NewObjectOperationViewModel.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nodify.Calculator
|
||||
{
|
||||
public class NewObjectOperationViewModel : SystemOperationViewModel
|
||||
{
|
||||
private string _selectedModel = "";
|
||||
|
||||
public ObservableCollection<string> AvailableModels { get; } = new ObservableCollection<string>();
|
||||
|
||||
public string SelectedModel
|
||||
{
|
||||
get => _selectedModel;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedModel, value))
|
||||
{
|
||||
RebuildInputs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public NewObjectOperationViewModel()
|
||||
{
|
||||
Title = "New Object";
|
||||
SystemOperationType = SystemOperations.NEW_OBJECT;
|
||||
RefreshAvailableModels();
|
||||
}
|
||||
|
||||
public void RefreshAvailableModels()
|
||||
{
|
||||
var current = _selectedModel;
|
||||
AvailableModels.Clear();
|
||||
AvailableModels.Add("");
|
||||
|
||||
var customModelDir = Path.Combine(ProjectManager.ProjectDirectory, "CustomModels");
|
||||
if (Directory.Exists(customModelDir))
|
||||
{
|
||||
var modelFiles = Directory.GetFiles(customModelDir, "*.cs");
|
||||
foreach (var file in modelFiles)
|
||||
{
|
||||
AvailableModels.Add(Path.GetFileNameWithoutExtension(file));
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(current) && AvailableModels.Contains(current))
|
||||
_selectedModel = current;
|
||||
else
|
||||
_selectedModel = "";
|
||||
}
|
||||
|
||||
public void RebuildInputs()
|
||||
{
|
||||
// Keep only Triangle (flow) connectors
|
||||
var toRemove = Input.Where(i => i.Shape != ConnectorShape.Triangle).ToList();
|
||||
foreach (var c in toRemove) Input.Remove(c);
|
||||
|
||||
// Update output connector
|
||||
var modelOutput = Output.FirstOrDefault(o => o.Shape == ConnectorShape.Square);
|
||||
if (modelOutput != null)
|
||||
{
|
||||
modelOutput.Title = string.IsNullOrEmpty(_selectedModel) ? "Object" : _selectedModel;
|
||||
modelOutput.DataType = string.IsNullOrEmpty(_selectedModel) ? "object" : _selectedModel;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_selectedModel))
|
||||
{
|
||||
Title = "New Object";
|
||||
return;
|
||||
}
|
||||
|
||||
Title = $"New {_selectedModel}";
|
||||
|
||||
// Recursively flatten model properties with dot notation
|
||||
var customModelDir = Path.Combine(ProjectManager.ProjectDirectory, "CustomModels");
|
||||
var visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var flatProps = new List<System.ValueTuple<string, string, bool, bool>>();
|
||||
FlattenModel(_selectedModel, "", customModelDir, visited, flatProps);
|
||||
|
||||
foreach (var entry in flatProps)
|
||||
{
|
||||
var path = entry.Item1;
|
||||
var type = entry.Item2;
|
||||
var isModel = entry.Item3;
|
||||
var isList = entry.Item4;
|
||||
ConnectorShape shape;
|
||||
Color color;
|
||||
|
||||
if (isList)
|
||||
{
|
||||
shape = ConnectorShape.Grid;
|
||||
color = NodeColors.List;
|
||||
}
|
||||
else if (isModel)
|
||||
{
|
||||
// For nested model types we still use Circle with a label since
|
||||
// the user will type/connect primitives via dot notation
|
||||
shape = ConnectorShape.Square;
|
||||
color = NodeColors.Model;
|
||||
}
|
||||
else
|
||||
{
|
||||
shape = ConnectorShape.Circle;
|
||||
color = ConnectorViewModel.GetColorForType(type);
|
||||
}
|
||||
|
||||
Input.Add(new ConnectorViewModel
|
||||
{
|
||||
Title = $"{path} ({type})",
|
||||
Shape = shape,
|
||||
ConnectorColor = color,
|
||||
DataType = isModel || isList ? type : type.ToLower()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively flattens a model's properties. Nested model properties are expanded
|
||||
/// with dot notation (e.g., "Address.Street (string)"). Primitives and lists are leaves.
|
||||
/// </summary>
|
||||
private static void FlattenModel(string modelName, string prefix, string modelDir,
|
||||
HashSet<string> visited, List<System.ValueTuple<string, string, bool, bool>> result)
|
||||
{
|
||||
if (visited.Contains(modelName)) return; // prevent circular references
|
||||
visited.Add(modelName);
|
||||
|
||||
var filePath = Path.Combine(modelDir, $"{modelName}.cs");
|
||||
if (!File.Exists(filePath)) return;
|
||||
|
||||
var content = File.ReadAllText(filePath);
|
||||
var properties = OperationFactory.GetPropertiesFromClassPublic(content);
|
||||
if (properties == null) return;
|
||||
|
||||
var primTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"string", "int", "double", "bool", "float", "decimal", "long",
|
||||
"object", "datetime", "byte", "short", "char", "guid"
|
||||
};
|
||||
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
var propType = prop.Type;
|
||||
var fullPath = string.IsNullOrEmpty(prefix) ? prop.Name : $"{prefix}.{prop.Name}";
|
||||
|
||||
bool isList = propType.StartsWith("List<") && propType.EndsWith(">");
|
||||
var innerType = isList ? propType.Substring(5, propType.Length - 6) : propType;
|
||||
|
||||
bool isNestedModel = !primTypes.Contains(innerType)
|
||||
&& File.Exists(Path.Combine(modelDir, $"{innerType}.cs"));
|
||||
|
||||
if (isList)
|
||||
{
|
||||
// List properties are always leaf inputs (Grid connector)
|
||||
result.Add(new System.ValueTuple<string, string, bool, bool>(fullPath, propType, false, true));
|
||||
}
|
||||
else if (isNestedModel)
|
||||
{
|
||||
// Recurse into nested model with dot notation prefix
|
||||
FlattenModel(innerType, fullPath, modelDir, visited, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Primitive leaf
|
||||
result.Add(new System.ValueTuple<string, string, bool, bool>(fullPath, propType, false, false));
|
||||
}
|
||||
}
|
||||
|
||||
visited.Remove(modelName); // allow same model in different branches
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,7 @@ namespace Nodify.Calculator.NodeHandlers
|
||||
TakeOperationViewModel => _handlers.OfType<TakeHandler>().FirstOrDefault(),
|
||||
ForEachOperationViewModel => _handlers.OfType<ForEachHandler>().FirstOrDefault(),
|
||||
AssertOperationViewModel => _handlers.OfType<AssertHandler>().FirstOrDefault(),
|
||||
NewObjectOperationViewModel => _handlers.OfType<NewObjectHandler>().FirstOrDefault(),
|
||||
APIOperationViewModel => _handlers.OfType<ApiHandler>().FirstOrDefault(),
|
||||
ExpandoOperationViewModel => _handlers.OfType<ExpandoHandler>().FirstOrDefault(),
|
||||
CalculatorOperationViewModel => _handlers.OfType<NormalHandler>().FirstOrDefault(),
|
||||
|
||||
@@ -82,33 +82,33 @@ namespace Nodify.Calculator.NodeHandlers
|
||||
|
||||
public OperationViewModel Create(OperationInfoViewModel info)
|
||||
{
|
||||
var op = new SystemOperationViewModel
|
||||
{
|
||||
Title = info.Title ?? "New Object",
|
||||
SystemOperationType = SystemOperations.NEW_OBJECT
|
||||
};
|
||||
var op = new NewObjectOperationViewModel();
|
||||
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 });
|
||||
var op = new NewObjectOperationViewModel();
|
||||
op.Input.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle });
|
||||
op.Output.Add(new ConnectorViewModel { Title = "", Shape = ConnectorShape.Triangle, IsInput = false });
|
||||
op.Output.Add(new ConnectorViewModel { Title = "Object", IsInput = false, Shape = ConnectorShape.Square, ConnectorColor = Color.MediumPurple, DataType = "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)
|
||||
// Restore selected model (triggers RebuildInputs via property setter)
|
||||
if (!string.IsNullOrEmpty(data.ClassName))
|
||||
{
|
||||
if (ic.Shape == "Triangle" || ic.Shape == "Square") continue;
|
||||
op.Input.Add(NodeHandlerRegistry.DeserializeConnector(ic, true));
|
||||
op.RefreshAvailableModels();
|
||||
op.SelectedModel = data.ClassName;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Legacy: restore from saved input connectors
|
||||
var dynamicInputs = data.InputConnectors.Where(ic => ic.Shape != "Triangle").ToList();
|
||||
foreach (var ic in dynamicInputs)
|
||||
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)
|
||||
@@ -117,6 +117,7 @@ namespace Nodify.Calculator.NodeHandlers
|
||||
modelOutput.DataType = savedModelOutput.DataType;
|
||||
modelOutput.ConnectorColor = Color.FromArgb(savedModelOutput.ColorArgb);
|
||||
}
|
||||
}
|
||||
|
||||
op.Title = data.Title;
|
||||
return op;
|
||||
@@ -126,6 +127,10 @@ namespace Nodify.Calculator.NodeHandlers
|
||||
{
|
||||
data.NodeType = "System";
|
||||
data.SystemOp = nameof(SystemOperations.NEW_OBJECT);
|
||||
if (vm is NewObjectOperationViewModel newObjVm)
|
||||
{
|
||||
data.ClassName = newObjVm.SelectedModel ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user