Added New object creation node for creating a new node from object from class
This commit is contained in:
@@ -31,6 +31,9 @@ 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);
|
||||
|
||||
@@ -57,6 +60,9 @@ 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);
|
||||
|
||||
@@ -336,6 +342,118 @@ 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 propColor = ConnectorViewModel.GetColorForType(prop.Type);
|
||||
newObjOp.Input.Add(new ConnectorViewModel
|
||||
{
|
||||
Title = $"{prop.Name} ({prop.Type})",
|
||||
Shape = ConnectorShape.Circle,
|
||||
ConnectorColor = propColor,
|
||||
DataType = prop.Type.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
|
||||
|
||||
@@ -456,6 +456,52 @@ namespace Nodify.Calculator
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle New Object nodes — construct JSON from property inputs
|
||||
if (op is SystemOperationViewModel newObjSysOp && newObjSysOp.SystemOperationType == SystemOperations.NEW_OBJECT)
|
||||
{
|
||||
OnLogMe?.Invoke($"Constructing new object: {newObjSysOp.Title}");
|
||||
var jObj = new JObject();
|
||||
// Iterate property inputs (skip triangle flow + square model connectors)
|
||||
foreach (var inp in op.Input)
|
||||
{
|
||||
if (inp.Shape == ConnectorShape.Triangle || inp.Shape == ConnectorShape.Square) continue;
|
||||
|
||||
// Extract property name from title like "Name (string)"
|
||||
var propName = inp.Title ?? "";
|
||||
var parenIdx = propName.IndexOf(" (");
|
||||
if (parenIdx > 0) propName = propName.Substring(0, parenIdx);
|
||||
|
||||
// Find connection feeding this input
|
||||
var inputCon = connections.FirstOrDefault(cn => cn.Input == inp);
|
||||
string val = null;
|
||||
if (inputCon != null)
|
||||
{
|
||||
var srcNodeId = inputCon.Output.Operation.NodeId;
|
||||
if (outputs.TryGetValue(srcNodeId, out var srcVal))
|
||||
val = srcVal;
|
||||
else if (inputCon.Output.Value != null)
|
||||
val = inputCon.Output.Value.ToString();
|
||||
}
|
||||
|
||||
if (val != null)
|
||||
{
|
||||
// Try to parse as JSON token, fallback to string
|
||||
try { jObj[propName] = JToken.Parse(val); }
|
||||
catch { jObj[propName] = val; }
|
||||
}
|
||||
else
|
||||
{
|
||||
jObj[propName] = null;
|
||||
}
|
||||
}
|
||||
|
||||
var json = jObj.ToString(Formatting.None);
|
||||
outputs[op.NodeId] = json;
|
||||
variables[op.NodeId] = json;
|
||||
OnLogMe?.Invoke($"Object constructed: {json}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Function nodes — execute the inner flow
|
||||
if (op is FunctionOperationViewModel funcOp)
|
||||
{
|
||||
|
||||
@@ -247,6 +247,10 @@ namespace Nodify.Calculator
|
||||
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;
|
||||
@@ -340,6 +344,30 @@ namespace Nodify.Calculator
|
||||
// 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
|
||||
|
||||
@@ -108,9 +108,19 @@ namespace Nodify.Calculator
|
||||
};
|
||||
debugNode.Input.Add("Value");
|
||||
|
||||
var newObjectNode = new OperationInfoViewModel()
|
||||
{
|
||||
Title = "New Object",
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.NEW_OBJECT,
|
||||
IsFlowNode = true
|
||||
};
|
||||
newObjectNode.Input.Add("");
|
||||
|
||||
systemNodes.Add(authNode);
|
||||
systemNodes.Add(copynode);
|
||||
systemNodes.Add(debugNode);
|
||||
systemNodes.Add(newObjectNode);
|
||||
systemNodes.Add(begin);
|
||||
systemNodes.Add(ending);
|
||||
systemNodes.Add(debugAndCreateModels);
|
||||
@@ -331,6 +341,35 @@ namespace Nodify.Calculator
|
||||
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.DEBUG)
|
||||
{
|
||||
var debugOp = new SystemOperationViewModel
|
||||
|
||||
@@ -19,7 +19,8 @@ namespace Nodify.Calculator
|
||||
SPLIT,
|
||||
AUTH,
|
||||
FUNCTION,
|
||||
DEBUG
|
||||
DEBUG,
|
||||
NEW_OBJECT
|
||||
}
|
||||
|
||||
public class SystemOperationViewModel : OperationViewModel
|
||||
|
||||
Reference in New Issue
Block a user