diff --git a/src/core/Flowsharp.Abstractions/Expressions/WorkflowExpression.cs b/src/core/Flowsharp.Abstractions/Expressions/WorkflowExpression.cs index 5fe39a237..4d9358697 100644 --- a/src/core/Flowsharp.Abstractions/Expressions/WorkflowExpression.cs +++ b/src/core/Flowsharp.Abstractions/Expressions/WorkflowExpression.cs @@ -2,6 +2,10 @@ { public class WorkflowExpression { + public WorkflowExpression() + { + } + public WorkflowExpression(string syntax, string expression) { Syntax = syntax; @@ -9,7 +13,7 @@ } public string Syntax { get; set; } - public string Expression { get; } + public string Expression { get; set; } public override string ToString() => Expression; } diff --git a/src/core/Flowsharp.Abstractions/Extensions/ActivityDescriptorExtensions.cs b/src/core/Flowsharp.Abstractions/Extensions/ActivityDescriptorExtensions.cs index 09615776e..139980994 100644 --- a/src/core/Flowsharp.Abstractions/Extensions/ActivityDescriptorExtensions.cs +++ b/src/core/Flowsharp.Abstractions/Extensions/ActivityDescriptorExtensions.cs @@ -1,16 +1,27 @@ using System; using Flowsharp.Models; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; namespace Flowsharp.Extensions { public static class ActivityDescriptorExtensions { - public static IActivity InstantiateActivity(this ActivityDescriptor descriptor, string json = null) + public static IActivity InstantiateActivity(this ActivityDescriptor descriptor, string json) { - var activity = json == null + var token = json != null ? JToken.Parse(json) : default; + return InstantiateActivity(descriptor, token); + } + + public static IActivity InstantiateActivity(this ActivityDescriptor descriptor, JToken token = default) + { + var activity = token == null ? Activator.CreateInstance(descriptor.ActivityType) - : JsonConvert.DeserializeObject(json, descriptor.ActivityType); + : token.ToObject(descriptor.ActivityType, JsonSerializer.Create(new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + })); return (IActivity) activity; } diff --git a/src/core/Flowsharp.Abstractions/Extensions/ExpandObjectExtensions.cs b/src/core/Flowsharp.Abstractions/Extensions/ExpandObjectExtensions.cs deleted file mode 100644 index 496fe95b5..000000000 --- a/src/core/Flowsharp.Abstractions/Extensions/ExpandObjectExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Dynamic; - -namespace Flowsharp.Extensions -{ - public static class ExpandObjectExtensions - { - public static T Get(this ExpandoObject expandoObject, string key, Func defaultValue = null) - { - var dictionary = (IDictionary) expandoObject; - return dictionary.ContainsKey(key) ? (T)dictionary[key] : defaultValue != null ? defaultValue() : default; - } - } -} \ No newline at end of file diff --git a/src/core/Flowsharp.Abstractions/Flowsharp.Abstractions.csproj b/src/core/Flowsharp.Abstractions/Flowsharp.Abstractions.csproj index ca6ea600d..7fc0e30a4 100644 --- a/src/core/Flowsharp.Abstractions/Flowsharp.Abstractions.csproj +++ b/src/core/Flowsharp.Abstractions/Flowsharp.Abstractions.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/core/Flowsharp.Abstractions/IActivity.cs b/src/core/Flowsharp.Abstractions/IActivity.cs index 17e40df08..f4615e546 100644 --- a/src/core/Flowsharp.Abstractions/IActivity.cs +++ b/src/core/Flowsharp.Abstractions/IActivity.cs @@ -4,7 +4,7 @@ namespace Flowsharp { public interface IActivity { - int Id { get; set; } + string Id { get; set; } string Name { get; } ActivityMetadata Metadata { get; set; } } diff --git a/src/core/Flowsharp.Abstractions/IActivityLibrary.cs b/src/core/Flowsharp.Abstractions/IActivityLibrary.cs index 3e58e7289..c8efc2a0d 100644 --- a/src/core/Flowsharp.Abstractions/IActivityLibrary.cs +++ b/src/core/Flowsharp.Abstractions/IActivityLibrary.cs @@ -7,7 +7,7 @@ namespace Flowsharp { public interface IActivityLibrary { - Task> GetActivitiesAsync(CancellationToken cancellationToken); - Task GetActivityByNameAsync(string name, CancellationToken cancellationToken); + Task> GetActivitiesAsync(CancellationToken cancellationToken); + Task GetActivityByNameAsync(string name, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/core/Flowsharp.Abstractions/IActivityProvider.cs b/src/core/Flowsharp.Abstractions/IActivityProvider.cs index e2c548d8f..a25ed2744 100644 --- a/src/core/Flowsharp.Abstractions/IActivityProvider.cs +++ b/src/core/Flowsharp.Abstractions/IActivityProvider.cs @@ -7,6 +7,6 @@ namespace Flowsharp { public interface IActivityProvider { - Task> GetActivitiesAsync(CancellationToken cancellationToken); + Task> GetActivitiesAsync(CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/core/Flowsharp.Abstractions/Models/Activity.cs b/src/core/Flowsharp.Abstractions/Models/Activity.cs index c2ff0cb1f..9161ec091 100644 --- a/src/core/Flowsharp.Abstractions/Models/Activity.cs +++ b/src/core/Flowsharp.Abstractions/Models/Activity.cs @@ -1,8 +1,8 @@ namespace Flowsharp.Models { - public abstract class Activity : Flowsharp.IActivity + public abstract class Activity : IActivity { - public int Id { get; set; } + public string Id { get; set; } public string Name => GetType().Name; public ActivityMetadata Metadata { get; set; } = new ActivityMetadata(); } diff --git a/src/core/Flowsharp.Abstractions/Models/ActivityExecutionContext.cs b/src/core/Flowsharp.Abstractions/Models/ActivityExecutionContext.cs index d3280f143..c5e6e3989 100644 --- a/src/core/Flowsharp.Abstractions/Models/ActivityExecutionContext.cs +++ b/src/core/Flowsharp.Abstractions/Models/ActivityExecutionContext.cs @@ -2,19 +2,19 @@ { public class ActivityExecutionContext { - public ActivityExecutionContext(Flowsharp.IActivity activity, ActivityDescriptor descriptor) + public ActivityExecutionContext(IActivity activity, ActivityDescriptor descriptor) { Activity = activity; Descriptor = descriptor; } - public Flowsharp.IActivity Activity { get; } + public IActivity Activity { get; } public ActivityDescriptor Descriptor { get; } } - public class ActivityExecutionContext : ActivityExecutionContext where T : Flowsharp.IActivity + public class ActivityExecutionContext : ActivityExecutionContext where T : IActivity { - public ActivityExecutionContext(Flowsharp.IActivity activity, ActivityDescriptor descriptor) : base(activity, descriptor) + public ActivityExecutionContext(IActivity activity, ActivityDescriptor descriptor) : base(activity, descriptor) { } diff --git a/src/core/Flowsharp.Abstractions/Models/ActivityMetadata.cs b/src/core/Flowsharp.Abstractions/Models/ActivityMetadata.cs index 997d83f41..abee8fecd 100644 --- a/src/core/Flowsharp.Abstractions/Models/ActivityMetadata.cs +++ b/src/core/Flowsharp.Abstractions/Models/ActivityMetadata.cs @@ -1,9 +1,9 @@ -using System.Dynamic; +using Newtonsoft.Json.Linq; namespace Flowsharp.Models { public class ActivityMetadata { - public ExpandoObject CustomFields { get; set; } = new ExpandoObject(); + public JObject CustomFields { get; set; } = new JObject(); } } \ No newline at end of file diff --git a/src/core/Flowsharp.Abstractions/Models/Connection.cs b/src/core/Flowsharp.Abstractions/Models/Connection.cs index 7bb8c82e9..1b02f1e88 100644 --- a/src/core/Flowsharp.Abstractions/Models/Connection.cs +++ b/src/core/Flowsharp.Abstractions/Models/Connection.cs @@ -6,11 +6,11 @@ namespace Flowsharp.Models { } - public Connection(Flowsharp.IActivity source, Flowsharp.IActivity target) : this(new SourceEndpoint(source), new TargetEndpoint(target)) + public Connection(IActivity source, IActivity target) : this(new SourceEndpoint(source), new TargetEndpoint(target)) { } - public Connection(Flowsharp.IActivity source, string sourceEndpointName, Flowsharp.IActivity target) : this(new SourceEndpoint(source, sourceEndpointName), new TargetEndpoint(target)) + public Connection(IActivity source, string sourceEndpointName, IActivity target) : this(new SourceEndpoint(source, sourceEndpointName), new TargetEndpoint(target)) { } diff --git a/src/core/Flowsharp.Abstractions/Models/CustomFieldDictionary.cs b/src/core/Flowsharp.Abstractions/Models/CustomFieldDictionary.cs new file mode 100644 index 000000000..469b5c7e4 --- /dev/null +++ b/src/core/Flowsharp.Abstractions/Models/CustomFieldDictionary.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; + +namespace Flowsharp.Models +{ + public class CustomFieldDictionary : Dictionary + { + + } +} \ No newline at end of file diff --git a/src/core/Flowsharp.Abstractions/Models/Endpoint.cs b/src/core/Flowsharp.Abstractions/Models/Endpoint.cs index 5a697dbb3..addd2c34e 100644 --- a/src/core/Flowsharp.Abstractions/Models/Endpoint.cs +++ b/src/core/Flowsharp.Abstractions/Models/Endpoint.cs @@ -6,11 +6,11 @@ namespace Flowsharp.Models { } - protected Endpoint(Flowsharp.IActivity activity) + protected Endpoint(IActivity activity) { Activity = activity; } - public Flowsharp.IActivity Activity { get; set; } + public IActivity Activity { get; set; } } } \ No newline at end of file diff --git a/src/core/Flowsharp.Abstractions/Models/SourceEndpoint.cs b/src/core/Flowsharp.Abstractions/Models/SourceEndpoint.cs index 7654f9f8a..2befd5357 100644 --- a/src/core/Flowsharp.Abstractions/Models/SourceEndpoint.cs +++ b/src/core/Flowsharp.Abstractions/Models/SourceEndpoint.cs @@ -6,7 +6,7 @@ namespace Flowsharp.Models { } - public SourceEndpoint(Flowsharp.IActivity activity, string name = null) : base(activity) + public SourceEndpoint(IActivity activity, string name = null) : base(activity) { Name = name; } diff --git a/src/core/Flowsharp.Abstractions/Models/TargetEndpoint.cs b/src/core/Flowsharp.Abstractions/Models/TargetEndpoint.cs index 7844aece9..9491d5cd6 100644 --- a/src/core/Flowsharp.Abstractions/Models/TargetEndpoint.cs +++ b/src/core/Flowsharp.Abstractions/Models/TargetEndpoint.cs @@ -6,7 +6,7 @@ namespace Flowsharp.Models { } - public TargetEndpoint(Flowsharp.IActivity activity) : base(activity) + public TargetEndpoint(IActivity activity) : base(activity) { } } diff --git a/src/core/Flowsharp.Abstractions/Models/Workflow.cs b/src/core/Flowsharp.Abstractions/Models/Workflow.cs index 5a28e139e..2672481a2 100644 --- a/src/core/Flowsharp.Abstractions/Models/Workflow.cs +++ b/src/core/Flowsharp.Abstractions/Models/Workflow.cs @@ -1,32 +1,36 @@ using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace Flowsharp.Models { public class Workflow { - public Workflow(IEnumerable activities, IEnumerable connections) : this() + public Workflow(IEnumerable activities, IEnumerable connections) : this() { Activities = activities.ToList(); Connections = connections.ToList(); } - + public Workflow() { CurrentScope = new WorkflowExecutionScope(); - Scopes = new Stack(new[]{ CurrentScope }); + Scopes = new Stack(new[] { CurrentScope }); Arguments = new Variables(); - BlockingActivities = new List(); + BlockingActivities = new List(); Metadata = new WorkflowMetadata(); } - + + [JsonConverter(typeof(StringEnumConverter))] public WorkflowStatus Status { get; set; } - public IList Activities { get; set; } = new List(); + + public IList Activities { get; set; } = new List(); public IList Connections { get; set; } = new List(); public Stack Scopes { get; set; } public WorkflowExecutionScope CurrentScope { get; set; } public Variables Arguments { get; set; } - public IList BlockingActivities { get; set; } + public IList BlockingActivities { get; set; } public WorkflowMetadata Metadata { get; set; } public WorkflowFault Fault { get; set; } } diff --git a/src/core/Flowsharp.Abstractions/Models/WorkflowExecutionContext.cs b/src/core/Flowsharp.Abstractions/Models/WorkflowExecutionContext.cs index 1d4e8e780..6235e64c8 100644 --- a/src/core/Flowsharp.Abstractions/Models/WorkflowExecutionContext.cs +++ b/src/core/Flowsharp.Abstractions/Models/WorkflowExecutionContext.cs @@ -7,20 +7,20 @@ namespace Flowsharp.Models public class WorkflowExecutionContext { private readonly IDictionary activityDescriptors; - private readonly Stack scheduledActivities; + private readonly Stack scheduledActivities; public WorkflowExecutionContext(Workflow workflow, IDictionary activityDescriptors) { this.activityDescriptors = activityDescriptors; Workflow = workflow; IsFirstPass = true; - scheduledActivities = new Stack(); + scheduledActivities = new Stack(); } public Workflow Workflow { get; } public bool HasScheduledActivities => scheduledActivities.Any(); public bool IsFirstPass { get; set; } - public Flowsharp.IActivity CurrentActivity { get; private set; } + public IActivity CurrentActivity { get; private set; } public WorkflowExecutionScope CurrentScope { @@ -28,7 +28,7 @@ namespace Flowsharp.Models set => Workflow.CurrentScope = value; } - public ActivityExecutionContext CreateActivityExecutionContext(Flowsharp.IActivity activity) => + public ActivityExecutionContext CreateActivityExecutionContext(IActivity activity) => new ActivityExecutionContext(activity, activityDescriptors[activity.Name]); public void BeginScope() @@ -42,12 +42,12 @@ namespace Flowsharp.Models CurrentScope = Workflow.Scopes.Peek(); } - public void ScheduleActivity(Flowsharp.IActivity activity) + public void ScheduleActivity(IActivity activity) { scheduledActivities.Push(activity); } - public Flowsharp.IActivity PopScheduledActivity() + public IActivity PopScheduledActivity() { CurrentActivity = scheduledActivities.Pop(); return CurrentActivity; @@ -58,9 +58,9 @@ namespace Flowsharp.Models CurrentScope.LastResult = value; } - public void Fault(Exception exception, Flowsharp.IActivity activity) => Fault(exception.Message, activity); + public void Fault(Exception exception, IActivity activity) => Fault(exception.Message, activity); - public void Fault(string errorMessage, Flowsharp.IActivity activity) + public void Fault(string errorMessage, IActivity activity) { Workflow.Fault = new WorkflowFault { diff --git a/src/core/Flowsharp.Abstractions/Models/WorkflowFault.cs b/src/core/Flowsharp.Abstractions/Models/WorkflowFault.cs index 43f48ed0b..0e1828350 100644 --- a/src/core/Flowsharp.Abstractions/Models/WorkflowFault.cs +++ b/src/core/Flowsharp.Abstractions/Models/WorkflowFault.cs @@ -2,7 +2,7 @@ { public class WorkflowFault { - public Flowsharp.IActivity FaultedActivity { get; set; } + public IActivity FaultedActivity { get; set; } public string Message { get; set; } } } \ No newline at end of file diff --git a/src/core/Flowsharp.Abstractions/Models/WorkflowMetadata.cs b/src/core/Flowsharp.Abstractions/Models/WorkflowMetadata.cs index 2d2ddaa84..e62250ce4 100644 --- a/src/core/Flowsharp.Abstractions/Models/WorkflowMetadata.cs +++ b/src/core/Flowsharp.Abstractions/Models/WorkflowMetadata.cs @@ -1,4 +1,4 @@ -using System.Dynamic; +using Newtonsoft.Json.Linq; namespace Flowsharp.Models { @@ -8,6 +8,6 @@ namespace Flowsharp.Models public string ParentId { get; set; } public string Name { get; set; } public string Description { get; set; } - public ExpandoObject CustomFields { get; set; } + public JObject CustomFields { get; set; } } } \ No newline at end of file diff --git a/src/core/Flowsharp.Abstractions/Serialization/Extensions/StringExtensions.cs b/src/core/Flowsharp.Abstractions/Serialization/Extensions/StringExtensions.cs new file mode 100644 index 000000000..bd8371da4 --- /dev/null +++ b/src/core/Flowsharp.Abstractions/Serialization/Extensions/StringExtensions.cs @@ -0,0 +1,23 @@ +using System.IO; +using System.Text; + +namespace Flowsharp.Serialization.Extensions +{ + public static class StringExtensions + { + public static Stream ToStream(this string text) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(text); + writer.Flush(); + stream.Position = 0; + return stream; + } + + public static byte[] ToBytes(this string text) + { + return Encoding.UTF8.GetBytes(text); + } + } +} \ No newline at end of file diff --git a/src/core/Flowsharp.Abstractions/Serialization/Tokenizers/WorkflowTokenizationContext.cs b/src/core/Flowsharp.Abstractions/Serialization/Tokenizers/WorkflowTokenizationContext.cs index 7733a6b6b..b4398c67e 100644 --- a/src/core/Flowsharp.Abstractions/Serialization/Tokenizers/WorkflowTokenizationContext.cs +++ b/src/core/Flowsharp.Abstractions/Serialization/Tokenizers/WorkflowTokenizationContext.cs @@ -5,7 +5,7 @@ namespace Flowsharp.Serialization.Tokenizers { public class WorkflowTokenizationContext { - public IDictionary ActivityIdLookup { get; set; } - public IDictionary ActivityLookup { get; set; } + public IDictionary ActivityIdLookup { get; set; } + public IDictionary ActivityLookup { get; set; } } } \ No newline at end of file diff --git a/src/core/Flowsharp.Core/ActivityLibrary.cs b/src/core/Flowsharp.Core/ActivityLibrary.cs index 605abe85c..6e3705a35 100644 --- a/src/core/Flowsharp.Core/ActivityLibrary.cs +++ b/src/core/Flowsharp.Core/ActivityLibrary.cs @@ -15,7 +15,7 @@ namespace Flowsharp this.providers = providers; } - public async Task> GetActivitiesAsync(CancellationToken cancellationToken) + public async Task> GetActivitiesAsync(CancellationToken cancellationToken) { var tasks = providers.Select(x => x.GetActivitiesAsync(cancellationToken)); var descriptorsList = await Task.WhenAll(tasks); @@ -24,7 +24,7 @@ namespace Flowsharp return descriptors; } - public async Task GetActivityByNameAsync(string name, CancellationToken cancellationToken) + public async Task GetActivityByNameAsync(string name, CancellationToken cancellationToken) { var descriptors = await GetActivitiesAsync(cancellationToken); return descriptors.SingleOrDefault(x => x.Name == name); diff --git a/src/core/Flowsharp.Core/Flowsharp.Core.csproj b/src/core/Flowsharp.Core/Flowsharp.Core.csproj index 4d8f0671c..61b3d2528 100644 --- a/src/core/Flowsharp.Core/Flowsharp.Core.csproj +++ b/src/core/Flowsharp.Core/Flowsharp.Core.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/core/Flowsharp.Core/Serialization/Tokenizers/ActivityTokenizer.cs b/src/core/Flowsharp.Core/Serialization/Tokenizers/ActivityTokenizer.cs index d28fc9636..7dce9eab9 100644 --- a/src/core/Flowsharp.Core/Serialization/Tokenizers/ActivityTokenizer.cs +++ b/src/core/Flowsharp.Core/Serialization/Tokenizers/ActivityTokenizer.cs @@ -15,7 +15,7 @@ namespace Flowsharp.Serialization.Tokenizers protected override IActivity Detokenize(WorkflowTokenizationContext context, JToken value) { var obj = (JObject) value; - var activityId = obj["activityId"].Value(); + var activityId = obj["activityId"].Value(); return context.ActivityLookup[activityId]; } } diff --git a/src/core/Flowsharp.Core/Serialization/Tokenizers/WorkflowTokenizer.cs b/src/core/Flowsharp.Core/Serialization/Tokenizers/WorkflowTokenizer.cs index dd3ba3a85..2b7ae9001 100644 --- a/src/core/Flowsharp.Core/Serialization/Tokenizers/WorkflowTokenizer.cs +++ b/src/core/Flowsharp.Core/Serialization/Tokenizers/WorkflowTokenizer.cs @@ -26,7 +26,7 @@ namespace Flowsharp.Serialization.Tokenizers public Task TokenizeAsync(Workflow value, CancellationToken cancellationToken) { var workflow = value; - var activityTuples = workflow.Activities.Select((x, i) => new {Activity = x, Id = i + 1}).ToList(); + var activityTuples = workflow.Activities.Select(x => new {Activity = x, Id = Guid.NewGuid().ToString()}).ToList(); var context = new WorkflowTokenizationContext { ActivityIdLookup = activityTuples.ToDictionary(x => x.Activity, x => x.Id), @@ -160,7 +160,7 @@ namespace Flowsharp.Serialization.Tokenizers var activity = item.Key; var activityModel = JObject.FromObject(activity, new JsonSerializer { ContractResolver = new CamelCasePropertyNamesContractResolver()}); - activityModel.AddFirst(new JProperty("name", activity.Name)); + activityModel["name"] = activity.Name; activityModels.Add(activityModel); } @@ -196,27 +196,27 @@ namespace Flowsharp.Serialization.Tokenizers return scopeLookup; } - private IEnumerable DeserializeHaltedActivities(JToken token, IDictionary activityDictionary) + private IEnumerable DeserializeHaltedActivities(JToken token, IDictionary activityDictionary) { var haltedActivityModels = (JArray) token["haltedActivities"] ?? new JArray(); foreach (var haltedActivityModel in haltedActivityModels) { - var activityId = haltedActivityModel.Value(); + var activityId = haltedActivityModel.Value(); var activity = activityDictionary[activityId]; yield return activity; } } - private IEnumerable DeserializeConnections(JToken token, IDictionary activityDictionary) + private IEnumerable DeserializeConnections(JToken token, IDictionary activityDictionary) { var connectionModels = (JArray) token["connections"] ?? new JArray(); foreach (var connectionModel in connectionModels) { - var sourceActivityId = connectionModel["source"]["activityId"].Value(); + var sourceActivityId = connectionModel["source"]["activityId"].Value(); var sourceEndpointName = connectionModel["source"]["endpoint"].Value(); - var targetActivityId = connectionModel["target"]["activityId"].Value(); + var targetActivityId = connectionModel["target"]["activityId"].Value(); var sourceActivity = activityDictionary[sourceActivityId]; var targetActivity = activityDictionary[targetActivityId]; @@ -224,10 +224,10 @@ namespace Flowsharp.Serialization.Tokenizers } } - private async Task> DeserializeActivitiesAsync(JToken token, CancellationToken cancellationToken) + private async Task> DeserializeActivitiesAsync(JToken token, CancellationToken cancellationToken) { var activityModels = (JArray)token["activities"] ?? new JArray(); - var dictionary = new Dictionary(); + var dictionary = new Dictionary(); foreach (var activityModel in activityModels) { @@ -241,7 +241,7 @@ namespace Flowsharp.Serialization.Tokenizers return dictionary; } - private Task GetActivityDescriptorAsync(string activityName, CancellationToken cancellationToken) + private Task GetActivityDescriptorAsync(string activityName, CancellationToken cancellationToken) { return activityLibrary.GetActivityByNameAsync(activityName, cancellationToken); } diff --git a/src/web/dashboard/Flowsharp.Web.Dashboard.csproj b/src/web/dashboard/Flowsharp.Web.Dashboard.csproj index 061048522..b02605667 100644 --- a/src/web/dashboard/Flowsharp.Web.Dashboard.csproj +++ b/src/web/dashboard/Flowsharp.Web.Dashboard.csproj @@ -4,9 +4,10 @@ netcoreapp2.1 - - - + + + + diff --git a/src/web/dashboard/Startup.cs b/src/web/dashboard/Startup.cs index a2b31b232..d321d48fe 100644 --- a/src/web/dashboard/Startup.cs +++ b/src/web/dashboard/Startup.cs @@ -1,7 +1,10 @@ -using Flowsharp.Web.OrchardCoreLite.Extensions; +using System.Linq; +using System.Reflection; +using Flowsharp.Web.OrchardCoreLite.Extensions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; +using OrchardCore.Modules.Manifest; namespace Flowsharp.Web.Dashboard { @@ -21,7 +24,7 @@ namespace Flowsharp.Web.Dashboard app.UseDeveloperExceptionPage(); } - app.UseOrchardCore();; + app.UseOrchardCore(); } } } \ No newline at end of file diff --git a/src/web/modules/Flowsharp.Web.Abstractions/Flowsharp.Web.Abstractions.csproj b/src/web/modules/Flowsharp.Web.Abstractions/Flowsharp.Web.Abstractions.csproj index defba3078..a44cdd5a2 100644 --- a/src/web/modules/Flowsharp.Web.Abstractions/Flowsharp.Web.Abstractions.csproj +++ b/src/web/modules/Flowsharp.Web.Abstractions/Flowsharp.Web.Abstractions.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/web/modules/Flowsharp.Web.Activities.Console/Flowsharp.Web.Activities.Console.csproj b/src/web/modules/Flowsharp.Web.Activities.Console/Flowsharp.Web.Activities.Console.csproj index 036730129..11a571827 100644 --- a/src/web/modules/Flowsharp.Web.Activities.Console/Flowsharp.Web.Activities.Console.csproj +++ b/src/web/modules/Flowsharp.Web.Activities.Console/Flowsharp.Web.Activities.Console.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/web/modules/Flowsharp.Web.Activities.Console/Views/WriteLine.Design.cshtml b/src/web/modules/Flowsharp.Web.Activities.Console/Views/WriteLine.Design.cshtml index 029313b1b..2a7e4794f 100644 --- a/src/web/modules/Flowsharp.Web.Activities.Console/Views/WriteLine.Design.cshtml +++ b/src/web/modules/Flowsharp.Web.Activities.Console/Views/WriteLine.Design.cshtml @@ -2,5 +2,5 @@

Write Line

- "Hello World" + "@Model.Activity.TextExpression"
\ No newline at end of file diff --git a/src/web/modules/Flowsharp.Web.Activities.Primitives/Flowsharp.Web.Activities.Primitives.csproj b/src/web/modules/Flowsharp.Web.Activities.Primitives/Flowsharp.Web.Activities.Primitives.csproj index 891ee8853..48788bba3 100644 --- a/src/web/modules/Flowsharp.Web.Activities.Primitives/Flowsharp.Web.Activities.Primitives.csproj +++ b/src/web/modules/Flowsharp.Web.Activities.Primitives/Flowsharp.Web.Activities.Primitives.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/web/modules/Flowsharp.Web.Management/Assets/package-lock.json b/src/web/modules/Flowsharp.Web.Management/Assets/package-lock.json new file mode 100644 index 000000000..9f8363fc6 --- /dev/null +++ b/src/web/modules/Flowsharp.Web.Management/Assets/package-lock.json @@ -0,0 +1,44 @@ +{ + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/bootstrap": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-4.1.2.tgz", + "integrity": "sha512-BiC/IxqyLS68UFp71t2Px80ql/FpaUKSy469aa6HTDFRkv/FvdzRZjF6ifDql/VKSLyin5AZa/yARLetMFM4rQ==", + "dev": true, + "requires": { + "@types/jquery": "*", + "popper.js": "^1.14.1" + } + }, + "@types/jquery": { + "version": "3.3.22", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.22.tgz", + "integrity": "sha512-a4JDcIhJhHYnoWCkG3xT2CZxXZeA92JeREESorg0DMQ3ZsjuKF48h7XK4l5Gl2GRa/ItGRpKMT0pyK88yRgqXQ==", + "dev": true, + "requires": { + "@types/sizzle": "*" + } + }, + "@types/node": { + "version": "10.12.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.11.tgz", + "integrity": "sha512-3iIOhNiPGTdcUNVCv9e5G7GotfvJJe2pc9w2UgDXlUwnxSZ3RgcUocIU+xYm+rTU54jIKih998QE4dMOyMN1NQ==", + "dev": true + }, + "@types/sizzle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", + "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==", + "dev": true + }, + "popper.js": { + "version": "1.14.6", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz", + "integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA==", + "dev": true + } + } +} diff --git a/src/web/modules/Flowsharp.Web.Management/Assets/package.json b/src/web/modules/Flowsharp.Web.Management/Assets/package.json new file mode 100644 index 000000000..5c3c370e0 --- /dev/null +++ b/src/web/modules/Flowsharp.Web.Management/Assets/package.json @@ -0,0 +1,10 @@ +{ + "private": true, + "version": "1.0.0", + "devDependencies": { + "@types/jquery": "^3.3.22", + "@types/node": "^10.12.2", + "@types/bootstrap": "^4.1.2" + }, + "dependencies": {} +} diff --git a/src/web/modules/Flowsharp.Web.Management/Assets/scripts/workflow-editor.ts b/src/web/modules/Flowsharp.Web.Management/Assets/scripts/workflow-editor.ts new file mode 100644 index 000000000..65c3303ec --- /dev/null +++ b/src/web/modules/Flowsharp.Web.Management/Assets/scripts/workflow-editor.ts @@ -0,0 +1,86 @@ +/// + +namespace Flowsharp { + const win: any = window; + + export class WorkflowEditor { + private workflowDesigner: WorkflowDesigner; + private containerElement: JQuery; + private workflowId: string; + + constructor(container: HTMLElement,) { + this.containerElement = $(container); + + const saveButton = this.containerElement.find('.save-workflow-button'); + const downloadButton = this.containerElement.find('.download-workflow-button'); + + this.workflowDesigner = (window).Flowsharp.workflowDesigner; + this.workflowId = this.containerElement.data('workflow-id'); + + saveButton.on('click', this.onSaveButtonClick); + downloadButton.on('click', this.onDownloadButtonClick); + } + + private onSaveButtonClick = (e: JQuery.Event) => { + e.preventDefault(); + const workflow = this.workflowDesigner.getWorkflow(); + const workflowJson = JSON.stringify(workflow); + + $.ajax({ + url: `/workflows/${this.workflowId}`, + type: 'PUT', + data: workflowJson, + contentType: 'application/json', + dataType: 'json' + }).done(this.displayNotification); + }; + + private onDownloadButtonClick = (e: JQuery.Event) => { + e.preventDefault(); + const workflowJson = this.serializeWorkflow(false); + + $.ajax({ + url: `/workflows/${this.workflowId}/download`, + method: 'POST', + data: workflowJson, + contentType: 'application/json', + xhrFields: { + responseType: 'blob' + }, + success: (data) => { + this.downloadData(data); + } + }); + }; + + private downloadJson = () => { + const workflowJson = this.serializeWorkflow(true); + const data = new Blob([workflowJson], { type: 'text/yaml' }); + this.downloadData(data); + }; + + private downloadData = (data: any) => { + const a = document.createElement('a'); + const url = window.URL.createObjectURL(data); + a.href = url; + a.download = this.workflowId; + a.click(); + window.URL.revokeObjectURL(url); + }; + + private serializeWorkflow = (pretty: boolean): string => { + const workflow = this.workflowDesigner.getWorkflow(); + return JSON.stringify(workflow, null, pretty ? 2 : null); + }; + + private displayNotification = () => { + alert("Saved!"); + }; + } + + $(() => { + $('.workflow-editor-container').each((i, e) => { + const editor = new WorkflowEditor(e); + }); + }); +} \ No newline at end of file diff --git a/src/web/modules/Flowsharp.Web.Management/Controllers/WorkflowsController.cs b/src/web/modules/Flowsharp.Web.Management/Controllers/WorkflowsController.cs index afa612f56..9e9babb24 100644 --- a/src/web/modules/Flowsharp.Web.Management/Controllers/WorkflowsController.cs +++ b/src/web/modules/Flowsharp.Web.Management/Controllers/WorkflowsController.cs @@ -1,34 +1,64 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Flowsharp.Models; using Flowsharp.Persistence; using Flowsharp.Persistence.Specifications; +using Flowsharp.Serialization; +using Flowsharp.Serialization.Extensions; +using Flowsharp.Serialization.Formatters; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json.Linq; +using OrchardCore.Modules; namespace Flowsharp.Web.Management.Controllers { [Route("[controller]")] + [IgnoreAntiforgeryToken] public class WorkflowsController : Controller { private readonly IWorkflowStore workflowStore; + private readonly IWorkflowSerializer workflowSerializer; + private readonly ITokenFormatter tokenFormatter; - public WorkflowsController(IWorkflowStore workflowStore) + public WorkflowsController( + IWorkflowStore workflowStore, + IWorkflowSerializer workflowSerializer, + ITokenFormatter tokenFormatter) { this.workflowStore = workflowStore; + this.workflowSerializer = workflowSerializer; + this.tokenFormatter = tokenFormatter; } public async Task Index(CancellationToken cancellationToken) { - var workflowDefinitions = await workflowStore.GetManyAsync(new AllWorkflows(), cancellationToken); - return View(workflowDefinitions.ToList()); + var workflows = await workflowStore.GetManyAsync(new AllWorkflows(), cancellationToken); + return View(workflows.ToList()); } - [HttpGet("edit/{id}")] + [HttpGet("{id}")] public async Task Edit(string id, CancellationToken cancellationToken) { - var workflowDefinition = await workflowStore.GetAsync(id, cancellationToken); - return View(workflowDefinition); + var workflow = await workflowStore.GetAsync(id, cancellationToken); + return View(workflow); + } + + [HttpPut("{id}")] + public async Task Update(string id, [FromBody]JToken workflowData, CancellationToken cancellationToken) + { + var workflow = await workflowSerializer.DeserializeAsync(workflowData, cancellationToken); + await workflowStore.UpdateAsync(workflow, cancellationToken); + return Ok(workflow); + } + + [HttpPost("{id}/download")] + public IActionResult Download(string id, [FromBody]JToken workflowData, CancellationToken cancellationToken) + { + var data = tokenFormatter.ToString(workflowData); + var bytes = data.ToStream(); + return File(bytes, "application/x-yaml", id); } } } \ No newline at end of file diff --git a/src/web/modules/Flowsharp.Web.Management/Flowsharp.Web.Management.csproj b/src/web/modules/Flowsharp.Web.Management/Flowsharp.Web.Management.csproj index 8a54ae19b..72f4473e3 100644 --- a/src/web/modules/Flowsharp.Web.Management/Flowsharp.Web.Management.csproj +++ b/src/web/modules/Flowsharp.Web.Management/Flowsharp.Web.Management.csproj @@ -5,6 +5,7 @@ + @@ -12,10 +13,10 @@ - - - - + + + + diff --git a/src/web/modules/Flowsharp.Web.Management/Views/Workflows/Edit.cshtml b/src/web/modules/Flowsharp.Web.Management/Views/Workflows/Edit.cshtml index 3e8fd32e8..abef501c8 100644 --- a/src/web/modules/Flowsharp.Web.Management/Views/Workflows/Edit.cshtml +++ b/src/web/modules/Flowsharp.Web.Management/Views/Workflows/Edit.cshtml @@ -1,12 +1,6 @@ @model Workflow - - -
+
@@ -35,18 +29,15 @@
-
Sample Workflow
This workflow is a sample workflow demonstrating core workflow functionality +
@Model.Metadata.Name
@Model.Metadata.Description
@@ -62,17 +53,9 @@
-
-
-
- - -
-
-
- \ No newline at end of file + + + \ No newline at end of file diff --git a/src/web/modules/Flowsharp.Web.Management/assets.json b/src/web/modules/Flowsharp.Web.Management/assets.json new file mode 100644 index 000000000..887fb3ae5 --- /dev/null +++ b/src/web/modules/Flowsharp.Web.Management/assets.json @@ -0,0 +1,8 @@ +[ + { + "inputs": [ + "assets/scripts/workflow-editor.ts" + ], + "output": "wwwroot/workflow-editor.js" + } +] \ No newline at end of file diff --git a/src/web/modules/Flowsharp.Web.OrchardCoreLite/Flowsharp.Web.OrchardCoreLite.csproj b/src/web/modules/Flowsharp.Web.OrchardCoreLite/Flowsharp.Web.OrchardCoreLite.csproj index 800ad2b8c..bc088add1 100644 --- a/src/web/modules/Flowsharp.Web.OrchardCoreLite/Flowsharp.Web.OrchardCoreLite.csproj +++ b/src/web/modules/Flowsharp.Web.OrchardCoreLite/Flowsharp.Web.OrchardCoreLite.csproj @@ -5,12 +5,12 @@ - - - - - - + + + + + + diff --git a/src/web/modules/Flowsharp.Web.OrchardCoreLite/Startup.cs b/src/web/modules/Flowsharp.Web.OrchardCoreLite/Startup.cs index ed61320e9..2ee821237 100644 --- a/src/web/modules/Flowsharp.Web.OrchardCoreLite/Startup.cs +++ b/src/web/modules/Flowsharp.Web.OrchardCoreLite/Startup.cs @@ -23,7 +23,7 @@ namespace Flowsharp.Web.OrchardCoreLite services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddSingleton(); services.AddResourceManagement(); diff --git a/src/web/modules/Flowsharp.Web.Persistence.FileSystem/Flowsharp.Web.Persistence.FileSystem.csproj b/src/web/modules/Flowsharp.Web.Persistence.FileSystem/Flowsharp.Web.Persistence.FileSystem.csproj index 5f9c0ce4d..bf2a86bbf 100644 --- a/src/web/modules/Flowsharp.Web.Persistence.FileSystem/Flowsharp.Web.Persistence.FileSystem.csproj +++ b/src/web/modules/Flowsharp.Web.Persistence.FileSystem/Flowsharp.Web.Persistence.FileSystem.csproj @@ -5,8 +5,8 @@ - - + + diff --git a/src/web/modules/Flowsharp.Web.Persistence.FileSystem/Services/FileSystemWorkflowStore.cs b/src/web/modules/Flowsharp.Web.Persistence.FileSystem/Services/FileSystemWorkflowStore.cs index 51a7e2daf..2075f0961 100644 --- a/src/web/modules/Flowsharp.Web.Persistence.FileSystem/Services/FileSystemWorkflowStore.cs +++ b/src/web/modules/Flowsharp.Web.Persistence.FileSystem/Services/FileSystemWorkflowStore.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Flowsharp.Models; using Flowsharp.Persistence; using Flowsharp.Serialization; +using Flowsharp.Serialization.Extensions; using Flowsharp.Serialization.Formatters; using Flowsharp.Web.Persistence.FileSystem.Extensions; using OrchardCore.FileStorage; @@ -43,15 +45,22 @@ namespace Flowsharp.Web.Persistence.FileSystem.Services throw new System.NotImplementedException(); } - public Task UpdateAsync(Workflow value, CancellationToken cancellationToken) + public async Task UpdateAsync(Workflow value, CancellationToken cancellationToken) { - throw new System.NotImplementedException(); + await fileStore.TryCreateDirectoryAsync("workflows"); + var data = await workflowSerializer.SerializeAsync(value, cancellationToken); + var path = fileStore.Combine("workflows", value.Metadata.Id); + + using (var stream = data.ToStream()) + { + await fileStore.CreateFileFromStream(path, stream, true); + } } private async Task> ListAsync(CancellationToken cancellationToken) { - await fileStore.TryCreateDirectoryAsync("definitions"); - var files = await fileStore.GetDirectoryContentAsync("definitions"); + await fileStore.TryCreateDirectoryAsync("workflows"); + var files = await fileStore.GetDirectoryContentAsync("workflows"); var loadTasks = files.Select(x => LoadWorkflowDefinitionAsync(x, cancellationToken)); return await Task.WhenAll(loadTasks); } diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/package.json b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/package.json index bc80516c6..1ea9c58ee 100644 --- a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/package.json +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/package.json @@ -8,7 +8,6 @@ }, "dependencies": { "decode-html": "^2.0.0", - "jsplumb": "^2.8.3", - "requirejs": "^2.3.6" + "jsplumb": "^2.8.3" } } diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/activity-editor.ts b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/activity-editor.ts index 3155c1018..df0024b7e 100644 --- a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/activity-editor.ts +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/activity-editor.ts @@ -5,7 +5,6 @@ namespace Flowsharp { export class ActivityEditor { - private baseUrl: string; private containerElement: JQuery; private titleElement: JQuery; private contentElement: JQuery; @@ -18,7 +17,6 @@ namespace Flowsharp { this.titleElement = this.containerElement.find('.modal-title'); this.contentElement = this.containerElement.find('.modal-body'); this.formElement = >this.containerElement.find('form'); - this.baseUrl = this.containerElement.data('base-url'); this.formElement.on('submit', this.onFormSubmit); } @@ -29,11 +27,13 @@ namespace Flowsharp { public display = (activityName: string, activity: IActivity): JQuery.Promise => { this.activityName = activityName; this.deferred = jQuery.Deferred(); + const action = activity.id == null ? 'create' : 'edit'; + const activityJson: string = JSON.stringify(activity); $.ajax({ type: 'POST', - url: `/activity/edit/${activityName}`, - data: activity, + url: `/activity/${action}/${activityName}`, + data: activityJson, contentType: 'application/json', dataType: 'html' }).done(this.displayActivity); diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/activity-metadata.ts b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/activity-metadata.ts new file mode 100644 index 000000000..56854e16f --- /dev/null +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/activity-metadata.ts @@ -0,0 +1,5 @@ +namespace Flowsharp { + export interface IActivityMetadata { + customFields: any; + } +} \ No newline at end of file diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/activity.ts b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/activity.ts index dd08284a7..b21e23d48 100644 --- a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/activity.ts +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/activity.ts @@ -1,7 +1,10 @@ +/// /// + namespace Flowsharp { export interface IActivity { id: string; endpoints: IEndpoint[]; + metadata: IActivityMetadata; } } \ No newline at end of file diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/transition.ts b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/connection.ts similarity index 79% rename from src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/transition.ts rename to src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/connection.ts index 31a79cf77..594b88199 100644 --- a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/transition.ts +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/connection.ts @@ -1,5 +1,5 @@ namespace Flowsharp { - export interface ITransition { + export interface IConnection { sourceActivityId: string; sourceOutcomeName: string; destinationActivityId: null; diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-designer-config.ts b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-designer-config.ts index cceafa602..25719a503 100644 --- a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-designer-config.ts +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-designer-config.ts @@ -16,15 +16,16 @@ namespace Flowsharp { } static getSourceEndpointOptions(activityId: any, endpointName: any): any { - const paintColor = '#7ab02c'; + const fill = '#ffeb3b'; + const stroke = '#ffeb3b'; return { endpoint: 'Dot', anchor: 'Continuous', paintStyle: { - stroke: paintColor, - fill: paintColor, + stroke: stroke, + fill: fill, radius: 7, - strokeWidth: 1 + strokeWidth: 5 }, isSource: true, connector: ['Flowchart', {stub: [40, 60], gap: 0, cornerRadius: 5, alwaysRespectStubs: true}], @@ -36,12 +37,12 @@ namespace Flowsharp { outlineWidth: 2 }, hoverPaintStyle: { - fill: '#216477', - stroke: '#216477' + fill: stroke, + stroke: fill }, connectorHoverStyle: { strokeWidth: 3, - stroke: '#216477', + stroke: stroke, outlineWidth: 5, outlineStroke: 'white' }, diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-designer.ts b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-designer.ts index cb74f4c3b..f41603a53 100644 --- a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-designer.ts +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-designer.ts @@ -3,11 +3,14 @@ /// /// /// +/// namespace Flowsharp { export class WorkflowDesigner { private plumber: any; private activityEditor: ActivityEditor; + private dragStart: { left: number; top: number }; + private hasDragged: boolean; constructor(private readonly container: HTMLElement, private readonly activityEditorContainer: HTMLElement) { this.plumber = WorkflowDesignerConfig.createJsPlumbInstance(container); @@ -15,36 +18,66 @@ namespace Flowsharp { this.initializeDropTarget(); this.activityEditor = new ActivityEditor(activityEditorContainer); } - - private static onDragOver = (e: JQuery.Event) => { - e.preventDefault(); + + public getWorkflow = (): IWorkflow => { + const containerElement = $(this.container); + const workflowMetadata: IWorkflowMetadata = containerElement.data('workflow-metadata'); + const workflowStatus: WorkflowStatus = containerElement.data('workflow-status'); + const activityElements = containerElement.find('.activity'); + const activities: IActivity[] = []; + + for (let index = 0; index < activityElements.length; index++) { + const activityElement = $(activityElements[index]); + const activity: IActivity = activityElement.data('activity-model'); + + activities.push(activity); + } + + return { + metadata: workflowMetadata, + activities: activities, + connections: [], + status: workflowStatus + }; }; private editActivity = (activityElement: JQuery) => { const displayText = activityElement.attr('title'); const activityName = activityElement.data('activity-name'); - const activity:IActivity = activityElement.data('activity-model'); + const activity: IActivity = activityElement.data('activity-model'); this.activityEditor.title = `Edit ${displayText}`; this.activityEditor.show(); - this.activityEditor.display(activityName, activity).done(this.updateActivity); + this.activityEditor + .display(activityName, activity) + .done((updatedHtml: string) => this.updateActivityElement(activityElement, updatedHtml)); }; - private addActivity = (activityHtml: string, x: number, y: number) => { + private addActivity = (activityHtml: string) => { const activityElement = $(activityHtml); - - activityElement.css({ x: x, y: y }); + this.container.appendChild(activityElement[0]); this.initializeElement(activityElement); }; - private updateActivity = (activityHtml: string) => { - const activityElement = $(activityHtml); - //this.initializeElement(activityElement); + private updateActivityElement = (activityElement: JQuery, updatedHtml: string) => { + this.plumber.deleteConnectionsForElement(activityElement); + this.plumber.removeAllEndpoints(activityElement); + activityElement.remove(); + + const updatedActivityElement = $(updatedHtml); + this.container.appendChild(updatedActivityElement[0]); + this.initializeElement(updatedActivityElement); + }; + + private updateActivityModel = (activityElement: JQuery, update: (activity: IActivity) => any) => { + const model: IActivity = activityElement.data('activity-model'); + update(model); + activityElement.data('activity-model', model); }; private initializeNodes = () => { this.plumber.batch(() => { - let activityElements = $(this.container).find('.activity'); + const activityElements = $(this.container).find('.activity'); for (let index = 0; index < activityElements.length; index++) { const activityElement = $(activityElements[index]); @@ -60,27 +93,40 @@ namespace Flowsharp { private initializeElement = (activityElement: JQuery) => { const activityId: string = activityElement.data('activity-id'); const activityEndpoints: any[] = activityElement.data('activity-endpoints'); - + this.plumber.draggable(activityElement, { - grid: [10, 10], containment: true, - // start: args => { - // //this.dragStart = {left: args.e.screenX, top: args.e.screenY}; - // }, - // stop: args => { - // //this.hasDragged = this.dragStart.left !== args.e.screenX || this.dragStart.top !== args.e.screenY; - // } + start: (args: any) => { + this.dragStart = {left: args.e.screenX, top: args.e.screenY}; + }, + stop: (args: any) => { + this.hasDragged = this.dragStart.left !== args.e.screenX || this.dragStart.top !== args.e.screenY; + + if (!this.hasDragged) { + return; + } + + this.updateActivityModel(activityElement, activity => { + const designer = activity.metadata.customFields.designer || {x: 0, y: 0}; + jQuery.extend(designer, { + x: args.pos[0], + y: args.pos[1] + }); + activity.metadata.customFields.designer = designer; + }); + } }); - + this.plumber.makeTarget(activityElement, { dropOptions: {hoverClass: 'hover'}, anchor: 'Continuous', endpoint: ['Blank', {radius: 8}] }); - + this.addSourceEndpoints(activityElement[0], activityId, activityEndpoints); - + activityElement.on('dblclick', this.onDoubleClick); + this.plumber.revalidate(activityElement[0]); }; private addSourceEndpoints = (activityElement: HTMLElement, activityId: string, endpoints: any[]) => { @@ -101,6 +147,10 @@ namespace Flowsharp { $(this.container).on('drop', this.onDrop) }; + private static onDragOver = (e: JQuery.Event) => { + e.preventDefault(); + }; + private onDrop = (e: JQuery.Event) => { e.preventDefault(); @@ -109,10 +159,19 @@ namespace Flowsharp { const activityName = activityDescriptor.name; const x = dragEvent.offsetX; const y = dragEvent.offsetY; + const activity: IActivity = { + id: null, + endpoints: [], + metadata: { + customFields: { + designer: {x: x, y: y} + } + } + }; this.activityEditor.title = `Add ${activityDescriptor.displayText}`; this.activityEditor.show(); - this.activityEditor.display(activityName, null).done((data: string) => this.addActivity(data, x, y)); + this.activityEditor.display(activityName, activity).done((data: string) => this.addActivity(data)); }; private onDoubleClick = (e: JQuery.Event) => { @@ -123,11 +182,15 @@ namespace Flowsharp { }; } - $(function () { + $(() => { $('.workflow-designer-container').each((i, e) => { const canvasContainer = $(e).find('.workflow-canvas')[0]; const activityEditorContainer = $(e).find('.activity-editor-modal')[0]; - const editor = new WorkflowDesigner(canvasContainer, activityEditorContainer); + const workflowDesigner = new WorkflowDesigner(canvasContainer, activityEditorContainer); + + const win = window; + win.Flowsharp = win.Flowsharp || {}; + win.Flowsharp.workflowDesigner = workflowDesigner; }); }); } \ No newline at end of file diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-metadata.ts b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-metadata.ts new file mode 100644 index 000000000..d82d46aa0 --- /dev/null +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-metadata.ts @@ -0,0 +1,9 @@ +namespace Flowsharp { + export interface IWorkflowMetadata { + id: string; + parentId: string; + name: string; + description: string; + customFields: any; + } +} \ No newline at end of file diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-status.ts b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-status.ts new file mode 100644 index 000000000..794f4426e --- /dev/null +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-status.ts @@ -0,0 +1,12 @@ +namespace Flowsharp { + export enum WorkflowStatus { + Idle, + Starting, + Resuming, + Executing, + Halted, + Finished, + Faulted, + Aborted + } +} \ No newline at end of file diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow.ts b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow.ts new file mode 100644 index 000000000..6215d259b --- /dev/null +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow.ts @@ -0,0 +1,13 @@ +/// +/// +/// +/// + +namespace Flowsharp { + export interface IWorkflow { + metadata: IWorkflowMetadata; + activities: IActivity[]; + connections: IConnection[]; + status: WorkflowStatus; + } +} \ No newline at end of file diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Controllers/ActivityController.cs b/src/web/modules/Flowsharp.Web.ViewComponents/Controllers/ActivityController.cs index 8452756ee..5a22bdd58 100644 --- a/src/web/modules/Flowsharp.Web.ViewComponents/Controllers/ActivityController.cs +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Controllers/ActivityController.cs @@ -25,8 +25,8 @@ namespace Flowsharp.Web.ViewComponents.Controllers private readonly IActivityShapeFactory activityShapeFactory; public ActivityController( - IActivityDisplayManager displayManager, - IActivityLibrary activityLibrary, + IActivityDisplayManager displayManager, + IActivityLibrary activityLibrary, IActivityShapeFactory activityShapeFactory) { this.displayManager = displayManager; @@ -34,15 +34,32 @@ namespace Flowsharp.Web.ViewComponents.Controllers this.activityShapeFactory = activityShapeFactory; } + [HttpPost("create/{activityName}")] + public async Task Create(string activityName, [FromBody] JToken json, CancellationToken cancellationToken) + { + var activityDescriptor = await activityLibrary.GetActivityByNameAsync(activityName, cancellationToken); + var activityModel = activityDescriptor.InstantiateActivity(json); + + activityModel.Id = Guid.NewGuid().ToString(); + + var editorShape = await displayManager.BuildEditorAsync(activityModel, this, false); + var model = new ActivityEditorViewModel + { + ActivityJson = JsonConvert.SerializeObject(activityModel), + ActivityEditorShape = editorShape, + }; + return View("Edit", model); + } + [HttpPost("edit/{activityName}")] - public async Task Edit(string activityName, [FromBody] string json, CancellationToken cancellationToken) + public async Task Edit(string activityName, [FromBody] JToken json, CancellationToken cancellationToken) { var activityDescriptor = await activityLibrary.GetActivityByNameAsync(activityName, cancellationToken); var activityModel = activityDescriptor.InstantiateActivity(json); var editorShape = await displayManager.BuildEditorAsync(activityModel, this, false); var model = new ActivityEditorViewModel { - ActivityJson = json, + ActivityJson = json.ToString(), ActivityEditorShape = editorShape, }; return View(model); diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Flowsharp.Web.ViewComponents.csproj b/src/web/modules/Flowsharp.Web.ViewComponents/Flowsharp.Web.ViewComponents.csproj index 195fa4e5b..881f553e4 100644 --- a/src/web/modules/Flowsharp.Web.ViewComponents/Flowsharp.Web.ViewComponents.csproj +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Flowsharp.Web.ViewComponents.csproj @@ -5,9 +5,9 @@ - - - + + + @@ -17,4 +17,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Services/ActivityShapeFactory.cs b/src/web/modules/Flowsharp.Web.ViewComponents/Services/ActivityShapeFactory.cs index 5163d4384..d507dc9a2 100644 --- a/src/web/modules/Flowsharp.Web.ViewComponents/Services/ActivityShapeFactory.cs +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Services/ActivityShapeFactory.cs @@ -1,9 +1,11 @@ +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using Flowsharp.Extensions; using Flowsharp.Web.Abstractions.Services; using Flowsharp.Web.ViewComponents.Models; +using Newtonsoft.Json.Linq; using OrchardCore.DisplayManagement; namespace Flowsharp.Web.ViewComponents.Services @@ -23,7 +25,7 @@ namespace Flowsharp.Web.ViewComponents.Services { dynamic shape = await displayManager.BuildDisplayAsync(activity, null, "Design"); var customFields = activity.Metadata.CustomFields; - var designerMetadata = customFields.Get("Designer", () => new ActivityDesignerMetadata()); + var designerMetadata = customFields.GetValue("Designer", StringComparison.OrdinalIgnoreCase)?.ToObject() ?? new ActivityDesignerMetadata(); var descriptor = await activityLibrary.GetActivityByNameAsync(activity.Name, CancellationToken.None); shape.Metadata.Type = $"Activity_Design"; diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/ViewComponents/ActivityLibrary.cs b/src/web/modules/Flowsharp.Web.ViewComponents/ViewComponents/ActivityLibrary.cs index f5c6195a1..542dbb3ab 100644 --- a/src/web/modules/Flowsharp.Web.ViewComponents/ViewComponents/ActivityLibrary.cs +++ b/src/web/modules/Flowsharp.Web.ViewComponents/ViewComponents/ActivityLibrary.cs @@ -30,12 +30,12 @@ namespace Flowsharp.Web.ViewComponents.ViewComponents return View(viewModel); } - private async Task> BuildActivityShapesAsync(IEnumerable descriptors) + private async Task> BuildActivityShapesAsync(IEnumerable descriptors) { return await Task.WhenAll(descriptors.Select(BuildActivityShapeAsync)); } - private async Task BuildActivityShapeAsync(Flowsharp.Models.ActivityDescriptor descriptor) + private async Task BuildActivityShapeAsync(ActivityDescriptor descriptor) { var activityShape = await shapeFactory.CreateAsync("Activity_Card", () => Task.FromResult(new ActivityDescriptorViewModel(descriptor))); activityShape.Metadata.Alternates.Add($"Activity_Card__{descriptor.Name}"); diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/ViewModels/ActivityDescriptorViewModel.cs b/src/web/modules/Flowsharp.Web.ViewComponents/ViewModels/ActivityDescriptorViewModel.cs index ebc4acad5..726d1e491 100644 --- a/src/web/modules/Flowsharp.Web.ViewComponents/ViewModels/ActivityDescriptorViewModel.cs +++ b/src/web/modules/Flowsharp.Web.ViewComponents/ViewModels/ActivityDescriptorViewModel.cs @@ -3,9 +3,9 @@ using OrchardCore.DisplayManagement.Views; namespace Flowsharp.Web.ViewComponents.ViewModels { - public class ActivityDescriptorViewModel : ShapeViewModel + public class ActivityDescriptorViewModel : ShapeViewModel { - public ActivityDescriptorViewModel(Flowsharp.Models.ActivityDescriptor value) : base(value) + public ActivityDescriptorViewModel(ActivityDescriptor value) : base(value) { } } diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Views/Activity.Design.cshtml b/src/web/modules/Flowsharp.Web.ViewComponents/Views/Activity.Design.cshtml index 3f4a3d3ac..0145561a0 100644 --- a/src/web/modules/Flowsharp.Web.ViewComponents/Views/Activity.Design.cshtml +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Views/Activity.Design.cshtml @@ -1,8 +1,3 @@ -@using Flowsharp.Web.ViewComponents.Models -@using Microsoft.Extensions.Localization -@using Newtonsoft.Json -@using Newtonsoft.Json.Serialization -@using IActivity = Flowsharp.IActivity @model dynamic @{ var activity = (IActivity)Model.Activity; diff --git a/src/web/modules/Flowsharp.Web.ViewComponents/Views/Shared/Components/WorkflowDesigner/Default.cshtml b/src/web/modules/Flowsharp.Web.ViewComponents/Views/Shared/Components/WorkflowDesigner/Default.cshtml index a016b67a7..30eac58a4 100644 --- a/src/web/modules/Flowsharp.Web.ViewComponents/Views/Shared/Components/WorkflowDesigner/Default.cshtml +++ b/src/web/modules/Flowsharp.Web.ViewComponents/Views/Shared/Components/WorkflowDesigner/Default.cshtml @@ -1,13 +1,16 @@ @model WorkflowDesignerViewModel - +@{ + var serializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }; + var workflowMetadataJson = JsonConvert.SerializeObject(Model.Workflow.Metadata, serializerSettings); +}
-
+
@foreach (var activityShape in Model.ActivityShapes) { @await DisplayAsync(activityShape) }
-