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
|
// Dynamic Split node: populate outputs when a model connects to the Square input
|
||||||
if (!IsLoading) HandleSplitNodeConnected(c);
|
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
|
// Dynamic Copy node: adapt connectors when something connects
|
||||||
if (!IsLoading) HandleCopyNodeConnected(c);
|
if (!IsLoading) HandleCopyNodeConnected(c);
|
||||||
|
|
||||||
@@ -57,6 +60,9 @@ namespace Nodify.Calculator
|
|||||||
// Dynamic Split node: clear outputs when model disconnects
|
// Dynamic Split node: clear outputs when model disconnects
|
||||||
HandleSplitNodeDisconnected(c);
|
HandleSplitNodeDisconnected(c);
|
||||||
|
|
||||||
|
// Dynamic New Object node: clear inputs when model disconnects
|
||||||
|
HandleNewObjectNodeDisconnected(c);
|
||||||
|
|
||||||
// Dynamic Copy node: reset on disconnect
|
// Dynamic Copy node: reset on disconnect
|
||||||
HandleCopyNodeDisconnected(c);
|
HandleCopyNodeDisconnected(c);
|
||||||
|
|
||||||
@@ -336,6 +342,118 @@ namespace Nodify.Calculator
|
|||||||
sysVm.Title = "Split";
|
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)
|
private void HandleCopyNodeConnected(ConnectionViewModel c)
|
||||||
{
|
{
|
||||||
// Find the COPY node's input connector
|
// Find the COPY node's input connector
|
||||||
|
|||||||
@@ -456,6 +456,52 @@ namespace Nodify.Calculator
|
|||||||
return;
|
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
|
// Handle Function nodes — execute the inner flow
|
||||||
if (op is FunctionOperationViewModel funcOp)
|
if (op is FunctionOperationViewModel funcOp)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -247,6 +247,10 @@ namespace Nodify.Calculator
|
|||||||
case SystemOperations.SPLIT:
|
case SystemOperations.SPLIT:
|
||||||
info.Input.Add("");
|
info.Input.Add("");
|
||||||
break;
|
break;
|
||||||
|
case SystemOperations.NEW_OBJECT:
|
||||||
|
info.Input.Add("");
|
||||||
|
info.IsFlowNode = true;
|
||||||
|
break;
|
||||||
case SystemOperations.IF:
|
case SystemOperations.IF:
|
||||||
info.Output.Add("");
|
info.Output.Add("");
|
||||||
info.IsFlowNode = true;
|
info.IsFlowNode = true;
|
||||||
@@ -340,6 +344,30 @@ namespace Nodify.Calculator
|
|||||||
// Update title from saved
|
// Update title from saved
|
||||||
sysVm.Title = nd.Title;
|
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)
|
else if (sysVm.SystemOperationType == SystemOperations.COPY)
|
||||||
{
|
{
|
||||||
// Restore adapted connectors
|
// Restore adapted connectors
|
||||||
|
|||||||
@@ -108,9 +108,19 @@ namespace Nodify.Calculator
|
|||||||
};
|
};
|
||||||
debugNode.Input.Add("Value");
|
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(authNode);
|
||||||
systemNodes.Add(copynode);
|
systemNodes.Add(copynode);
|
||||||
systemNodes.Add(debugNode);
|
systemNodes.Add(debugNode);
|
||||||
|
systemNodes.Add(newObjectNode);
|
||||||
systemNodes.Add(begin);
|
systemNodes.Add(begin);
|
||||||
systemNodes.Add(ending);
|
systemNodes.Add(ending);
|
||||||
systemNodes.Add(debugAndCreateModels);
|
systemNodes.Add(debugAndCreateModels);
|
||||||
@@ -331,6 +341,35 @@ namespace Nodify.Calculator
|
|||||||
return funcOp;
|
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)
|
if (info.sysOp == SystemOperations.DEBUG)
|
||||||
{
|
{
|
||||||
var debugOp = new SystemOperationViewModel
|
var debugOp = new SystemOperationViewModel
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ namespace Nodify.Calculator
|
|||||||
SPLIT,
|
SPLIT,
|
||||||
AUTH,
|
AUTH,
|
||||||
FUNCTION,
|
FUNCTION,
|
||||||
DEBUG
|
DEBUG,
|
||||||
|
NEW_OBJECT
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SystemOperationViewModel : OperationViewModel
|
public class SystemOperationViewModel : OperationViewModel
|
||||||
|
|||||||
Reference in New Issue
Block a user