Add project files.

This commit is contained in:
2025-08-20 11:07:36 +03:30
parent 3863c74931
commit 1a308a4069
899 changed files with 85379 additions and 0 deletions
@@ -0,0 +1,16 @@
@using Elsa.Studio.Contracts
<MudText Typo="Typo.body2">Client version: @PackageVersion</MudText>
@code {
[Inject] private IClientInformationProvider ClientInformationProvider { get; set; } = default!;
private string PackageVersion { get; set; } = "Loading...";
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
var info = await ClientInformationProvider.GetInfoAsync();
PackageVersion = info.PackageVersion;
}
}
@@ -0,0 +1,88 @@
@using Elsa.Studio.DomInterop.Contracts
@using Elsa.Studio.Localization.Time.Components
@using Elsa.Studio.Models
<MudSimpleTable Outlined="true" Striped="false" Dense="true" Elevation="0" Bordered="false">
<tbody>
@{
var data = HideEmptyValues
? Data.Where(x => !string.IsNullOrWhiteSpace(x.Text)).ToList()
: Data;
}
@if (data.Any())
{
@foreach (var item in data)
{
<tr>
<td style="width: 200px;">@item.Label</td>
<td style="width: 50px;">
<MudIconButton Icon="@Icons.Material.Outlined.ContentCopy" Size="Size.Small" Title="Copy" OnClick="@(() => OnCopyClicked(item.Text!))" Disabled="@(string.IsNullOrWhiteSpace(item.Text))"/>
</td>
<td>
@if (!string.IsNullOrWhiteSpace(item.Link))
{
<MudLink Typo="Typo.body2" Href="@item.Link">@item.Text</MudLink>
}
else if (item.OnClick != null)
{
<MudLink Typo="Typo.body2" OnClick="@item.OnClick">@item.Text</MudLink>
}
else
{
@if (item.Label == "Created" || item.Label == "Updated" || item.Label == "Finished")
{
<span><Timestamp Value="@Convert.ToDateTime(item.Text)"></Timestamp></span>
}
else
{
<span>@item.Text</span>
}
}
</td>
</tr>
}
}
else
{
if (ShowNoDataAlert)
{
<MudAlert Severity="Severity.Normal" Dense="true" Variant="Variant.Text">@NoDataMessage</MudAlert>
}
}
</tbody>
</MudSimpleTable>
@code {
/// <summary>
/// The data to display.
/// </summary>
[Parameter]
public DataPanelModel Data { get; set; } = new ();
/// <summary>
/// If true, empty values will be hidden.
/// </summary>
[Parameter]
public bool HideEmptyValues { get; set; }
/// <summary>
/// If true, a message will be displayed when there is no data.
/// </summary>
[Parameter]
public bool ShowNoDataAlert { get; set; }
/// <summary>
/// The message to display when there is no data.
/// </summary>
[Parameter]
public string NoDataMessage { get; set; } = "No data available.";
[Inject] private IClipboard Clipboard { get; set; } = null!;
private async Task OnCopyClicked(string value)
{
await Clipboard.CopyText(value);
}
}
@@ -0,0 +1,13 @@
@using Elsa.Studio.Branding
<MudStack>
<div class="d-flex gap-2 align-center">
<MudImage Src="@Provider.LogoUrl" ObjectFit="ObjectFit.ScaleDown" Fluid="true" Width="32"
Height="32"></MudImage>
<MudText Typo="Typo.h6">@Provider.AppName </MudText>
</div>
</MudStack>
@code{
[Parameter] public IBrandingProvider Provider { get; set; }
}
@@ -0,0 +1,56 @@
@using Elsa.Api.Client.Resources.Scripting.Extensions;
@using Elsa.Api.Client.Resources.Scripting.Models;
@using Variant = MudBlazor.Variant
<div class="d-flex flex-grow-1 gap-4 align-content-start relative">
<div class="flex-1">
<MudField Variant="Variant.Outlined" Label="@DisplayName" HelperText="@Description" Margin="Margin.Dense"
Class="@(DisplayCodeEditor ? "d-block" : "d-none")" Disabled="ReadOnly">
<StandaloneCodeEditor
@ref="_monacoEditor"
Id="@_monacoEditorId"
ConstructionOptions="ConfigureMonacoEditor"
CssClass="studio-expression-input-monaco-editor"
OnDidInit="OnMonacoInitializedAsync"
OnDidChangeModelContent="OnMonacoContentChangedAsync"/>
</MudField>
@if (!DisplayCodeEditor)
{
@ChildContent
}
</div>
@{
RenderFragment DisplayDropdownItem(ExpressionDescriptor? item) => @<div>
@{
var isChecked = item?.Type == _selectedExpressionType;
var iconColor = isChecked ? Color.Secondary : Color.Transparent;
var displayName = item?.DisplayName ?? DefaultOption;
<MudMenuItem Class="studio-expression-input-menu-item"
OnClick="@(() => OnSyntaxSelectedAsync(item?.Type))" Icon="@Icons.Material.Filled.Check"
IconSize="Size.Small" IconColor="iconColor">@displayName</MudMenuItem>
}
</div>;
<div class="flex-none pt-1">
<MudMenu Label="@ButtonLabel" Icon="@ButtonIcon" Variant="@ButtonVariant" Color="@ButtonColor"
EndIcon="@ButtonEndIcon" IconColor="@ButtonEndColor" Dense="true" Disabled="ReadOnly">
@DisplayDropdownItem(null)
<MudDivider/>
@foreach (var item in BrowsableExpressionDescriptors.Where(i => !string.IsNullOrEmpty(i.GetMonacoLanguage())))
{
@DisplayDropdownItem(item)
}
<MudDivider/>
@foreach (var item in BrowsableExpressionDescriptors.Skip(1).Where(i => string.IsNullOrEmpty(i.GetMonacoLanguage())))
{
@DisplayDropdownItem(item)
}
</MudMenu>
</div>
}
</div>
@@ -0,0 +1,231 @@
using BlazorMonaco.Editor;
using Elsa.Api.Client.Resources.Scripting.Extensions;
using Elsa.Api.Client.Resources.Scripting.Models;
using Elsa.Studio.Contracts;
using Elsa.Studio.Extensions;
using Elsa.Studio.Models;
using Elsa.Studio.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using MudBlazor;
using ThrottleDebounce;
namespace Elsa.Studio.Components;
/// <summary>
/// A component that renders an input for an expression.
/// </summary>
public partial class ExpressionEditor : IDisposable
{
private readonly string[] _expressionTypeBlacklist = ["Literal", "Object", "Variable"];
private string? _selectedExpressionType;
private StandaloneCodeEditor? _monacoEditor;
private bool _isInternalContentChange;
private bool _isInitialized;
private string _monacoEditorId = $"monaco-editor-{Guid.NewGuid()}:N";
private string? _lastMonacoEditorContent;
private RateLimitedFunc<Expression, Task> _throttledValueChanged;
private ICollection<ExpressionDescriptor> _expressionDescriptors = new List<ExpressionDescriptor>();
private TaskCompletionSource<bool>? _initializationTcs;
/// <inheritdoc />
public ExpressionEditor()
{
_throttledValueChanged = Debouncer.Debounce<Expression, Task>(InvokeValueChangedCallbackAsync, TimeSpan.FromMilliseconds(500));
}
/// <summary>
/// The content to render inside the editor.
/// </summary>
[Parameter] public RenderFragment ChildContent { get; set; } = null!;
[Parameter] public Expression? Expression { get; set; } = new(string.Empty, string.Empty);
[Parameter] public string DefaultOption { get; set; } = "Default";
[Parameter] public string DisplayName { get; set; } = string.Empty;
[Parameter] public string Description { get; set; } = string.Empty;
[Parameter] public bool ReadOnly { get; set; }
[Parameter] public IDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();
[Parameter] public Func<Expression?, Task>? ExpressionChanged { get; set; }
[Inject] private TypeDefinitionService TypeDefinitionService { get; set; } = null!;
[Inject] private IExpressionService ExpressionService { get; set; } = null!;
[Inject] private IJSRuntime JSRuntime { get; set; } = null!;
[Inject] private IEnumerable<IMonacoHandler> MonacoHandlers { get; set; } = null!;
[Inject] private IUIHintService UIHintService { get; set; } = null!;
private IEnumerable<ExpressionDescriptor> BrowsableExpressionDescriptors => _expressionDescriptors.Where(x => x.IsBrowsable);
private ExpressionDescriptor? SelectedExpressionDescriptor => _expressionDescriptors.FirstOrDefault(x => x.Type == _selectedExpressionType);
private string SelectedExpressionTypeDisplayName => SelectedExpressionDescriptor?.DisplayName ?? DefaultOption;
private string MonacoLanguage => SelectedExpressionDescriptor?.GetMonacoLanguage() ?? string.Empty;
private bool DisplayCodeEditor => _selectedExpressionType != null && _selectedExpressionType != DefaultOption;
private bool DisplayModePicker => !DisplayCodeEditor;
private string? ButtonIcon => DisplayModePicker ? Icons.Material.Filled.MoreVert : null;
private string? ButtonLabel => DisplayModePicker ? null : SelectedExpressionTypeDisplayName;
private Variant ButtonVariant => DisplayModePicker ? default : Variant.Filled;
private Color ButtonColor => DisplayModePicker ? default : Color.Primary;
private string? ButtonEndIcon => DisplayModePicker ? null : Icons.Material.Filled.KeyboardArrowDown;
private Color ButtonEndColor => DisplayModePicker ? default : Color.Secondary;
private string? MonacoSyntax { get; set; }
private bool MonacoSyntaxExist => !string.IsNullOrEmpty(MonacoLanguage);
/// <summary>
/// Updates the internal state of the editor with the provided expression.
/// </summary>
/// <param name="expression">The expression to update the editor with. Can be null.</param>
public async Task UpdateAsync(Expression? expression)
{
if (!_isInitialized || _monacoEditor == null)
{
_initializationTcs ??= new();
await _initializationTcs.Task;
}
await UpdateMonacoEditorAsync(expression);
}
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
var expressionDescriptors = (await ExpressionService.ListDescriptorsAsync()).ExceptBy(_expressionTypeBlacklist, x => x.Type).ToList();
_selectedExpressionType = expressionDescriptors.First().Type;
_expressionDescriptors = expressionDescriptors.ToList();
}
private async Task UpdateMonacoLanguageAsync()
{
if (_monacoEditor == null)
return;
if (SelectedExpressionDescriptor?.Type == null)
return;
if (string.IsNullOrWhiteSpace(MonacoLanguage))
return;
var model = await _monacoEditor.GetModel();
await Global.SetModelLanguage(JSRuntime, model, MonacoLanguage);
await RunMonacoHandlersAsync(_monacoEditor);
}
/// <inheritdoc />
protected override Task OnParametersSetAsync()
{
_selectedExpressionType = Expression?.Type;
return base.OnParametersSetAsync();
}
private StandaloneEditorConstructionOptions ConfigureMonacoEditor(StandaloneCodeEditor editor)
{
return new()
{
Language = MonacoLanguage,
Value = Expression?.Value?.ToString() ?? "",
FontFamily = "Roboto Mono, monospace",
RenderLineHighlight = "none",
FixedOverflowWidgets = true,
Minimap = new()
{
Enabled = false
},
AutomaticLayout = true,
LineNumbers = "on",
Theme = "vs",
RoundedSelection = true,
ScrollBeyondLastLine = false,
OverviewRulerLanes = 0,
OverviewRulerBorder = false,
LineDecorationsWidth = 0,
HideCursorInOverviewRuler = true,
GlyphMargin = false,
ReadOnly = ReadOnly,
DomReadOnly = ReadOnly
};
}
private async Task OnSyntaxSelectedAsync(string? syntax)
{
_selectedExpressionType = syntax;
var value = Expression?.Value?.ToString() ?? "";
var expressionType = syntax ?? DefaultOption;
var expression = new Expression(expressionType, value);
await InvokeValueChangedCallbackAsync(expression);
await UpdateMonacoLanguageAsync();
StateHasChanged();
}
private async Task OnMonacoContentChangedAsync(ModelContentChangedEvent e)
{
if (_isInternalContentChange)
return;
var value = await _monacoEditor!.GetValue();
// This event gets fired even when the content hasn't changed, for example, when the containing pane is resized.
// This happens from within the monaco editor itself (or the Blazor wrapper, not sure).
if (value == _lastMonacoEditorContent)
return;
var expression = new Expression(_selectedExpressionType!, value);
_lastMonacoEditorContent = value;
await ThrottleValueChangedCallbackAsync(expression);
}
private async Task ThrottleValueChangedCallbackAsync(Expression expression) => await _throttledValueChanged.InvokeAsync(expression);
private async Task InvokeValueChangedCallbackAsync(Expression? expression)
{
await InvokeAsync(async () =>
{
if (ExpressionChanged != null)
await ExpressionChanged.Invoke(expression);
});
}
private async Task OnMonacoInitializedAsync()
{
_isInitialized = true;
if (_initializationTcs != null)
{
_initializationTcs.SetResult(true);
_initializationTcs = null;
return;
}
await UpdateMonacoEditorAsync(Expression);
}
private async Task UpdateMonacoEditorAsync(Expression? expression)
{
_isInternalContentChange = true;
var model = await _monacoEditor!.GetModel();
var expressionText = expression?.Value?.ToString() ?? string.Empty;
if (expression?.Type != null)
_selectedExpressionType = expression.Type;
_lastMonacoEditorContent = expressionText;
await model.SetValue(expressionText);
_isInternalContentChange = false;
await Global.SetModelLanguage(JSRuntime, model, MonacoLanguage);
await RunMonacoHandlersAsync(_monacoEditor);
}
private async Task RunMonacoHandlersAsync(StandaloneCodeEditor editor)
{
if (SelectedExpressionDescriptor == null)
return;
var context = new MonacoContext(editor, SelectedExpressionDescriptor, CustomProperties);
foreach (var handler in MonacoHandlers)
await handler.InitializeAsync(context);
}
/// <inheritdoc />
public void Dispose()
{
_throttledValueChanged.Dispose();
}
}
@@ -0,0 +1,54 @@
@using Elsa.Api.Client.Resources.Scripting.Extensions;
@using Elsa.Api.Client.Resources.Scripting.Models;
@using Elsa.Studio.Enums
<div class="d-flex flex-grow-1 gap-4 align-content-start relative">
<div class="flex-1">
@if (ShowMonacoEditor)
{
<FieldExtension UIHintComponent=@ComponentType EditorContext="@EditorContext">
<ChildContent>
<MudField Variant="Variant.Outlined" Label="@DisplayName" HelperText="@Description" Margin="Margin.Dense" Disabled="EditorContext.IsReadOnly">
<StandaloneCodeEditor @ref="_monacoEditor"
Id="@_monacoEditorId"
ConstructionOptions="ConfigureMonacoEditor"
OnDidInit="OnMonacoInitializedAsync"
OnDidChangeModelContent="OnMonacoContentChangedAsync"
CssClass="studio-expression-input-monaco-editor" />
</MudField>
</ChildContent>
</FieldExtension>
}
else
{
<FieldExtension UIHintComponent=@ComponentType EditorContext="@EditorContext" ChildContent="@ChildContent" />
}
</div>
@if (EditorContext.InputDescriptor.IsWrapped)
{
RenderFragment DisplayContent(ExpressionDescriptor item) => @<div>
@{
var isChecked = item.Type == _selectedExpressionType;
var iconColor = isChecked ? Color.Secondary : Color.Transparent;
<MudMenuItem Class="studio-expression-input-menu-item" OnClick="@(() => OnSyntaxSelectedAsync(item.Type))" Icon="@Icons.Material.Filled.Check" IconSize="Size.Small" IconColor="iconColor">@item.DisplayName</MudMenuItem>
}</div>;
<div class="flex-none pt-1">
<MudMenu Label="@ButtonLabel" Icon="@ButtonIcon" Variant="@ButtonVariant" Color="@ButtonColor" EndIcon="@ButtonEndIcon" IconColor="@ButtonEndColor" Dense="true" Disabled="EditorContext.IsReadOnly">
@DisplayContent(BrowsableExpressionDescriptors.First())
<MudDivider />
@foreach (var item in BrowsableExpressionDescriptors.Where(i=>!string.IsNullOrEmpty(i.GetMonacoLanguage())))
{
@DisplayContent(item);
}
<MudDivider />
@foreach (var item in BrowsableExpressionDescriptors.Skip(1).Where(i=>string.IsNullOrEmpty(i.GetMonacoLanguage())))
{
@DisplayContent(item);
}
</MudMenu>
</div>
}
</div>
@@ -0,0 +1,244 @@
using BlazorMonaco.Editor;
using Elsa.Api.Client.Resources.ActivityDescriptors.Models;
using Elsa.Api.Client.Resources.Scripting.Extensions;
using Elsa.Api.Client.Resources.Scripting.Models;
using Elsa.Api.Client.Shared.Models;
using Elsa.Studio.Contracts;
using Elsa.Studio.Extensions;
using Elsa.Studio.Models;
using Elsa.Studio.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using MudBlazor;
using ThrottleDebounce;
namespace Elsa.Studio.Components;
/// <summary>
/// A component that renders an input for an expression.
/// </summary>
public partial class ExpressionInput : IDisposable
{
private const string DefaultSyntax = "Literal";
private readonly string[] _uiSyntaxes = ["Literal", "Object"];
private string _selectedExpressionType = DefaultSyntax;
private string _selectedExpressionTypeDisplayName = DefaultSyntax;
private StandaloneCodeEditor? _monacoEditor = null!;
private bool _isInternalContentChange;
private string _monacoEditorId = $"monaco-editor-{Guid.NewGuid()}:N";
private string? _lastMonacoEditorContent;
private RateLimitedFunc<WrappedInput, Task> _throttledValueChanged;
private ICollection<ExpressionDescriptor> _expressionDescriptors = new List<ExpressionDescriptor>();
/// <inheritdoc />
public ExpressionInput()
{
_throttledValueChanged = Debouncer.Debounce<WrappedInput, Task>(InvokeValueChangedCallbackAsync, TimeSpan.FromMilliseconds(500));
}
/// <summary>
/// The context for the editor.
/// </summary>
[Parameter] public DisplayInputEditorContext EditorContext { get; set; } = null!;
/// <summary>
/// The content to render inside the editor.
/// </summary>
[Parameter] public RenderFragment ChildContent { get; set; } = null!;
/// <summary>
/// A flag indicating whether the editor should only display code.
/// </summary>
[Parameter] public bool IsCodeOnly { get; set; }
[Inject] private TypeDefinitionService TypeDefinitionService { get; set; } = null!;
[Inject] private IExpressionService ExpressionService { get; set; } = null!;
[Inject] private IJSRuntime JSRuntime { get; set; } = null!;
[Inject] private IEnumerable<IMonacoHandler> MonacoHandlers { get; set; } = null!;
[Inject] private IUIHintService UIHintService { get; set; } = null!;
private IEnumerable<ExpressionDescriptor> BrowsableExpressionDescriptors => _expressionDescriptors.Where(x => x.IsBrowsable);
private ExpressionDescriptor? SelectedExpressionDescriptor => _expressionDescriptors.FirstOrDefault(x => x.Type == _selectedExpressionType);
private string MonacoLanguage => SelectedExpressionDescriptor?.GetMonacoLanguage() ?? string.Empty;
private string UISyntax => EditorContext.UIHintHandler.UISyntax;
private string ComponentType => EditorContext.InputDescriptor.UIHint;
private bool IsUISyntax => !IsCodeOnly && _selectedExpressionType == UISyntax;
private string? ButtonIcon => IsUISyntax ? Icons.Material.Filled.MoreVert : null;
private string? ButtonLabel => IsUISyntax ? null : _selectedExpressionTypeDisplayName;
private Variant ButtonVariant => IsUISyntax ? default : Variant.Filled;
private Color ButtonColor => IsUISyntax ? default : Color.Primary;
private string? ButtonEndIcon => IsUISyntax ? null : Icons.Material.Filled.KeyboardArrowDown;
private Color ButtonEndColor => IsUISyntax ? default : Color.Secondary;
private bool ShowMonacoEditor => IsCodeOnly || (!IsUISyntax && EditorContext.InputDescriptor.IsWrapped && MonacoSyntaxExist);
private string DisplayName => EditorContext.InputDescriptor.DisplayName ?? EditorContext.InputDescriptor.Name;
private string? Description => EditorContext.InputDescriptor.Description;
private string InputValue => EditorContext.GetExpressionValueOrDefault();
private string? MonacoSyntax { get; set; }
private bool MonacoSyntaxExist => !string.IsNullOrEmpty(MonacoLanguage);
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
var defaultDescriptor = new ExpressionDescriptor(UISyntax, "Default");
var expressionDescriptors = (await ExpressionService.ListDescriptorsAsync()).ExceptBy(_uiSyntaxes, x => x.Type).ToList();
if (IsCodeOnly)
_selectedExpressionType = expressionDescriptors.First().Type;
else
expressionDescriptors = expressionDescriptors.Prepend(defaultDescriptor).ToList();
_expressionDescriptors = expressionDescriptors.ToList();
}
private async Task UpdateMonacoLanguageAsync()
{
if (_monacoEditor == null)
return;
var expressionDescriptor = SelectedExpressionDescriptor;
if (expressionDescriptor == null)
return;
var monacoLanguage = expressionDescriptor.GetMonacoLanguage();
_selectedExpressionTypeDisplayName = expressionDescriptor.DisplayName;
if (string.IsNullOrWhiteSpace(monacoLanguage))
return;
var model = await _monacoEditor.GetModel();
await Global.SetModelLanguage(JSRuntime, model, monacoLanguage);
await RunMonacoHandlersAsync(_monacoEditor);
}
/// <inheritdoc />
protected override Task OnParametersSetAsync()
{
var selectedExpressionDescriptor = EditorContext.SelectedExpressionDescriptor;
_selectedExpressionType = selectedExpressionDescriptor?.Type ?? UISyntax;
_selectedExpressionTypeDisplayName = selectedExpressionDescriptor?.DisplayName ?? UISyntax;
UpdateUIHintAsync(EditorContext);
return base.OnParametersSetAsync();
}
private StandaloneEditorConstructionOptions ConfigureMonacoEditor(StandaloneCodeEditor editor)
{
return new()
{
Language = MonacoLanguage,
Value = InputValue,
FontFamily = "Roboto Mono, monospace",
RenderLineHighlight = "none",
FixedOverflowWidgets = true,
Minimap = new()
{
Enabled = false
},
AutomaticLayout = true,
LineNumbers = "on",
Theme = "vs",
RoundedSelection = true,
ScrollBeyondLastLine = false,
OverviewRulerLanes = 0,
OverviewRulerBorder = false,
LineDecorationsWidth = 0,
HideCursorInOverviewRuler = true,
GlyphMargin = false,
ReadOnly = EditorContext.IsReadOnly,
DomReadOnly = EditorContext.IsReadOnly
};
}
private async Task OnMonacoInitializedAsync()
{
_isInternalContentChange = true;
var model = await _monacoEditor!.GetModel();
_lastMonacoEditorContent = InputValue;
await model.SetValue(InputValue);
_isInternalContentChange = false;
await Global.SetModelLanguage(JSRuntime, model, MonacoLanguage);
await RunMonacoHandlersAsync(_monacoEditor);
}
private async Task OnSyntaxSelectedAsync(string syntax)
{
_selectedExpressionType = syntax;
var value = InputValue;
var input = (WrappedInput?)EditorContext.Value ?? new WrappedInput();
input.Expression = new(_selectedExpressionType, value);
await InvokeValueChangedCallbackAsync(input);
await UpdateMonacoLanguageAsync();
}
/// <summary>
/// Used to display the UI hint when the user selects any other choice than a language supported by the monaco editor.
/// </summary>
private void UpdateUIHintAsync(DisplayInputEditorContext editorContext)
{
if (_selectedExpressionType == "Variable")
{
var uiHintHandler = UIHintService.GetHandler("variable-picker");
var editor = uiHintHandler.DisplayInputEditor(editorContext);
ChildContent = editor;
return;
}
if (MonacoSyntax == null
&& _selectedExpressionType == editorContext.SelectedExpressionDescriptor?.Type
&& editorContext.SelectedExpressionDescriptor.Properties.TryGetValue("UIHint", out var uiHint))
{
var uiHintHandler = UIHintService.GetHandler(uiHint);
var editor = uiHintHandler.DisplayInputEditor(editorContext);
_selectedExpressionTypeDisplayName = editorContext.SelectedExpressionDescriptor.DisplayName;
ChildContent = editor;
}
}
private async Task OnMonacoContentChangedAsync(ModelContentChangedEvent e)
{
if (_isInternalContentChange)
return;
var value = await _monacoEditor!.GetValue();
// This event gets fired even when the content hasn't changed, but for example when the containing pane is resized.
// This happens from within the monaco editor itself (or the Blazor wrapper, not sure).
if (value == _lastMonacoEditorContent)
return;
var input = (WrappedInput?)EditorContext.Value ?? new WrappedInput();
input.Expression = new(_selectedExpressionType, value);
_lastMonacoEditorContent = value;
await ThrottleValueChangedCallbackAsync(input);
}
private async Task ThrottleValueChangedCallbackAsync(WrappedInput input) => await _throttledValueChanged.InvokeAsync(input);
private async Task InvokeValueChangedCallbackAsync(WrappedInput input)
{
await InvokeAsync(async () => await EditorContext.OnValueChanged(input));
}
private async Task RunMonacoHandlersAsync(StandaloneCodeEditor editor)
{
var customProps = new Dictionary<string, object>
{
{ nameof(ActivityDescriptor), EditorContext.ActivityDescriptor },
{ nameof(PropertyDescriptor), EditorContext.InputDescriptor },
{ "WorkflowDefinitionId", EditorContext.WorkflowDefinition.DefinitionId }
};
var expressionDescriptor = SelectedExpressionDescriptor?? new ExpressionDescriptor("Literal", "Default");
var context = new MonacoContext(editor, expressionDescriptor, customProps);
foreach (var handler in MonacoHandlers)
await handler.InitializeAsync(context);
}
/// <inheritdoc />
void IDisposable.Dispose() => _throttledValueChanged.Dispose();
}
@@ -0,0 +1,79 @@
@using Elsa.Studio.Contracts
@using Elsa.Studio.Enums
@using Elsa.Studio.Models
<style>
.field-container {
transition: border 0.3s ease-in-out;
border-radius: 8px;
padding: 10px;
margin: -10px;
border: 1px solid transparent;
}
.field-container.hovered {
border-color: #ccc
}
.no-padding-stack {
padding-top: 0 !important;
padding-bottom: 0 !important;
}
</style>
@if (hasExtensions)
{
<MudStack Spacing="1" Class=@($"field-container {(isHovered && hasExtensions ? "hovered" : "")}") @onmouseenter="() => isHovered = true" @onmouseleave="() => isHovered = false">
@foreach (var extension in TopExtensions ?? Enumerable.Empty<IUIFieldExtensionHandler>())
{
@extension.DisplayExtension(EditorContext)
}
@ChildContent
@foreach (var extension in BottomExtensions ?? Enumerable.Empty<IUIFieldExtensionHandler>())
{
@extension.DisplayExtension(EditorContext)
}
</MudStack>
}
else
{
@ChildContent
}
@code
{
private bool isHovered = false;
private bool hasExtensions => AllExtensions?.Count != 0;
private List<IUIFieldExtensionHandler>? AllExtensions;
private List<IUIFieldExtensionHandler>? TopExtensions;
private List<IUIFieldExtensionHandler>? BottomExtensions;
/// <summary>
/// The UI Hint component type the extensions should be rendered for.
/// </summary>
[Parameter] public string UIHintComponent { get; set; } = default!;
/// <summary>
/// The editor context.
/// </summary>
[Parameter] public DisplayInputEditorContext EditorContext { get; set; } = default!;
/// <summary>
/// The content wrap with <see cref="IUIFieldExtensionHandler"/> extensions.
/// </summary>
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
[Inject] private IUIFieldExtensionService FieldExtensionService { get; set; }
/// <inheritdoc />
protected override void OnInitialized()
{
AllExtensions = FieldExtensionService.TryGetHandlers(UIHintComponent, EditorContext);
if (AllExtensions is not null)
{
TopExtensions = AllExtensions.Where(p => p.Position == FieldExtensionPosition.Top).ToList();
BottomExtensions = AllExtensions.Where(p => p.Position == FieldExtensionPosition.Bottom).ToList();
}
}
}
@@ -0,0 +1,33 @@
@using Elsa.Studio.Contracts
@using Elsa.Studio.Models
@inject IMenuService MenuService;
<MudNavMenu Bordered="true" Color="Color.Primary" Rounded="true" Margin="Margin.Dense" Class="pa-2">
@foreach (var menuItemGroup in _menuItemsGroups.Values)
{
var menuItems = _menuItems.Where(x => x.GroupName == menuItemGroup.Name).ToList();
@if (menuItems.Any())
{
<MudText Typo="Typo.body2" Class="px-4 py-4 mud-text-secondary">@menuItemGroup.Text</MudText>
foreach (var menuItem in menuItems)
{
<NavMenuItem MenuItem="menuItem" />
}
}
}
</MudNavMenu>
@code
{
private IDictionary<string, MenuItemGroup> _menuItemsGroups = new Dictionary<string, MenuItemGroup>();
private IEnumerable<MenuItem> _menuItems = new List<MenuItem>();
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
_menuItemsGroups = (await MenuService.GetMenuItemGroupsAsync()).ToDictionary(x => x.Name);
_menuItems = await MenuService.GetMenuItemsAsync();
}
}
@@ -0,0 +1,20 @@
@using Elsa.Studio.Models
@if (!MenuItem.SubMenuItems.Any())
{
<MudNavLink Href="@MenuItem.Href" Match="@MenuItem.Match" Icon="@MenuItem.Icon">@MenuItem.Text</MudNavLink>
}
else
{
<MudNavGroup Icon="@MenuItem.Icon" Title="@MenuItem.Text">
@foreach (var subItem in MenuItem.SubMenuItems)
{
<NavMenuItem MenuItem="@subItem"/>
}
</MudNavGroup>
}
@code
{
[Parameter]
public MenuItem MenuItem { get; set; } = default!;
}
@@ -0,0 +1,5 @@
<MudText Typo="Typo.h5" Color="Color.Primary" Class="my-6">@Text</MudText>
@code {
[Parameter] public string Text { get; set; } = "Untitled";
}
@@ -0,0 +1,17 @@
<MudDialog>
<DialogContent>
<MudStack Spacing="0" AlignItems="AlignItems.Start" Row="false">
<ClientPackageVersion/>
<ServerPackageVersion/>
</MudStack>
</DialogContent>
<DialogActions>
<MudButton Color="Color.Primary" OnClick="@Close">Close</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!;
void Close() => MudDialog.Close(DialogResult.Ok(true));
}
@@ -0,0 +1,20 @@
<div style="@($"max-height: {MaxHeight}px; overflow-y: scroll;")">
<div class="pa-4">
@ChildContent
</div>
</div>
@code
{
/// <summary>
/// The maximum height of the <see cref="ScrollableWell"/> component.
/// </summary>
[Parameter]
public int MaxHeight { get; set; } = 300;
/// <summary>
/// The content to be rendered inside the <see cref="ScrollableWell"/> component.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
@@ -0,0 +1,16 @@
@using Elsa.Studio.Contracts
@inherits StudioComponentBase
<MudText Typo="Typo.body2">Server version: @PackageVersion</MudText>
@code {
[Inject] private IServerInformationProvider ServerInformationProvider { get; set; } = default!;
private string PackageVersion { get; set; } = "Loading...";
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
var info = await ServerInformationProvider.GetInfoAsync();
PackageVersion = info.PackageVersion;
}
}
@@ -0,0 +1,8 @@
using Elsa.Studio.Services;
using Microsoft.AspNetCore.Components;
namespace Elsa.Studio.Components;
/// Base class for components. This class sets the <see cref="BlazorServiceAccessor.Services"/> property to the <see cref="IServiceProvider"/> instance.
/// 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 abstract class StudioComponentBase : ComponentBase;
@@ -0,0 +1 @@
<MudAlert Severity="Severity.Normal" Icon="@Icons.Material.Outlined.Announcement">Unauthorized</MudAlert>
@@ -0,0 +1,16 @@
<div class="pa-2" style="@($"overflow-y: scroll; max-height: calc(100vh - var(--mud-appbar-height) - {ExtraPadding}px);")">
@ChildContent
</div>
@code
{
/// <summary>
/// The content to be rendered inside the <see cref="VerticalWell"/> component.
/// </summary>
[Parameter] public RenderFragment? ChildContent { get; set; }
/// <summary>
/// The amount of extra padding to add to the bottom of the <see cref="VerticalWell"/> component.
/// </summary>
[Parameter] public int ExtraPadding { get; set; }
}
@@ -0,0 +1,11 @@
<div class="pa-4">
@ChildContent
</div>
@code
{
/// <summary>
/// The content to be rendered inside the <see cref="Well"/> component.
/// </summary>
[Parameter] public RenderFragment? ChildContent { get; set; }
}
@@ -0,0 +1,4 @@
@foreach (var widget in Widgets)
{
@widget.Render(Attributes)
}
@@ -0,0 +1,45 @@
using Elsa.Studio.Contracts;
using Microsoft.AspNetCore.Components;
namespace Elsa.Studio.Components;
/// <summary>
/// Represents a zone in which widgets can be rendered.
/// </summary>
public partial class Zone : IDisposable
{
/// <summary>
/// Gets or sets the zone name.
/// </summary>
[Parameter]
public string Name { get; set; } = default!;
/// <summary>
/// Gets or sets the attributes that are passed to the widgets.
/// </summary>
[Parameter]
public IDictionary<string, object?> Attributes { get; set; } = new Dictionary<string, object?>();
[Inject] private IWidgetRegistry WidgetRegistry { get; set; } = default!;
[Inject] private IFeatureService FeatureService { get; set; } = default!;
private ICollection<IWidget> Widgets { get; set; } = new List<IWidget>();
/// <inheritdoc />
protected override void OnInitialized()
{
FeatureService.Initialized += OnFeatureServiceInitialized;
Widgets = WidgetRegistry.List(Name).ToList();
}
private void OnFeatureServiceInitialized()
{
Widgets = WidgetRegistry.List(Name).ToList();
StateHasChanged();
}
/// <inheritdoc />
public void Dispose()
{
FeatureService.Initialized -= OnFeatureServiceInitialized;
}
}