LocalFlowEnvironment文件丢失,需要重写

This commit is contained in:
fengjiayi
2025-06-22 21:53:37 +08:00
parent 999060b67a
commit 97df2a04b2
58 changed files with 4285 additions and 354 deletions

View File

@@ -96,11 +96,19 @@ namespace Serein.NodeFlow.Model
{
PreviousNodes = new Dictionary<ConnectionInvokeType, List<IFlowNode>>();
SuccessorNodes = new Dictionary<ConnectionInvokeType, List<IFlowNode>>();
NeedResultNodes = new Dictionary<ConnectionArgSourceType, List<IFlowNode>>();
foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
{
PreviousNodes[ctType] = new List<IFlowNode>();
SuccessorNodes[ctType] = new List<IFlowNode>();
}
foreach (ConnectionArgSourceType ctType in NodeStaticConfig.ConnectionArgSourceTypes)
{
NeedResultNodes[ctType] = new List<IFlowNode>();
}
ChildrenNode = new List<IFlowNode>();
DebugSetting = new NodeDebugSetting(this);
this.Env = environment;
@@ -116,6 +124,11 @@ namespace Serein.NodeFlow.Model
/// 不同分支的子节点(流程调用)
/// </summary>
public Dictionary<ConnectionInvokeType, List<IFlowNode>> SuccessorNodes { get; set; }
/// <summary>
/// 需要该节点返回值作为入参参数的节点集合
/// </summary>
public Dictionary<ConnectionArgSourceType, List<IFlowNode>> NeedResultNodes { get;}
/// <summary>
/// 该节点的容器节点

View File

@@ -57,7 +57,7 @@ namespace Serein.NodeFlow.Model
return;
}
/// <summary>
/* /// <summary>
/// 移除该节点
/// </summary>
public virtual void Remove()
@@ -101,7 +101,7 @@ namespace Serein.NodeFlow.Model
this.DisplayName = null;
this.Env = null;
}
}*/
/// <summary>
/// 执行节点对应的方法

View File

@@ -2,7 +2,7 @@
using Serein.Library;
using System.Security.AccessControl;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Node
{
/// <summary>
/// 单动作节点(用于动作控件)

View File

@@ -3,7 +3,7 @@ using Serein.Library;
using Serein.Library.Utils;
using System;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Node
{
/// <summary>
/// 触发器节点
@@ -27,9 +27,9 @@ namespace Serein.NodeFlow.Model
#region
if (DebugSetting.IsInterrupt) // 执行触发前
{
string guid = this.Guid.ToString();
await this.DebugSetting.GetInterruptTask.Invoke();
await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已取消,开始执行后继分支");
string guid = Guid.ToString();
await DebugSetting.GetInterruptTask.Invoke();
await Console.Out.WriteLineAsync($"[{MethodDetails.MethodName}]中断已取消,开始执行后继分支");
}
#endregion
@@ -61,7 +61,7 @@ namespace Serein.NodeFlow.Model
if (dynamicFlipflopContext.Type == TriggerDescription.Overtime)
{
throw new FlipflopException(base.MethodDetails.MethodName + "触发器超时触发。Guid" + base.Guid);
throw new FlipflopException(MethodDetails.MethodName + "触发器超时触发。Guid" + Guid);
}
object result = dynamicFlipflopContext.Value;
var flowReslt = new FlowResult(this, context, result);

View File

@@ -119,7 +119,7 @@ namespace Serein.NodeFlow.Model
partial void OnIsShareParamChanged(bool value)
{
if (targetNode is null)
if (targetNode is null || targetNode.MethodDetails is null)
{
return;
}
@@ -219,14 +219,13 @@ namespace Serein.NodeFlow.Model
}
public override void Remove()
/*public override void Remove()
{
var tmp = this;
targetNode = null;
CacheMethodDetails = null;
}
*/
/// <summary>

View File

@@ -96,7 +96,7 @@ namespace Serein.NodeFlow.Model
{
foreach (var nodeModel in ChildrenNode)
{
await nodeModel.Env.TakeOutNodeToContainerAsync(nodeModel.CanvasDetails.Guid, nodeModel.Guid);
nodeModel.Env.TakeOutNodeToContainer(nodeModel.CanvasDetails.Guid, nodeModel.Guid);
}
DataNode = null;
}
@@ -162,7 +162,7 @@ namespace Serein.NodeFlow.Model
KeyName = nodeInfo.CustomData?.KeyName;
}
/// <summary>
/* /// <summary>
/// 需要移除数据节点
/// </summary>
public override void Remove()
@@ -172,7 +172,7 @@ namespace Serein.NodeFlow.Model
}
// 移除数据节点
_ = this.Env.RemoveNodeAsync(DataNode.CanvasDetails.Guid, DataNode.Guid);
}
}*/
}
}

View File

@@ -226,7 +226,7 @@ namespace Serein.NodeFlow.Model
scriptContext.OnExit();
};
var envEvent = (IFlowEnvironmentEvent)context.Env;
var envEvent = context.Env.Event;
envEvent.FlowRunComplete += onFlowStop; // 防止运行后台流程
if (token.IsCancellationRequested) return null;

View File

@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Model
namespace Serein.NodeFlow.Model.Node
{
public class SingleUINode : NodeModelBase
{
@@ -24,7 +24,7 @@ namespace Serein.NodeFlow.Model
var result = await base.ExecutingAsync(context, token);
if (result.Value is IEmbeddedContent adapter)
{
this.Adapter = adapter;
Adapter = adapter;
context.NextOrientation = ConnectionInvokeType.IsSucceed;
}
else

View File

@@ -0,0 +1,451 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.NodeFlow.Env;
using Serein.NodeFlow.Model.Node;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Serein.Library.Api.NodeConnectChangeEventArgs;
namespace Serein.NodeFlow.Model.Operation
{
/// <summary>
/// 节点连接状态发生改变
/// </summary>
internal class ChangeNodeConnectionOperation : OperationBase
{
public override string Theme => nameof(ChangeNodeConnectionOperation);
/// <summary>
/// 所在画布
/// </summary>
public required string CanvasGuid { get; set; }
/// <summary>
/// 连接关系中始节点的Guid
/// </summary>
public required string FromNodeGuid { get; set; }
/// <summary>
/// 连接关系中目标节点的Guid
/// </summary>
public required string ToNodeGuid { get; set; }
/// <summary>
/// 起始节点连接控制点类型
/// </summary>
public JunctionType FromNodeJunctionType { get; set; }
/// <summary>
/// 目标节点连接控制点类型
/// </summary>
public JunctionType ToNodeJunctionType { get; set; }
/// 连接类型
/// </summary>
public ConnectionInvokeType ConnectionInvokeType { get; set; }
/// <summary>
/// 表示此次需要在两个节点之间创建连接关系,或是移除连接关系
/// </summary>
public ConnectChangeType ChangeType { get; set; }
/// <summary>
/// 指示需要创建什么类型的连接线
/// </summary>
public JunctionOfConnectionType JunctionOfConnectionType { get; set; } = JunctionOfConnectionType.None;
/// <summary>
/// 节点对应的方法入参所需参数来源
/// </summary>
public ConnectionArgSourceType ConnectionArgSourceType { get; set; }
/// <summary>
/// 第几个参数
/// </summary>
public int ArgIndex { get; set; } = -1;
public override bool IsCanUndo => false;
#region
private FlowCanvasDetails FlowCanvas;
private IFlowNode FromNode;
private IFlowNode ToNode;
#endregion
public override bool ValidationParameter()
{
if (JunctionOfConnectionType == JunctionOfConnectionType.None)
return false;
if (JunctionOfConnectionType == JunctionOfConnectionType.Arg && ArgIndex == -1)
return false;
if (!flowModelService.ContainsCanvasModel(CanvasGuid) // 不存在画布
|| !flowModelService.ContainsNodeModel(FromNodeGuid) // 不存在节点
|| !flowModelService.ContainsNodeModel(ToNodeGuid)) // 不存在节点
{
return false;
}
return true;
}
public override bool Execute()
{
if (!ValidationParameter()) return false;
if (!flowModelService.TryGetCanvasModel(CanvasGuid, out FlowCanvas) // 不存在画布
|| !flowModelService.TryGetNodeModel(FromNodeGuid, out FromNode) // 不存在节点
|| !flowModelService.TryGetNodeModel(ToNodeGuid, out ToNode)) // 不存在节点
{
return false;
}
if(ChangeType == ConnectChangeType.Create) // 创建连线时需要检查
{
(var jcType, var isCanConnection) = CheckConnect(FromNode, ToNode, FromNodeJunctionType, ToNodeJunctionType);
if (!isCanConnection)
{
SereinEnv.WriteLine(InfoType.WARN, "出现非预期的连接行为");
return false; // 出现不符预期的连接行为,忽略此次连接行为
}
// 如果起始控制点是“方法执行”,目标控制点是“方法调用”,需要反转 from to 节点
if (jcType == JunctionOfConnectionType.Invoke
&& FromNodeJunctionType == JunctionType.Execute
&& ToNodeJunctionType == JunctionType.NextStep)
{
// 如果 起始控制点 是“方法调用”,需要反转 from to 节点
(FromNode, ToNode) = (ToNode, FromNode);
}
// 如果起始控制点是“方法入参”,目标控制点是“返回值”,需要反转 from to 节点
if (jcType == JunctionOfConnectionType.Arg
&& FromNodeJunctionType == JunctionType.ArgData
&& ToNodeJunctionType == JunctionType.ReturnData)
{
(FromNode, ToNode) = (ToNode, FromNode);
}
}
//if (toNode is SingleFlipflopNode flipflopNode)
//{
// flowTaskManagement?.TerminateGlobalFlipflopRuning(flipflopNode); // 假设被连接的是全局触发器,尝试移除
//}
var state = (JunctionOfConnectionType, ChangeType) switch
{
(JunctionOfConnectionType.Invoke, NodeConnectChangeEventArgs.ConnectChangeType.Create) => CreateInvokeConnection(), // 创建节点之间的调用关系
(JunctionOfConnectionType.Invoke, NodeConnectChangeEventArgs.ConnectChangeType.Remove) => RemoveInvokeConnection(), // 移除节点之间的调用关系
(JunctionOfConnectionType.Arg, NodeConnectChangeEventArgs.ConnectChangeType.Create) => CreateArgConnection(), // 创建节点之间的参数传递关系
(JunctionOfConnectionType.Arg, NodeConnectChangeEventArgs.ConnectChangeType.Remove) => RemoveArgConnection(), // 移除节点之间的参数传递关系
_ => false
};
return state;
}
public override void ToInfo()
{
throw new NotImplementedException();
}
/// <summary>
/// 创建方法调用关系
/// </summary>
private bool CreateInvokeConnection()
{
IFlowNode fromNode = FromNode ;
IFlowNode toNode = ToNode;
ConnectionInvokeType invokeType = ConnectionInvokeType;
if (fromNode.ControlType == NodeControlType.FlowCall)
{
SereinEnv.WriteLine(InfoType.ERROR, $"流程接口节点不可调用下一个节点。" +
$"{Environment.NewLine}流程节点:{fromNode.Guid}");
return false;
}
var isOverwriting = false;
ConnectionInvokeType overwritingCt = ConnectionInvokeType.None;
var isPass = false;
#region
foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
{
var count1 = fromNode.SuccessorNodes[ctType].Count(it => it.Guid.Equals(toNode.Guid));
var count2 = toNode.PreviousNodes[ctType].Count(it => it.Guid.Equals(fromNode.Guid));
var hasError1 = count1 > 0;
var hasError2 = count2 > 0;
if (hasError1 && hasError2)
{
if (ctType == invokeType)
{
SereinEnv.WriteLine(InfoType.WARN, $"起始节点已与目标节点存在连接。" +
$"{Environment.NewLine}起始节点:{fromNode.Guid}" +
$"{Environment.NewLine}目标节点:{toNode.Guid}");
return false;
}
isOverwriting = true; // 需要移除连接再创建连接
overwritingCt = ctType;
}
else
{
// 检查是否可能存在异常
if (!hasError1 && hasError2)
{
SereinEnv.WriteLine(InfoType.ERROR, $"起始节点不是目标节点的父节点,目标节点却是起始节点的子节点。" +
$"{Environment.NewLine}起始节点:{fromNode.Guid}" +
$"{Environment.NewLine}目标节点:{toNode.Guid}");
isPass = false;
}
else if (hasError1 && !hasError2)
{
//
SereinEnv.WriteLine(InfoType.ERROR, $"起始节点不是目标节点的父节点,目标节点却是起始节点的子节点。" +
$"{Environment.NewLine}起始节点:{fromNode.Guid}" +
$"{Environment.NewLine}目标节点:{toNode.Guid}" +
$"");
isPass = false;
}
else
{
isPass = true;
}
}
}
#endregion
if (isPass)
{
if (isOverwriting) // 需要替换
{
fromNode.SuccessorNodes[overwritingCt].Remove(toNode); // 从起始节点原有类别的子分支中移除
toNode.PreviousNodes[overwritingCt].Remove(fromNode); // 从目标节点原有类别的父分支中移除
}
fromNode.SuccessorNodes[invokeType].Add(toNode); // 添加到起始节点新类别的子分支
toNode.PreviousNodes[invokeType].Add(fromNode); // 添加到目标节点新类别的父分支
flowEnvironmentEvent.OnNodeConnectChanged(
new NodeConnectChangeEventArgs(
FlowCanvas.Guid,
fromNode.Guid, // 从哪个节点开始
toNode.Guid, // 连接到那个节点
JunctionOfConnectionType.Invoke,
invokeType, // 连接线的样式类型
NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接
));
// Invoke
// GetResult
return true;
}
else
{
return false;
}
}
/// <summary>
/// 移除方法调用关系
/// </summary>
private bool RemoveInvokeConnection()
{
FromNode.SuccessorNodes[ConnectionInvokeType].Remove(ToNode);
ToNode.PreviousNodes[ConnectionInvokeType].Remove(FromNode);
flowEnvironmentEvent.OnNodeConnectChanged(
new NodeConnectChangeEventArgs(
FlowCanvas.Guid,
FromNode.Guid,
ToNode.Guid,
JunctionOfConnectionType.Invoke,
ConnectionInvokeType,
NodeConnectChangeEventArgs.ConnectChangeType.Remove));
/* if (string.IsNullOrEmpty(ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid))
{
return false;
}
toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid = null;
toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData; // 恢复默认值
if (OperatingSystem.IsWindows())
{
UIContextOperation?.Invoke(() => Event.OnNodeConnectChanged(
new NodeConnectChangeEventArgs(
canvasGuid,
fromNode.Guid,
toNode.Guid,
argIndex,
JunctionOfConnectionType.Arg,
ConnectionArgSourceType.GetPreviousNodeData,
NodeConnectChangeEventArgs.ConnectChangeType.Remove)));
}*/
return true;
}
/// <summary>
/// 创建参数连接关系
/// </summary>
/// <exception cref="Exception"></exception>
private bool CreateArgConnection()
{
IFlowNode fromNodeControl = ToNode;
IFlowNode toNodeControl = ToNode;
ConnectionArgSourceType type = ConnectionArgSourceType;
int index = ArgIndex;
var toNodeArgSourceGuid = ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid; // 目标节点对应参数可能已经有其它连接
var toNodeArgSourceType = ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType;
if (FromNode.Guid == toNodeArgSourceGuid
&& toNodeArgSourceType == ConnectionArgSourceType)
{
SereinEnv.WriteLine(InfoType.INFO, $"节点之间已建立过连接关系,此次操作将不会执行" +
$"起始节点:{FromNode.Guid}" +
$"目标节点:{ToNode.Guid}" +
$"参数索引:{ArgIndex}" +
$"参数类型:{ConnectionArgSourceType}");
/*flowEnvironmentEvent.OnNodeConnectChanged(
new NodeConnectChangeEventArgs(
FlowCanvas.Guid,
FromNode.Guid, // 从哪个节点开始
ToNode.Guid, // 连接到那个节点
ArgIndex, // 连接线的样式类型
JunctionOfConnectionType.Arg,
ConnectionArgSourceType,
NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接
)); // 通知UI */
return true;
}
if (!string.IsNullOrEmpty(toNodeArgSourceGuid)) // 更改关系获取
{
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid = null;
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData; // 恢复默认值
flowEnvironmentEvent.OnNodeConnectChanged(
new NodeConnectChangeEventArgs(
FlowCanvas.Guid,
FromNode.Guid,
ToNode.Guid,
ArgIndex,
JunctionOfConnectionType.Arg,
ConnectionArgSourceType.GetPreviousNodeData,
NodeConnectChangeEventArgs.ConnectChangeType.Remove));
}
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid = FromNode.Guid; // 设置
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType = ConnectionArgSourceType;
flowEnvironmentEvent.OnNodeConnectChanged(
new NodeConnectChangeEventArgs(
FlowCanvas.Guid,
FromNode.Guid, // 从哪个节点开始
ToNode.Guid, // 连接到那个节点
ArgIndex, // 连接线的样式类型
JunctionOfConnectionType.Arg,
ConnectionArgSourceType,
NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接
)); // 通知UI
return true;
}
/// <summary>
/// 移除参数连接关系
/// </summary>
/// <param name="fromNodeControl"></param>
/// <param name="toNodeControl"></param>
/// <param name="index"></param>
private bool RemoveArgConnection()
{
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid = null;
ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData; // 恢复默认值
if (OperatingSystem.IsWindows())
{
flowEnvironmentEvent.OnNodeConnectChanged(
new NodeConnectChangeEventArgs(
FlowCanvas.Guid,
FromNode.Guid,
ToNode.Guid,
ArgIndex,
JunctionOfConnectionType.Arg,
ConnectionArgSourceType.GetPreviousNodeData,
NodeConnectChangeEventArgs.ConnectChangeType.Remove));
}
return true;
}
/// <summary>
/// 检查连接是否合法
/// </summary>
/// <param name="fromNode">发起连接的起始节点</param>
/// <param name="toNode">要连接的目标节点</param>
/// <param name="fromNodeJunctionType">发起连接节点的控制点类型</param>
/// <param name="toNodeJunctionType">被连接节点的控制点类型</param>
/// <returns></returns>
public static (JunctionOfConnectionType, bool) CheckConnect(IFlowNode fromNode,
IFlowNode toNode,
JunctionType fromNodeJunctionType,
JunctionType toNodeJunctionType)
{
var type = JunctionOfConnectionType.None;
var state = false;
if (fromNodeJunctionType == JunctionType.Execute)
{
if (toNodeJunctionType == JunctionType.NextStep && !fromNode.Guid.Equals(toNode.Guid))
{
// “方法执行”控制点拖拽到“下一节点”控制点,且不是同一个节点, 添加方法执行关系
type = JunctionOfConnectionType.Invoke;
state = true;
}
//else if (toNodeJunctionType == JunctionType.ArgData && fromNode.Guid.Equals(toNode.Guid))
//{
// // “方法执行”控制点拖拽到“方法入参”控制点且是同一个节点则添加获取参数关系表示生成入参参数时自动从该节点的上一节点获取flowdata
// type = JunctionOfConnectionType.Arg;
// state = true;
//}
}
else if (fromNodeJunctionType == JunctionType.NextStep && !fromNode.Guid.Equals(toNode.Guid))
{
// “下一节点”控制点只能拖拽到“方法执行”控制点,且不能是同一个节点
if (toNodeJunctionType == JunctionType.Execute && !fromNode.Guid.Equals(toNode.Guid))
{
type = JunctionOfConnectionType.Invoke;
state = true;
}
}
else if (fromNodeJunctionType == JunctionType.ArgData)
{
//if (toNodeJunctionType == JunctionType.Execute && fromNode.Guid.Equals(toNode.Guid)) // 添加获取参数关系
//{
// // “方法入参”控制点拖拽到“方法执行”控制点且是同一个节点则添加获取参数关系生成入参参数时自动从该节点的上一节点获取flowdata
// type = JunctionOfConnectionType.Arg;
// state = true;
//}
if (toNodeJunctionType == JunctionType.ReturnData && !fromNode.Guid.Equals(toNode.Guid))
{
// “”控制点拖拽到“方法返回值”控制点且不是同一个节点添加获取参数关系生成参数时从目标节点获取flowdata
type = JunctionOfConnectionType.Arg;
state = true;
}
}
else if (fromNodeJunctionType == JunctionType.ReturnData)
{
if (toNodeJunctionType == JunctionType.ArgData && !fromNode.Guid.Equals(toNode.Guid))
{
// “方法返回值”控制点拖拽到“方法入参”控制点且不是同一个节点添加获取参数关系生成参数时从目标节点获取flowdata
type = JunctionOfConnectionType.Arg;
state = true;
}
}
// 剩下的情况都是不符预期的连接行为,忽略。
return (type, state);
}
}
}

View File

@@ -0,0 +1,90 @@
using Microsoft.CodeAnalysis;
using Serein.Library.Api;
using Serein.NodeFlow.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Model.Operation
{
internal class ChangeParameterOperation : OperationBase
{
public override string Theme => nameof(ChangeParameterOperation);
public string NodeGuid { get; set; }
public bool IsAdd{ get; set; }
public int ParamIndex { get; set; }
private IFlowNode nodeModel;
public override bool ValidationParameter()
{
if (!flowModelService.TryGetNodeModel(NodeGuid, out var nodeModel))
{
return false;
}
this.nodeModel = nodeModel;
var pds = nodeModel.MethodDetails.ParameterDetailss;
var parameterCount = pds.Length;
if (ParamIndex >= parameterCount)
{
return false; // 需要被添加的下标索引大于入参数组的长度
}
if (IsAdd)
{
if (pds[ParamIndex].IsParams == false)
{
return false; // 对应的入参并非可选参数中的一部分
}
}
else
{
}
return true;
}
public override bool Execute()
{
if (!ValidationParameter()) return false;
if (IsAdd)
{
if (nodeModel.MethodDetails.AddParamsArg(ParamIndex))
{
return true;
}
else
{
return false;
}
}
else
{
if (nodeModel.MethodDetails.RemoveParamsArg(ParamIndex))
{
return true;
}
else
{
return true;
}
}
}
public override void ToInfo()
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,98 @@
using Serein.Library.Api;
using Serein.NodeFlow.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Model.Operation
{
/// <summary>
/// 放置节点操作
/// </summary>
internal class ContainerPlaceNodeOperation : OperationBase
{
public override string Theme => nameof(ContainerPlaceNodeOperation);
/// <summary>
/// 所在画布
/// </summary>
public string CanvasGuid { get; set; }
/// <summary>
/// 子节点,该数据为此次事件的主节点
/// </summary>
public string NodeGuid { get; set; }
/// <summary>
/// 父节点
/// </summary>
public string ContainerNodeGuid { get; set; }
/// <summary>
/// 父节点
/// </summary>
private INodeContainer ContainerNode;
/// <summary>
/// 子节点,该数据为此次事件的主节点
/// </summary>
private IFlowNode Node;
public override bool ValidationParameter()
{
if (!flowModelService.ContainsCanvasModel(CanvasGuid))
{
return false;
}
// 获取目标节点与容器节点
if (!flowModelService.TryGetNodeModel(NodeGuid, out var nodeModel))
{
return false;
}
if (!flowModelService.TryGetNodeModel(ContainerNodeGuid, out var containerNode))
{
return false;
}
if (nodeModel.ContainerNode is INodeContainer tmpContainer)
{
//SereinEnv.WriteLine(InfoType.WARN, $"节点放置失败,节点[{nodeGuid}]已经放置于容器节点[{((IFlowNode)tmpContainer).Guid}]");
return false;
}
if(containerNode is not INodeContainer containerNode2)
{
return false;
}
Node = nodeModel;
ContainerNode = containerNode2;
return true;
}
public override bool Execute()
{
if (!ValidationParameter()) return false;
ContainerNode.PlaceNode(Node);
flowEnvironmentEvent.OnNodePlace(new NodePlaceEventArgs(CanvasGuid, NodeGuid, ContainerNodeGuid)); // 通知UI更改节点放置位置
return true;
}
public override bool Undo()
{
ContainerNode.TakeOutNode(Node);
flowEnvironmentEvent.OnNodeTakeOut(new NodeTakeOutEventArgs(CanvasGuid, NodeGuid)); // 重新放置在画布上
return true;
}
public override void ToInfo()
{
}
}
}

View File

@@ -0,0 +1,89 @@
using Serein.Library.Api;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Model.Operation
{
/// <summary>
/// 取出节点操作
/// </summary>
internal class ContainerTakeOutNodeOperation : OperationBase
{
public override string Theme => nameof(ContainerTakeOutNodeOperation);
/// <summary>
/// 所在画布
/// </summary>
public string CanvasGuid { get; set; }
/// <summary>
/// 子节点,该数据为此次事件的主节点
/// </summary>
public string NodeGuid { get; set; }
/// <summary>
/// 父节点
/// </summary>
private INodeContainer ContainerNode;
/// <summary>
/// 子节点,该数据为此次事件的主节点
/// </summary>
private IFlowNode Node;
public override bool ValidationParameter()
{
if (!flowModelService.ContainsCanvasModel(CanvasGuid))
{
flowEnvironment.WriteLine(Library.InfoType.INFO, $"节点取出失败,目标画布不存在[{NodeGuid}]");
return false;
}
// 获取目标节点与容器节点
if (!flowModelService.TryGetNodeModel(NodeGuid, out var nodeModel))
{
flowEnvironment.WriteLine(Library.InfoType.INFO, $"节点取出失败,目标节点不存在[{NodeGuid}]");
return false;
}
if (nodeModel.ContainerNode is not INodeContainer containerNode)
{
flowEnvironment.WriteLine(Library.InfoType.INFO, $"节点取出失败,节点并非容器节点[{nodeModel.Guid}]");
return false;
}
Node = nodeModel;
ContainerNode = containerNode;
return true;
}
public override bool Execute()
{
if (!ValidationParameter()) return false;
ContainerNode.TakeOutNode(Node);
flowEnvironmentEvent.OnNodeTakeOut(new NodeTakeOutEventArgs(CanvasGuid, NodeGuid)); // 重新放置在画布上
return true;
}
public override bool Undo()
{
ContainerNode.PlaceNode(Node);
if (ContainerNode is IFlowNode containerFlowNode)
{
flowEnvironmentEvent.OnNodePlace(new NodePlaceEventArgs(CanvasGuid, NodeGuid, containerFlowNode.Guid)); // 通知UI更改节点放置位置
}
return true;
}
public override void ToInfo()
{
}
}
}

View File

@@ -0,0 +1,51 @@
using Serein.Library;
using Serein.Library.Api;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Model.Operation
{
internal class CreateCanvasOperation : OperationBase
{
public override string Theme => nameof(CreateCanvasOperation);
public override bool IsCanUndo => false;
public required FlowCanvasDetailsInfo CanvasInfo { get; set; }
private FlowCanvasDetails? flowCanvasDetails;
public override bool ValidationParameter()
{
if (CanvasInfo is null)
return false; // 没有必须的参数
if (string.IsNullOrEmpty(CanvasInfo.Guid))
return false; // 不能没有Guid
if(flowModelService.ContainsCanvasModel(CanvasInfo.Guid))
return false; // 画布已存在
return true;
}
public override bool Execute()
{
if(!ValidationParameter()) return false;
var cavasnModel = new FlowCanvasDetails(flowEnvironment);
cavasnModel.LoadInfo(CanvasInfo);
flowModelService.AddCanvasModel(cavasnModel);
this.flowCanvasDetails = cavasnModel; ;
flowEnvironmentEvent.OnCanvasCreated(new CanvasCreateEventArgs(cavasnModel));
return true;
}
public override void ToInfo()
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,145 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.NodeFlow.Model.Node;
using Serein.NodeFlow.Services;
using Serein.NodeFlow.Tool;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Model.Operation
{
internal class CreateNodeOperation : OperationBase
{
public override string Theme => nameof(CreateNodeOperation);
public required string CanvasGuid { get; set; }
public required NodeControlType NodeControlType { get; set; }
public required PositionOfUI Position { get; set; }
public required MethodDetailsInfo? MethodDetailsInfo { get; set; }
/// <summary>
/// 是否为基础节点
/// </summary>
private bool IsBaseNode => NodeControlType.IsBaseNode();
/// <summary>
/// 执行成功后所创建的节点
/// </summary>
private IFlowNode? flowNode;
/// <summary>
/// 节点所在画布
/// </summary>
private FlowCanvasDetails flowCanvasDetails;
public override bool ValidationParameter()
{
// 检查是否存在画布
var canvasModel = flowModelService.GetCanvasModel(CanvasGuid);
if(canvasModel is null)
return false;
// 检查类型(防非预期的调用)
if (NodeControlType == NodeControlType.None)
return false;
// 检查放置位置是否超限(防非预期的调用)
if (Position.X < 0 || Position.Y < 0
|| Position.X > canvasModel.Width
|| Position.Y > canvasModel.Height)
return false;
// 所创建的节点并非基础节点,却没有传入方法信息,将会导致创建失败
if (!IsBaseNode && MethodDetailsInfo is null)
return false;
// 缓存画布model提高性能
this.flowCanvasDetails = canvasModel;
return true;
}
public override bool Execute()
{
if (!ValidationParameter()) return false; // 执行时验证
IFlowNode? nodeModel;
if (IsBaseNode)
{
nodeModel = FlowNodeExtension.CreateNode(flowEnvironment, NodeControlType); // 加载基础节点
}
else
{
if(MethodDetailsInfo is null)
{
return false;
//throw new InvalidOperationException($"无法创建节点因为MethodDetailsInfo属性为null");
}
if (!flowLibraryManagement.TryGetMethodDetails(MethodDetailsInfo.AssemblyName, // 创建节点
MethodDetailsInfo.MethodName,
out var methodDetails))
{
return false;
//throw new InvalidOperationException($"无法创建节点,因为没有找到{MethodDetailsInfo.AssemblyName}.{MethodDetailsInfo.MethodName}方法,请检查是否已加载对应程序集");
}
nodeModel = FlowNodeExtension.CreateNode(flowEnvironment, NodeControlType, methodDetails); // 一般的加载节点方法
}
nodeModel.Guid ??= Guid.NewGuid().ToString();
nodeModel.Position = Position; // 设置位置
// 节点与画布互相绑定
nodeModel.CanvasDetails = flowCanvasDetails;
flowCanvasDetails.Nodes.Add(nodeModel);
flowModelService.AddNodeModel(nodeModel);
this.flowNode = nodeModel;
flowEnvironmentEvent.OnNodeCreated(new NodeCreateEventArgs(flowCanvasDetails.Guid, nodeModel, Position));
return true;
}
public override bool Undo()
{
if (!ValidationParameter()) return false; // 撤销时验证
if(flowNode is null) return false; // 没有创建过节点
var canvasGuid = flowCanvasDetails.Guid;
var nodeGuid = flowNode.Guid;
flowEnvironment.RemoveNode(canvasGuid, nodeGuid);
return true;
}
public override void ToInfo()
{
throw new NotImplementedException();
}
/*private bool TryAddNode(IFlowNode nodeModel)
{
nodeModel.Guid ??= Guid.NewGuid().ToString();
NodeModels.TryAdd(nodeModel.Guid, nodeModel);
// 如果是触发器,则需要添加到专属集合中
if (nodeModel is SingleFlipflopNode flipflopNode)
{
var guid = flipflopNode.Guid;
if (!FlipflopNodes.Exists(it => it.Guid.Equals(guid)))
{
FlipflopNodes.Add(flipflopNode);
}
}
return true;
}*/
}
}

View File

@@ -1,92 +1,118 @@
using System;
using Serein.Library;
using Serein.Library.Api;
using Serein.NodeFlow.Services;
using Serein.NodeFlow.Tool;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Model.Operation
{
internal interface IOperation
{
/// <summary>
/// 用于判断是否可以撤销
/// </summary>
bool IsCanUndo { get; }
/// <summary>
/// 执行操作前验证数据
/// </summary>
/// <returns></returns>
bool ValidationParameter();
/// <summary>
/// 执行操作
/// </summary>
bool Execute();
/// <summary>
/// 撤销操作
/// </summary>
bool Undo();
}
class Test {
internal abstract class OperationBase : IOperation
{
/// <summary>
/// 运行环境
/// </summary>
[AutoInjection]
protected IFlowEnvironment flowEnvironment;
/// <summary>
/// 撤销栈
/// 节点管理服务
/// </summary>
private Stack<IOperation> undoStack = [];
[AutoInjection]
protected FlowModelService flowModelService;
/// <summary>
/// 重做栈
/// 流程依赖服务
/// </summary>
private Stack<IOperation> redoStack = [];
[AutoInjection]
protected FlowLibraryManagement flowLibraryManagement;
/// <summary>
/// 流程事件服务
/// </summary>
[AutoInjection]
protected IFlowEnvironmentEvent flowEnvironmentEvent;
public abstract string Theme { get;}
/// <summary>
/// 是否支持特效
/// </summary>
public virtual bool IsCanUndo => true;
/*
// 执行新命令时,将命令推入撤销栈,并清空重做栈
undoStack.Push(operation);
redoStack.Clear();
*/
/// <summary>
/// 验证参数
/// </summary>
/// <returns></returns>
public abstract bool ValidationParameter();
/// <summary>
/// 执行
/// </summary>
public abstract bool Execute();
/// <summary>
/// 撤销
/// </summary>
public void Undo()
public virtual bool Undo()
{
if (undoStack.Count > 0)
if (!IsCanUndo)
{
var command = undoStack.Pop();
command.Undo(); // 执行撤销
redoStack.Push(command); // 将撤销的命令推入重做栈
Debug.WriteLine($"该操作暂未提供撤销功能[{Theme}]");
return false;
}
return true;
}
/// <summary>
/// 重做
/// 导出操作信息
/// </summary>
public void Redo()
{
if (redoStack.Count > 0)
{
var command = redoStack.Pop();
command.Execute();
undoStack.Push(command); // 将重做的命令推入撤销栈
}
}
public abstract void ToInfo();
}
internal class OperationInfo
{
}
internal abstract class OperationBase : IOperation
{
/// <summary>
/// 操作的主题
/// </summary>
public required string Theme { get; set; }
public abstract void Execute();
public abstract void Undo();
public abstract void ToInfo();
class Test {
protected OperationBase()
{
}
protected OperationBase(OperationInfo info)
{
}
}
internal interface IOperation
{
void Execute(); // 执行操作
void Undo(); // 撤销操作
}
}

View File

@@ -0,0 +1,59 @@
using Serein.Library;
using Serein.Library.Api;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Model.Operation
{
internal class RemoveCanvasOperation : OperationBase
{
public override string Theme => nameof(RemoveCanvasOperation);
public override bool IsCanUndo => false;
public required string CanvasGuid { get; set; }
private FlowCanvasDetailsInfo? flowCanvasDetailsInfo;
private FlowCanvasDetails? flowCanvasDetails;
public override bool ValidationParameter()
{
var canvasModel = flowModelService.GetCanvasModel(CanvasGuid);
if (canvasModel is null) return false; // 画布不存在
var nodeCount = canvasModel.Nodes.Count;
if (nodeCount > 0)
{
SereinEnv.WriteLine(InfoType.WARN, "无法删除具有节点的画布");
return false;
}
this.flowCanvasDetails = canvasModel;
return true;
}
public override bool Execute()
{
if (!ValidationParameter()) return false;
if (flowCanvasDetails is null)
{
// 验证过画布存在,但这时画布不存在了
// 考虑到多线程操作影响,一般不会进入这个逻辑分支
var canvasModel = flowModelService.GetCanvasModel(CanvasGuid);
if (canvasModel is null) return false; // 画布不存在
flowCanvasDetails = canvasModel;
}
flowModelService.RemoveCanvasModel(flowCanvasDetails);
flowCanvasDetailsInfo = flowCanvasDetails.ToInfo();
flowEnvironmentEvent.OnCanvasRemoved(new CanvasRemoveEventArgs(flowCanvasDetails.Guid));
return true;
}
public override void ToInfo()
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,216 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Serein.Library;
using Serein.Library.Api;
using Serein.Script.Node;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Model.Operation
{
internal class RemoveNodeOperation : OperationBase
{
public override string Theme => throw new NotImplementedException();
public required string CanvasGuid { get; internal set; }
public required string NodeGuid { get; internal set; }
/// <summary>
/// 节点所在画布
/// </summary>
private FlowCanvasDetails flowCanvasDetails;
/// <summary>
/// 被删除的节点
/// </summary>
private IFlowNode flowNode;
/// <summary>
/// 移除节点时删除连线所触发的事件参数的缓存
/// </summary>
private List<NodeConnectChangeEventArgs> EventArgs { get; } = new List<NodeConnectChangeEventArgs>();
public override bool ValidationParameter()
{
var canvasModel = flowModelService.GetCanvasModel(CanvasGuid);
var nodeModel = flowModelService.GetNodeModel(NodeGuid);
if(canvasModel is null)
{
return false;
}
if(nodeModel is null)
{
return false;
}
flowCanvasDetails = canvasModel;
flowNode = nodeModel;
return true;
}
public override bool Execute()
{
if (!ValidationParameter()) return false;
// 需要移除对应的方法调用、以及参数获取调用
// 还需要记录移除的事件参数,用以撤销恢复
#region
foreach (var item in flowNode.PreviousNodes)
{
var connectionType = item.Key; // 连接类型
var previousNodes = item.Value; // 对应类型的父节点集合
foreach (IFlowNode previousNode in previousNodes)
{
previousNode.SuccessorNodes[connectionType].Remove(flowNode);
var e = new NodeConnectChangeEventArgs(
CanvasGuid, // 画布
previousNode.Guid, // 父节点Guid
flowNode.Guid, // 被移除的节点Guid
JunctionOfConnectionType.Invoke, // 方法调用关系
connectionType, // 对应的连接关系
NodeConnectChangeEventArgs.ConnectChangeType.Remove); // 移除连线
EventArgs.Add(e); // 缓存事件参数
flowEnvironmentEvent.OnNodeConnectChanged(e);
}
}
if (flowNode.ControlType == NodeControlType.FlowCall)
{
// 根据流程接口节点目前的设计,暂未支持能连接下一个节点
}
else
{
// 遍历所有后继节点,从那些后继节点中的前置节点集合中移除该节点
foreach (var item in flowNode.SuccessorNodes)
{
var connectionType = item.Key; // 方法调用连接类型
var successorNodes = item.Value; // 对应类型的父节点集合
foreach (IFlowNode successorNode in successorNodes)
{
successorNode.SuccessorNodes[connectionType].Remove(flowNode);
var e = new NodeConnectChangeEventArgs(
CanvasGuid, // 画布
flowNode.Guid, // 被移除的节点Guid
successorNode.Guid, // 子节点Guid
JunctionOfConnectionType.Invoke, // 方法调用关系
connectionType, // 对应的连接关系
NodeConnectChangeEventArgs.ConnectChangeType.Remove); // 移除连线
EventArgs.Add(e); // 缓存事件参数
flowEnvironmentEvent.OnNodeConnectChanged(e);
}
}
}
#endregion
#region
// 需要找到有哪些节点的入参参数,被设置为了该节点,然后将其删除
// 因为节点自身没有记录哪些节点选取了自己作为参数来源节点,所以需要遍历所有节点
foreach (var item in flowNode.NeedResultNodes)
{
var connectionType = item.Key; // 参数来源连接类型
var argNodes = item.Value; // 对应类型的入参需求节点集合
foreach (var argNode in argNodes)
{
var md = argNode.MethodDetails;
if (md is null) continue;
var pds = md.ParameterDetailss;
if (pds is null || pds.Length == 0) continue;
foreach(var parameter in pds)
{
if (!parameter.ArgDataSourceNodeGuid.Equals(flowNode.Guid)) continue;
// 找到了对应的入参控制点了
var e = new NodeConnectChangeEventArgs(
CanvasGuid, // 画布
flowNode.Guid, // 被移除的节点Guid
argNode.Guid, // 子节点Guid
parameter.Index, // 作用在第几个参数上,用于指示移除第几个参数的连线
JunctionOfConnectionType.Arg, // 指示移除的是参数连接线
connectionType, // 对应的连接关系
NodeConnectChangeEventArgs.ConnectChangeType.Remove); // 移除连线
EventArgs.Add(e); // 缓存事件参数
flowEnvironmentEvent.OnNodeConnectChanged(e);
}
}
}
#endregion
flowModelService.RemoveNodeModel(flowNode); // 从记录中移除
//flowNode.Remove(); // 调用节点的移除方法
if(flowEnvironment.UIContextOperation is null)
{
flowCanvasDetails?.Nodes.Remove(flowNode);
}
else
{
// 存在UI上下文操作当前运行环境极有可能运行在有UI线程的平台上
// 为了避免直接修改 ObservableCollection 集合导致异常产生故而使用UI线程上下文操作运行
flowEnvironment.UIContextOperation?.Invoke(() =>
{
flowCanvasDetails?.Nodes.Remove(flowNode);
});
}
flowEnvironmentEvent.OnNodeRemoved(new NodeRemoveEventArgs(CanvasGuid, NodeGuid));
return true;
}
public override bool Undo()
{
// 先恢复被删除的节点
// 撤销删除节点时,还需要恢复连线状态
foreach (NodeConnectChangeEventArgs e in EventArgs)
{
NodeConnectChangeEventArgs? newEventArgs = null;
if (e.JunctionOfConnectionType == JunctionOfConnectionType.Invoke)
{
newEventArgs = new NodeConnectChangeEventArgs(
e.CanvasGuid, // 画布
e.FromNodeGuid, // 被移除的节点Guid
e.ToNodeGuid, // 子节点Guid
e.JunctionOfConnectionType, // 指示需要恢复的是方法调用线
e.ConnectionInvokeType, // 对应的连接关系
NodeConnectChangeEventArgs.ConnectChangeType.Create); // 创建连线
}
else if (e.JunctionOfConnectionType == JunctionOfConnectionType.Arg)
{
newEventArgs = new NodeConnectChangeEventArgs(
e.CanvasGuid, // 画布
e.FromNodeGuid, // 被移除的节点Guid
e.ToNodeGuid, // 子节点Guid
e.ArgIndex, // 作用在第几个参数上,用于指示移除第几个参数的连线
e.JunctionOfConnectionType, // 指示需要恢复的是参数连接线
e.ConnectionArgSourceType, // 对应的连接关系
NodeConnectChangeEventArgs.ConnectChangeType.Create); // 创建连线
}
else
{
newEventArgs = null;
}
if (newEventArgs != null)
{
// 使用反转了的事件参数进行触发
flowEnvironmentEvent.OnNodeConnectChanged(newEventArgs);
}
}
return true;
}
public override void ToInfo()
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,91 @@
using Serein.Library;
using Serein.Library.Api;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Model.Operation
{
/// <summary>
/// 将调用顺序置为优先
/// </summary>
internal class SetConnectPriorityInvokeOperation : OperationBase
{
public override string Theme => nameof(SetConnectPriorityInvokeOperation);
public string FromNodeGuid { get; set; }
public string ToNodeGuid { get; set; }
public ConnectionInvokeType ConnectionType { get; set; }
private IFlowNode FromNode;
private IFlowNode ToNode;
private int lastIdx = -1;
public override bool ValidationParameter()
{
if (ConnectionType == ConnectionInvokeType.None)
{
return false;
}
// 获取起始节点与目标节点
if (!flowModelService.TryGetNodeModel(FromNodeGuid, out var fromNode) || !flowModelService.TryGetNodeModel(ToNodeGuid, out var toNode))
{
return false;
}
if (fromNode is null || toNode is null) return false;
FromNode = fromNode;
ToNode = toNode;
return true;
}
/// <summary>
/// 成为首项
/// </summary>
public override bool Execute()
{
if(!ValidationParameter()) return false;
if (FromNode.SuccessorNodes.TryGetValue(ConnectionType, out var nodes))
{
var idx = nodes.IndexOf(ToNode);
if (idx > -1)
{
lastIdx = idx;
nodes.RemoveAt(idx);
nodes.Insert(0, ToNode);
}
}
return true;
}
/// <summary>
/// 恢复原来的位置
/// </summary>
public override bool Undo()
{
if (FromNode.SuccessorNodes.TryGetValue(ConnectionType, out var nodes))
{
var idx = nodes.IndexOf(ToNode);
if (idx > -1)
{
nodes.RemoveAt(idx);
nodes.Insert(lastIdx, ToNode);
lastIdx = 0;
}
}
return true;
}
public override void ToInfo()
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,78 @@
using Serein.Library;
using Serein.Library.Api;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.NodeFlow.Model.Operation
{
/// <summary>
/// 设置起始节点
/// </summary>
internal class SetStartNodeOperation : OperationBase
{
public override string Theme => nameof(SetStartNodeOperation);
public string CanvasGuid { get; set; }
public string NewNodeGuid { get; set; }
private FlowCanvasDetails CanvasModel;
private IFlowNode NewStartNodeModel;
private IFlowNode? OldStartNodeModel;
public override bool ValidationParameter()
{
if (!flowModelService.TryGetCanvasModel(CanvasGuid, out CanvasModel)
|| !flowModelService.TryGetNodeModel(NewNodeGuid, out NewStartNodeModel))
{
return false;
}
return true;
}
public override bool Execute()
{
if (!ValidationParameter()) return false;
if (CanvasModel.StartNode is not null
&& flowModelService.TryGetNodeModel(CanvasModel.StartNode.Guid, out var flowNode))
{
OldStartNodeModel = flowNode;
}
CanvasModel.StartNode = NewStartNodeModel;
flowEnvironmentEvent.OnStartNodeChanged(new StartNodeChangeEventArgs(CanvasModel.Guid, OldStartNodeModel?.Guid, NewStartNodeModel.Guid));
return true;
}
public override bool Undo()
{
if(OldStartNodeModel is null)
{
return false;
}
var newStartNode = OldStartNodeModel;
var oldStartNode = NewStartNodeModel;
NewStartNodeModel = newStartNode;
OldStartNodeModel = oldStartNode;
CanvasModel.StartNode = oldStartNode;
flowEnvironmentEvent.OnStartNodeChanged(new StartNodeChangeEventArgs(CanvasModel.Guid, oldStartNode?.Guid, newStartNode.Guid));
return true;
}
public override void ToInfo()
{
throw new NotImplementedException();
}
}
}