diff --git a/src/designer/elsa-workflows-designer/src/components/activities/flowchart/flowchart.tsx b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/flowchart.tsx
index f718fdeac..7766229c0 100644
--- a/src/designer/elsa-workflows-designer/src/components/activities/flowchart/flowchart.tsx
+++ b/src/designer/elsa-workflows-designer/src/components/activities/flowchart/flowchart.tsx
@@ -15,11 +15,11 @@ import {NodeFactory} from "./node-factory";
import {Container} from "typedi";
import {EventBus} from "../../../services";
import {ConnectionCreatedEventArgs, FlowchartEvents} from "./events";
+import {ContextMenuAnchorPoint, MenuItemGroup} from "../../shared/context-menu/models";
+import descriptorsStore from "../../../data/descriptors-store";
import PositionEventArgs = NodeView.PositionEventArgs;
import FromJSONData = Model.FromJSONData;
-import {ContextMenuAnchorPoint, MenuItemGroup} from "../../shared/context-menu/models";
import PointLike = Point.PointLike;
-import descriptorsStore from "../../../data/descriptors-store";
@Component({
tag: 'elsa-flowchart',
@@ -84,8 +84,7 @@ export class FlowchartComponent implements ContainerActivityComponent {
const graph = this.graph;
const {descriptor, x, y} = args;
let id = args.id ?? uuid();
- const pageToLocal = graph.pageToLocal(x, y);
- const point: PointLike = pageToLocal;
+ const point: PointLike = graph.pageToLocal(x, y);
const sx = point.x;
const sy = point.y;
@@ -218,9 +217,6 @@ export class FlowchartComponent implements ContainerActivityComponent {
flowchart.activities = activities;
flowchart.connections = connections;
flowchart.start = startActivity?.id;
- flowchart.metadata = {...flowchart.metadata};
- flowchart.applicationProperties = {};
- flowchart.variables = [];
return flowchart;
}
@@ -406,8 +402,9 @@ export class FlowchartComponent implements ContainerActivityComponent {
}
onGraphChanged = async () => {
- if (this.silent)
+ if (this.silent) {
return;
+ }
this.graphUpdated.emit({exportGraph: this.exportInternal});
}
diff --git a/src/designer/elsa-workflows-designer/src/components/designer/activity-templates/default-activity-template.tsx b/src/designer/elsa-workflows-designer/src/components/designer/activity-templates/default-activity-template.tsx
index 45843669c..ca127b093 100644
--- a/src/designer/elsa-workflows-designer/src/components/designer/activity-templates/default-activity-template.tsx
+++ b/src/designer/elsa-workflows-designer/src/components/designer/activity-templates/default-activity-template.tsx
@@ -123,54 +123,90 @@ export class DefaultActivityTemplate {
const textColor = canStartWorkflow ? 'text-white' : 'text-gray-700';
const borderColor = port.name == this.selectedPortName ? 'border-blue-600' : 'border-gray-300';
const portName = camelCase(port.name);
- const childActivity: Activity = activity ? activity[portName] : null;
- const childActivityDescriptor: ActivityDescriptor = childActivity != null ? descriptorsStore.activityDescriptors.find(x => x.activityType == childActivity.typeName) : null;
- let childActivityDisplayText = childActivity?.metadata?.displayText;
+ const activityProperty: Activity = activity ? activity[portName] : null;
+ const childActivityDescriptor: ActivityDescriptor = activityProperty != null ? descriptorsStore.activityDescriptors.find(x => x.activityType == activityProperty.typeName) : null;
+ let childActivityDisplayText = activityProperty?.metadata?.displayText;
if (isNullOrWhitespace(childActivityDisplayText))
childActivityDisplayText = childActivityDescriptor?.displayName;
+ const renderActivityProperty = () => {
+
+ if (!activityProperty) {
+ return (
+
+ );
+ }
+
+ const propertyIsArray = Array.isArray(activityProperty);
+
+ if (!propertyIsArray) {
+ return (
+ this.onChildActivityClick(e, activity, activityProperty, port)}>
+
+
+ {childActivityDisplayText}
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ {childActivityDisplayText}
+
+
+
+
+ );
+ }
+
return (
this.portElements.push(el)}>
{port.displayName}
- {childActivity ? (
-
this.onChildActivityClick(e, activity, childActivity, port)}
- >
-
-
- {childActivityDisplayText}
-
-
-
-
- ) : (
-
)}
+ {renderActivityProperty()}
);
diff --git a/src/designer/elsa-workflows-designer/src/components/designer/workflow-definition-editor/workflow-definition-editor.tsx b/src/designer/elsa-workflows-designer/src/components/designer/workflow-definition-editor/workflow-definition-editor.tsx
index 134686bd4..d013e96b1 100644
--- a/src/designer/elsa-workflows-designer/src/components/designer/workflow-definition-editor/workflow-definition-editor.tsx
+++ b/src/designer/elsa-workflows-designer/src/components/designer/workflow-definition-editor/workflow-definition-editor.tsx
@@ -63,11 +63,6 @@ export class WorkflowDefinitionEditor {
await this.updateLayout();
}
- @Listen('collapsed')
- private async handlePanelCollapsed(e: CustomEvent) {
- //this.selectedActivity = null;
- }
-
@Listen('containerSelected')
private async handleContainerSelected(e: CustomEvent) {
this.selectedActivity = this.getCurrentContainer();
@@ -114,12 +109,17 @@ export class WorkflowDefinitionEditor {
this.currentWorkflowPath = [...this.currentWorkflowPath, item];
const portName = camelCase(e.detail.port.name);
const parentActivity = this.nodeMap[parentActivityId];
- const childActivity = parentActivity[portName] as Activity;
+ const activityProperty = parentActivity[portName] as Activity | Array;
+ const isContainer = Array.isArray(activityProperty);
- if (!childActivity) {
+ if (!activityProperty) {
await this.canvas.reset();
} else {
- await this.canvas.importGraph(childActivity);
+ if (isContainer) {
+ await this.canvas.importGraph(parentActivity);
+ } else {
+ await this.canvas.importGraph(activityProperty as Activity);
+ }
}
this.selectedActivity = this.getCurrentContainer();
@@ -152,7 +152,7 @@ export class WorkflowDefinitionEditor {
@Method()
async updateWorkflowDefinition(workflowDefinition: WorkflowDefinition): Promise {
this.workflowDefinitionState = workflowDefinition;
- this.nodeMap = createActivityMap(flatten(walkActivities(workflowDefinition.root)));
+ this.nodeMap = this.createNodeMap(workflowDefinition);
if (this.currentWorkflowPath.length == 0) {
this.currentWorkflowPath = [{activityId: workflowDefinition.root.id, portName: null}];
@@ -266,7 +266,7 @@ export class WorkflowDefinitionEditor {
private getWorkflowDefinitionInternal = async (): Promise => {
const activity: Activity = await this.canvas.exportGraph();
const workflowDefinition = this.workflowDefinitionState;
- const nodeMap = this.nodeMap;
+ const nodeMap = this.nodeMap; //this.createNodeMap(workflowDefinition);
const currentWorkflowPath = this.currentWorkflowPath;
const currentWorkflowNavigationItem = currentWorkflowPath[currentWorkflowPath.length - 1];
const currentActivityId = currentWorkflowNavigationItem.activityId;
@@ -279,8 +279,15 @@ export class WorkflowDefinitionEditor {
}
if (!!currentPortName) {
- const portName = camelCase(currentPortName);
- currentActivity[portName] = activity;
+ // If we're editing a container (Flowchart) activity, the exported activity will be the current activity. No need to update anything here.
+ if (currentActivity.id !== activity.id) {
+ const portName = camelCase(currentPortName);
+ currentActivity[portName] = activity;
+ this.nodeMap = this.createNodeMap(workflowDefinition)
+ } else {
+ debugger;
+ nodeMap[currentActivityId] = activity;
+ }
} else {
workflowDefinition.root = activity;
@@ -336,6 +343,8 @@ export class WorkflowDefinitionEditor {
return this.nodeMap[currentItem.activityId];
};
+ private createNodeMap = (workflowDefinition: WorkflowDefinition) => createActivityMap(flatten(walkActivities(workflowDefinition.root)));
+
private onActivityPickerPanelStateChanged = async (e: PanelStateChangedArgs) => await this.updateContainerLayout('activity-picker-closed', e.expanded)
private onWorkflowEditorPanelStateChanged = async (e: PanelStateChangedArgs) => await this.updateContainerLayout('object-editor-closed', e.expanded)
private onActivityEditorPanelStateChanged = async (e: PanelStateChangedArgs) => await this.updateContainerLayout('activity-editor-closed', e.expanded)
diff --git a/src/designer/elsa-workflows-designer/src/models/core.ts b/src/designer/elsa-workflows-designer/src/models/core.ts
index ff689088c..2ccd8ed4b 100644
--- a/src/designer/elsa-workflows-designer/src/models/core.ts
+++ b/src/designer/elsa-workflows-designer/src/models/core.ts
@@ -81,6 +81,7 @@ export interface ActivityDescriptor {
outputs: Array;
kind: ActivityKind;
ports: Array;
+ isContainer: boolean;
}
export interface PropertyDescriptor {
diff --git a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.cs b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.cs
index 6bb78a13a..2ecffe14c 100644
--- a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.cs
+++ b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.cs
@@ -17,10 +17,13 @@ public class Flowchart : Container
[Node] public IActivity? Start { get; set; }
public ICollection Connections { get; set; } = new List();
- protected override void ScheduleChildren(ActivityExecutionContext context)
+ protected override async ValueTask ScheduleChildrenAsync(ActivityExecutionContext context)
{
if (Start == null!)
+ {
+ await context.CompleteActivityAsync();
return;
+ }
context.ScheduleActivity(Start);
}
diff --git a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Serialization/FlowchartJsonConverter.cs b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Serialization/FlowchartJsonConverter.cs
index 136cb0d28..55d77c391 100644
--- a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Serialization/FlowchartJsonConverter.cs
+++ b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Serialization/FlowchartJsonConverter.cs
@@ -9,26 +9,26 @@ public class FlowchartJsonConverter : JsonConverter
{
private const string AllActivitiesKey = "AllActivities";
private const string AllConnectionsKey = "AllConnections";
-
+
public override Activities.Flowchart Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (!JsonDocument.TryParseValue(ref reader, out var doc))
throw new JsonException("Failed to parse JsonDocument");
- var connectionsElement = doc.RootElement.GetProperty("connections");
+ var connectionsElement = doc.RootElement.TryGetProperty("connections", out var connectionsEl) ? connectionsEl : default;
var metadataElement = doc.RootElement.GetProperty("metadata");
- var activitiesElement = doc.RootElement.GetProperty("activities");
+ var activitiesElement = doc.RootElement.TryGetProperty("activities", out var activitiesEl) ? activitiesEl : default;
var id = doc.RootElement.GetProperty("id").GetString()!;
var startId = doc.RootElement.TryGetProperty("start", out var startElement) ? startElement.GetString() : default;
- var activities = activitiesElement.Deserialize>(options) ?? new List();
+ var activities = activitiesElement.ValueKind != JsonValueKind.Undefined ? activitiesElement.Deserialize>(options) ?? new List() : new List();
var metadata = metadataElement.Deserialize>(options) ?? new Dictionary();
var start = activities.FirstOrDefault(x => x.Id == startId) ?? activities.FirstOrDefault();
var connectionSerializerOptions = new JsonSerializerOptions(options);
var activityDictionary = activities.ToDictionary(x => x.Id);
-
+
connectionSerializerOptions.Converters.Add(new ConnectionJsonConverter(activityDictionary));
- var connections = connectionsElement.Deserialize>(connectionSerializerOptions) ?? new List();
-
+ var connections = connectionsElement.ValueKind != JsonValueKind.Undefined ? connectionsElement.Deserialize>(connectionSerializerOptions) ?? new List() : new List();
+
var flowChart = new Activities.Flowchart
{
Id = id,
@@ -51,7 +51,7 @@ public class FlowchartJsonConverter : JsonConverter
var activities = value.Activities;
var connectionSerializerOptions = new JsonSerializerOptions(options);
var activityDictionary = activities.ToDictionary(x => x.Id);
-
+
connectionSerializerOptions.Converters.Add(new ConnectionJsonConverter(activityDictionary));
var allActivities = value.ApplicationProperties.TryGetValue(AllActivitiesKey, out var a) ? a : activities;
diff --git a/src/modules/Elsa.Workflows.Core/Models/ActivityDescriptor.cs b/src/modules/Elsa.Workflows.Core/Models/ActivityDescriptor.cs
index 39231adb2..e5fdcc4df 100644
--- a/src/modules/Elsa.Workflows.Core/Models/ActivityDescriptor.cs
+++ b/src/modules/Elsa.Workflows.Core/Models/ActivityDescriptor.cs
@@ -12,10 +12,14 @@ public class ActivityDescriptor
public string? Description { get; init; }
public ICollection Inputs { get; init; } = new List();
public ICollection Outputs { get; init; } = new List();
-
[JsonIgnore] public Func Constructor { get; init; } = default!;
public ActivityKind Kind { get; set; } = ActivityKind.Action;
public ICollection Ports { get; init; } = new List();
+
+ ///
+ /// A value indicating whether this activity is a container of child activities.
+ ///
+ public bool IsContainer { get; set; }
}
public record ActivityConstructorContext(JsonElement Element, JsonSerializerOptions SerializerOptions);
\ No newline at end of file
diff --git a/src/modules/Elsa.Workflows.Management/Implementations/ActivityDescriber.cs b/src/modules/Elsa.Workflows.Management/Implementations/ActivityDescriber.cs
index 0108e5dc2..02024d6e7 100644
--- a/src/modules/Elsa.Workflows.Management/Implementations/ActivityDescriber.cs
+++ b/src/modules/Elsa.Workflows.Management/Implementations/ActivityDescriber.cs
@@ -9,6 +9,7 @@ using Elsa.Workflows.Management.Extensions;
using Elsa.Workflows.Management.Models;
using Elsa.Workflows.Management.Services;
using Humanizer;
+using IContainer = Elsa.Workflows.Core.Services.IContainer;
namespace Elsa.Workflows.Management.Implementations;
@@ -64,6 +65,7 @@ public class ActivityDescriber : IActivityDescriber
Ports = outboundPorts.ToList(),
Inputs = DescribeInputProperties(inputProperties).ToList(),
Outputs = DescribeOutputProperties(outputProperties).ToList(),
+ IsContainer = typeof(IContainer).IsAssignableFrom(activityType),
Constructor = context =>
{
var activity = _activityFactory.Create(activityType, context);