Add project files.

This commit is contained in:
2025-08-20 11:07:36 +03:30
parent 3863c74931
commit 1a308a4069
899 changed files with 85379 additions and 0 deletions
@@ -0,0 +1,12 @@
using Elsa.Studio.Contracts;
namespace Elsa.Studio.Abstractions;
/// <summary>
/// A base class for modules.
/// </summary>
public abstract class FeatureBase : IFeature
{
/// <inheritdoc />
public virtual ValueTask InitializeAsync(CancellationToken cancellationToken = default) => ValueTask.CompletedTask;
}
@@ -0,0 +1,54 @@
using Elsa.Studio.Contracts;
using Elsa.Studio.Enums;
using Elsa.Studio.Models;
using Microsoft.AspNetCore.Components;
namespace Elsa.Studio.Abstractions;
/// <summary>
/// Provides a base handler for field extensions.
/// </summary>
public abstract class FieldExtensionHandlerBase : IUIFieldExtensionHandler
{
/// <summary>
/// <inheritdoc/>
/// </summary>
public virtual int DisplayOrder { get; set; } = 1;
/// <summary>
/// <inheritdoc/>
/// </summary>
public virtual bool IncludeForAll { get; set; } = false;
/// <summary>
/// <inheritdoc/>
/// </summary>
public virtual FieldExtensionPosition Position { get; set; } = FieldExtensionPosition.Top;
/// <summary>
/// <inheritdoc/>
/// </summary>
public virtual string UIHintComponent { get; set; } = string.Empty;
/// <summary>
/// <inheritdoc/>
/// </summary>
public virtual List<string> ActivityTypes { get; set; } = [];
/// <summary>
/// <inheritdoc/>
/// </summary>
public virtual List<string> Syntaxes { get; set; } = [];
/// <summary>
/// <inheritdoc/>
/// </summary>
public virtual bool GetExtensionForInputComponent(string componentName) =>
(string.IsNullOrEmpty(UIHintComponent) && !ActivityTypes.Any() && !Syntaxes.Any() && IncludeForAll)
|| UIHintComponent.Equals(componentName, StringComparison.OrdinalIgnoreCase);
/// <summary>
/// <inheritdoc/>
/// </summary>
public abstract RenderFragment DisplayExtension(DisplayInputEditorContext context);
}
@@ -0,0 +1,18 @@
namespace Elsa.Studio.Attributes;
/// <summary>
/// Marks a class as a remote feature.
/// </summary>
public class RemoteFeatureAttribute : Attribute
{
/// <inheritdoc />
public RemoteFeatureAttribute(string name)
{
Name = name;
}
/// <summary>
/// Gets the name of the remote feature.
/// </summary>
public string Name { get; }
}
@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Elsa.Studio.Contracts;
public interface IActivityTab
{
string Title { get; }
Func<IDictionary<string, object?>, RenderFragment> Render { get; }
}
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Components;
namespace Elsa.Studio.Contracts;
public interface IAppBarService
{
/// <summary>
/// Invoked when the app bar items change.
/// </summary>
event Action AppBarItemsChanged;
/// <summary>
/// A collection of components to render in the app bar.
/// </summary>
IEnumerable<RenderFragment> AppBarItems { get; }
/// <summary>
/// Adds a component to the app bar.
/// </summary>
/// <typeparam name="T">The type of the component.</typeparam>
void AddAppBarItem<T>() where T : IComponent;
}
@@ -0,0 +1,19 @@
namespace Elsa.Studio.Contracts;
/// <summary>
/// Provides connection details to the backend.
/// </summary>
public interface IBackendApiClientProvider
{
/// <summary>
/// Gets the URL to the backend.
/// </summary>
Uri Url { get; }
/// <summary>
/// Gets an API client from the backend connection provider.
/// </summary>
/// <typeparam name="T">The API client type.</typeparam>
/// <returns>The API client.</returns>
ValueTask<T> GetApiAsync<T>(CancellationToken cancellationToken = default) where T : class;
}
@@ -0,0 +1,10 @@
namespace Elsa.Studio.Contracts;
/// <summary>
/// Represents an interface for accessing the current IServiceProvider in Blazor.
/// </summary>
public interface IBlazorServiceAccessor
{
/// Gets or sets the current IServiceProvider.
IServiceProvider Services { get; set; }
}
@@ -0,0 +1,14 @@
using Elsa.Studio.Models;
namespace Elsa.Studio.Contracts;
/// <summary>
/// Provides information about the client.
/// </summary>
public interface IClientInformationProvider
{
/// <summary>
/// Gets information about the client.
/// </summary>
ValueTask<ClientInformation> GetInfoAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,14 @@
using Elsa.Api.Client.Resources.Scripting.Models;
namespace Elsa.Studio.Contracts;
/// <summary>
/// Provides available expression descriptors.
/// </summary>
public interface IExpressionProvider
{
/// <summary>
/// Lists all expression descriptors.
/// </summary>
ValueTask<IEnumerable<ExpressionDescriptor>> ListAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,19 @@
using Elsa.Api.Client.Resources.Scripting.Models;
namespace Elsa.Studio.Contracts;
/// <summary>
/// Provides syntax services.
/// </summary>
public interface IExpressionService
{
/// <summary>
/// Lists all expression descriptors.
/// </summary>
Task<IEnumerable<ExpressionDescriptor>> ListDescriptorsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Gets a syntax provider by name.
/// </summary>
Task<ExpressionDescriptor?> GetByTypeAsync(string type, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,13 @@
namespace Elsa.Studio.Contracts;
/// <summary>
/// Represents a feature that can be registered with the dashboard.
/// </summary>
public interface IFeature
{
/// <summary>
/// Called by the dashboard to initialize the feature.
/// </summary>
/// <param name="cancellationToken">A cancellation token.</param>
ValueTask InitializeAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,22 @@
namespace Elsa.Studio.Contracts;
/// <summary>
/// Manages features.
/// </summary>
public interface IFeatureService
{
/// <summary>
/// Event that is triggered when the features have been initialized.
/// </summary>
event Action? Initialized;
/// <summary>
/// Returns all features.
/// </summary>
IEnumerable<IFeature> GetFeatures();
/// <summary>
/// Initializes all features.
/// </summary>
Task InitializeFeaturesAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,30 @@
namespace Elsa.Studio.Contracts;
/// <summary>
/// Represents a mediator that can be used to publish notifications and subscribe to notifications.
/// </summary>
public interface IMediator
{
/// <summary>
/// Subscribes to a notification.
/// </summary>
/// <param name="handler"></param>
/// <typeparam name="TNotification"></typeparam>
/// <typeparam name="THandler"></typeparam>
void Subscribe<TNotification, THandler>(THandler handler) where THandler : INotificationHandler<TNotification> where TNotification : INotification;
/// <summary>
/// Unsubscribes from a notification.
/// </summary>
void Unsubscribe<TNotification, THandler>(THandler handler) where THandler : INotificationHandler<TNotification> where TNotification : INotification;
/// <summary>
/// Unsubscribes from a notification.
/// </summary>
void Unsubscribe(INotificationHandler handler);
/// <summary>
/// Publishes a notification.
/// </summary>
Task NotifyAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification;
}
@@ -0,0 +1,16 @@
using Elsa.Studio.Models;
namespace Elsa.Studio.Contracts;
/// <summary>
/// Returns menu item groups to the dashboard.
/// </summary>
public interface IMenuGroupProvider
{
/// <summary>
/// Returns menu item groups to the dashboard.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A collection of <see cref="MenuItemGroup"/> instances.</returns>
ValueTask<IEnumerable<MenuItemGroup>> GetMenuGroupsAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,10 @@
using Elsa.Studio.Models;
namespace Elsa.Studio.Contracts;
/// Provides menu items to the dashboard.
public interface IMenuProvider
{
/// Returns a list of menu items.
ValueTask<IEnumerable<MenuItem>> GetMenuItemsAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,23 @@
using Elsa.Studio.Models;
namespace Elsa.Studio.Contracts;
/// <summary>
/// A service that provides menu items and menu item groups.
/// </summary>
public interface IMenuService
{
/// <summary>
/// Returns all menu items from all menu providers.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A list of <see cref="MenuItem"/> instances.</returns>
ValueTask<IEnumerable<MenuItem>> GetMenuItemsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Returns all menu item groups from all menu group providers.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A list of <see cref="MenuItemGroup"/> instances.</returns>
ValueTask<IEnumerable<MenuItemGroup>> GetMenuItemGroupsAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,5 @@
namespace Elsa.Studio.Contracts;
public interface INotification
{
}
@@ -0,0 +1,22 @@
namespace Elsa.Studio.Contracts;
/// <summary>
/// Represents a notification handler.
/// </summary>
public interface INotificationHandler
{
}
/// <summary>
/// Represents a notification handler.
/// </summary>
/// <typeparam name="T">The type of the notification.</typeparam>
public interface INotificationHandler<in T> : INotificationHandler where T : INotification
{
/// <summary>
/// Handles the given notification.
/// </summary>
/// <param name="notification">The notification to handle.</param>
/// <param name="cancellationToken">The cancellation token.</param>
Task HandleAsync(T notification, CancellationToken cancellationToken);
}
@@ -0,0 +1,14 @@
using Elsa.Studio.Models;
namespace Elsa.Studio.Contracts;
/// <summary>
/// Provides access to the current remote backend.
/// </summary>
public interface IRemoteBackendAccessor
{
/// <summary>
/// Gets or sets the current backend.
/// </summary>
RemoteBackend RemoteBackend { get; }
}
@@ -0,0 +1,22 @@
using Elsa.Api.Client.Resources.Features.Models;
namespace Elsa.Studio.Contracts;
/// <summary>
/// Provides a way to check if a feature is enabled.
/// </summary>
public interface IRemoteFeatureProvider
{
/// <summary>
/// Returns a value indicating whether the feature with the specified name is enabled.
/// </summary>
/// <param name="featureName">The name of the feature.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>A value indicating whether the feature with the specified name is enabled.</returns>
Task<bool> IsEnabledAsync(string featureName, CancellationToken cancellationToken = default);
/// <summary>
/// Returns a list of installed features.
/// </summary>
Task<IEnumerable<FeatureDescriptor>> ListAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,14 @@
using Elsa.Studio.Models;
namespace Elsa.Studio.Contracts;
/// <summary>
/// Provides information about the server.
/// </summary>
public interface IServerInformationProvider
{
/// <summary>
/// Gets information about the server.
/// </summary>
ValueTask<ServerInformation> GetInfoAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,13 @@
namespace Elsa.Studio.Contracts;
/// <summary>
/// Represents a startup task that is invoked once during application startup.
/// </summary>
public interface IStartupTask
{
/// <summary>
/// Invoked during application startup.
/// </summary>
/// <param name="cancellationToken">A cancellation token.</param>
ValueTask ExecuteAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,12 @@
namespace Elsa.Studio.Contracts;
/// <summary>
/// Runs startup tasks.
/// </summary>
public interface IStartupTaskRunner
{
/// <summary>
/// Runs startup tasks.
/// </summary>
Task RunStartupTasksAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,36 @@
using MudBlazor;
namespace Elsa.Studio.Contracts;
/// <summary>
/// Provides theme information to the dashboard.
/// </summary>
public interface IThemeService
{
/// <summary>
/// Raised when the current theme changes.
/// </summary>
event Action CurrentThemeChanged;
/// <summary>
/// Raised when the dark mode changes.
/// </summary>
event Action IsDarkModeChanged;
/// <summary>
/// The current theme.
/// </summary>
MudTheme CurrentTheme { get; set; }
/// <summary>
/// Returns the current palette, depending on whether the dashboard is in dark mode.
/// </summary>
#pragma warning disable CS0618
Palette CurrentPalette => IsDarkMode ? CurrentTheme.PaletteDark : CurrentTheme.PaletteLight;
#pragma warning restore CS0618
/// <summary>
/// Whether the dashboard is in dark mode.
/// </summary>
public bool IsDarkMode { get; set; }
}
@@ -0,0 +1,52 @@
using Elsa.Studio.Enums;
using Elsa.Studio.Models;
using Microsoft.AspNetCore.Components;
namespace Elsa.Studio.Contracts
{
/// <summary>
/// Provides a contract for UI field extension handlers.
/// </summary>
public interface IUIFieldExtensionHandler
{
/// <summary>
/// The order in which the extensions should be displayed.
/// </summary>
int DisplayOrder { get; set; }
/// <summary>
/// If set to true and no restricting types are set, either with <c>UIHintComponent</c>, <c>ActivityTypes</c> or <c>Syntaxes</c>, this extension will be rendered for in all field types.
/// </summary>
bool IncludeForAll { get; set; }
/// <summary>
/// The position to render the extension within the field.
/// </summary>
FieldExtensionPosition Position { get; set; }
/// <summary>
/// The UIHint component this extension should be rendered for.
/// </summary>
string UIHintComponent { get; set; }
/// <summary>
/// The activities this extension should be rendered for.
/// </summary>
List<string> ActivityTypes { get; set; }
/// <summary>
/// The syntaxes this extension should be rendered for.
/// </summary>
List<string> Syntaxes { get; set; }
/// <summary>
/// Returns true if the handler extension the specified or is empty.
/// </summary>
bool GetExtensionForInputComponent(string componentName);
/// <summary>
/// Returns a <see cref="RenderFragment"/> of the added extension.
/// </summary>
RenderFragment DisplayExtension(DisplayInputEditorContext context);
}
}
@@ -0,0 +1,17 @@
using Elsa.Studio.Models;
namespace Elsa.Studio.Contracts;
/// <summary>
/// Provides a service contact for resolving UI field extensions
/// </summary>
public interface IUIFieldExtensionService
{
/// <summary>
/// Try to return a list of extension handlers for a component
/// </summary>
/// <param name="componentName">The component name to get the handler for. This should be the name of the component calling this function.</param>
/// <param name="context">The display input editor context</param>
/// <returns></returns>
List<IUIFieldExtensionHandler>? TryGetHandlers(string componentName, DisplayInputEditorContext context);
}
@@ -0,0 +1,25 @@
using Elsa.Studio.Models;
using Microsoft.AspNetCore.Components;
namespace Elsa.Studio.Contracts;
/// <summary>
/// Provides a contract for UI hint handlers.
/// </summary>
public interface IUIHintHandler
{
/// <summary>
/// Returns true if the handler supports the specified UI hint.
/// </summary>
bool GetSupportsUIHint(string uiHint);
/// <summary>
/// Returns the UI syntax for the handler.
/// </summary>
string UISyntax { get; }
/// <summary>
/// Returns a <see cref="RenderFragment"/> that renders the input editor.
/// </summary>
RenderFragment DisplayInputEditor(DisplayInputEditorContext context);
}
@@ -0,0 +1,7 @@
namespace Elsa.Studio.Contracts;
public interface IUIHintService
{
IUIHintHandler GetHandler(string uiHint);
//RenderFragment DisplayInputEditor(DisplayInputEditorContext context);
}
@@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Components;
namespace Elsa.Studio.Contracts;
/// <summary>
/// Provides the <see cref="RenderFragment"/> to display the login page.
/// </summary>
public interface IUnauthorizedComponentProvider
{
/// <summary>
/// Returns the <see cref="RenderFragment"/> to display the login page.
/// </summary>
RenderFragment GetUnauthorizedComponent();
}
@@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Components;
namespace Elsa.Studio.Contracts;
/// <summary>
/// Represents a widget.
/// </summary>
public interface IWidget
{
/// <summary>
/// Gets the zone in which the widget should be rendered.
/// </summary>
string Zone { get; }
/// <summary>
/// Gets the order in which the widget should be rendered.
/// </summary>
double Order { get; }
/// <summary>
/// Gets the fragment to render.
/// </summary>
Func<IDictionary<string, object?>, RenderFragment> Render { get; }
}
@@ -0,0 +1,17 @@
namespace Elsa.Studio.Contracts;
/// <summary>
/// Provides a registry of widgets.
/// </summary>
public interface IWidgetRegistry
{
/// <summary>
/// Adds the specified widget to the registry.
/// </summary>
void Add(IWidget widget);
/// <summary>
/// Returns a list of widgets for the specified zone.
/// </summary>
IEnumerable<IWidget> List(string zone);
}
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Core Elsa Studio services.</Description>
<PackageTags>elsa studio core</PackageTags>
<RootNamespace>Elsa.Studio</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization"/>
<PackageReference Include="Microsoft.AspNetCore.Components.CustomElements"/>
<PackageReference Include="Microsoft.AspNetCore.Components.Web"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions"/>
<PackageReference Include="Microsoft.JSInterop"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Blazored.FluentValidation"/>
<PackageReference Include="Blazored.LocalStorage"/>
<PackageReference Include="CodeBeam.MudBlazor.Extensions"/>
<PackageReference Include="FluentValidation"/>
<PackageReference Include="MudBlazor"/>
<PackageReference Include="Radzen.Blazor"/>
<PackageReference Include="ShortGuid"/>
<PackageReference Include="ThrottleDebounce"/>
</ItemGroup>
<!-- <ItemGroup Label="Elsa">-->
<!-- <ProjectReference Include="..\..\..\..\..\elsa-core\3.5.0\src\clients\Elsa.Api.Client\Elsa.Api.Client.csproj"/>-->
<!-- </ItemGroup>-->
<ItemGroup Label="Elsa">
<PackageReference Include="Elsa.Api.Client"/>
</ItemGroup>
</Project>
@@ -0,0 +1,41 @@
namespace Elsa.Studio;
/// <summary>
/// Provides Elsa Studio icons.
/// </summary>
public static class ElsaStudioIcons
{
/// <summary>
/// Provides Tabler icons.
/// </summary>
public static class Tabler
{
public const string Pencil = """<svg 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"/> <path d="M4 20h4l10.5 -10.5a1.5 1.5 0 0 0 -4 -4l-10.5 10.5v4" /> <line x1="13.5" y1="6.5" x2="17.5" y2="10.5" /></svg>""";
public const string Text = """<svg 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"/> <line x1="4" y1="6" x2="20" y2="6" /> <line x1="4" y1="18" x2="9" y2="18" /> <path d="M4 12h13a3 3 0 0 1 0 6h-4l2 -2m0 4l-2 -2" /></svg>""";
public const string Cloud = """<svg 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"/> <path d="M7 18a4.6 4.4 0 0 1 0 -9h0a5 4.5 0 0 1 11 2h1a3.5 3.5 0 0 1 0 7h-12" /></svg>""";
public const string SwitchDiagonal = """<svg 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"/> <polyline points="15 4 19 4 19 8" /> <line x1="14.75" y1="9.25" x2="19" y2="4" /> <line x1="5" y1="19" x2="9" y2="15" /> <polyline points="15 19 19 19 19 15" /> <line x1="5" y1="5" x2="19" y2="19" /></svg>""";
public const string CheckCircle = """<svg 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="12" cy="12" r="9" /> <path d="M9 12l2 2l4 -4" /></svg>""";
public const string CheckShield = """<svg 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"/> <path d="M9 12l2 2l4 -4" /> <path d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3" /></svg>""";
public const string Hexagon = """<svg 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"/> <path d="M12.971 3.54l6 3.333A2 2 0 0120 8.62v6.536a2 2 0 0 1 -1.029 1.748l-6 3.333a2 2 0 0 1 -1.942 0l-6-3.333A2 2 0 014 15.157V8.62a2 2 0 0 1 1.029 -1.748l6-3.333a2 2 0 0 1 1.942 0z" /></svg>""";
public const string GitMerge = """<svg 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"/> <path d="M12.971 3.54l6 3.333A2 2 0 0120 8.62v6.536a2 2 0 0 1 -1.029 1.748l-6 3.333a2 2 0 0 1 -1.942 0l-6-3.333A2 2 0 014 15.157V8.62a2 2 0 0 1 1.029 -1.748l6-3.333a2 2 0 0 1 1.942 0z" /></svg>""";
public const string GitFork = """<svg 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="12" cy="18" r="2" /> <circle cx="7" cy="6" r="2" /> <circle cx="17" cy="6" r="2" /> <path d="M7 8v2a2 2 0 0 0 2 2h6a2 2 0 0 0 2 -2v-2" /> <line x1="12" y1="12" x2="12" y2="16" /></svg>""";
public const string World = """<svg 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="12" cy="12" r="9" /> <line x1="3.6" y1="9" x2="20.4" y2="9" /> <line x1="3.6" y1="15" x2="20.4" y2="15" /> <path d="M11.5 3a17 17 0 0 0 0 18" /> <path d="M12.5 3a17 17 0 0 1 0 18" /></svg>""";
public const string RepeatOne = """<svg 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"/> <path d="M4 12v-3a3 3 0 0 1 3 -3h13m-3 -3l3 3l-3 3" /> <path d="M20 12v3a3 3 0 0 1 -3 3h-13m3 3l-3-3l3-3" /> <path d="M11 11l1 -1v4" /></svg>""";
public const string Back1 = """<svg 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"/> <path d="M9 11l-4 4l4 4m-4 -4h11a4 4 0 0 0 0 -8h-1" /></svg>""";
public const string Italic = """<svg 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="12" cy="18" r="2" /> <circle cx="7" cy="6" r="2" /> <circle cx="17" cy="6" r="2" /> <path d="M7 8v2a2 2 0 0 0 2 2h6a2 2 0 0 0 2 -2v-2" /> <line x1="12" y1="12" x2="12" y2="16" /></svg>""";
public const string RotateClockwise2 = """<svg 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"/> <path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" /> <path d="M11 19.95a8 8 0 0 1 -5.3 -12.8" stroke-dasharray=".001 4.13" /></svg>""";
}
/// <summary>
/// Provides Heroicons.
/// </summary>
public static class Heroicons
{
public const string Question = """<svg width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>""";
public const string Exclamation = """<svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>""";
public const string PencilPaper = """<svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>""";
public const string Outgoing = """<svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/></svg><svg class="h-8 w-8 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/></svg>""";
public const string Incoming = """<svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg><svg class="h-8 w-8 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>""";
}
}
@@ -0,0 +1,17 @@
namespace Elsa.Studio.Enums;
/// <summary>
/// Provides field extension render positions.
/// </summary>
public enum FieldExtensionPosition
{
/// <summary>
/// Positions the extension at the top of the field.
/// </summary>
Top = 0,
/// <summary>
/// Positions the extension at the bottom of the field.
/// </summary>
Bottom = 1
}
@@ -0,0 +1,39 @@
using Elsa.Studio.Contracts;
using JetBrains.Annotations;
namespace Elsa.Studio.Extensions;
/// <summary>
/// Contains extension methods for <see cref="IMediator"/>.
/// </summary>
[UsedImplicitly]
public static class MediatorExtensions
{
/// <summary>
/// Subscribes the given handler to the given notification.
/// Use this from components' Initialize methods.
/// The component itself can be the handler
/// to subscribe itself if it implements <see cref="INotificationHandler{T}"/>.
/// </summary>
public static void Subscribe<TNotification>(this IMediator mediator, INotificationHandler handler) where TNotification : INotification
{
var handlerType = handler.GetType();
var subscribeMethod = typeof(IMediator).GetMethod(nameof(IMediator.Subscribe))!;
var genericSubscribeMethod = subscribeMethod.MakeGenericMethod(typeof(TNotification), handlerType);
genericSubscribeMethod.Invoke(mediator, [handler]);
}
/// <summary>
/// Unsubscribes the given handler from the given notification.
/// Use this from components' Dispose methods.
/// The component itself can be the handler
/// to unsubscribe itself if it implements <see cref="INotificationHandler{T}"/>.
/// </summary>
public static void Unsubscribe<TNotification>(this IMediator mediator, INotificationHandler handler) where TNotification : INotification
{
var handlerType = handler.GetType();
var unsubscribeMethod = typeof(IMediator).GetMethod(nameof(IMediator.Unsubscribe))!;
var genericUnsubscribeMethod = unsubscribeMethod.MakeGenericMethod(typeof(TNotification), handlerType);
genericUnsubscribeMethod.Invoke(mediator, [handler]);
}
}
@@ -0,0 +1,33 @@
using System.Reflection;
namespace Elsa.Studio.Extensions;
/// <summary>
/// Provides extension methods for the <see cref="object"/> class.
/// </summary>
public static class ObjectExtension
{
/// <summary>
/// Converts the specified object to a dictionary.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static Dictionary<string, object?> ToDictionary(this object obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
var dictionary = new Dictionary<string, object?>();
foreach (var property in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!property.CanRead)
continue;
var value = property.GetValue(obj);
dictionary.Add(property.Name, value);
}
return dictionary;
}
}
@@ -0,0 +1,31 @@
using ThrottleDebounce;
namespace Elsa.Studio.Extensions;
/// <summary>
/// Adds extension methods for <see cref="RateLimitedFunc{TResult}"/> and <see cref="RateLimitedFunc{T, TResult}"/>.
/// </summary>
public static class RateLimitedFuncExtensions
{
/// <summary>
/// Invokes the specified rate limited function.
/// </summary>
public static async Task InvokeAsync(this RateLimitedFunc<Task> rateLimitedFunc)
{
var task = rateLimitedFunc.Invoke();
if (task != null)
await task;
}
/// <summary>
/// Invokes the specified rate limited function.
/// </summary>
public static async Task InvokeAsync<T>(this RateLimitedFunc<T, Task> rateLimitedFunc, T arg1)
{
var task = rateLimitedFunc.Invoke(arg1);
if (task != null)
await task;
}
}
@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace Elsa.Studio.Extensions;
/// <summary>
/// Provides extension methods for the <see cref="RenderTreeBuilder"/> class.
/// </summary>
public static class RenderTreeBuilderExtensions
{
/// <summary>
/// Creates a component of the specified type.
/// </summary>
public static void CreateComponent<T>(this RenderTreeBuilder builder) where T : IComponent
{
var sequence = 0;
CreateComponent<T>(builder, ref sequence);
}
/// <summary>
/// Creates a component of the specified type.
/// </summary>
public static void CreateComponent<T>(this RenderTreeBuilder builder, ref int sequence) where T : IComponent
{
builder.OpenComponent<T>(sequence++);
builder.CloseComponent();
}
}
@@ -0,0 +1,90 @@
using Elsa.Api.Client.Extensions;
using Elsa.Api.Client.Options;
using Elsa.Studio.Contracts;
using Elsa.Studio.Localization;
using Elsa.Studio.Models;
using Elsa.Studio.Options;
using Elsa.Studio.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Elsa.Studio.Extensions;
/// <summary>
/// Contains extension methods for the <see cref="IServiceCollection"/> interface.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds the core services.
/// </summary>
public static IServiceCollection AddCoreInternal(this IServiceCollection services)
{
services
.AddScoped<IBlazorServiceAccessor, BlazorServiceAccessor>()
.AddScoped<IMenuService, DefaultMenuService>()
.AddScoped<IMenuGroupProvider, DefaultMenuGroupProvider>()
.AddScoped<IThemeService, DefaultThemeService>()
.AddScoped<IAppBarService, DefaultAppBarService>()
.AddScoped<IFeatureService, DefaultFeatureService>()
.AddScoped<IUIHintService, DefaultUIHintService>()
.AddScoped<IUIFieldExtensionService, DefaultUIFieldExtensionService>()
.AddScoped<IExpressionService, DefaultExpressionService>()
.AddScoped<IStartupTaskRunner, DefaultStartupTaskRunner>()
.AddScoped<IServerInformationProvider, EmptyServerInformationProvider>()
.AddScoped<IClientInformationProvider, AssemblyClientInformationProvider>()
.AddScoped<IWidgetRegistry, DefaultWidgetRegistry>()
;
// Mediator.
services.AddScoped<IMediator, DefaultMediator>();
//Localization
services.AddSingleton<ILocalizationProvider, DefaultLocalizationProvider>();
services.AddSingleton<ILocalizer, DefaultLocalizer>();
return services;
}
/// <summary>
/// Adds backend services to the service collection.
/// </summary>
public static IServiceCollection AddRemoteBackend(this IServiceCollection services, BackendApiConfig? config = null)
{
services.Configure(config?.ConfigureBackendOptions ?? (_ => { }));
services.AddDefaultApiClients(config?.ConfigureHttpClientBuilder);
services.TryAddScoped<IRemoteBackendAccessor, DefaultRemoteBackendAccessor>();
services.TryAddScoped<IBackendApiClientProvider, DefaultBackendApiClientProvider>();
return services;
}
public static IServiceCollection AddRemoteApi<TApi>(this IServiceCollection services, BackendApiConfig? config = null) where TApi : class
{
services.Configure(config?.ConfigureBackendOptions ?? (_ => { }));
services.AddApiClient<TApi>(config?.ConfigureHttpClientBuilder);
return services;
}
/// <summary>
/// Adds the specified <see cref="INotificationHandler"/>.
/// </summary>
public static IServiceCollection AddNotificationHandler<T>(this IServiceCollection services) where T: class, INotificationHandler
{
return services.AddScoped<INotificationHandler, T>();
}
/// <summary>
/// Adds the specified <see cref="IUIHintHandler"/>.
/// </summary>
public static IServiceCollection AddUIHintHandler<T>(this IServiceCollection services) where T : class, IUIHintHandler
{
return services.AddScoped<IUIHintHandler, T>();
}
/// <summary>
/// Adds the specified <see cref="IUIFieldExtensionHandler"/>.
/// </summary>
public static IServiceCollection AddUIFieldEnhancerHandler<T>(this IServiceCollection services) where T : class, IUIFieldExtensionHandler
{
return services.AddScoped<IUIFieldExtensionHandler, T>();
}
}
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
@@ -0,0 +1,9 @@
namespace Elsa.Studio.Localization;
internal class DefaultLocalizationProvider : ILocalizationProvider
{
public string GetTranslation(string key)
{
return key;
}
}
@@ -0,0 +1,58 @@
using Microsoft.Extensions.Localization;
namespace Elsa.Studio.Localization;
/// <inheritdoc />
public class DefaultLocalizer(ILocalizationProvider provider) : ILocalizer
{
/// <inheritdoc />
public LocalizedString this[string key]
{
get
{
if (string.IsNullOrWhiteSpace(key))
return new LocalizedString(string.Empty, string.Empty, true);
var notFound = false;
var translation = provider.GetTranslation(key);
if (string.IsNullOrEmpty(translation))
{
translation = key;
notFound = true;
}
return new LocalizedString(key, translation, notFound);
}
}
/// <summary>
/// Gets the localized string with the specified key and formats it with the provided arguments.
/// </summary>
/// <param name="key">The key of the localized string.</param>
/// <param name="arguments">The arguments to format the localized string with.</param>
/// <returns>The localized string formatted with the provided arguments.</returns>
public LocalizedString this[string key, params object[] arguments]
{
get
{
if (string.IsNullOrWhiteSpace(key))
return new LocalizedString(string.Empty, string.Empty, true);
var notFound = false;
var translation = provider.GetTranslation(key);
if (string.IsNullOrEmpty(translation))
{
translation = string.Format(key, arguments);
notFound = true;
}
else
{
translation = string.Format(translation, arguments);
}
return new LocalizedString(key, translation, notFound);
}
}
}
@@ -0,0 +1,6 @@
namespace Elsa.Studio.Localization;
public interface ILocalizationProvider
{
string GetTranslation(string key);
}
@@ -0,0 +1,22 @@
using Microsoft.Extensions.Localization;
namespace Elsa.Studio.Localization;
/// <summary>
/// Interface for localization.
/// </summary>
public interface ILocalizer
{
/// <summary>
/// Gets the localized string for the given key.
/// </summary>
/// <param name="key">The key for the localized string.</param>
LocalizedString this[string key] { get; }
/// <summary>
/// Gets the localized string for the given key and formats it with the provided arguments.
/// </summary>
/// <param name="key">The key for the localized string.</param>
/// <param name="arguments">The arguments to format the localized string with.</param>
LocalizedString this[string key, params object[] arguments] { get; }
}
@@ -0,0 +1,9 @@
using Elsa.Studio.Models;
namespace Elsa.Studio;
public static class MenuItemGroups
{
public static readonly MenuItemGroup General = new("general", "General", 0f);
public static readonly MenuItemGroup Settings = new("security", "Settings", 1000f);
}
@@ -0,0 +1,10 @@
using Elsa.Api.Client.Options;
using Elsa.Studio.Options;
namespace Elsa.Studio.Models;
public class BackendApiConfig
{
public Action<ElsaClientBuilderOptions>? ConfigureHttpClientBuilder { get; set; }
public Action<BackendOptions>? ConfigureBackendOptions { get; set; }
}
@@ -0,0 +1,7 @@
namespace Elsa.Studio.Models;
/// <summary>
/// Contains information about the client.
/// </summary>
/// <param name="PackageVersion">The installed package version of Elsa Studio.</param>
public record ClientInformation(string PackageVersion);
@@ -0,0 +1,198 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using Elsa.Api.Client.Extensions;
using Elsa.Api.Client.Resources.ActivityDescriptors.Models;
using Elsa.Api.Client.Resources.Scripting.Models;
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
using Elsa.Api.Client.Shared.Models;
using Elsa.Studio.Contracts;
namespace Elsa.Studio.Models;
/// <summary>
/// Provides contextual information to the input editor.
/// </summary>
public class DisplayInputEditorContext
{
/// <summary>
/// The workflow definition.
/// </summary>
public WorkflowDefinition WorkflowDefinition { get; set; } = null!;
/// <summary>
/// The activity.
/// </summary>
public JsonObject Activity { get; set; } = null!;
/// <summary>
/// The activity descriptor.
/// </summary>
public ActivityDescriptor ActivityDescriptor { get; set; } = null!;
/// <summary>
/// The input descriptor.
/// </summary>
public InputDescriptor InputDescriptor { get; set; } = null!;
/// <summary>
/// The input value.
/// </summary>
public object? Value { get; set; }
/// <summary>
/// The UI hint handler.
/// </summary>
public IUIHintHandler UIHintHandler { get; set; } = null!;
/// <summary>
/// The syntax provider.
/// </summary>
public ExpressionDescriptor? SelectedExpressionDescriptor { get; set; }
/// <summary>
/// A delegate that is invoked when the input value changes.
/// </summary>
public Func<object?, Task> OnValueChanged { get; set; } = null!;
/// <summary>
/// A value indicating whether the input is read-only.
/// </summary>
public bool IsReadOnly { get; set; }
/// <summary>
/// Returns the input value or the default value if the input value is null.
/// </summary>
public T? GetValueOrDefault<T>()
{
return (Value ?? InputDescriptor.DefaultValue).ConvertTo<T>();
}
/// <summary>
/// Returns the wrapped input literal value.
/// </summary>
/// <returns></returns>
public string GetLiteralValueOrDefault()
{
if (!InputDescriptor.IsWrapped)
return Value?.ToString() ?? InputDescriptor.DefaultValue?.ToString() ?? string.Empty;
var wrappedInput = Value as WrappedInput;
var expression = wrappedInput?.Expression;
if (expression?.Type != "Literal")
return InputDescriptor.DefaultValue?.ToString() ?? string.Empty;
var value = expression.Value;
return value?.ToString() ?? InputDescriptor.DefaultValue?.ToString() ?? string.Empty;
}
/// <summary>
/// Returns the wrapped input object value.
/// </summary>
/// <returns></returns>
public string GetObjectValueOrDefault()
{
if (!InputDescriptor.IsWrapped)
return Serialize(Value ?? InputDescriptor.DefaultValue);
var wrappedInput = Value as WrappedInput;
var expression = wrappedInput?.Expression;
if (expression?.Type != "Object")
return Serialize(InputDescriptor.DefaultValue);
var value = expression.Value;
return value?.ToString() ?? InputDescriptor.DefaultValue?.ToString() ?? string.Empty;
}
/// <summary>
/// Returns the input expression value if this is a wrapped input (i.e. Input{T}) or naked value otherwise. If either value is null, the default value is returned.
/// </summary>
public string GetExpressionValueOrDefault()
{
if (!InputDescriptor.IsWrapped)
return Value?.ToString() ?? InputDescriptor.DefaultValue?.ToString() ?? string.Empty;
var wrappedInput = Value as WrappedInput;
var expression = wrappedInput?.Expression;
var value = expression?.ToString();
return value ?? InputDescriptor.DefaultValue?.ToString() ?? string.Empty;
}
/// <summary>
/// Updates the naked input value.
/// </summary>
public async Task UpdateValueAsync(object? value)
{
await OnValueChanged(value);
}
/// <summary>
/// Updates the wrapped input expression.
/// </summary>
/// <param name="expression"></param>
public async Task UpdateExpressionAsync(Expression expression)
{
var wrappedInput = Value as WrappedInput;
// Update input.
wrappedInput ??= new();
wrappedInput.Expression = expression;
Value = wrappedInput;
// Notify that the input has changed.
await OnValueChanged(wrappedInput);
}
/// <summary>
/// Updates the input value or sets it as a literal expression,
/// depending on the wrapping configuration of the <see cref="InputDescriptor"/>.
/// </summary>
/// <param name="value">The new value to be set, either directly or wrapped as a literal expression.</param>
public Task UpdateValueOrLiteralExpressionAsync(string value)
{
return UpdateValueOrExpressionAsync(value, _ => value, "Literal");
}
/// <summary>
/// Updates the input value or sets it as an object expression,
/// depending on the wrapping configuration of the <see cref="InputDescriptor"/>.
/// </summary>
/// <param name="value">The new value to be set, either directly or wrapped as an object expression.</param>
public Task UpdateValueOrObjectExpressionAsync(object value)
{
return UpdateValueOrExpressionAsync(value, Serialize, "Object");
}
/// <summary>
/// Updates the input value or sets it as an expression,
/// depending on the wrapping configuration of the <see cref="InputDescriptor"/>.
/// </summary>
/// <param name="value">The new value to be set, either directly or wrapped as an expression.</param>
/// <param name="toString">A delegate to a function that stringifies the value for storing inside an <see cref="Expression"/> object.</param>
/// <param name="expressionType">The expression type</param>
public async Task UpdateValueOrExpressionAsync(object? value, Func<object?, string?> toString, string expressionType)
{
if (InputDescriptor.IsWrapped)
{
var stringValue = toString(value);
var expression = new Expression(expressionType, stringValue);
await UpdateExpressionAsync(expression);
return;
}
await UpdateValueAsync(value);
}
private static string Serialize(object? value)
{
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
return value != null ? JsonSerializer.Serialize(value, options) : string.Empty;
}
}
@@ -0,0 +1,44 @@
using Microsoft.AspNetCore.Components.Routing;
namespace Elsa.Studio.Models;
/// <summary>
/// A menu item.
/// </summary>
public class MenuItem
{
/// <summary>
/// The icon to use in SVG format.
/// </summary>
public string? Icon { get; set; }
/// <summary>
/// The path to navigate to.
/// </summary>
public string Href { get; set; } = default!;
/// <summary>
/// The <see cref="NavLinkMatch"/> to use.
/// </summary>
public NavLinkMatch Match { get; set; } = NavLinkMatch.Prefix;
/// <summary>
/// The text to display.
/// </summary>
public string Text { get; set; } = default!;
/// <summary>
/// The order of the menu item.
/// </summary>
public float Order { get; set; }
/// <summary>
/// A list of sub menu items.
/// </summary>
public ICollection<MenuItem> SubMenuItems { get; set; } = new List<MenuItem>();
/// <summary>
/// The name of the group this menu item belongs to.
/// </summary>
public string GroupName { get; set; } = "General";
}
@@ -0,0 +1,4 @@
namespace Elsa.Studio.Models;
/// A menu item group.
public record MenuItemGroup(string Name, string Text, float Order = 0f);
@@ -0,0 +1,21 @@
namespace Elsa.Studio.Models;
/// <summary>
/// Represent a remote backend.
/// </summary>
public class RemoteBackend
{
/// <summary>
/// Initializes a new instance of the <see cref="RemoteBackend"/> class.
/// </summary>
/// <param name="url">The URL of the backend.</param>
public RemoteBackend(Uri url)
{
Url = url;
}
/// <summary>
/// The URL of the backend.
/// </summary>
public Uri Url { get; }
}
@@ -0,0 +1,90 @@
namespace Elsa.Studio.Models;
/// <summary>
/// Represents a result that is either successful or failed.
/// </summary>
/// <typeparam name="TSuccess">The type of the success value.</typeparam>
/// <typeparam name="TFailure">The type of the failure value.</typeparam>
public class Result<TSuccess, TFailure>
{
/// <summary>
/// Creates a new successful result.
/// </summary>
public Result(TSuccess success)
{
Success = success;
}
/// <summary>
/// Creates a new failed result.
/// </summary>
/// <param name="failure"></param>
public Result(TFailure failure)
{
Failure = failure;
}
/// <summary>
/// The success value.
/// </summary>
public TSuccess? Success { get; set; }
/// <summary>
/// The failure value.
/// </summary>
public TFailure? Failure { get; set; }
/// <summary>
/// Returns true if the result is successful.
/// </summary>
public bool IsSuccess => Success is not null;
/// <summary>
/// Returns true if the result is failed.
/// </summary>
public bool IsFailed => Failure is not null;
/// <summary>
/// Invokes the specified handler if the result is successful.
/// </summary>
/// <returns>The success result.</returns>
public async Task<TSuccess> OnSuccessAsync(Func<TSuccess, Task> handler)
{
if (!IsSuccess) return default!;
await handler(Success!);
return Success!;
}
/// <summary>
/// Invokes the specified handler if the result is successful.
/// </summary>
/// <returns>The success result.</returns>
public TSuccess OnSuccess(Action<TSuccess> handler)
{
if (!IsSuccess) return default!;
handler(Success!);
return Success!;
}
/// <summary>
/// Invokes the specified handler if the result is failed.
/// </summary>
/// <returns>The failed result.</returns>
public async Task<TFailure> OnFailedAsync(Func<TFailure, Task> handler)
{
if (!IsFailed) return default!;
await handler(Failure!);
return Failure!;
}
/// <summary>
/// Invokes the specified handler if the result is failed.
/// </summary>
/// <returns>The failed result.</returns>
public TFailure OnFailed(Action<TFailure> handler)
{
if (!IsFailed) return default!;
handler(Failure!);
return Failure!;
}
}
@@ -0,0 +1,7 @@
namespace Elsa.Studio.Models;
/// <summary>
/// Contains information about the server.
/// </summary>
/// <param name="PackageVersion">The installed package version of Elsa.</param>
public record ServerInformation(string PackageVersion);
@@ -0,0 +1,12 @@
namespace Elsa.Studio.Options;
/// <summary>
/// Provides options about the backend.
/// </summary>
public class BackendOptions
{
/// <summary>
/// The URL of the backend.
/// </summary>
public Uri Url { get; set; } = default!;
}
@@ -0,0 +1,26 @@
using System.Reflection;
using Elsa.Studio.Contracts;
using Elsa.Studio.Models;
namespace Elsa.Studio.Services;
/// <summary>
/// Provides client information by extracting metadata from the executing assembly.
/// </summary>
public class AssemblyClientInformationProvider : IClientInformationProvider
{
/// <inheritdoc />
public ValueTask<ClientInformation> GetInfoAsync(CancellationToken cancellationToken = default)
{
var assembly = Assembly.GetExecutingAssembly();
var versionAttribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
var version = RemoveAutoGeneratedPostfix(versionAttribute);
return new(new ClientInformation(version));
}
private static string RemoveAutoGeneratedPostfix(AssemblyInformationalVersionAttribute? versionAttribute)
{
return versionAttribute?.InformationalVersion.Split("+")[0]!;
}
}
@@ -0,0 +1,34 @@
using System.Reflection;
using Elsa.Studio.Contracts;
namespace Elsa.Studio.Services;
/// <summary>
/// Decorates an API client with a Blazor service accessor, ensuring that the service provider is available to the API client when calling DI-resolved delegating handlers.
/// </summary>
public class BlazorScopedProxyApi<T> : DispatchProxy
{
private T _decoratedApi = default!;
private IBlazorServiceAccessor _blazorServiceAccessor = null!;
private IServiceProvider _serviceProvider = null!;
/// <summary>
/// Initializes the BlazorScopedProxyApi instance with the specified parameters.
/// </summary>
/// <param name="decoratedApi">The API client instance to be decorated.</param>
/// <param name="blazorServiceAccessor">The Blazor service accessor instance for setting the scoped service provider.</param>
/// <param name="serviceProvider">The service provider for dependency injection.</param>
public void Initialize(T decoratedApi, IBlazorServiceAccessor blazorServiceAccessor, IServiceProvider serviceProvider)
{
_decoratedApi = decoratedApi;
_blazorServiceAccessor = blazorServiceAccessor;
_serviceProvider = serviceProvider;
}
/// <inheritdoc />
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
_blazorServiceAccessor.Services = _serviceProvider;
return targetMethod?.Invoke(_decoratedApi, args);
}
}
@@ -0,0 +1,35 @@
using Elsa.Studio.Contracts;
namespace Elsa.Studio.Services;
/// This class is used to access the IServiceProvider from a different DI scope.
/// See: https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-7.0#access-server-side-blazor-services-from-a-different-di-scope
public sealed class BlazorServiceAccessor : IBlazorServiceAccessor
{
private static readonly AsyncLocal<BlazorServiceHolder> CurrentServiceHolder = new();
/// <summary>
/// Gets or sets the current IServiceProvider.
/// </summary>
public IServiceProvider Services
{
get => CurrentServiceHolder.Value!.Services!;
set
{
if (CurrentServiceHolder.Value is { } holder)
{
// Clear the current IServiceProvider trapped in the AsyncLocal.
holder.Services = null;
}
// Use object indirection to hold the IServiceProvider in an AsyncLocal
// so it can be cleared in all ExecutionContexts when it's cleared.
CurrentServiceHolder.Value = new() { Services = value };
}
}
private sealed class BlazorServiceHolder
{
public IServiceProvider? Services { get; set; }
}
}
@@ -0,0 +1,20 @@
using Elsa.Studio.Contracts;
using Elsa.Studio.Extensions;
using Microsoft.AspNetCore.Components;
namespace Elsa.Studio.Services;
public class DefaultAppBarService : IAppBarService
{
private readonly ICollection<RenderFragment> _appBarItems = new List<RenderFragment>();
private int _appBarSequence;
public event Action? AppBarItemsChanged;
public IEnumerable<RenderFragment> AppBarItems => _appBarItems.ToList();
public void AddAppBarItem<T>() where T : IComponent
{
_appBarItems.Add(builder => builder.CreateComponent<T>(ref _appBarSequence));
AppBarItemsChanged?.Invoke();
}
}
@@ -0,0 +1,24 @@
using System.Reflection;
using Elsa.Api.Client.Extensions;
using Elsa.Studio.Contracts;
namespace Elsa.Studio.Services;
/// <summary>
/// Provides API clients to the remote backend.
/// </summary>
public class DefaultBackendApiClientProvider(IRemoteBackendAccessor remoteBackendAccessor, IBlazorServiceAccessor blazorServiceAccessor, IServiceProvider serviceProvider) : IBackendApiClientProvider
{
/// <inheritdoc />
public Uri Url => remoteBackendAccessor.RemoteBackend.Url;
/// <inheritdoc />
public ValueTask<T> GetApiAsync<T>(CancellationToken cancellationToken) where T : class
{
var backendUrl = remoteBackendAccessor.RemoteBackend.Url;
var client = serviceProvider.CreateApi<T>(backendUrl);
var decorator = DispatchProxy.Create<T, BlazorScopedProxyApi<T>>();
(decorator as BlazorScopedProxyApi<T>)!.Initialize(client, blazorServiceAccessor, serviceProvider);
return new(decorator);
}
}
@@ -0,0 +1,59 @@
using System.Reflection;
using Elsa.Studio.Attributes;
using Elsa.Studio.Contracts;
namespace Elsa.Studio.Services;
/// <inheritdoc />
public class DefaultFeatureService : IFeatureService
{
private readonly IEnumerable<IFeature> _features;
private readonly IRemoteFeatureProvider _remoteFeatureProvider;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultFeatureService"/> class.
/// </summary>
public DefaultFeatureService(IEnumerable<IFeature> features, IRemoteFeatureProvider remoteFeatureProvider)
{
_features = features;
_remoteFeatureProvider = remoteFeatureProvider;
}
/// <inheritdoc />
public event Action? Initialized;
/// <inheritdoc />
public IEnumerable<IFeature> GetFeatures()
{
return _features.ToList();
}
/// <inheritdoc />
public async Task InitializeFeaturesAsync(CancellationToken cancellationToken = default)
{
var remoteFeatures = (await _remoteFeatureProvider.ListAsync(cancellationToken)).ToList();
foreach (var feature in GetFeatures())
{
var remoteFeatureName = feature.GetType().GetCustomAttribute<RemoteFeatureAttribute>()?.Name;
if (!string.IsNullOrWhiteSpace(remoteFeatureName))
{
// Check if the remote feature is enabled.
var remoteFeatureIsEnabled = remoteFeatures.Any(x => x.FullName == remoteFeatureName);
if (!remoteFeatureIsEnabled)
continue;
}
await feature.InitializeAsync(cancellationToken);
}
OnInitialized();
}
private void OnInitialized()
{
Initialized?.Invoke();
}
}
@@ -0,0 +1,53 @@
using Elsa.Studio.Contracts;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Studio.Services;
/// <summary>
/// A default implementation of <see cref="IMediator"/>.
/// </summary>
public class DefaultMediator : IMediator
{
private readonly IServiceProvider _serviceProvider;
private readonly ISet<INotificationHandler> _handlers = new HashSet<INotificationHandler>();
/// <summary>
/// Initializes a new instance of the <see cref="DefaultMediator"/> class.
/// </summary>
public DefaultMediator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
/// <inheritdoc />
public void Subscribe<TNotification, THandler>(THandler handler) where TNotification : INotification where THandler : INotificationHandler<TNotification>
{
_handlers.Add(handler);
}
/// <inheritdoc />
public void Unsubscribe<TNotification, THandler>(THandler handler) where TNotification : INotification where THandler : INotificationHandler<TNotification>
{
_handlers.Remove(handler);
}
/// <inheritdoc />
public void Unsubscribe(INotificationHandler handler)
{
var handlers =_handlers.Where(x => x == handler).ToList();
foreach (var h in handlers)
_handlers.Remove(h);
}
/// <inheritdoc />
public Task NotifyAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification
{
// Collect handlers registered manually, and handlers registered via DI.
var manualHandlers = _handlers.OfType<INotificationHandler<TNotification>>().ToList();
var registeredHandlers = _serviceProvider.GetServices<INotificationHandler>().OfType<INotificationHandler<TNotification>>().ToList();
var handlers = manualHandlers.Concat(registeredHandlers).ToList();
var tasks = handlers.Select(x => x.HandleAsync(notification, cancellationToken));
return Task.WhenAll(tasks);
}
}
@@ -0,0 +1,18 @@
using Elsa.Studio.Contracts;
using Elsa.Studio.Models;
namespace Elsa.Studio.Services;
public class DefaultMenuGroupProvider : IMenuGroupProvider
{
public ValueTask<IEnumerable<MenuItemGroup>> GetMenuGroupsAsync(CancellationToken cancellationToken = default)
{
var groups = new List<MenuItemGroup>
{
MenuItemGroups.General,
MenuItemGroups.Settings
};
return new(groups);
}
}
@@ -0,0 +1,48 @@
using Elsa.Studio.Contracts;
using Elsa.Studio.Models;
namespace Elsa.Studio.Services;
/// <inheritdoc />
public class DefaultMenuService : IMenuService
{
private readonly IEnumerable<IMenuProvider> _menuProviders;
private readonly IEnumerable<IMenuGroupProvider> _menuGroupProviders;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultMenuService"/> class.
/// </summary>
public DefaultMenuService(IEnumerable<IMenuProvider> menuProviders, IEnumerable<IMenuGroupProvider> menuGroupProviders)
{
_menuProviders = menuProviders;
_menuGroupProviders = menuGroupProviders;
}
/// <inheritdoc />
public async ValueTask<IEnumerable<MenuItem>> GetMenuItemsAsync(CancellationToken cancellationToken = default)
{
var menu = new List<MenuItem>();
foreach (var menuProvider in _menuProviders)
{
var menuItems = await menuProvider.GetMenuItemsAsync(cancellationToken);
menu.AddRange(menuItems);
}
return menu.OrderBy(x => x.Order).ToList();
}
/// <inheritdoc />
public async ValueTask<IEnumerable<MenuItemGroup>> GetMenuItemGroupsAsync(CancellationToken cancellationToken = default)
{
var groups = new List<MenuItemGroup>();
foreach (var menuGroupProvider in _menuGroupProviders)
{
var menuGroups = await menuGroupProvider.GetMenuGroupsAsync(cancellationToken);
groups.AddRange(menuGroups);
}
return groups.DistinctBy(x => x.Name).OrderBy(x => x.Order).ToList();
}
}
@@ -0,0 +1,25 @@
using Elsa.Studio.Contracts;
using Elsa.Studio.Models;
using Elsa.Studio.Options;
using Microsoft.Extensions.Options;
namespace Elsa.Studio.Services;
/// <summary>
/// A default implementation of <see cref="IRemoteBackendAccessor"/> that uses the <see cref="BackendOptions"/> to determine the URL of the remote backend.
/// </summary>
public class DefaultRemoteBackendAccessor : IRemoteBackendAccessor
{
private readonly IOptions<BackendOptions> _options;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultRemoteBackendAccessor"/> class.
/// </summary>
public DefaultRemoteBackendAccessor(IOptions<BackendOptions> options)
{
_options = options;
}
/// <inheritdoc />
public RemoteBackend RemoteBackend => new(_options.Value.Url);
}
@@ -0,0 +1,25 @@
using Elsa.Studio.Contracts;
namespace Elsa.Studio.Services;
/// <inheritdoc />
public class DefaultStartupTaskRunner : IStartupTaskRunner
{
private readonly IEnumerable<IStartupTask> _startupTasks;
/// <summary>
///
/// </summary>
/// <param name="startupTasks"></param>
public DefaultStartupTaskRunner(IEnumerable<IStartupTask> startupTasks)
{
_startupTasks = startupTasks;
}
/// <inheritdoc />
public async Task RunStartupTasksAsync(CancellationToken cancellationToken = default)
{
foreach (var task in _startupTasks)
await task.ExecuteAsync(cancellationToken);
}
}
@@ -0,0 +1,38 @@
using Elsa.Api.Client.Resources.Scripting.Models;
using Elsa.Studio.Contracts;
namespace Elsa.Studio.Services;
/// <inheritdoc />
public class DefaultExpressionService : IExpressionService
{
private readonly IExpressionProvider _expressionProvider;
private ICollection<ExpressionDescriptor> _descriptors = default!;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultExpressionService"/> class.
/// </summary>
public DefaultExpressionService(IExpressionProvider expressionProvider)
{
_expressionProvider = expressionProvider;
}
/// <inheritdoc />
public async Task<IEnumerable<ExpressionDescriptor>> ListDescriptorsAsync(CancellationToken cancellationToken = default)
{
if (_descriptors == null!)
{
var descriptors = await _expressionProvider.ListAsync(cancellationToken);
_descriptors = descriptors.ToList();
}
return _descriptors;
}
/// <inheritdoc />
public async Task<ExpressionDescriptor?> GetByTypeAsync(string type, CancellationToken cancellationToken = default)
{
var descriptors = await ListDescriptorsAsync(cancellationToken);
return descriptors.FirstOrDefault(x => x.Type == type);
}
}
@@ -0,0 +1,70 @@
using Elsa.Studio.Contracts;
using MudBlazor;
using MudBlazor.Utilities;
namespace Elsa.Studio.Services;
/// <inheritdoc />
public class DefaultThemeService : IThemeService
{
private MudTheme _currentTheme = CreateDefaultTheme();
private bool _isDarkMode;
/// <inheritdoc />
public event Action? CurrentThemeChanged;
/// <inheritdoc />
public event Action? IsDarkModeChanged;
/// <inheritdoc />
public MudTheme CurrentTheme
{
get => _currentTheme;
set
{
_currentTheme = value;
CurrentThemeChanged?.Invoke();
}
}
/// <inheritdoc />
public bool IsDarkMode
{
get => _isDarkMode;
set
{
_isDarkMode = value;
IsDarkModeChanged?.Invoke();
}
}
private static MudTheme CreateDefaultTheme()
{
var theme = new MudTheme
{
LayoutProperties =
{
DefaultBorderRadius = "4px",
},
PaletteLight =
{
Primary = new("0ea5e9"),
DrawerBackground = new("#f8fafc"),
AppbarBackground = new("#0ea5e9"),
AppbarText = new("#ffffff"),
Background = new("#ffffff"),
Surface = new("#f8fafc")
},
PaletteDark =
{
Primary = new("0ea5e9"),
AppbarBackground = new("#0f172a"),
DrawerBackground = new("#0f172a"),
Background = new("#0f172a"),
Surface = new("#182234"),
}
};
return theme;
}
}
@@ -0,0 +1,27 @@
using Elsa.Studio.Contracts;
using Elsa.Studio.Models;
namespace Elsa.Studio.Services;
/// <summary>
/// Provides a default implementation of UI field extension services.
/// </summary>
/// <param name="handlers"></param>
public class DefaultUIFieldExtensionService(IEnumerable<IUIFieldExtensionHandler> handlers) : IUIFieldExtensionService
{
/// <summary>
/// <inheritdoc/>
/// </summary>
public List<IUIFieldExtensionHandler>? TryGetHandlers(string componentName, DisplayInputEditorContext context)
{
var activityName = context.ActivityDescriptor.Name;
var syntax = context.InputDescriptor.DefaultSyntax ?? string.Empty;
var matchingHandlers = handlers
.Where(x => x.GetExtensionForInputComponent(componentName)
&& (!x.ActivityTypes.Any() || x.ActivityTypes.Contains(activityName))
&& (!x.Syntaxes.Any() || x.Syntaxes.Contains(syntax)))
.OrderBy(x => x.DisplayOrder)
.ToList();
return matchingHandlers;
}
}
@@ -0,0 +1,35 @@
using Elsa.Studio.Contracts;
namespace Elsa.Studio.Services;
public class DefaultUIHintService(IEnumerable<IUIHintHandler> handlers) : IUIHintService
{
public IUIHintHandler GetHandler(string uiHint)
{
var handler = handlers.FirstOrDefault(x => x.GetSupportsUIHint(uiHint));
return handler ?? new UnsupportedUIHintHandler();
}
// public RenderFragment DisplayInputEditor(DisplayInputEditorContext context)
// {
// var handler = _handlers.FirstOrDefault(x => x.GetSupportsUIHint(context.InputDescriptor.UIHint));
// return handler == null ? DisplayUnsupportedUIHint(context) : handler.DisplayInputEditor(context);
// }
// private RenderFragment DisplayUnsupportedUIHint(DisplayInputEditorContext context)
// {
// return builder =>
// {
// builder.OpenComponent<MudAlert>(0);
// builder.AddAttribute(1, nameof(MudAlert.Severity), Severity.Warning);
// builder.AddAttribute(2, nameof(MudAlert.Class), "my-2");
// builder.AddAttribute(3, nameof(MudText.ChildContent), (RenderFragment)(textBuilder =>
// {
// textBuilder.AddContent(0, $"Unsupported UI hint: {context.InputDescriptor.UIHint}");
// }));
// builder.CloseElement();
// };
// }
}
@@ -0,0 +1,29 @@
using Elsa.Studio.Contracts;
namespace Elsa.Studio.Services;
/// <inheritdoc />
public class DefaultWidgetRegistry : IWidgetRegistry
{
private HashSet<IWidget> Widgets { get; set; } = new();
/// <summary>
/// Initializes a new instance of the <see cref="DefaultWidgetRegistry"/> class.
/// </summary>
public DefaultWidgetRegistry(IEnumerable<IWidget> widgets)
{
foreach (var widget in widgets) Add(widget);
}
/// <inheritdoc />
public void Add(IWidget widget)
{
Widgets.Add(widget);
}
/// <inheritdoc />
public IEnumerable<IWidget> List(string zone)
{
return Widgets.Where(x => x.Zone == zone).OrderBy(x => x.Order);
}
}
@@ -0,0 +1,16 @@
using Elsa.Studio.Contracts;
using Elsa.Studio.Models;
namespace Elsa.Studio.Services;
/// <summary>
/// A default implementation of <see cref="IServerInformationProvider"/> that returns dummy data.
/// </summary>
public class EmptyServerInformationProvider : IServerInformationProvider
{
/// <inheritdoc />
public ValueTask<ServerInformation> GetInfoAsync(CancellationToken cancellationToken = default)
{
return new (new ServerInformation("0.0.0"));
}
}
@@ -0,0 +1,29 @@
using Elsa.Api.Client.Resources.Scripting.Contracts;
using Elsa.Api.Client.Resources.Scripting.Models;
using Elsa.Studio.Contracts;
namespace Elsa.Studio.Services;
/// <summary>
/// Provides available expression descriptors from a remote API.
/// </summary>
public class RemoteExpressionProvider : IExpressionProvider
{
private readonly IBackendApiClientProvider _backendApiClientProvider;
/// <summary>
/// Initializes a new instance of the <see cref="RemoteExpressionProvider"/> class.
/// </summary>
public RemoteExpressionProvider(IBackendApiClientProvider backendApiClientProvider)
{
_backendApiClientProvider = backendApiClientProvider;
}
/// <inheritdoc />
public async ValueTask<IEnumerable<ExpressionDescriptor>> ListAsync(CancellationToken cancellationToken = default)
{
var api = await _backendApiClientProvider.GetApiAsync<IExpressionDescriptorsApi>(cancellationToken);
var response = await api.ListAsync(cancellationToken);
return response.Items;
}
}
@@ -0,0 +1,37 @@
using System.Net;
using Elsa.Api.Client.Resources.Features.Contracts;
using Elsa.Api.Client.Resources.Features.Models;
using Elsa.Studio.Contracts;
using Refit;
namespace Elsa.Studio.Services;
/// <summary>
/// A feature service that uses a remote backend to retrieve feature flags.
/// </summary>
public class RemoteFeatureProvider(IBackendApiClientProvider remoteBackendApiClientProvider) : IRemoteFeatureProvider
{
/// <inheritdoc />
public async Task<bool> IsEnabledAsync(string featureName, CancellationToken cancellationToken = default)
{
var api = await remoteBackendApiClientProvider.GetApiAsync<IFeaturesApi>(cancellationToken);
try
{
_ = await api.GetAsync(featureName, cancellationToken);
return true;
}
catch (ApiException e) when(e.StatusCode == HttpStatusCode.NotFound)
{
return false;
}
}
/// <inheritdoc />
public async Task<IEnumerable<FeatureDescriptor>> ListAsync(CancellationToken cancellationToken = default)
{
var api = await remoteBackendApiClientProvider.GetApiAsync<IFeaturesApi>(cancellationToken);
var response = await api.ListAsync(cancellationToken);
return response.Items;
}
}
@@ -0,0 +1,17 @@
using Elsa.Studio.Contracts;
using Elsa.Studio.Models;
namespace Elsa.Studio.Services;
/// <summary>
///
/// </summary>
public class StaticClientInformationProvider : IClientInformationProvider
{
/// <inheritdoc />
public ValueTask<ClientInformation> GetInfoAsync(CancellationToken cancellationToken = default)
{
var version = ToolVersion.Version.ToString();
return new(new ClientInformation(version));
}
}
@@ -0,0 +1,31 @@
using Elsa.Studio.Contracts;
using Elsa.Studio.Models;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace Elsa.Studio.Services;
/// <summary>
/// A fallback UI handler that is used when no other handler is found for a given UI hint.
/// </summary>
public class UnsupportedUIHintHandler : IUIHintHandler
{
public bool GetSupportsUIHint(string uiHint) => false;
public string UISyntax => "Unsupported";
public RenderFragment DisplayInputEditor(DisplayInputEditorContext context)
{
return builder =>
{
builder.OpenComponent<MudAlert>(0);
builder.AddAttribute(1, nameof(MudAlert.Severity), Severity.Warning);
builder.AddAttribute(2, nameof(MudAlert.Class), "my-2");
builder.AddAttribute(3, nameof(MudText.ChildContent), (RenderFragment)(textBuilder =>
{
textBuilder.AddContent(0, $"Unsupported UI hint: {context.InputDescriptor.UIHint}");
}));
builder.CloseElement();
};
}
}
@@ -0,0 +1,8 @@
namespace Elsa.Studio;
public static class TokenNames
{
public const string AccessToken = "accessToken";
public const string IdToken = "idToken";
public const string RefreshToken = "refreshToken";
}
@@ -0,0 +1,18 @@
namespace Elsa.Studio;
/// <summary>
/// Represents the version of the tool.
/// </summary>
public static class ToolVersion
{
/// <summary>
/// The version of the tool.
/// </summary>
public static readonly Version Version = new(3, 5, 0, 0);
/// <summary>
/// Gets the display version of the tool.
/// </summary>
/// <returns></returns>
public static string GetDisplayVersion() => Version.ToString(2);
}