Add project files.
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
<Project>
|
||||
|
||||
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
|
||||
|
||||
<ItemGroup Label="Files">
|
||||
<None Include="..\..\..\icon.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Elsa Studio bundle of commonly used packages.</Description>
|
||||
<PackageTags>elsa studio framework bundle</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\framework\Elsa.Studio.Shell\Elsa.Studio.Shell.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Studio.Dashboard\Elsa.Studio.Dashboard.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Studio.Workflows\Elsa.Studio.Workflows.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -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,8 @@
|
||||
# Elsa.Studio
|
||||
|
||||
This is a bundle package that includes the following packages:
|
||||
|
||||
- Elsa.Studio.Backend
|
||||
- Elsa.Studio.Dashboard
|
||||
- Elsa.Studio.Workflows
|
||||
- Elsa.Studio.Shell
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Blazor Server impementation for certain abstractions provided by Elsa.Studio.Core.</Description>
|
||||
<PackageTags>elsa studio blazor server</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazored.LocalStorage"/>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Studio.Core\Elsa.Studio.Core.csproj"/>
|
||||
<ProjectReference Include="..\Elsa.Studio.Shared\Elsa.Studio.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,23 @@
|
||||
using Elsa.Studio.Core.BlazorServer.HostedServices;
|
||||
using Elsa.Studio.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Studio.Core.BlazorServer.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Contains extension methods for the <see cref="IServiceCollection"/> interface.
|
||||
/// </summary>
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds core services with Blazor Server implementations.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddCore(this IServiceCollection services)
|
||||
{
|
||||
services.AddCoreInternal();
|
||||
services.AddSharedServices();
|
||||
services.AddHostedService<RunStartupTasksHostedService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Elsa.Studio.Contracts;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Elsa.Studio.Core.BlazorServer.HostedServices;
|
||||
|
||||
/// <summary>
|
||||
/// Executes all registered <see cref="IStartupTask"/> services.
|
||||
/// </summary>
|
||||
public class RunStartupTasksHostedService : IHostedService
|
||||
{
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="RunStartupTasksHostedService"/>.
|
||||
/// </summary>
|
||||
/// <param name="serviceScopeFactory"></param>
|
||||
public RunStartupTasksHostedService(IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resolves all registered <see cref="IStartupTask"/> services and executes them.
|
||||
/// </summary>
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var startupTaskRunner = scope.ServiceProvider.GetRequiredService<IStartupTaskRunner>();
|
||||
|
||||
await startupTaskRunner.RunStartupTasksAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Blazor WebAssembly impementation for certain abstractions provided by Elsa.Studio.Core.</Description>
|
||||
<PackageTags>elsa studio blazor webassembly</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazored.LocalStorage"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Studio.Core\Elsa.Studio.Core.csproj"/>
|
||||
<ProjectReference Include="..\Elsa.Studio.Shared\Elsa.Studio.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,21 @@
|
||||
using Elsa.Studio.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Studio.Core.BlazorWasm.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Contains extension methods for the <see cref="IServiceCollection"/> interface.
|
||||
/// </summary>
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds core services with WASM implementations.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddCore(this IServiceCollection services)
|
||||
{
|
||||
services.AddSharedServices();
|
||||
services.AddCoreInternal();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -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,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);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "elsa-studio-dom-interop",
|
||||
"description": "Bundle DOM interop functions.",
|
||||
"repository": "https://github.com/elsa-workflows/elsa-dashboard",
|
||||
"license": "MIT",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"typescript": "^5.3.3",
|
||||
"ts-loader": "^9.5.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./clipboard/";
|
||||
@@ -0,0 +1,7 @@
|
||||
export const copyText = async function (text) {
|
||||
await navigator.clipboard
|
||||
.writeText(text)
|
||||
.catch(function (error) {
|
||||
alert(error);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './copy-text';
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./dom/";
|
||||
@@ -0,0 +1,7 @@
|
||||
import {getElement} from "./get-element";
|
||||
|
||||
export function clickElement(elementOrQuerySelector: Element | string) {
|
||||
const element = getElement(elementOrQuerySelector) as HTMLElement;
|
||||
|
||||
element.click();
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import {getElement} from "./get-element";
|
||||
|
||||
export function getBoundingClientRect(elementOrQuerySelector: Element | string): DOMRect {
|
||||
const element = getElement(elementOrQuerySelector);
|
||||
return element.getBoundingClientRect();
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function getElement(elementOrQuerySelector: Element | string): Element {
|
||||
return typeof elementOrQuerySelector === 'string' ? document.querySelector(elementOrQuerySelector) : elementOrQuerySelector;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user