This commit is contained in:
Sipke Schoorstra 2018-12-01 20:41:38 +01:00
parent 0bc7c3d9d4
commit 9819534eee
48 changed files with 489 additions and 328 deletions

View file

@ -8,7 +8,7 @@ namespace Flowsharp.Activities.Console.Activities
/// </summary>
public class WriteLine : Activity
{
public WriteLine()
public WriteLine() : this(null)
{
}

View file

@ -0,0 +1,18 @@
using System;
using Flowsharp.Models;
using Newtonsoft.Json;
namespace Flowsharp.Extensions
{
public static class ActivityDescriptorExtensions
{
public static IActivity InstantiateActivity(this ActivityDescriptor descriptor, string json = null)
{
var activity = json == null
? Activator.CreateInstance(descriptor.ActivityType)
: JsonConvert.DeserializeObject(json, descriptor.ActivityType);
return (IActivity) activity;
}
}
}

View file

@ -5,8 +5,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="OrchardCore.Application.Mvc.Targets" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.Resources" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.Application.Mvc.Targets" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.Resources" Version="1.0.0-beta3-69736" />
</ItemGroup>
<ItemGroup>

View file

@ -24,11 +24,18 @@ namespace Flowsharp.Web.Abstractions.Drivers
/// <summary>
/// Base class for activity drivers using a strongly typed view model.
/// </summary>
public abstract class ActivityDisplayDriver<TActivity, TEditViewModel> : ActivityDisplayDriver<TActivity> where TEditViewModel : class, new() where TActivity : class, IActivity
public abstract class ActivityDisplayDriver<TActivity, TEditViewModel> : ActivityDisplayDriver<TActivity>
where TActivity : class, IActivity
where TEditViewModel : class, new()
{
public override IDisplayResult Edit(TActivity model)
{
return Initialize(GetEditorShapeType(model), (Func<TEditViewModel, Task>)(viewModel => EditActivityAsync(model, viewModel))).Location("Content");
var result = Initialize(
GetEditorShapeType(model),
(Func<TEditViewModel, Task>)(viewModel => EditActivityAsync(model, viewModel)))
.Location("Content");
return result;
}
public override async Task<IDisplayResult> UpdateAsync(TActivity model, IUpdateModel updater)
@ -44,7 +51,7 @@ namespace Flowsharp.Web.Abstractions.Drivers
/// <summary>
/// Edit the view model before it's used in the editor.
/// </summary>
protected virtual Task EditActivityAsync(IActivity activity, TEditViewModel model)
protected virtual Task EditActivityAsync(TActivity activity, TEditViewModel model)
{
EditActivity(activity, model);
@ -54,14 +61,14 @@ namespace Flowsharp.Web.Abstractions.Drivers
/// <summary>
/// Edit the view model before it's used in the editor.
/// </summary>
protected virtual void EditActivity(IActivity activity, TEditViewModel model)
protected virtual void EditActivity(TActivity activity, TEditViewModel model)
{
}
/// <summary>
/// Updates the activity when the view model is validated.
/// </summary>
protected virtual Task UpdateActivityAsync(TEditViewModel model, IActivity activity)
protected virtual Task UpdateActivityAsync(TEditViewModel model, TActivity activity)
{
UpdateActivity(model, activity);
@ -71,10 +78,10 @@ namespace Flowsharp.Web.Abstractions.Drivers
/// <summary>
/// Updates the activity when the view model is validated.
/// </summary>
protected virtual void UpdateActivity(TEditViewModel model, IActivity activity)
protected virtual void UpdateActivity(TEditViewModel model, TActivity activity)
{
}
protected string GetEditorShapeType(IActivity activity) => $"{activity.Name}_Edit";
protected virtual string GetEditorShapeType(TActivity activity) => $"{activity.Name}_Edit";
}
}

View file

@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-69736" />
</ItemGroup>
<ItemGroup>

View file

@ -1,4 +1,3 @@
using Flowsharp.Models;
using OrchardCore.DisplayManagement;
namespace Flowsharp.Web.Abstractions.Services

View file

@ -0,0 +1,11 @@
using System.Threading;
using System.Threading.Tasks;
using OrchardCore.DisplayManagement;
namespace Flowsharp.Web.Abstractions.Services
{
public interface IActivityShapeFactory
{
Task<IShape> BuildDesignShapeAsync(IActivity activity, CancellationToken cancellationToken);
}
}

View file

@ -1,9 +1,20 @@
using Flowsharp.Activities.Console.Activities;
using Flowsharp.Expressions;
using Flowsharp.Web.Abstractions.Drivers;
using Flowsharp.Web.Activities.Console.ViewModels;
namespace Flowsharp.Web.Activities.Console.Drivers
{
public class WriteLineDriver : ActivityDisplayDriver<WriteLine>
public class WriteLineDriver : ActivityDisplayDriver<WriteLine, WriteLineViewModel>
{
protected override void EditActivity(WriteLine activity, WriteLineViewModel model)
{
model.TextExpression = activity.TextExpression.Expression;
}
protected override void UpdateActivity(WriteLineViewModel model, WriteLine activity)
{
activity.TextExpression = new WorkflowExpression<string>(activity.TextExpression.Syntax, model.TextExpression);
}
}
}

View file

@ -10,13 +10,14 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69736" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\ReadLine.Design.cshtml" />
<Content Include="Views\ReadLine.Thumbnail.cshtml" />
<Content Include="Views\WriteLine.Design.cshtml" />
<Content Include="Views\WriteLine.Edit.cshtml" />
<Content Include="Views\WriteLine.Thumbnail.cshtml" />
<Content Include="Views\_ViewImports.cshtml" />
</ItemGroup>

View file

@ -0,0 +1,7 @@
namespace Flowsharp.Web.Activities.Console.ViewModels
{
public class WriteLineViewModel
{
public string TextExpression { get; set; }
}
}

View file

@ -0,0 +1,3 @@
@model WriteLineViewModel
<label asp-for="TextExpression">@T["Text Expression"]</label>
<input asp-for="TextExpression" class="form-control"/>

View file

@ -1,4 +1,5 @@
@using Flowsharp.Activities.Console.Activities
@using Flowsharp.Web.Activities.Console.ViewModels
@using Flowsharp.Web.Abstractions.ViewModels
@inherits OrchardCore.DisplayManagement.Razor.RazorPage<TModel>
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View file

@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69736" />
</ItemGroup>
<ItemGroup>

View file

@ -12,10 +12,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="OrchardCore.FileStorage.FileSystem" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.Mvc.Core" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.FileStorage.FileSystem" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.Mvc.Core" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-69736" />
</ItemGroup>
<ItemGroup>

View file

@ -5,11 +5,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OrchardCore" Version="1.0.0-beta3-69369"/>
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-69230"/>
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69230"/>
<PackageReference Include="OrchardCore.ResourceManagement" Version="1.0.0-beta3-69369"/>
<PackageReference Include="OrchardCore.Infrastructure" Version="1.0.0-beta3-69369"/>
<PackageReference Include="OrchardCore" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.ResourceManagement" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.Infrastructure" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.Settings" Version="1.0.0-beta3-69736" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,29 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
using OrchardCore.Settings;
namespace Flowsharp.Web.OrchardCoreLite.Services
{
public class NullSiteService : ISiteService
{
public Task<ISite> GetSiteSettingsAsync()
{
var settings = new SiteSettings
{
Properties =
{
["CurrentThemeName"] = "TheAdminatorTheme"
}
};
return Task.FromResult<ISite>(settings);
}
public Task UpdateSiteSettingsAsync(ISite site)
{
return Task.CompletedTask;
}
public IChangeToken ChangeToken => new CancellationChangeToken(CancellationToken.None);
}
}

View file

@ -1,4 +1,5 @@
using System;
using Flowsharp.Web.OrchardCoreLite.Services;
using Flowsharp.Web.OrchardCoreLite.Theming;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
@ -8,6 +9,7 @@ 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;
@ -15,17 +17,13 @@ namespace Flowsharp.Web.OrchardCoreLite
{
public class Startup : StartupBase
{
public Startup(IConfiguration configuration, IShellHost shellHost)
{
}
public override void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IShellFeaturesManager, ShellFeaturesManager>();
services.AddScoped<IShellDescriptorFeaturesManager, ShellDescriptorFeaturesManager>();
services.AddScoped<IShellStateManager, NullShellStateManager>();
services.AddScoped<IThemeSelector, SettingsThemeSelector>();
services.AddScoped<ISiteService, NullSiteService>();
services.AddResourceManagement();
@ -39,7 +37,6 @@ namespace Flowsharp.Web.OrchardCoreLite
public override void Configure(IApplicationBuilder builder, IRouteBuilder routes, IServiceProvider serviceProvider)
{
}
}
}

View file

@ -5,8 +5,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.FileStorage.FileSystem" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.FileStorage.FileSystem" Version="1.0.0-beta3-69736" />
</ItemGroup>
<ItemGroup>

View file

@ -0,0 +1,78 @@
///<reference path='../../node_modules/@types/jquery/index.d.ts' />
///<reference path='../../node_modules/@types/bootstrap/index.d.ts' />
///<reference path="activity.ts"/>
namespace Flowsharp {
export class ActivityEditor {
private baseUrl: string;
private containerElement: JQuery<HTMLElement>;
private titleElement: JQuery<HTMLElement>;
private contentElement: JQuery<HTMLElement>;
private formElement: JQuery<HTMLFormElement>;
private activityName: string;
private deferred: JQuery.Deferred<any>;
constructor(container: HTMLElement) {
this.containerElement = $(container);
this.titleElement = this.containerElement.find('.modal-title');
this.contentElement = this.containerElement.find('.modal-body');
this.formElement = <JQuery<HTMLFormElement>>this.containerElement.find('form');
this.baseUrl = this.containerElement.data('base-url');
this.formElement.on('submit', this.onFormSubmit);
}
public show = () => this.containerElement.modal('show');
public hide = () => this.containerElement.modal('hide');
public display = (activityName: string, activity: IActivity): JQuery.Promise<any> => {
this.activityName = activityName;
this.deferred = jQuery.Deferred<any>();
$.ajax({
type: 'POST',
url: `/activity/edit/${activityName}`,
data: activity,
contentType: 'application/json',
dataType: 'html'
}).done(this.displayActivity);
return this.deferred.promise();
};
private displayActivity = (data: any) => {
const html = <string>data;
this.contentElement.html(html);
};
public get title(): string {
return this.titleElement.text();
}
public set title(value: string) {
this.titleElement.text(value);
}
private onFormSubmit = (e: JQuery.Event) => {
$.ajax({
type: 'POST',
url: `/activity/update/${this.activityName}`,
data: this.formElement.serialize(),
dataType: 'html'
}).done((data: any, textStatus: JQuery.Ajax.SuccessTextStatus, xhr: any) => {
const activityElement: JQuery = $(data);
if (activityElement.is('.activity-editor-fields')) {
this.displayActivity(data);
}
else {
this.deferred.resolve(data);
this.hide();
}
});
e.preventDefault();
}
}
}

View file

@ -1,6 +1,5 @@
///<reference path='../../../node_modules/@types/jquery/index.d.ts' />
///<reference path='../models/index.d.ts' />
///<reference path='../../decode-html/decode-html.ts' />
///<reference path='../../node_modules/@types/jquery/index.d.ts' />
///<reference path='../decode-html/decode-html.ts' />
namespace Flowsharp {
export class ActivityLibrary {
@ -24,4 +23,9 @@ namespace Flowsharp {
this.activities.on('dragstart', ActivityLibrary.onDragStart)
}
}
$(function () {
const activityLibraryContainer: HTMLDivElement = <HTMLDivElement>document.getElementById('flowsharp-activity-library');
const activityLibrary = new ActivityLibrary(activityLibraryContainer);
});
}

View file

@ -1,9 +0,0 @@
///<reference path='../../../node_modules/@types/jquery/index.d.ts' />
///<reference path='./activity-library.ts' />
namespace Flowsharp {
$(function () {
const activityLibraryContainer: HTMLDivElement = <HTMLDivElement>document.getElementById('flowsharp-activity-library');
const activityLibrary = new ActivityLibrary(activityLibraryContainer);
});
}

View file

@ -0,0 +1,7 @@
///<reference path="endpoint.ts"/>
namespace Flowsharp {
export interface IActivity {
id: string;
endpoints: IEndpoint[];
}
}

View file

@ -0,0 +1,6 @@
namespace Flowsharp {
export interface IEndpoint {
name: string;
displayName: string;
}
}

View file

@ -1,22 +0,0 @@
namespace Flowsharp {
export class Activity {
private id: null;
private x: number;
private y: number;
private isStart: boolean;
private isBlocking: boolean;
private isEvent: boolean;
private endpoints: any[];
constructor() {
this.id = null;
this.x = 0;
this.y = 0;
this.isStart = false;
this.isBlocking = false;
this.isEvent = false;
this.endpoints = [];
}
}
}

View file

@ -1,11 +0,0 @@
namespace Flowsharp {
export class Endpoint {
private name: null;
private displayName: null;
constructor() {
this.name = null;
this.displayName = null;
}
}
}

View file

@ -1,2 +0,0 @@
///<reference path='./activity.ts' />
///<reference path='./activity-descriptor.ts' />

View file

@ -1,13 +0,0 @@
namespace Flowsharp {
export class Transition {
private sourceActivityId: null;
private sourceOutcomeName: null;
private destinationActivityId: null;
constructor() {
this.sourceActivityId = null;
this.sourceOutcomeName = null;
this.destinationActivityId = null;
}
}
}

View file

@ -1,15 +0,0 @@
namespace Flowsharp {
export class WorkflowType {
private id: null;
private activities: any[];
private transitions: any[];
private removedActivities: any[];
constructor() {
this.id = null;
this.activities = [];
this.transitions = [];
this.removedActivities = [];
}
}
}

View file

@ -0,0 +1,7 @@
namespace Flowsharp {
export interface ITransition {
sourceActivityId: string;
sourceOutcomeName: string;
destinationActivityId: null;
}
}

View file

@ -1,3 +1,4 @@
///<reference path="../@types/jsplumb.d.ts"/>
namespace Flowsharp {
export class WorkflowDesignerConfig {

View file

@ -0,0 +1,133 @@
///<reference path='../../node_modules/@types/jquery/index.d.ts' />
///<reference path='../@types/jsplumb.d.ts' />
///<reference path='./workflow-designer-config.ts' />
///<reference path='./activity-editor.ts' />
///<reference path="activity-descriptor.ts"/>
namespace Flowsharp {
export class WorkflowDesigner {
private plumber: any;
private activityEditor: ActivityEditor;
constructor(private readonly container: HTMLElement, private readonly activityEditorContainer: HTMLElement) {
this.plumber = WorkflowDesignerConfig.createJsPlumbInstance(container);
this.initializeNodes();
this.initializeDropTarget();
this.activityEditor = new ActivityEditor(activityEditorContainer);
}
private static onDragOver = (e: JQuery.Event) => {
e.preventDefault();
};
private editActivity = (activityElement: JQuery<HTMLElement>) => {
const displayText = activityElement.attr('title');
const activityName = activityElement.data('activity-name');
const activity:IActivity = activityElement.data('activity-model');
this.activityEditor.title = `Edit ${displayText}`;
this.activityEditor.show();
this.activityEditor.display(activityName, activity).done(this.updateActivity);
};
private addActivity = (activityHtml: string, x: number, y: number) => {
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 initializeNodes = () => {
this.plumber.batch(() => {
let activityElements = $(this.container).find('.activity');
for (let index = 0; index < activityElements.length; index++) {
const activityElement = $(activityElements[index]);
this.initializeElement(activityElement);
}
//this.updateConnections();
activityElements.show();
});
};
private initializeElement = (activityElement: JQuery<HTMLElement>) => {
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;
// }
});
this.plumber.makeTarget(activityElement, {
dropOptions: {hoverClass: 'hover'},
anchor: 'Continuous',
endpoint: ['Blank', {radius: 8}]
});
this.addSourceEndpoints(activityElement[0], activityId, activityEndpoints);
activityElement.on('dblclick', this.onDoubleClick);
};
private addSourceEndpoints = (activityElement: HTMLElement, activityId: string, endpoints: any[]) => {
for (let i = 0; i < endpoints.length; i++) {
const endpoint: any = endpoints[i];
const sourceEndpointOptions: any = WorkflowDesignerConfig.getSourceEndpointOptions(activityId, endpoint.name);
this.plumber.addEndpoint(activityElement, {
connectorOverlays: [['Label', {
label: endpoint.name,
cssClass: 'connection-label'
}]]
}, sourceEndpointOptions);
}
};
private initializeDropTarget = () => {
$(this.container).on('dragover', WorkflowDesigner.onDragOver);
$(this.container).on('drop', this.onDrop)
};
private onDrop = (e: JQuery.Event) => {
e.preventDefault();
const dragEvent = <DragEvent>e.originalEvent;
const activityDescriptor: ActivityDescriptor = JSON.parse(dragEvent.dataTransfer.getData('activity-descriptor-json'));
const activityName = activityDescriptor.name;
const x = dragEvent.offsetX;
const y = dragEvent.offsetY;
this.activityEditor.title = `Add ${activityDescriptor.displayText}`;
this.activityEditor.show();
this.activityEditor.display(activityName, null).done((data: string) => this.addActivity(data, x, y));
};
private onDoubleClick = (e: JQuery.Event) => {
e.preventDefault();
const activityElement: JQuery<HTMLElement> = $(<HTMLElement>e.originalEvent.currentTarget);
this.editActivity(activityElement);
};
}
$(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);
});
});
}

View file

@ -1,40 +0,0 @@
///<reference path='../../../node_modules/@types/jquery/index.d.ts' />
///<reference path='../../../node_modules/@types/bootstrap/index.d.ts' />
namespace Flowsharp {
export class ActivityEditor {
public show = () => this.containerElement.modal('show');
public hide = () => this.containerElement.modal('hide');
public display = (activityName: string) => $.ajax({
type: 'POST',
url: `/activity/display/${activityName}`,
data: {},
contentType: 'application/json',
dataType: 'json'
}).done(this.displayActivity);
private baseUrl: string;
private containerElement: JQuery<HTMLElement>;
private titleElement: JQuery<HTMLElement>;
private contentElement: JQuery<HTMLElement>;
private displayActivity = (data: any) => {
const html = <string>data;
this.contentElement.html(html);
};
constructor(container: HTMLElement) {
this.containerElement = $(container);
this.titleElement = this.containerElement.find('.modal-title');
this.contentElement = this.containerElement.find('.modal-body');
this.baseUrl = this.containerElement.data('base-url');
}
public get title(): string {
return this.titleElement.text();
}
public set title(value: string) {
this.titleElement.text(value);
}
}
}

View file

@ -1,115 +0,0 @@
///<reference path='../../../node_modules/@types/jquery/index.d.ts' />
///<reference path='../../@types/jsplumb.d.ts' />
///<reference path='../models/index.d.ts' />
///<reference path='./config.ts' />
///<reference path='./activity-editor.ts' />
namespace Flowsharp {
export class WorkflowDesigner {
private plumber: any;
private activityElements: any;
private activities: any[];
private static onDragOver = (e: JQuery.Event) => {
e.preventDefault();
};
private activityEditor: ActivityEditor;
private onDrop = (e: JQuery.Event) => {
e.preventDefault();
const dragEvent = <DragEvent>e.originalEvent;
const activityDescriptor: ActivityDescriptor = JSON.parse(dragEvent.dataTransfer.getData('activity-descriptor-json'));
const activityName = activityDescriptor.name;
this.activityEditor.title = `Add ${activityDescriptor.displayText}`;
this.activityEditor.display(activityName);
this.activityEditor.show();
}
constructor(private readonly container: HTMLElement, private readonly activityEditorContainer: HTMLElement) {
this.readActivities();
this.plumber = WorkflowDesignerConfig.createJsPlumbInstance(container);
this.initializeNodes();
this.initializeDropTarget();
this.activityEditor = new ActivityEditor(activityEditorContainer);
}
private readActivities() {
const activityElements: any = $(this.container).find('.activity');
const activities = [];
for (let index = 0; index < activityElements.length; index++) {
const activityElement: any = $(activityElements[index]);
const activity: any = activityElement.data['activity-json'];
activities.push(activity);
}
this.activityElements = activityElements;
this.activities = activities;
}
private initializeNodes() {
// Suspend drawing and initialize.
this.plumber.batch(() => {
let activityElements = this.activityElements;
for (let index = 0; index < activityElements.length; index++) {
const activityElement = activityElements[index];
const activity = this.activities[index];
this.initializeElement(activityElement, activity);
}
// Connect activities.
//this.updateConnections();
// Make all activity elements visible.
activityElements.show();
});
}
private initializeElement(activityElement: HTMLElement, activity: Activity) {
const $activityElement = $(activityElement);
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;
// }
});
// Configure the activity as a target.
this.plumber.makeTarget(activityElement, {
dropOptions: {hoverClass: 'hover'},
anchor: 'Continuous',
endpoint: ['Blank', {radius: 8}]
});
// Add source endpoints.
this.addSourceEndpoints(activity, activityElement);
}
private addSourceEndpoints(activity: any, activityElement: any) {
for (let i = 0; i < activity.endpoints.length; i++) {
const endpoint: any = activity.endpoints[i];
const sourceEndpointOptions: any = WorkflowDesignerConfig.getSourceEndpointOptions(activity.id, endpoint.name);
this.plumber.addEndpoint(activityElement, {
connectorOverlays: [['Label', {
label: endpoint.name,
cssClass: 'connection-label'
}]]
}, sourceEndpointOptions);
}
}
private initializeDropTarget() {
$(this.container).on('dragover', WorkflowDesigner.onDragOver);
$(this.container).on('drop', this.onDrop)
}
}
}

View file

@ -1,12 +0,0 @@
///<reference path='../../../node_modules/@types/jquery/index.d.ts' />
///<reference path='./designer.ts' />
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);
});
});
}

View file

@ -1,7 +1,13 @@
using System;
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 Flowsharp.Web.ViewComponents.ViewModels;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -16,20 +22,48 @@ namespace Flowsharp.Web.ViewComponents.Controllers
{
private readonly IActivityDisplayManager displayManager;
private readonly IActivityLibrary activityLibrary;
private readonly IActivityShapeFactory activityShapeFactory;
public ActivityController(IActivityDisplayManager displayManager, IActivityLibrary activityLibrary)
public ActivityController(
IActivityDisplayManager displayManager,
IActivityLibrary activityLibrary,
IActivityShapeFactory activityShapeFactory)
{
this.displayManager = displayManager;
this.activityLibrary = activityLibrary;
this.activityShapeFactory = activityShapeFactory;
}
[HttpPost("display/{activityName}")]
public async Task<IActionResult> Display(string activityName, [FromBody] string json, CancellationToken cancellationToken)
[HttpPost("edit/{activityName}")]
public async Task<IActionResult> Edit(string activityName, [FromBody] string json, CancellationToken cancellationToken)
{
var activityDescriptor = await activityLibrary.GetActivityByNameAsync(activityName, cancellationToken);
var activityModel = (IActivity)JToken.Parse(json ?? "{}").ToObject(activityDescriptor.ActivityType);
var editorShape = await displayManager.BuildEditorAsync(activityModel, this, true);
return View(editorShape);
var activityModel = activityDescriptor.InstantiateActivity(json);
var editorShape = await displayManager.BuildEditorAsync(activityModel, this, false);
var model = new ActivityEditorViewModel
{
ActivityJson = json,
ActivityEditorShape = editorShape,
};
return View(model);
}
[HttpPost("update/{activityName}")]
public async Task<IActionResult> Update(string activityName, [FromForm] ActivityEditorUpdateModel model, CancellationToken cancellationToken)
{
var activityDescriptor = await activityLibrary.GetActivityByNameAsync(activityName, cancellationToken);
var activityModel = (IActivity) JToken.Parse(model.ActivityJson ?? "{}").ToObject(activityDescriptor.ActivityType);
var editorShape = await displayManager.UpdateEditorAsync(activityModel, this, false);
if (!ModelState.IsValid)
return View("Edit", new ActivityEditorViewModel
{
ActivityJson = JsonConvert.SerializeObject(activityModel),
ActivityEditorShape = editorShape
});
dynamic designShape = await activityShapeFactory.BuildDesignShapeAsync(activityModel, cancellationToken);
return View("Display", designShape);
}
}
}

View file

@ -5,20 +5,16 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.Resources" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.Resources" Version="1.0.0-beta3-69736" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\core\Flowsharp.Abstractions\Flowsharp.Abstractions.csproj" />
<ProjectReference Include="..\..\..\core\Flowsharp.Core\Flowsharp.Core.csproj" />
<ProjectReference Include="..\..\..\core\Flowsharp.Persistence.Abstractions\Flowsharp.Persistence.Abstractions.csproj" />
<ProjectReference Include="..\Flowsharp.Web.Abstractions\Flowsharp.Web.Abstractions.csproj"/>
</ItemGroup>
<ItemGroup>
<Content Include="Assets\scripts\flowsharp\workflow-designer\activity-editor.ts"/>
<ProjectReference Include="..\Flowsharp.Web.Abstractions\Flowsharp.Web.Abstractions.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,38 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Flowsharp.Extensions;
using Flowsharp.Web.Abstractions.Services;
using Flowsharp.Web.ViewComponents.Models;
using OrchardCore.DisplayManagement;
namespace Flowsharp.Web.ViewComponents.Services
{
public class ActivityShapeFactory : IActivityShapeFactory
{
private readonly IActivityDisplayManager displayManager;
private readonly IActivityLibrary activityLibrary;
public ActivityShapeFactory(IActivityDisplayManager displayManager, IActivityLibrary activityLibrary)
{
this.displayManager = displayManager;
this.activityLibrary = activityLibrary;
}
public async Task<IShape> BuildDesignShapeAsync(IActivity activity, CancellationToken cancellationToken)
{
dynamic shape = await displayManager.BuildDisplayAsync(activity, null, "Design");
var customFields = activity.Metadata.CustomFields;
var designerMetadata = customFields.Get("Designer", () => new ActivityDesignerMetadata());
var descriptor = await activityLibrary.GetActivityByNameAsync(activity.Name, CancellationToken.None);
shape.Metadata.Type = $"Activity_Design";
shape.Endpoints = descriptor.GetEndpoints().ToList();
shape.ActivityDescriptor = descriptor;
shape.Activity = activity;
shape.Designer = designerMetadata;
return shape;
}
}
}

View file

@ -12,7 +12,8 @@ namespace Flowsharp.Web.ViewComponents
{
services
.AddFlowsharpCore()
.AddScoped<IActivityDisplayManager, ActivityDisplayManager>();
.AddScoped<IActivityDisplayManager, ActivityDisplayManager>()
.AddScoped<IActivityShapeFactory, ActivityShapeFactory>();
}
}
}

View file

@ -16,49 +16,34 @@ namespace Flowsharp.Web.ViewComponents.ViewComponents
public class WorkflowDesigner : ViewComponent
{
private readonly IWorkflowStore workflowStore;
private readonly IActivityLibrary activityLibrary;
private readonly IActivityDisplayManager displayManager;
private readonly IActivityShapeFactory activityShapeFactory;
public WorkflowDesigner(
IWorkflowStore workflowStore,
IActivityLibrary activityLibrary,
IActivityDisplayManager displayManager)
IActivityShapeFactory activityShapeFactory)
{
this.workflowStore = workflowStore;
this.activityLibrary = activityLibrary;
this.displayManager = displayManager;
this.activityShapeFactory = activityShapeFactory;
}
public async Task<IViewComponentResult> InvokeAsync(string workflowId, CancellationToken cancellationToken)
{
var workflow = await workflowStore.GetAsync(workflowId, cancellationToken);
var activities = workflow.Activities;
var activityShapes = await BuildActivityShapesAsync(activities, workflow.Metadata.Id, "Design", cancellationToken);
var activityShapes = await BuildActivityShapesAsync(activities, cancellationToken);
var viewModel = new WorkflowDesignerViewModel(workflow, activityShapes);
return View(viewModel);
}
private async Task<ICollection<dynamic>> BuildActivityShapesAsync(IEnumerable<IActivity> activities, string workflowId, string displayType, CancellationToken cancellationToken)
private async Task<ICollection<dynamic>> BuildActivityShapesAsync(IEnumerable<IActivity> activities, CancellationToken cancellationToken)
{
return await Task.WhenAll(activities.Select((x, i) => BuildActivityShapeAsync(x, i, workflowId, displayType, cancellationToken)));
return await Task.WhenAll(activities.Select((x, i) => BuildActivityShapeAsync(x, cancellationToken)));
}
private async Task<dynamic> BuildActivityShapeAsync(IActivity activity, int index, string workflowId, string displayType, CancellationToken cancellationToken)
private async Task<dynamic> BuildActivityShapeAsync(IActivity activity, CancellationToken cancellationToken)
{
var activityDescriptor = await activityLibrary.GetActivityByNameAsync(activity.Name, cancellationToken);
dynamic activityShape = await displayManager.BuildDisplayAsync(activity, null, displayType);
var customFields = activity.Metadata.CustomFields;
var designerMetadata = customFields.Get("Designer", () => new ActivityDesignerMetadata());
activityShape.Metadata.Type = $"Activity_{displayType}";
activityShape.Endpoints = activityDescriptor.GetEndpoints().ToList();
activityShape.Activity = activity;
activityShape.WorkflowId = workflowId;
activityShape.Index = index;
activityShape.Designer = designerMetadata;
return activityShape;
return await activityShapeFactory.BuildDesignShapeAsync(activity, cancellationToken);
}
}
}

View file

@ -0,0 +1,7 @@
namespace Flowsharp.Web.ViewComponents.ViewModels
{
public class ActivityEditorUpdateModel
{
public string ActivityJson { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Flowsharp.Web.ViewComponents.ViewModels
{
public class ActivityEditorViewModel
{
public dynamic ActivityEditorShape { get; set; }
public string ActivityJson { get; set; }
}
}

View file

@ -6,6 +6,7 @@
@model dynamic
@{
var activity = (IActivity)Model.Activity;
var descriptor = (ActivityDescriptor) Model.ActivityDescriptor;
var id = activity.Id;
var endpoints = (IList<LocalizedString>)Model.Endpoints;
var serializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
@ -18,9 +19,11 @@
<div
class="activity"
style="left:@(x)px; top:@(y)px;"
id="@($"activity-{id}")"
id="@($"activity-{id}")"
title="@descriptor.DisplayText"
data-activity-name="@descriptor.Name"
data-activity-id="@id"
data-activity="@activityJson"
data-activity-model="@activityJson"
data-activity-endpoints="@activityEndpoints">
@await DisplayAsync(Model.Content)
</div>

View file

@ -0,0 +1,5 @@
@model ActivityEditorViewModel
<div class="activity-editor-fields">
<input type="hidden" asp-for="ActivityJson"/>
@await DisplayAsync(Model.ActivityEditorShape.Content)
</div>

View file

@ -16,10 +16,13 @@
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-primary">Save changes</button>
</div>
<form method="post">
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1 @@
@{ Layout = null; }

View file

@ -15,10 +15,9 @@
{
"inputs": [
"assets/node_modules/jsplumb/dist/js/jsplumb.js",
"assets/scripts/flowsharp/workflow-designer/activity-editor.ts",
"assets/scripts/flowsharp/workflow-designer/config.ts",
"assets/scripts/flowsharp/workflow-designer/designer.ts",
"assets/scripts/flowsharp/workflow-designer/index.ts"
"assets/scripts/flowsharp/activity-editor.ts",
"assets/scripts/flowsharp/workflow-designer-config.ts",
"assets/scripts/flowsharp/workflow-designer.ts"
],
"output": "wwwroot/workflow-designer.js"
},
@ -30,8 +29,7 @@
},
{
"inputs": [
"assets/scripts/flowsharp/activity-library/activity-library.ts",
"assets/scripts/flowsharp/activity-library/index.ts"
"assets/scripts/flowsharp/activity-library/activity-library.ts"
],
"output": "wwwroot/activity-library.js"
}

View file

@ -5,9 +5,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.ResourceManagement" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.Theme.Targets" Version="1.0.0-beta3-69369" />
<PackageReference Include="OrchardCore.DisplayManagement" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.ResourceManagement" Version="1.0.0-beta3-69736" />
<PackageReference Include="OrchardCore.Theme.Targets" Version="1.0.0-beta3-69736" />
</ItemGroup>
<ItemGroup>