From f457ed8a7c44e55785668c454fb9785e24e803eb Mon Sep 17 00:00:00 2001 From: af Date: Mon, 6 May 2024 13:59:22 +0200 Subject: [PATCH] Added conditional inputs --- .../Components/ActivityProperties/Models.cs | 3 +- .../ActivityProperties/Tabs/InputsTab.razor | 5 +- .../Tabs/InputsTab.razor.cs | 219 +++++++++++++++++- 3 files changed, 220 insertions(+), 7 deletions(-) diff --git a/src/modules/Elsa.Studio.Workflows/Components/WorkflowDefinitionEditor/Components/ActivityProperties/Models.cs b/src/modules/Elsa.Studio.Workflows/Components/WorkflowDefinitionEditor/Components/ActivityProperties/Models.cs index 957477079..b34b385a0 100644 --- a/src/modules/Elsa.Studio.Workflows/Components/WorkflowDefinitionEditor/Components/ActivityProperties/Models.cs +++ b/src/modules/Elsa.Studio.Workflows/Components/WorkflowDefinitionEditor/Components/ActivityProperties/Models.cs @@ -1,5 +1,6 @@ +using Elsa.Api.Client.Resources.ActivityDescriptors.Models; using Microsoft.AspNetCore.Components; namespace Elsa.Studio.Workflows.Components.WorkflowDefinitionEditor.Components.ActivityProperties; -public record ActivityInputDisplayModel(RenderFragment Editor); \ No newline at end of file +public record ActivityInputDisplayModel(RenderFragment Editor, InputDescriptor InputDescriptor); \ No newline at end of file diff --git a/src/modules/Elsa.Studio.Workflows/Components/WorkflowDefinitionEditor/Components/ActivityProperties/Tabs/InputsTab.razor b/src/modules/Elsa.Studio.Workflows/Components/WorkflowDefinitionEditor/Components/ActivityProperties/Tabs/InputsTab.razor index 8112b9c03..5ed49f39e 100644 --- a/src/modules/Elsa.Studio.Workflows/Components/WorkflowDefinitionEditor/Components/ActivityProperties/Tabs/InputsTab.razor +++ b/src/modules/Elsa.Studio.Workflows/Components/WorkflowDefinitionEditor/Components/ActivityProperties/Tabs/InputsTab.razor @@ -4,7 +4,10 @@ @foreach (var inputModel in InputDisplayModels) { - @inputModel.Editor + if (IsVisibleInput(inputModel)) + { + @inputModel.Editor + } } \ No newline at end of file diff --git a/src/modules/Elsa.Studio.Workflows/Components/WorkflowDefinitionEditor/Components/ActivityProperties/Tabs/InputsTab.razor.cs b/src/modules/Elsa.Studio.Workflows/Components/WorkflowDefinitionEditor/Components/ActivityProperties/Tabs/InputsTab.razor.cs index f12209962..96101a508 100644 --- a/src/modules/Elsa.Studio.Workflows/Components/WorkflowDefinitionEditor/Components/ActivityProperties/Tabs/InputsTab.razor.cs +++ b/src/modules/Elsa.Studio.Workflows/Components/WorkflowDefinitionEditor/Components/ActivityProperties/Tabs/InputsTab.razor.cs @@ -64,6 +64,10 @@ public InputsTab() private ICollection OutputDescriptors { get; set; } = new List(); private ICollection InputDisplayModels { get; set; } = new List(); + private List _selectedStates = []; + private static Dictionary _InputDescriptors = new(); + private static Dictionary _previousStates = new(); + /// protected override async Task OnParametersSetAsync() { @@ -73,20 +77,63 @@ protected override async Task OnParametersSetAsync() InputDescriptors = ActivityDescriptor.Inputs.ToList(); OutputDescriptors = ActivityDescriptor.Outputs.ToList(); InputDisplayModels = (await BuildInputEditorModels(Activity, ActivityDescriptor, InputDescriptors)).ToList(); + StateHasChanged(); } private async Task> BuildInputEditorModels(JsonObject activity, ActivityDescriptor activityDescriptor, ICollection inputDescriptors) { var models = new List(); - var browsableInputDescriptors = inputDescriptors.Where(x => x.IsBrowsable == true).ToList(); - foreach (var inputDescriptor in browsableInputDescriptors) + foreach (var inputDescriptor in inputDescriptors) { var inputName = inputDescriptor.Name.Camelize(); var value = activity.GetProperty(inputName); var wrappedInput = inputDescriptor.IsWrapped ? ToWrappedInput(value) : default; var syntaxProvider = wrappedInput != null ? ExpressionDescriptorProvider.GetByType(wrappedInput.Expression.Type) : default; + // Check if we have a custom input + JsonDocument? InputDescriptor = null; + if (inputDescriptor.Description is not null) + { + SetInputDescriptor(inputDescriptor); + InputDescriptor = GetInputDescriptor(inputDescriptor); + + if (InputDescriptor is not null) + { + string? inputType = InputDescriptor.RootElement.GetProperty("InputType").GetString(); + // Check if we have conditional inputs + if (inputType == "StateDropdown") + { + if (wrappedInput is not null) + { + // Add the current value to the selected states + AddSelectedState(inputDescriptor, wrappedInput); + UpdateDescription(inputDescriptor, wrappedInput); + } + else if (wrappedInput is null && inputDescriptor.DefaultValue is not null) + { + // Add the default value to the selected states + AddSelectedState(inputDescriptor, inputDescriptor.DefaultValue); + UpdateDescription(inputDescriptor, inputDescriptor.DefaultValue); + } + else if (wrappedInput is null || (wrappedInput is not null && string.IsNullOrEmpty(wrappedInput.Expression.ToString()))) + { + // Empty state && InputDescriptor exists, therefore we hide the description + inputDescriptor.Description = ""; + } + + } + else if (inputType == "ConditionalInput") + { + inputDescriptor.Description = InputDescriptor.RootElement.GetProperty("Description").EnumerateArray().First().GetString(); + } + else + { + inputDescriptor.Description = InputDescriptor.RootElement.GetProperty("Description").GetString(); + } + } + } + // Check if refresh is needed. if (inputDescriptor.UISpecifications != null && inputDescriptor.UISpecifications.TryGetValue("Refresh", out var refreshInput) @@ -109,17 +156,179 @@ private async Task> BuildInputEditorModel Value = input, SelectedExpressionDescriptor = syntaxProvider, UIHintHandler = uiHintHandler, - IsReadOnly = (Workspace?.IsReadOnly ?? false) || (inputDescriptor.IsReadOnly ?? false), + IsReadOnly = Workspace?.IsReadOnly ?? false }; - context.OnValueChanged = async v => await HandleValueChangedAsync(context, v); + context.OnValueChanged = HandleValueChangedAsync(context, inputDescriptor); var editor = uiHintHandler.DisplayInputEditor(context); - models.Add(new ActivityInputDisplayModel(editor)); + models.Add(new ActivityInputDisplayModel(editor, inputDescriptor)); } return models; } + private string GetInputDescriptorKey(InputDescriptor inputDescriptor) + { + return ActivityDescriptor?.Name + inputDescriptor.Name; + } + + private JsonDocument? GetInputDescriptor(InputDescriptor inputDescriptor) + { + return _InputDescriptors.GetValueOrDefault(GetInputDescriptorKey(inputDescriptor)); + } + + private void SetInputDescriptor(InputDescriptor inputDescriptor) + { + try + { + var InputDescriptor = JsonDocument.Parse(inputDescriptor.Description!); + _InputDescriptors[GetInputDescriptorKey(inputDescriptor)] = InputDescriptor; + } + catch + { + // Ignore --> Standard Elsa 3 descriptor + } + } + + private void AddSelectedState(InputDescriptor inputDescriptor, WrappedInput? v) + { + var InputDescriptor = GetInputDescriptor(inputDescriptor); + if (v is null || InputDescriptor is null) return; + + var valueAsString = v!.Expression.ToString(); + AddSelectedState(inputDescriptor, valueAsString); + UpdateDescription(inputDescriptor, v); + } + + private void AddSelectedState(InputDescriptor inputDescriptor, object? value) + { + if (value is null) return; + + var InputDescriptor = GetInputDescriptor(inputDescriptor); + if (InputDescriptor is null) return; + + var inputDescriptorKey = GetInputDescriptorKey(inputDescriptor); + // Remove the previous state + var previousState = _previousStates.GetValueOrDefault(inputDescriptorKey, string.Empty); + if (!string.IsNullOrEmpty(previousState)) + { + RemoveSelectedState(previousState); + } + + var valueAsString = value as string; + if (value is JsonElement) + { + valueAsString = ((JsonElement)value).GetString(); + } + + var stateNames = InputDescriptor.RootElement.GetProperty("Options").EnumerateArray(); + var stateIds = InputDescriptor.RootElement.GetProperty("Ids").EnumerateArray(); + + for (var i = 0; i < stateNames.Count(); ++i) + { + var current = stateNames.ElementAt(i); + if (current.GetString() == valueAsString) + { + var id = stateIds.ElementAt(i).GetString()!; + + // Ensure that we have no duplicates + RemoveSelectedState(id); + + _selectedStates.Add(id); + _previousStates[inputDescriptorKey] = id; + break; + } + } + + StateHasChanged(); + } + + private void RemoveSelectedState(string name) + { + _selectedStates.Remove(name); + } + + private Func HandleValueChangedAsync(DisplayInputEditorContext context, InputDescriptor inputDescriptor) + { + return async v => + { + await HandleValueChangedAsync(context, v); + + var InputDescriptor = GetInputDescriptor(inputDescriptor); + // Check if we have a custom input descriptor + if (InputDescriptor is null) + { + return; + } + + // Update the selected states + if (InputDescriptor.RootElement.GetProperty("InputType").GetString() == "StateDropdown") + { + AddSelectedState(inputDescriptor, v as WrappedInput); + } + }; + } + + public void UpdateDescription(InputDescriptor inputDescriptor, object? value) + { + if (value is null) + { + return; + } + + var valueAsString = ""; + if (value is WrappedInput) + { + valueAsString = ((WrappedInput)value).Expression.ToString(); + } + else if (value is JsonElement) + { + valueAsString = ((JsonElement)value).GetString(); + } + + var InputDescriptor = GetInputDescriptor(inputDescriptor)!; + // Update with the correct description + var options = InputDescriptor.RootElement.GetProperty("Options").EnumerateArray(); + var descriptions = InputDescriptor.RootElement.GetProperty("Description").EnumerateArray(); + var foundDescription = false; + for (var i = 0; i < options.Count(); ++i) + { + if (options.ElementAt(i).GetString() == valueAsString) + { + inputDescriptor.Description = descriptions.ElementAt(i).GetString(); + foundDescription = true; + break; + } + } + + if (!foundDescription) + { + inputDescriptor.Description = ""; + } + } + + public bool IsVisibleInput(ActivityInputDisplayModel model) + { + + var InputDescriptor = GetInputDescriptor(model.InputDescriptor); + + if (InputDescriptor == null) return true; + + // Check we have a match for the selected state + if (InputDescriptor.RootElement.GetProperty("InputType").GetString() == "ConditionalInput") + { + var validStates = InputDescriptor.RootElement.GetProperty("ShowForStates"); + foreach (var validState in validStates.EnumerateArray()) + { + var value = validState.GetString(); + if (value is not null && _selectedStates.Contains(value)) return true; + } + return false; + } + + return true; + } + private async Task RefreshDescriptor(JsonObject activity, ActivityDescriptor activityDescriptor, IEnumerable inputDescriptors, InputDescriptor currentInputDescriptor) { var activityTypeName = activityDescriptor.TypeName;