Activity picker + editor flows

This commit is contained in:
Sipke Schoorstra 2020-11-15 13:24:08 +01:00
parent 32b349625f
commit 80912563d8
28 changed files with 204195 additions and 228 deletions

View file

@ -1,17 +1,20 @@
using System.Runtime.Serialization;
namespace Elsa.Client.Models
{
[DataContract]
public class ActivityDefinition
{
public string ActivityId { get; set; } = default!;
public string Type { get; set; } = default!;
public string? Name { get; set; }
public string? DisplayName { get; set; }
public string? Description { get; set; }
public int? Left { get; set; }
public int? Top { get; set; }
public bool PersistWorkflow { get; set; }
public bool LoadWorkflowContext { get; set; }
public bool SaveWorkflowContext { get; set; }
public ActivityDefinitionProperties Properties { get; set; } = new ActivityDefinitionProperties();
[DataMember(Order = 1)] public string ActivityId { get; set; } = default!;
[DataMember(Order = 2)] public string Type { get; set; } = default!;
[DataMember(Order = 3)] public string? Name { get; set; }
[DataMember(Order = 4)] public string? DisplayName { get; set; }
[DataMember(Order = 5)] public string? Description { get; set; }
[DataMember(Order = 6)] public int? Left { get; set; }
[DataMember(Order = 7)] public int? Top { get; set; }
[DataMember(Order = 8)] public bool PersistWorkflow { get; set; }
[DataMember(Order = 9)] public bool LoadWorkflowContext { get; set; }
[DataMember(Order = 10)] public bool SaveWorkflowContext { get; set; }
[DataMember(Order = 11)] public ActivityDefinitionProperties Properties { get; set; } = new ActivityDefinitionProperties();
}
}

View file

@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Elsa.Client.Models
{
[DataContract]
public class ActivityDefinitionProperties : Dictionary<string, ActivityDefinitionPropertyValue>
{
}

View file

@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Elsa.Client.Models
{
[DataContract]
public class CompositeActivityDefinition : ActivityDefinition
{
public CompositeActivityDefinition()
@ -10,8 +12,8 @@ namespace Elsa.Client.Models
Connections = new System.Collections.Generic.List<ConnectionDefinition>();
Type = "CompositeActivity";
}
public ICollection<ActivityDefinition> Activities { get; set; }
public ICollection<ConnectionDefinition> Connections { get; set; }
[DataMember(Order = 1)] public ICollection<ActivityDefinition> Activities { get; set; }
[DataMember(Order = 2)] public ICollection<ConnectionDefinition> Connections { get; set; }
}
}

View file

@ -1,5 +1,8 @@
using System.Runtime.Serialization;
namespace Elsa.Client.Models
{
[DataContract]
public class ConnectionDefinition
{
public ConnectionDefinition()
@ -12,9 +15,9 @@ namespace Elsa.Client.Models
TargetActivityId = targetActivityId;
Outcome = outcome;
}
public string? SourceActivityId { get; set; }
public string? TargetActivityId { get; set; }
public string? Outcome { get; set; }
[DataMember(Order = 1)] public string? SourceActivityId { get; set; }
[DataMember(Order = 2)] public string? TargetActivityId { get; set; }
[DataMember(Order = 3)] public string? Outcome { get; set; }
}
}

View file

@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Elsa.Client.Models
{
[DataContract]
public class Variables
{
public Variables()
@ -18,7 +20,7 @@ namespace Elsa.Client.Models
Data = data;
}
public IDictionary<string, object?> Data { get; set; }
[DataMember(Order = 1)]public IDictionary<string, object?> Data { get; set; }
public object? Get(string name) => Has(name) ? Data[name] : default;

View file

@ -1,10 +1,12 @@
using System;
using System.Runtime.Serialization;
namespace Elsa.Client.Models
{
[DataContract]
public class WorkflowContextOptions
{
public Type ContextType { get; set; } = default!;
public WorkflowContextFidelity ContextFidelity { get; set; }
[DataMember(Order = 1)] public Type ContextType { get; set; } = default!;
[DataMember(Order = 2)] public WorkflowContextFidelity ContextFidelity { get; set; }
}
}

View file

@ -1,5 +1,8 @@
namespace Elsa.Client.Models
using System.Runtime.Serialization;
namespace Elsa.Client.Models
{
[DataContract]
public class WorkflowDefinition : CompositeActivityDefinition
{
public WorkflowDefinition()
@ -8,17 +11,17 @@
Type = "Workflow";
}
public int Id { get; set; }
public string WorkflowDefinitionId { get; set; } = default!;
public string WorkflowDefinitionVersionId { get; set; } = default!;
public int Version { get; set; }
public Variables? Variables { get; set; }
public WorkflowContextOptions? ContextOptions { get; set; }
public bool IsSingleton { get; set; }
public WorkflowPersistenceBehavior PersistenceBehavior { get; set; }
public bool DeleteCompletedInstances { get; set; }
public bool IsEnabled { get; set; }
public bool IsPublished { get; set; }
public bool IsLatest { get; set; }
[DataMember(Order = 1)] public int Id { get; set; }
[DataMember(Order = 2)] public string WorkflowDefinitionId { get; set; } = default!;
[DataMember(Order = 3)] public string WorkflowDefinitionVersionId { get; set; } = default!;
[DataMember(Order = 4)] public int Version { get; set; }
[DataMember(Order = 5)] public Variables? Variables { get; set; }
[DataMember(Order = 6)] public WorkflowContextOptions? ContextOptions { get; set; }
[DataMember(Order = 7)] public bool IsSingleton { get; set; }
[DataMember(Order = 8)] public WorkflowPersistenceBehavior PersistenceBehavior { get; set; }
[DataMember(Order = 9)] public bool DeleteCompletedInstances { get; set; }
[DataMember(Order = 10)] public bool IsEnabled { get; set; }
[DataMember(Order = 11)] public bool IsPublished { get; set; }
[DataMember(Order = 12)] public bool IsLatest { get; set; }
}
}

File diff suppressed because one or more lines are too long

View file

@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.0" />
</ItemGroup>

View file

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using ElsaDashboard.Application.Models;
using ElsaDashboard.Application.Services;
using Microsoft.AspNetCore.Components;
@ -6,6 +7,29 @@ namespace ElsaDashboard.Application.Extensions
{
public static class FlyoutPanelServiceExtensions
{
public static ValueTask ShowAsync<T>(this IFlyoutPanelService service, string title) where T : ComponentBase => service.ShowAsync(typeof(T), title);
public static ValueTask ShowAsync<T>(this IFlyoutPanelService service, string title) where T : ComponentBase =>
service.ShowAsync<T>(new FlyoutPanelOptions
{
Title = title
});
public static ValueTask ShowAsync<T>(this IFlyoutPanelService service, string title, object parameters, params ButtonDescriptor[] buttons) where T : ComponentBase =>
service.ShowAsync<T>(new FlyoutPanelOptions
{
Title = title,
Buttons = buttons,
Parameters = parameters.ToDictionary()
});
public static ValueTask ShowAsync<T>(this IFlyoutPanelService service, FlyoutPanelOptions options) where T : ComponentBase =>
service.ShowAsync(new FlyoutPanelOptions
{
ContentComponentType = typeof(T),
Buttons = options.Buttons,
Parameters = options.Parameters,
Title = options.Title
});
}
}

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.Linq;
namespace ElsaDashboard.Application.Extensions
{
public static class ObjectExtensions
{
public static Dictionary<string, object?> ToDictionary(this object value) => value.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(value, null));
}
}

View file

@ -0,0 +1,15 @@
using System;
using Elsa.Client.Models;
namespace ElsaDashboard.Application.Models
{
public class ActivityDescriptorSelectedEventArgs : EventArgs
{
public ActivityDescriptor ActivityDescriptor { get; }
public ActivityDescriptorSelectedEventArgs(ActivityDescriptor activityDescriptor)
{
ActivityDescriptor = activityDescriptor;
}
}
}

View file

@ -1,4 +1,4 @@
namespace ElsaDashboard.Application.Pages.Designer
namespace ElsaDashboard.Application.Models
{
public enum ActivityTraitFilter
{

View file

@ -0,0 +1,15 @@
using System;
using ElsaDashboard.Application.Services;
namespace ElsaDashboard.Application.Models
{
public class ButtonClickEventArgs : EventArgs
{
public ButtonClickEventArgs(ButtonDescriptor button)
{
Button = button;
}
public ButtonDescriptor Button { get; }
}
}

View file

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using ElsaDashboard.Application.Services;
using ElsaDashboard.Application.Shared;
namespace ElsaDashboard.Application.Models
{
public class FlyoutPanelOptions
{
public Type ContentComponentType { get; set; } = typeof(FlyoutPanelPlaceholder);
public ICollection<ButtonDescriptor> Buttons { get; set; } = new List<ButtonDescriptor>();
public string? Title { get; set; }
public IDictionary<string, object?> Parameters { get; set; } = new Dictionary<string, object?>();
}
}

View file

@ -1,85 +1,25 @@
<div class="flex-1">
<div class="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<!-- Project name -->
@using Humanizer
<div class="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
@foreach (var property in ActivityDescriptor.Properties)
{
var controlId = property.Name.ToLowerInvariant().Underscore();
<div class="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div>
<label for="project_name" class="block text-sm font-medium leading-5 text-gray-900 sm:mt-px sm:pt-2">
Project name
<label for="@controlId" class="block text-sm font-medium leading-5 text-gray-900 sm:mt-px sm:pt-2">
@property.Label
</label>
</div>
<div class="sm:col-span-2">
<div class="rounded-md shadow-sm">
<input id="project_name" class="form-input block w-full sm:text-sm sm:leading-5" placeholder="">
<input id="@controlId" class="form-input block w-full sm:text-sm sm:leading-5" placeholder="">
</div>
@if (!string.IsNullOrWhiteSpace(property.Hint))
{
<p class="mt-2 text-sm text-gray-500">@property.Hint</p>
}
</div>
</div>
<!-- Project description -->
<div class="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div>
<label for="project_description" class="block text-sm font-medium leading-5 text-gray-900 sm:mt-px sm:pt-2">
Description
</label>
</div>
<div class="sm:col-span-2">
<div class="flex rounded-md shadow-sm">
<textarea id="project_description" rows="3" class="form-textarea block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"></textarea>
</div>
</div>
</div>
<!-- Team members -->
<div class="space-y-2 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-center sm:px-6 sm:py-5">
<div>
<h3 class="text-sm font-medium leading-5 text-gray-900">
Team Members
</h3>
</div>
<div class="sm:col-span-2">
<div class="flex space-x-2">
<a href="#" class="flex-shrink-0 rounded-full hover:opacity-75 focus:outline-none focus:shadow-outline transition ease-in-out duration-150">
<img class="inline-block h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80" alt="Tom Warner">
</a>
<a href="#" class="flex-shrink-0 rounded-full hover:opacity-75 focus:outline-none focus:shadow-outline transition ease-in-out duration-150">
<img class="inline-block h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80" alt="Tom Warner">
</a>
<a href="#" class="flex-shrink-0 rounded-full hover:opacity-75 focus:outline-none focus:shadow-outline transition ease-in-out duration-150">
<img class="inline-block h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80" alt="Tom Warner">
</a>
<a href="#" class="flex-shrink-0 rounded-full hover:opacity-75 focus:outline-none focus:shadow-outline transition ease-in-out duration-150">
<img class="inline-block h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80" alt="Tom Warner">
</a>
<a href="#" class="flex-shrink-0 rounded-full hover:opacity-75 focus:outline-none focus:shadow-outline transition ease-in-out duration-150">
<img class="inline-block h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1586297098710-0382a496c814?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80" alt="Tom Warner">
</a>
<button type="button" class="flex-shrink-0 inline-flex h-8 w-8 items-center justify-center rounded-full border-2 border-dashed border-gray-200 text-gray-400 hover:text-gray-500 hover:border-gray-300 focus:text-gray-500 focus:border-gray-300 focus:outline-none transition ease-in-out duration-150" aria-label="Add team member">
<svg class="h-5 w-5" x-description="Heroicon name: plus" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
<div class="flex-shrink-0 px-4 border-t border-gray-200 py-5 sm:px-6">
<div class="space-x-3 flex justify-end">
<span class="inline-flex rounded-md shadow-sm">
<button type="button" class="py-2 px-4 border border-gray-300 rounded-md text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition duration-150 ease-in-out">
Cancel
</button>
</span>
<span class="inline-flex rounded-md shadow-sm">
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition duration-150 ease-in-out">
Create
</button>
</span>
</div>
</div>
</div>
}
</div>

View file

@ -5,6 +5,6 @@ namespace ElsaDashboard.Application.Pages.Designer
{
partial class ActivityEditor
{
//[Parameter] public ActivityDescriptor ActivityDescriptor { get; set; } = default!;
[Parameter] public ActivityDescriptor ActivityDescriptor { get; set; } = default!;
}
}

View file

@ -1,63 +1,61 @@
<div class="flex-1">
<div class="p-6">
<div class="relative rounded-md shadow-sm">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-6 w-6 text-gray-400" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z"/>
<circle cx="10" cy="10" r="7"/>
<line x1="21" y1="21" x2="15" y2="15"/>
</svg>
</div>
<input type="text" @bind="ActivitySearchText" @bind:event="oninput" class="form-input block w-full pl-10 sm:text-sm sm:leading-5" placeholder="Search activities">
<div class="p-6">
<div class="relative rounded-md shadow-sm">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-6 w-6 text-gray-400" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z"/>
<circle cx="10" cy="10" r="7"/>
<line x1="21" y1="21" x2="15" y2="15"/>
</svg>
</div>
<input type="text" @bind="ActivitySearchText" @bind:event="oninput" class="form-input block w-full pl-10 sm:text-sm sm:leading-5" placeholder="Search activities">
</div>
</div>
<div class="border-b border-gray-200">
<div class="px-6">
<nav class="-mb-px flex space-x-6" x-descriptions="Tab component">
@foreach (var activityTypeFilter in ActivityTraitFilters)
<div class="border-b border-gray-200">
<div class="px-6">
<nav class="-mb-px flex space-x-6" x-descriptions="Tab component">
@foreach (var activityTypeFilter in ActivityTraitFilters)
{
var activeClass = activityTypeFilter == SelectedActivityTraitFilter ? "border-blue-500 text-blue-600 focus:text-blue-800 focus:border-blue-700" : "border-transparent text-gray-500 focus:text-gray-700 focus:border-gray-300";
<a href="#" @onclick:preventDefault @onclick="@(() => OnActivityTypeFilterClick(activityTypeFilter))" class="whitespace-no-wrap pb-4 px-1 border-b-2 font-medium text-sm leading-5 focus:outline-none @activeClass" aria-current="page">
@activityTypeFilter.ToString()
</a>
}
</nav>
</div>
</div>
<div class="p-6 space-y-6 overflow-y-auto">
@foreach (var grouping in ActivityGroupings)
{
<div>
<h2>@grouping.Key</h2>
<ul class="grid grid-cols-1 gap-4 mt-3" x-max="1">
@foreach (var activity in grouping)
{
var activeClass = activityTypeFilter == SelectedActivityTraitFilter ? "border-blue-500 text-blue-600 focus:text-blue-800 focus:border-blue-700" : "border-transparent text-gray-500 focus:text-gray-700 focus:border-gray-300";
<a href="#" @onclick:preventDefault @onclick="@(() => OnActivityTypeFilterClick(activityTypeFilter))" class="whitespace-no-wrap pb-4 px-1 border-b-2 font-medium text-sm leading-5 focus:outline-none @activeClass" aria-current="page">
@activityTypeFilter.ToString()
</a>
<li @onclick="(() => OnActivityClick(activity))" class="relative col-span-1 flex shadow-sm rounded-md select-none cursor-pointer">
<div class="flex-shrink-0 flex items-center justify-center w-16 bg-blue-600 text-white text-sm leading-5 font-medium rounded-l-md">
<svg class="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md">
<div class="flex-1 px-4 py-2 text-sm leading-5">
<span class="text-gray-900 font-medium hover:text-gray-600 transition ease-in-out duration-150">
@activity.DisplayName
</span>
<p class="text-gray-500">
@activity.Description
</p>
</div>
</div>
</li>
}
</nav>
</ul>
</div>
</div>
<div class="p-6 space-y-6 overflow-y-auto">
@foreach (var grouping in ActivityGroupings)
{
<div>
<h2>@grouping.Key</h2>
<ul class="grid grid-cols-1 gap-4 mt-3" x-max="1">
@foreach (var activity in grouping)
{
<li @onclick="(() => OnActivityClick(activity))" class="relative col-span-1 flex shadow-sm rounded-md select-none cursor-pointer">
<div class="flex-shrink-0 flex items-center justify-center w-16 bg-blue-600 text-white text-sm leading-5 font-medium rounded-l-md">
<svg class="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md">
<div class="flex-1 px-4 py-2 text-sm leading-5">
<span class="text-gray-900 font-medium hover:text-gray-600 transition ease-in-out duration-150">
@activity.DisplayName
</span>
<p class="text-gray-500">
@activity.Description
</p>
</div>
</div>
</li>
}
</ul>
</div>
}
</div>
}
</div>

View file

@ -3,18 +3,16 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Elsa.Client.Models;
using ElsaDashboard.Application.Extensions;
using ElsaDashboard.Application.Services;
using ElsaDashboard.Application.Models;
using ElsaDashboard.Shared.Rpc;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace ElsaDashboard.Application.Pages.Designer
{
partial class ActivityPicker
{
[Parameter] public EventCallback<ActivityDescriptorSelectedEventArgs> ActivitySelected { get; set; }
[Inject] private IActivityService ActivityService { get; set; } = default!;
[Inject] private IFlyoutPanelService FlyoutPanelService { get; set; } = default!;
private ActivityTraitFilter SelectedActivityTraitFilter { get; set; }
private string? ActivitySearchText { get; set; }
private ICollection<ActivityTraitFilter> ActivityTraitFilters => new[] { ActivityTraitFilter.All, ActivityTraitFilter.Actions, ActivityTraitFilter.Triggers };
@ -57,13 +55,11 @@ namespace ElsaDashboard.Application.Pages.Designer
where
(activity.Description ?? "").Contains(searchText, StringComparison.InvariantCultureIgnoreCase) ||
(activity.DisplayName ?? "").Contains(searchText, StringComparison.InvariantCultureIgnoreCase) ||
activity.Type.Contains(searchText, StringComparison.InvariantCultureIgnoreCase)
activity.Type.Contains(searchText, StringComparison.InvariantCultureIgnoreCase) ||
activity.Category.Contains(searchText, StringComparison.InvariantCultureIgnoreCase)
select activity;
}
private async Task OnActivityClick(ActivityDescriptor activity)
{
await FlyoutPanelService.ShowAsync<ActivityEditor>(activity.DisplayName);
}
private Task OnActivityClick(ActivityDescriptor activityDescriptor) => ActivitySelected.InvokeAsync(new ActivityDescriptorSelectedEventArgs(activityDescriptor));
}
}

View file

@ -26,7 +26,7 @@
<div class="inline-flex flex flex-col items-center">
<button id="start-button" type="button"
class="px-6 py-3 border border-transparent text-base leading-6 font-medium rounded-md text-white bg-green-600 hover:bg-green-500 focus:outline-none focus:border-green-700 focus:shadow-outline-green active:bg-green-700 transition ease-in-out duration-150"
@onclick="@(async e => await OnStartClick(e))">
@onclick="@(async e => await OnStartClick())">
Start
</button>
<div id="start-button-plus" class="mt-6">

View file

@ -1,5 +1,8 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Elsa.Client.Models;
using ElsaDashboard.Application.Extensions;
using ElsaDashboard.Application.Models;
using ElsaDashboard.Application.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
@ -23,13 +26,36 @@ namespace ElsaDashboard.Application.Pages.Designer
public async ValueTask DisposeAsync()
{
if(_designerModule != null!)
if (_designerModule != null!)
await _designerModule.DisposeAsync();
}
private async ValueTask OnStartClick(MouseEventArgs e)
private async ValueTask ClosePanelAsync() => await FlyoutPanelService.HideAsync();
private async ValueTask ShowActivityPickerAsync()
{
await FlyoutPanelService.ShowAsync<ActivityPicker>("Activities");
await FlyoutPanelService.ShowAsync<ActivityPicker>(
"Activities",
new { ActivitySelected = new EventCallback<ActivityDescriptorSelectedEventArgs>(this, (Func<ActivityDescriptorSelectedEventArgs, ValueTask>) OnActivityPickedAsync) },
new ButtonDescriptor("Cancel", new EventCallback<ButtonClickEventArgs>(this, (Func<ButtonClickEventArgs, ValueTask>) (_ => ClosePanelAsync()))));
}
private async ValueTask AddActivityAsync(ActivityDescriptor activityDescriptor)
{
await FlyoutPanelService.HideAsync();
}
private async ValueTask OnStartClick() => await ShowActivityPickerAsync();
private async ValueTask OnActivityPickedAsync(ActivityDescriptorSelectedEventArgs e)
{
var activityDescriptor = e.ActivityDescriptor;
await FlyoutPanelService.ShowAsync<ActivityEditor>(
activityDescriptor.DisplayName,
new { activityDescriptor },
new ButtonDescriptor("Cancel", new EventCallback<ButtonClickEventArgs>(this, (Func<ButtonClickEventArgs, ValueTask>) (_ => ShowActivityPickerAsync()))),
new ButtonDescriptor("OK", new EventCallback<ButtonClickEventArgs>(this, (Func<ButtonClickEventArgs, ValueTask>) (_ => AddActivityAsync(activityDescriptor))), true));
}
private async ValueTask OnActivityClick(MouseEventArgs e)

View file

@ -0,0 +1,24 @@
using ElsaDashboard.Application.Models;
using Microsoft.AspNetCore.Components;
namespace ElsaDashboard.Application.Services
{
public sealed record ButtonDescriptor
{
public ButtonDescriptor(string text, EventCallback<ButtonClickEventArgs>? clickHandler = default, bool isPrimary = false)
{
Text = text;
ClickHandler = clickHandler;
IsPrimary = isPrimary;
}
public ButtonDescriptor(string text, EventCallback<ButtonClickEventArgs>? clickHandler) : this(text, clickHandler, false)
{
}
public string Text { get; }
public bool IsPrimary { get; }
public EventCallback<ButtonClickEventArgs>? ClickHandler { get; }
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using ElsaDashboard.Application.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
@ -17,22 +18,20 @@ namespace ElsaDashboard.Application.Services
public event Action? OnShow;
public event Action? OnHide;
public FlyoutPanelOptions Options { get; private set; } = new();
public Type? ContentComponentType { get; private set; }
public string? Title { get; private set; }
public async ValueTask ShowAsync(Type contentComponentType, string title)
public async ValueTask ShowAsync(FlyoutPanelOptions options)
{
if (!typeof(ComponentBase).IsAssignableFrom(contentComponentType))
throw new ArgumentException($"{contentComponentType!.FullName} must be a Blazor Component");
Options = options;
if (!typeof(ComponentBase).IsAssignableFrom(options.ContentComponentType))
throw new ArgumentException($"{options.ContentComponentType.FullName} must be a Blazor Component");
await InitJSModuleAsync();
await _module.InvokeVoidAsync("show");
ContentComponentType = contentComponentType;
Title = title;
OnShow?.Invoke();
}
public async ValueTask HideAsync()
{
await InitJSModuleAsync();
@ -45,7 +44,7 @@ namespace ElsaDashboard.Application.Services
if (_module != null!)
await _module.DisposeAsync();
}
private async Task InitJSModuleAsync()
{
_module = await _jsRuntime.InvokeAsync<IJSObjectReference>("import", "./_content/ElsaDashboard.Application/flyoutPanel.js");

View file

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using ElsaDashboard.Application.Models;
namespace ElsaDashboard.Application.Services
{
@ -7,9 +8,8 @@ namespace ElsaDashboard.Application.Services
{
event Action? OnShow;
event Action? OnHide;
Type? ContentComponentType { get; }
string? Title { get; }
ValueTask ShowAsync(Type contentComponentType, string title);
FlyoutPanelOptions Options { get; }
ValueTask ShowAsync(FlyoutPanelOptions options);
ValueTask HideAsync();
}
}

View file

@ -1,37 +1,47 @@
<div x-show="$store.flyoutPanel.show" x-data="{}" x-on:keydown.window.escape="$store.flyoutPanel.show = false" class="fixed inset-0 overflow-hidden">
@using ElsaDashboard.Application.Services
<div x-show="$store.flyoutPanel.show" x-data="{}" x-on:keydown.window.escape="$store.flyoutPanel.show = false" class="fixed inset-0 overflow-hidden">
<div class="absolute inset-0 overflow-hidden">
<section x-on:click.away="$store.flyoutPanel.show = false" class="absolute inset-y-0 right-0 pl-10 max-w-md md:max-w-full flex sm:pl-16">
<div class="w-screen max-w-2xl" x-description="Slide-over panel, show/hide based on slide-over state." x-show="$store.flyoutPanel.show == true" x-transition:enter="transform transition ease-in-out duration-500 sm:duration-700" x-transition:enter-start="translate-x-full" x-transition:enter-end="translate-x-0" x-transition:leave="transform transition ease-in-out duration-500 sm:duration-700" x-transition:leave-start="translate-x-0" x-transition:leave-end="translate-x-full">
<div class="h-full flex flex-col bg-white shadow-xl overflow-y-scroll">
<!-- Header -->
<header class="p-6">
<div class="flex items-start justify-between space-x-3">
@if (!string.IsNullOrWhiteSpace(Title))
{
<h2 class="text-lg leading-7 font-medium text-gray-900">
@Title
</h2>
}
<div class="h-7 flex items-center">
<button x-on:click="$store.flyoutPanel.show = false" aria-label="Close panel" class="text-gray-400 hover:text-gray-500 transition ease-in-out duration-150">
<svg class="h-6 w-6" x-description="Heroicon name: x" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
<div class="flex-1">
<!-- Header -->
<header class="p-6">
<div class="flex items-start justify-between space-x-3">
@if (!string.IsNullOrWhiteSpace(Title))
{
<h2 class="text-lg leading-7 font-medium text-gray-900">
@Title
</h2>
}
<div class="h-7 flex items-center">
<button x-on:click="$store.flyoutPanel.show = false" aria-label="Close panel" class="text-gray-400 hover:text-gray-500 transition ease-in-out duration-150">
<svg class="h-6 w-6" x-description="Heroicon name: x" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
</div>
</div>
</header>
@if (Content != null)
{
</header>
@Content
}
else
</div>
@if (Buttons.Any())
{
<div class="p-6">
<p>Loading...</p>
<div class="flex-shrink-0 px-4 py-4 space-x-4 flex justify-end">
@{ var buttonKey = 0; }
@foreach (var button in Buttons)
{
var styleClass = button.IsPrimary ? "border-transparent text-white bg-blue-600 hover:bg-blue-500 focus:border-blue-700 active:bg-blue-700" : "border-gray-300 text-gray-700 hover:text-gray-500 focus:border-blue-300 active:bg-gray-50 active:text-gray-800";
<span @key="@buttonKey" class="inline-flex rounded-md shadow-sm">
<button @onclick="async () => await OnButtonClickAsync(button)" type="button" class="inline-flex justify-center py-2 px-4 border rounded-md text-sm leading-5 font-medium focus:outline-none focus:shadow-outline-blue transition duration-150 ease-in-out @styleClass">
@button.Text
</button>
</span>
buttonKey++;
}
</div>
}
</div>

View file

@ -1,5 +1,10 @@
using ElsaDashboard.Application.Services;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ElsaDashboard.Application.Models;
using ElsaDashboard.Application.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace ElsaDashboard.Application.Shared
{
@ -7,7 +12,8 @@ namespace ElsaDashboard.Application.Shared
{
[Inject] private IFlyoutPanelService FlyoutPanelService { get; set; } = default!;
[Parameter] public string? Title { get; set; }
[Parameter] public RenderFragment? Content { get; set; }
[Parameter] public RenderFragment Content { get; set; } = default!;
[Parameter] public ICollection<ButtonDescriptor> Buttons { get; set; } = new List<ButtonDescriptor>();
protected override void OnInitialized()
{
@ -16,17 +22,31 @@ namespace ElsaDashboard.Application.Shared
private async void Update()
{
var options = FlyoutPanelService.Options;
await InvokeAsync(() =>
{
Content = builder =>
{
builder.OpenComponent(0, FlyoutPanelService.ContentComponentType!);
builder.OpenComponent(0, options.ContentComponentType);
var i = 0;
foreach (var (name, value) in options.Parameters)
builder.AddAttribute(i++, name, value);
builder.CloseComponent();
};
Title = FlyoutPanelService.Title;
Buttons = options.Buttons;
Title = options.Title;
StateHasChanged();
});
}
private async ValueTask OnButtonClickAsync(ButtonDescriptor button)
{
if(button.ClickHandler != null)
await button.ClickHandler.Value.InvokeAsync(new ButtonClickEventArgs(button));
}
}
}

View file

@ -0,0 +1,2 @@
<div class="p-6">
</div>

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Elsa.Client.Models;
using Newtonsoft.Json.Linq;
using ProtoBuf.Meta;
namespace ElsaDashboard.Shared.Surrogates