This commit is contained in:
Sipke Schoorstra 2018-12-24 12:31:18 +01:00
parent 5a6ed376f2
commit 8fd519e22a
50 changed files with 478 additions and 222 deletions

View file

@ -47,8 +47,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flowsharp.Web.Persistence.F
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flowsharp.Persistence.FileSystem", "src\core\Flowsharp.Persistence.FileSystem\Flowsharp.Persistence.FileSystem.csproj", "{1579117E-229F-4E48-812B-7A235A4DB7E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flowsharp.Web.OrchardCoreLite", "src\web\modules\Flowsharp.Web.OrchardCoreLite\Flowsharp.Web.OrchardCoreLite.csproj", "{A8DE5D84-6445-43AC-B0D9-EE5E9FB2E900}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flowsharp.Web.Abstractions", "src\web\modules\Flowsharp.Web.Abstractions\Flowsharp.Web.Abstractions.csproj", "{E04F98D6-F6B3-475F-BCAA-F0D586EE3965}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Activities", "Activities", "{9DEEA8AF-736A-4B53-9EC8-48E19C466984}"
@ -117,10 +115,6 @@ Global
{1579117E-229F-4E48-812B-7A235A4DB7E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1579117E-229F-4E48-812B-7A235A4DB7E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1579117E-229F-4E48-812B-7A235A4DB7E5}.Release|Any CPU.Build.0 = Release|Any CPU
{A8DE5D84-6445-43AC-B0D9-EE5E9FB2E900}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8DE5D84-6445-43AC-B0D9-EE5E9FB2E900}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8DE5D84-6445-43AC-B0D9-EE5E9FB2E900}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8DE5D84-6445-43AC-B0D9-EE5E9FB2E900}.Release|Any CPU.Build.0 = Release|Any CPU
{E04F98D6-F6B3-475F-BCAA-F0D586EE3965}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E04F98D6-F6B3-475F-BCAA-F0D586EE3965}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E04F98D6-F6B3-475F-BCAA-F0D586EE3965}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -170,7 +164,6 @@ Global
{71687B0E-37D1-4AEB-A620-1F884CAC4C8E} = {DE9B2BDA-F1C3-44EB-9B13-9A59A14F2018}
{7A30960A-1B95-4564-B2C8-2DFDDB06C594} = {DE9B2BDA-F1C3-44EB-9B13-9A59A14F2018}
{1579117E-229F-4E48-812B-7A235A4DB7E5} = {35F44BE9-13D0-417C-A01A-F0787BEE6DC3}
{A8DE5D84-6445-43AC-B0D9-EE5E9FB2E900} = {DE9B2BDA-F1C3-44EB-9B13-9A59A14F2018}
{9DEEA8AF-736A-4B53-9EC8-48E19C466984} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}
{E7DE9E53-2CF0-4F45-BF82-C470A19F0CF7} = {9DEEA8AF-736A-4B53-9EC8-48E19C466984}
{B98B80CB-D3A3-437C-A61C-ED82C8C77B2F} = {9DEEA8AF-736A-4B53-9EC8-48E19C466984}

View file

@ -12,7 +12,7 @@ namespace Flowsharp.Activities.Console.Handlers
public class ReadLineHandler : ActivityHandler<ReadLine>
{
private readonly TextReader input;
public ReadLineHandler(IStringLocalizer<ReadLineHandler> localizer)
{
T = localizer;
@ -24,18 +24,21 @@ namespace Flowsharp.Activities.Console.Handlers
}
private IStringLocalizer<ReadLineHandler> T { get; }
protected override LocalizedString GetEndpoint() => T["Done"];
public override LocalizedString Category => T["Console"];
public override LocalizedString DisplayText => T["Read Line"];
public override LocalizedString Description => T["Read a line from the console"];
protected override async Task<ActivityExecutionResult> OnExecuteAsync(ReadLine activity, WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
if (input == null)
if (input == null)
return Halt();
var value = await input.ReadLineAsync();
workflowContext.SetLastResult(value);
return TriggerEndpoint("Done");
}
protected override ActivityExecutionResult OnResume(ReadLine activity, WorkflowExecutionContext workflowContext)

View file

@ -33,6 +33,10 @@ namespace Flowsharp.Activities.Console.Handlers
protected override LocalizedString GetEndpoint() => T["Done"];
public override LocalizedString Category => T["Console"];
public override LocalizedString DisplayText => T["Write Line"];
public override LocalizedString Description => T["Write a line to the console"];
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WriteLine activity, WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
var text = await evaluator.EvaluateAsync(activity.TextExpression, workflowContext, cancellationToken);

View file

@ -0,0 +1,10 @@
using Flowsharp.Expressions;
using Flowsharp.Models;
namespace Flowsharp.Activities.Primitives.Activities
{
public class ForEach : Activity
{
}
}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>

View file

@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Activities.Primitives.Activities;
using Flowsharp.Handlers;
using Flowsharp.Models;
using Flowsharp.Results;
using Microsoft.Extensions.Localization;
namespace Flowsharp.Activities.Primitives.Handlers
{
public class ForEachHandler : ActivityHandler<ForEach>
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
public ForEachHandler(IStringLocalizer<ForEachHandler> localizer, IWorkflowExpressionEvaluator expressionEvaluator)
{
T = localizer;
this.expressionEvaluator = expressionEvaluator;
}
public override LocalizedString Category => T["Control Flow"];
public override LocalizedString DisplayText => T["For Each"];
public override LocalizedString Description => T["Iterate over a list of items."];
public IStringLocalizer<ForEachHandler> T { get; }
public override IEnumerable<LocalizedString> GetEndpoints() => Endpoints(T["Next"], T["Done"]);
protected override async Task<ActivityExecutionResult> OnExecuteAsync(ForEach activity, WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
return TriggerEndpoint("Done");
}
}
}

View file

@ -19,6 +19,9 @@ namespace Flowsharp.Activities.Primitives.Handlers
this.expressionEvaluator = expressionEvaluator;
}
public override LocalizedString Category => T["Control Flow"];
public override LocalizedString DisplayText => T["If/Else Branch"];
public override LocalizedString Description => T["Evaluate a boolean condition and continues execution based on the outcome."];
public IStringLocalizer<IfElseHandler> T { get; }
public override IEnumerable<LocalizedString> GetEndpoints() => Endpoints(T["True"], T["False"]);

View file

@ -18,6 +18,9 @@ namespace Flowsharp.Activities.Primitives.Handlers
this.expressionEvaluator = expressionEvaluator;
}
public override LocalizedString Category => T["Primitives"];
public override LocalizedString DisplayText => T["Set Variable"];
public override LocalizedString Description => T["Set a custom variable on the workflow."];
public IStringLocalizer<SetVariableHandler> T { get; }
protected override LocalizedString GetEndpoint() => T["Done"];

View file

@ -0,0 +1,18 @@
using System.Collections.Generic;
using Microsoft.Extensions.Localization;
namespace Flowsharp.Comparers
{
public class LocalizedStringEqualityComparer : IEqualityComparer<LocalizedString>
{
public bool Equals(LocalizedString x, LocalizedString y)
{
return x.Name.Equals(y.Name);
}
public int GetHashCode(LocalizedString obj)
{
return obj.Name.GetHashCode();
}
}
}

View file

@ -0,0 +1,19 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Comparers;
using Microsoft.Extensions.Localization;
namespace Flowsharp.Extensions
{
public static class ActivityLibraryExtensions
{
public static async Task<IEnumerable<LocalizedString>> GetCategoriesAsync(this IActivityLibrary activityLibrary, CancellationToken cancellationToken)
{
var descriptors = await activityLibrary.GetActivitiesAsync(cancellationToken);
return descriptors.Select(x => x.Category).Distinct(new LocalizedStringEqualityComparer()).OrderBy(x => x.Value);
}
}
}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>

View file

@ -11,6 +11,7 @@ namespace Flowsharp.Handlers
public abstract class ActivityHandlerBase<T> : IActivityHandler where T : IActivity
{
public Type ActivityType => typeof(T);
public abstract LocalizedString Category { get; }
public virtual LocalizedString DisplayText => new LocalizedString(ActivityType.Name, ActivityType.Name);
public virtual LocalizedString Description => new LocalizedString("", "");
public Task<bool> CanExecuteAsync(ActivityExecutionContext activityContext, WorkflowExecutionContext workflowContext, CancellationToken cancellationToken) => OnCanExecuteAsync((ActivityExecutionContext<T>) activityContext, workflowContext, cancellationToken);

View file

@ -14,6 +14,11 @@ namespace Flowsharp
/// The type of activity handled by this handler.
/// </summary>
Type ActivityType { get; }
/// <summary>
/// The category of the activity.
/// </summary>
LocalizedString Category { get; }
/// <summary>
/// The friendly name of the activity.

View file

@ -10,6 +10,7 @@ namespace Flowsharp.Models
public class ActivityDescriptor
{
public string Name { get; set; }
public LocalizedString Category { get; set; }
public Type ActivityType { get; set; }
public LocalizedString DisplayText { get; set; }
public LocalizedString Description { get; set; }

View file

@ -20,6 +20,7 @@ namespace Flowsharp.ActivityProviders
var descriptors = handlers.Select(x => new ActivityDescriptor
{
Name = x.ActivityType.Name,
Category = x.Category,
ActivityType = x.ActivityType,
DisplayText = x.DisplayText,
Description = x.Description,

View file

@ -9,6 +9,13 @@ namespace Flowsharp.Handlers
{
public class UnknownActivityHandler : ActivityHandler<UnknownActivity>
{
public UnknownActivityHandler(IStringLocalizer<UnknownActivityHandler> localizer)
{
T = localizer;
}
public IStringLocalizer<UnknownActivityHandler> T { get; }
public override LocalizedString Category => T["System"];
public override IEnumerable<LocalizedString> GetEndpoints() => Enumerable.Empty<LocalizedString>();
protected override ActivityExecutionResult OnExecute(UnknownActivity activity, WorkflowExecutionContext workflowContext)

View file

@ -4,7 +4,7 @@
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="OrchardCore.Application.Mvc.Targets" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.Resources" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.Settings" Version="1.0.0-beta3-69807" />
@ -43,11 +43,9 @@
<ProjectReference Include="..\modules\Flowsharp.Web.Activities.Console\Flowsharp.Web.Activities.Console.csproj" />
<ProjectReference Include="..\modules\Flowsharp.Web.Activities.Primitives\Flowsharp.Web.Activities.Primitives.csproj" />
<ProjectReference Include="..\modules\Flowsharp.Web.Management\Flowsharp.Web.Management.csproj" />
<ProjectReference Include="..\modules\Flowsharp.Web.OrchardCoreLite\Flowsharp.Web.OrchardCoreLite.csproj" />
<ProjectReference Include="..\modules\Flowsharp.Web.Persistence.FileSystem\Flowsharp.Web.Persistence.FileSystem.csproj" />
<ProjectReference Include="..\modules\Flowsharp.Web.ViewComponents\Flowsharp.Web.ViewComponents.csproj" />
<ProjectReference Include="..\themes\Flowsharp.Themes.Bootstrap\Flowsharp.Themes.Bootstrap.csproj" />
<ProjectReference Include="..\themes\TheAdminatorTheme\TheAdminatorTheme.csproj" />
</ItemGroup>
</Project>

View file

@ -1,6 +1,6 @@
using System.Linq;
using System.Reflection;
using Flowsharp.Web.OrchardCoreLite.Extensions;
using Flowsharp.Web.Management.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

View file

@ -0,0 +1,9 @@
using Flowsharp.Activities.Primitives.Activities;
using Flowsharp.Web.Abstractions.Drivers;
namespace Flowsharp.Web.Activities.Primitives.Drivers
{
public class ForEachDriver : ActivityDisplayDriver<ForEach>
{
}
}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>

View file

@ -14,6 +14,7 @@ namespace Flowsharp.Web.Activities.Primitives
{
services
.AddActivity<IfElseHandler, IfElseDriver>()
.AddActivity<ForEachHandler, ForEachDriver>()
.AddActivity<UnknownActivityHandler, UnknownActivityDriver>();
}
}

View file

@ -1,12 +1,11 @@
///<reference path="../../../Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/workflow-designer.ts"/>
///<reference path="../../../Flowsharp.Web.ViewComponents/Assets/scripts/flowsharp/activity-picker.ts"/>
namespace Flowsharp {
const win: any = window;
export class WorkflowEditor {
private workflowDesigner: WorkflowDesigner;
private containerElement: JQuery<HTMLElement>;
private workflowId: string;
private readonly workflowId: string;
private readonly workflowDesigner: WorkflowDesigner;
private readonly containerElement: JQuery<HTMLElement>;
constructor(container: HTMLElement,) {
this.containerElement = $(container);
@ -14,14 +13,15 @@ namespace Flowsharp {
const saveButton = this.containerElement.find('.save-workflow-button');
const downloadButton = this.containerElement.find('.download-workflow-button');
this.workflowDesigner = (<any>window).Flowsharp.workflowDesigner;
//this.workflowDesigner = (<any>window).Flowsharp.workflowDesigner;
this.workflowDesigner = new WorkflowDesigner($('.workflow-designer-container')[0]);
this.workflowId = this.containerElement.data('workflow-id');
saveButton.on('click', this.onSaveButtonClick);
downloadButton.on('click', this.onDownloadButtonClick);
saveButton.on('click', this.onSaveClick);
downloadButton.on('click', this.onDownloadClick);
}
private onSaveButtonClick = (e: JQuery.Event) => {
private onSaveClick = (e: JQuery.Event) => {
e.preventDefault();
const workflow = this.workflowDesigner.getWorkflow();
const workflowJson = JSON.stringify(workflow);
@ -35,7 +35,7 @@ namespace Flowsharp {
}).done(this.displayNotification);
};
private onDownloadButtonClick = (e: JQuery.Event) => {
private onDownloadClick = (e: JQuery.Event) => {
e.preventDefault();
const workflowJson = this.serializeWorkflow(false);

View file

@ -1,7 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.Environment.Shell;
namespace Flowsharp.Web.OrchardCoreLite.Extensions
namespace Flowsharp.Web.Management.Extensions
{
public static class OrchardCoreServiceCollectionExtensions
{
@ -11,7 +11,6 @@ namespace Flowsharp.Web.OrchardCoreLite.Extensions
services
.AddOrchardCore()
.AddMvc()
.AddTenantFeatures("Flowsharp.Web.OrchardCoreLite")
.WithTenants()
.AddTheming()
.AddCaching();

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
@ -13,10 +13,14 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="OrchardCore" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.FileStorage.FileSystem" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.Mvc.Core" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.ResourceManagement" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.Infrastructure" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.Settings" Version="1.0.0-beta3-69807" />
</ItemGroup>
<ItemGroup>

View file

@ -3,7 +3,7 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
using OrchardCore.Settings;
namespace Flowsharp.Web.OrchardCoreLite.Services
namespace Flowsharp.Web.Management.Services
{
public class NullSiteService : ISiteService
{

View file

@ -1,8 +1,32 @@
using Flowsharp.Web.Management.Services;
using Flowsharp.Web.Management.Theming;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.DisplayManagement.Theming;
using OrchardCore.Environment.Shell;
using OrchardCore.Modules;
using OrchardCore.ResourceManagement.TagHelpers;
using OrchardCore.Settings;
namespace Flowsharp.Web.Management
{
public class Startup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IShellFeaturesManager, ShellFeaturesManager>();
services.AddScoped<IShellDescriptorFeaturesManager, ShellDescriptorFeaturesManager>();
services.AddScoped<IShellStateManager, NullShellStateManager>();
services.AddScoped<IThemeSelector, SettingsThemeSelector>();
services.AddSingleton<ISiteService, NullSiteService>();
services.AddResourceManagement();
services
.AddTagHelpers<LinkTagHelper>()
.AddTagHelpers<MetaTagHelper>()
.AddTagHelpers<ResourcesTagHelper>()
.AddTagHelpers<ScriptTagHelper>()
.AddTagHelpers<StyleTagHelper>();
}
}
}

View file

@ -2,7 +2,7 @@
using OrchardCore.DisplayManagement.Theming;
using OrchardCore.Environment.Shell;
namespace Flowsharp.Web.OrchardCoreLite.Theming
namespace Flowsharp.Web.Management.Theming
{
public class SettingsThemeSelector : IThemeSelector
{

View file

@ -3,10 +3,9 @@
<div class="workflow-editor-container" data-workflow-id="@Model.Metadata.Id">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">@T["Edit {0}", Model.Metadata.Name]</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group mr-2">
<button type="button" class="btn btn-sm btn-primary">Save</button>
</div>
<div class="btn-toolbar mb-2 mb-md-1">
<button type="button" class="btn btn-sm btn-primary">Add Activity</button>
<button type="button" class="btn btn-sm btn-secondary">Save</button>
</div>
</div>
@await Component.InvokeAsync("WorkflowDesigner", new { workflowId = Model.Metadata.Id })

View file

@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OrchardCore" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.ResourceManagement" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.Infrastructure" Version="1.0.0-beta3-69807" />
<PackageReference Include="OrchardCore.Settings" Version="1.0.0-beta3-69807" />
</ItemGroup>
</Project>

View file

@ -1,6 +0,0 @@
using OrchardCore.Modules.Manifest;
[assembly: Module(
Name = "Orchard Core Integration",
Description = "An integration module that registers lightweight implementations of certain Orchard Core abstractions to enable theming and modules without depending on services such as admin."
)]

View file

@ -1,42 +0,0 @@
using System;
using Flowsharp.Web.OrchardCoreLite.Services;
using Flowsharp.Web.OrchardCoreLite.Theming;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.DisplayManagement.Theming;
using OrchardCore.Environment.Shell;
using OrchardCore.Modules;
using OrchardCore.ResourceManagement.TagHelpers;
using OrchardCore.Settings;
using LinkTagHelper = Microsoft.AspNetCore.Mvc.TagHelpers.LinkTagHelper;
using ScriptTagHelper = Microsoft.AspNetCore.Mvc.TagHelpers.ScriptTagHelper;
namespace Flowsharp.Web.OrchardCoreLite
{
public class Startup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IShellFeaturesManager, ShellFeaturesManager>();
services.AddScoped<IShellDescriptorFeaturesManager, ShellDescriptorFeaturesManager>();
services.AddScoped<IShellStateManager, NullShellStateManager>();
services.AddScoped<IThemeSelector, SettingsThemeSelector>();
services.AddSingleton<ISiteService, NullSiteService>();
services.AddResourceManagement();
services
.AddTagHelpers<LinkTagHelper>()
.AddTagHelpers<MetaTagHelper>()
.AddTagHelpers<ResourcesTagHelper>()
.AddTagHelpers<ScriptTagHelper>()
.AddTagHelpers<StyleTagHelper>();
}
public override void Configure(IApplicationBuilder builder, IRouteBuilder routes, IServiceProvider serviceProvider)
{
}
}
}

View file

@ -1,31 +0,0 @@
///<reference path='../../node_modules/@types/jquery/index.d.ts' />
///<reference path='../decode-html/decode-html.ts' />
namespace Flowsharp {
export class ActivityLibrary {
private readonly activities: JQuery<HTMLElement>;
constructor(private container: HTMLElement) {
this.activities = $(container).find('.activity');
this.initialize();
}
private static onDragStart(e: JQuery.Event) {
const element: JQuery<HTMLElement> = $(<HTMLElement>e.target);
const activityDescriptorJson: string = decode.decodeHTMLEntities(element.data('activity-descriptor-json'));
const dragEvent = <DragEvent>e.originalEvent;
dragEvent.dataTransfer.setData('activity-descriptor-json', activityDescriptorJson);
}
private initialize() {
this.activities.on('dragstart', ActivityLibrary.onDragStart)
}
}
$(function () {
const activityLibraryContainer: HTMLDivElement = <HTMLDivElement>document.getElementById('flowsharp-activity-library');
const activityLibrary = new ActivityLibrary(activityLibraryContainer);
});
}

View file

@ -0,0 +1,88 @@
///<reference path='../../node_modules/@types/jquery/index.d.ts' />
///<reference path='../../node_modules/@types/bootstrap/index.d.ts' />
namespace Flowsharp {
export class ActivityPicker {
private readonly container: JQuery<HTMLElement>;
constructor(private containerElement: HTMLElement) {
this.container = $(containerElement);
this.container.find('a[data-activity-name]').on('click', this.onAddActivityClick);
this.container.find('.activity-picker-categories').on('click', '.nav-link', e => {
this.applyFilter($(e.target).attr('href').substr(1), null);
});
this.container.find('.modal-activities input[type=search]').on('keyup', e => {
this.applyFilter(null, <string>$(e.target).val());
});
this.container.on('show.bs.modal', (event) => {
const button = $(event.relatedTarget); // Button that triggered the modal.
const title = button.data('picker-title');
const type = button.data('activity-type');
const modal = this.container;
modal.find('[href="#all"]').click();
modal.find('.modal-title').text(title);
modal.data('activity-type', type);
this.applyFilter(null, null);
})
}
public show = () => this.container.modal('show');
public hide = () => this.container.modal('hide');
private onAddActivityClick = (e: JQuery.Event): void => {
e.preventDefault();
var activityName: string = $(e.target).data('activity-name');
const event = new CustomEvent("activity-selected", {detail: {activityName: activityName}});
this.container[0].dispatchEvent(event);
};
private applyFilter = (category: string, q: string) => {
const type = this.container.data('activity-type');
category = category || $('.activity-picker-categories .nav-link.active').attr('href').substr(1);
q = q || <string>this.container.find('input[type=search]').val();
const $cards = $('.activity.card').show();
// Remove activities whose type doesn't match the configured activity type.
$cards.filter((i, el) => {
return $(el).data('activity-type') != type;
}).hide();
if (q.length > 0) {
// Remove activities whose title doesn't match the query.
$cards.filter((i, el) => {
return $(el).find('.card-title').text().toLowerCase().indexOf(q.toLowerCase()) < 0 && q && q.length > 0;
}).hide();
} else {
// Remove activities whose category doesn't match the selected one.
$cards.filter((i, el) => {
return $(el).data('category').toLowerCase() != category.toLowerCase() && category.toLowerCase() != 'all';
}).hide();
}
// Show or hide categories based on whether there are any available activities.
$('.activity-picker-categories [data-category]').each((i, el) => {
const categoryListItem = $(el);
const category = categoryListItem.data('category');
// Count number of activities within this category and for the specified activity type (Event or Task).
const activityCount = $(`.activity.card[data-category='${category}'][data-activity-type='${type}']`).length;
activityCount == 0 ? categoryListItem.hide() : categoryListItem.show();
});
};
}
$(function () {
const activityPickerContainer: HTMLDivElement = <HTMLDivElement>document.getElementById('flowsharp-activity-picker');
const activityPicker = new ActivityPicker(activityPickerContainer);
const win = <any>window;
win.Flowsharp = win.Flowsharp || {};
win.Flowsharp.activityPicker = activityPicker;
});
}

View file

@ -7,23 +7,27 @@
namespace Flowsharp {
export class WorkflowDesigner {
private readonly container: JQuery<HTMLElement>;
private readonly canvasContainer: JQuery<HTMLElement>;
private readonly plumber: any;
private readonly 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);
constructor(containerElement: HTMLElement) {
this.container = $(containerElement);
this.canvasContainer = this.container.find('.workflow-canvas');
const activityEditorContainer = this.container.find('.activity-editor-modal');
this.plumber = WorkflowDesignerConfig.createJsPlumbInstance(containerElement);
this.initializeNodes();
this.initializeDropTarget();
this.activityEditor = new ActivityEditor(activityEditorContainer);
this.activityEditor = new ActivityEditor(activityEditorContainer[0]);
}
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 workflowMetadata: IWorkflowMetadata = this.canvasContainer.data('workflow-metadata');
const workflowStatus: WorkflowStatus = this.canvasContainer.data('workflow-status');
const activityElements = this.canvasContainer.find('.activity');
const activities: IActivity[] = [];
const connections: IConnection[] = [];
@ -73,7 +77,7 @@ namespace Flowsharp {
private addActivity = (activityHtml: string) => {
const activityElement = $(activityHtml);
this.container.appendChild(activityElement[0]);
this.canvasContainer[0].appendChild(activityElement[0]);
this.initializeElement(activityElement);
};
@ -83,7 +87,7 @@ namespace Flowsharp {
activityElement.remove();
const updatedActivityElement = $(updatedHtml);
this.container.appendChild(updatedActivityElement[0]);
this.canvasContainer[0].appendChild(updatedActivityElement[0]);
this.initializeElement(updatedActivityElement);
};
@ -95,7 +99,7 @@ namespace Flowsharp {
private initializeNodes = () => {
this.plumber.batch(() => {
const activityElements = $(this.container).find('.activity');
const activityElements = $(this.canvasContainer).find('.activity');
for (let index = 0; index < activityElements.length; index++) {
const activityElement = $(activityElements[index]);
@ -147,7 +151,7 @@ namespace Flowsharp {
};
private createConnections = () => {
const connections: IConnection[] = $(this.container).data('workflow-connections');
const connections: IConnection[] = $(this.canvasContainer).data('workflow-connections');
const plumber = this.plumber;
for (let connection of connections) {
@ -175,8 +179,8 @@ namespace Flowsharp {
};
private initializeDropTarget = () => {
$(this.container).on('dragover', WorkflowDesigner.onDragOver);
$(this.container).on('drop', this.onDrop)
$(this.canvasContainer).on('dragover', WorkflowDesigner.onDragOver);
$(this.canvasContainer).on('drop', this.onDrop)
};
private static onDragOver = (e: JQuery.Event) => {
@ -213,15 +217,15 @@ namespace Flowsharp {
};
}
$(() => {
$('.workflow-designer-container').each((i, e) => {
const canvasContainer = $(e).find('.workflow-canvas')[0];
const activityEditorContainer = $(e).find('.activity-editor-modal')[0];
const workflowDesigner = new WorkflowDesigner(canvasContainer, activityEditorContainer);
const win = <any>window;
win.Flowsharp = win.Flowsharp || {};
win.Flowsharp.workflowDesigner = workflowDesigner;
});
});
// $(() => {
// $('.workflow-designer-container').each((i, e) => {
// const canvasContainer = $(e).find('.workflow-canvas')[0];
// const activityEditorContainer = $(e).find('.activity-editor-modal')[0];
// const workflowDesigner = new WorkflowDesigner(canvasContainer, activityEditorContainer);
//
// const win = <any>window;
// win.Flowsharp = win.Flowsharp || {};
// win.Flowsharp.workflowDesigner = workflowDesigner;
// });
// });
}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
@ -17,4 +17,8 @@
<ProjectReference Include="..\Flowsharp.Web.Abstractions\Flowsharp.Web.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<RazorGenerate Remove="Views\Shared\Components\ActivityLibrary\Default.cshtml" />
</ItemGroup>
</Project>

View file

@ -3,6 +3,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Extensions;
using Flowsharp.Models;
using Flowsharp.Web.Abstractions.Services;
using Flowsharp.Web.ViewComponents.Models;
using Newtonsoft.Json.Linq;

View file

@ -2,19 +2,24 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Extensions;
using Flowsharp.Models;
using Flowsharp.Persistence;
using Flowsharp.Web.Abstractions.Services;
using Flowsharp.Web.ViewComponents.Models;
using Flowsharp.Web.ViewComponents.ViewModels;
using Microsoft.AspNetCore.Mvc;
using OrchardCore.DisplayManagement;
namespace Flowsharp.Web.ViewComponents.ViewComponents
{
public class ActivityLibrary : ViewComponent
public class ActivityPicker : ViewComponent
{
private readonly IActivityLibrary activityLibrary;
private readonly IShapeFactory shapeFactory;
public ActivityLibrary(IActivityLibrary activityLibrary,
public ActivityPicker(
IActivityLibrary activityLibrary,
IShapeFactory shapeFactory)
{
this.activityLibrary = activityLibrary;
@ -23,24 +28,22 @@ namespace Flowsharp.Web.ViewComponents.ViewComponents
public async Task<IViewComponentResult> InvokeAsync(CancellationToken cancellationToken)
{
var descriptors = await activityLibrary.GetActivitiesAsync(cancellationToken);
var activityShapes = await BuildActivityShapesAsync(descriptors);
var viewModel = new ActivityLibraryViewModel(activityShapes);
var activities = await activityLibrary.GetActivitiesAsync(cancellationToken);
var activityShapes = await BuildActivityShapesAsync(activities, cancellationToken);
var categories = await activityLibrary.GetCategoriesAsync(cancellationToken);
var viewModel = new ActivityPickerViewModel(activityShapes, categories);
return View(viewModel);
}
private async Task<ICollection<IShape>> BuildActivityShapesAsync(IEnumerable<ActivityDescriptor> descriptors)
private async Task<ICollection<IShape>> BuildActivityShapesAsync(IEnumerable<ActivityDescriptor> activities, CancellationToken cancellationToken)
{
return await Task.WhenAll(descriptors.Select(BuildActivityShapeAsync));
return await Task.WhenAll(activities.Select((x, i) => BuildActivityShapeAsync(x, cancellationToken)));
}
private async Task<IShape> BuildActivityShapeAsync(ActivityDescriptor descriptor)
private async Task<IShape> BuildActivityShapeAsync(ActivityDescriptor activityDescriptor, CancellationToken cancellationToken)
{
var activityShape = await shapeFactory.CreateAsync("Activity_Card", () => Task.FromResult<IShape>(new ActivityDescriptorViewModel(descriptor)));
activityShape.Metadata.Alternates.Add($"Activity_Card__{descriptor.Name}");
activityShape.Metadata.Type = "Activity_Card";
return activityShape;
return await shapeFactory.CreateAsync("Activity_Card", new ActivityCardViewModel(activityDescriptor));
}
}
}

View file

@ -0,0 +1,12 @@
using Flowsharp.Models;
using OrchardCore.DisplayManagement.Views;
namespace Flowsharp.Web.ViewComponents.ViewModels
{
public class ActivityCardViewModel : ShapeViewModel<ActivityDescriptor>
{
public ActivityCardViewModel(ActivityDescriptor value) : base(value)
{
}
}
}

View file

@ -1,15 +0,0 @@
using System.Collections.Generic;
using OrchardCore.DisplayManagement;
namespace Flowsharp.Web.ViewComponents.ViewModels
{
public class ActivityLibraryViewModel
{
public ActivityLibraryViewModel(IEnumerable<IShape> activityShapes)
{
ActivityShapes = activityShapes;
}
public IEnumerable<IShape> ActivityShapes { get; }
}
}

View file

@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Localization;
using OrchardCore.DisplayManagement;
namespace Flowsharp.Web.ViewComponents.ViewModels
{
public class ActivityPickerViewModel
{
public ActivityPickerViewModel(IEnumerable<IShape> activityShapes, IEnumerable<LocalizedString> categories)
{
ActivityCategories = categories.ToList();
ActivityShapes = activityShapes.ToList();
}
public IReadOnlyCollection<LocalizedString> ActivityCategories { get; set; }
public IReadOnlyCollection<IShape> ActivityShapes { get; }
}
}

View file

@ -1,19 +1,14 @@
@model ActivityDescriptorViewModel
@model dynamic
@{
var descriptor = Model.Value;
var descriptorModel = new
{
name = descriptor.Name,
displayText = descriptor.DisplayText.Value,
description = descriptor.Description.Value
};
var descriptorJson = Json.Serialize(descriptorModel);
var descriptor = (ActivityDescriptor)Model.Value;
}
<div class="activity fxw-nw ai-c p-20 bdB bgc-white bgcH-grey-50 cur-p" draggable="true" data-activity-descriptor-json="@Html.Encode(descriptorJson)">
<div class="peers">
<div class="peer"><i class="mR-10 lh-2 ti-email"></i></div>
<div class="peer"><h5 class="fw-300 c-grey-800">@descriptor.Name</h5></div>
<div class="card activity" data-category="@descriptor.Category.Name.HtmlClassify()" data-activity-type="Action">
<div class="card-body">
<h4 class="card-title">@descriptor.DisplayText</h4>
<p>@descriptor.Description</p>
</div>
<small class="fsz-sm lh-0 c-grey-500">@descriptor.Description</small>
</div>
<div class="card-footer text-muted text-xs-right">
<a class="btn btn-primary btn-sm" href="#" data-activity-name="@descriptor.Name">@T["Add"]</a>
</div>
</div>

View file

@ -1,12 +0,0 @@
@model ActivityLibraryViewModel
<div id="flowsharp-activity-library">
@foreach (var activityShape in Model.ActivityShapes)
{
@await DisplayAsync(activityShape)
}
</div>
<style asp-name="flowsharp-activity-library-style" asp-src="~/Flowsharp.Web.ViewComponents/activity-library.css"></style>
<script asp-name="flowsharp-shared-script" asp-src="~/Flowsharp.Web.ViewComponents/shared.js" at="Foot"></script>
<script asp-name="flowsharp-activity-library-script" asp-src="~/Flowsharp.Web.ViewComponents/activity-library.js" depends-on="flowsharp-shared-script" at="Foot"></script>

View file

@ -0,0 +1,44 @@
@model ActivityPickerViewModel
<div id="flowsharp-activity-picker" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="Activity Picker" aria-hidden="true" style="display: none;" data-activity-type="Trigger|Action">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@T["Add Activity"]</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm-3 col-md-3 col-lg-2">
<div class="form-group">
<input class="form-control" type="search" placeholder="@T["Filter"]" aria-label="@T["Filter"]" autofocus>
</div>
<ul class="nav nav-pills flex-column activity-picker-categories">
<li class="nav-item">
<a class="nav-link active" href="#all" data-toggle="pill">All</a>
</li>
@foreach (var category in Model.ActivityCategories)
{
<li class="nav-item" data-category="@category.Name.HtmlClassify()">
<a class="nav-link" href="#@category.Name.HtmlClassify()" data-toggle="pill">@category</a>
</li>
}
</ul>
</div>
<div class="col-sm-9 col-md-9 col-lg-10">
<div class="card-columns tab-content">
@foreach (var activityShape in Model.ActivityShapes)
{
@await DisplayAsync(activityShape)
}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script asp-name="flowsharp-activity-picker-script" asp-src="~/Flowsharp.Web.ViewComponents/activity-picker.js" at="Foot"></script>

View file

@ -1,4 +1,5 @@
@using OrchardCore.DisplayManagement.Views
@using OrchardCore.Mvc.Utilities
@using Flowsharp.Models
@using Flowsharp.Web.ViewComponents.Models
@using Flowsharp.Web.ViewComponents.ViewModels

View file

@ -23,14 +23,8 @@
},
{
"inputs": [
"assets/styles/activity-library.scss"
"assets/scripts/flowsharp/activity-picker.ts"
],
"output": "wwwroot/activity-library.css"
},
{
"inputs": [
"assets/scripts/flowsharp/activity-library/activity-library.ts"
],
"output": "wwwroot/activity-library.js"
"output": "wwwroot/activity-picker.js"
}
]

View file

@ -19,6 +19,12 @@
<ItemGroup>
<Content Include="Assets\scripts\dashboard.ts" />
<Content Include="Assets\styles\dashboard.scss" />
<Content Include="Views\Flowsharp.Web.Management\Workflows\Edit.cshtml" />
<Content Include="Views\Flowsharp.Web.Management\Workflows\Index.cshtml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\modules\Flowsharp.Web.Management\Flowsharp.Web.Management.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,23 @@
@model Workflow
<div class="workflow-editor-container" data-workflow-id="@Model.Metadata.Id">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">@T["Edit {0}", Model.Metadata.Name]</h1>
<div class="btn-toolbar mb-2 mb-md-1">
<nav class="admin-toolbar">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<button type="button" class="add-trigger-button btn btn-sm btn-secondary" data-toggle="modal" data-target="#flowsharp-activity-picker" data-picker-title="@T["Available Triggers"]" data-activity-type="Trigger">@T["Add Trigger"]</button>
<button type="button" class="add-action-button btn btn-sm btn-secondary" data-toggle="modal" data-target="#flowsharp-activity-picker" data-picker-title="@T["Available Actions"]" data-activity-type="Action">@T["Add Action"]</button>
</li>
</ul>
</nav>
</div>
</div>
@await Component.InvokeAsync("ActivityPicker")
@await Component.InvokeAsync("WorkflowDesigner", new { workflowId = Model.Metadata.Id })
</div>
<div class="fixed-bottom p-3 m-3 clearfix">
<button type="button" class="btn btn-sm btn-primary float-right">Save</button>
</div>
<script asp-name="flowsharp-workflow-editor-script" asp-src="~/Flowsharp.Web.Management/workflow-editor.js" at="Foot"></script>

View file

@ -0,0 +1,48 @@
@model ICollection<Workflow>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="bgc-white bd bdrs-3 p-20 mB-20">
<h4 class="c-grey-900 mB-20">Workflow Definitions</h4>
<table id="dataTable" class="table table-striped table-bordered dataTable" width="100%" role="grid" aria-describedby="dataTable_info" style="width: 100%;" data-turbolinks="false">
<thead>
<tr role="row">
<th class="sorting" tabindex="0" aria-controls="dataTable" rowspan="1" colspan="1" aria-label="Id: activate to sort column ascending" style="width: 214px;">ID</th>
<th class="sorting_asc" tabindex="0" aria-controls="dataTable" rowspan="1" colspan="1" aria-sort="ascending" aria-label="Name: activate to sort column descending" style="width: 281px;">Name</th>
<th class="sorting" tabindex="0" aria-controls="dataTable" rowspan="1" colspan="1" aria-label="Position: activate to sort column ascending" style="width: 413px;">Description</th>
<th class="sorting" tabindex="0" aria-controls="dataTable" rowspan="1" colspan="1" aria-label="Office: activate to sort column ascending" style="width: 214px;">Actions</th>
</tr>
</thead>
<tfoot>
<tr>
<th rowspan="1" colspan="1">ID</th>
<th rowspan="1" colspan="1">Name</th>
<th rowspan="1" colspan="1">Description</th>
<th rowspan="1" colspan="1">Actions</th>
</tr>
</tfoot>
<tbody>
@{
var index = 0;
}
@foreach (var workflowDefinition in Model)
{
var rowClass = index % 2 == 0 ? "even" : "odd";
index++;
<tr role="row" class="@rowClass">
<td>
<a asp-area="Flowsharp.Web.Management" asp-controller="Workflows" asp-action="Edit" asp-route-id="@workflowDefinition.Metadata.Id">@workflowDefinition.Metadata.Id</a>
</td>
<td class="sorting_1">@workflowDefinition.Metadata.Name</td>
<td>@workflowDefinition.Metadata.Description</td>
<td><a asp-area="Flowsharp.Web.Management" asp-controller="Workflows" asp-action="Edit" asp-route-id="@workflowDefinition.Metadata.Id">edit</a> | delete | enable</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>

View file

@ -1,4 +1,3 @@
@using Microsoft.AspNetCore.Html
<!doctype html>
<html lang="en">
<head>
@ -27,7 +26,6 @@
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4">
@await RenderBodyAsync()
</main>
</div>
</div>

View file

@ -1,3 +1,4 @@
@using Flowsharp.Models
@inherits OrchardCore.DisplayManagement.Razor.RazorPage<TModel>
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, OrchardCore.DisplayManagement