Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions playground/Stress/Stress.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@
var interactionService = commandContext.ServiceProvider.GetRequiredService<IInteractionService>();
var dinnerInput = new InteractionInput
{
InputType = InputType.Select,
InputType = InputType.Choice,
Label = "Dinner",
Placeholder = "Select dinner",
Required = true,
Expand Down Expand Up @@ -231,10 +231,10 @@
var inputs = new List<InteractionInput>
{
new InteractionInput { InputType = InputType.Text, Label = "Name", Placeholder = "Enter name", Required = true },
new InteractionInput { InputType = InputType.Password, Label = "Password", Placeholder = "Enter password", Required = true },
new InteractionInput { InputType = InputType.SecretText, Label = "Password", Placeholder = "Enter password", Required = true },
dinnerInput,
numberOfPeopleInput,
new InteractionInput { InputType = InputType.Checkbox, Label = "Remember me", Placeholder = "What does this do?", Required = true },
new InteractionInput { InputType = InputType.Boolean, Label = "Remember me", Placeholder = "What does this do?", Required = true },
};
var result = await interactionService.PromptInputsAsync(
"Input request",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,41 @@
<p>@((MarkupString)Content.Interaction.Message)</p>
}

<EditForm EditContext="@_editContext">
<EditForm EditContext="@_editContext" OnValidSubmit="@SubmitAsync">
<FluentStack Orientation="Orientation.Vertical" VerticalGap="12">
@foreach (var vm in _inputDialogInputViewModels)
{
@*
* AutoComplete value of one-time-code on password input prevents the browser asking to save the value.
* Immediate value of true on text inputs ensures the value is set to the server token with every key press in textbox.
*@
var localItem = vm;
<div class="interaction-input">
@switch (vm.Input.InputType)
{
case InputType.Text:
<FluentTextField @bind-Value="localItem.Value" Label="@localItem.Input.Label" Placeholder="@localItem.Input.Placeholder" Required="localItem.Input.Required" />
<FluentTextField @ref="@_elementRefs[localItem]"
@bind-Value="localItem.Value"
Label="@localItem.Input.Label"
Placeholder="@localItem.Input.Placeholder"
Required="localItem.Input.Required"
Immediate="true" />
<ValidationMessage For="@(() => localItem.Value)" />
break;
case InputType.Password:
<FluentTextField @bind-Value="localItem.Value" Label="@localItem.Input.Label" Placeholder="@localItem.Input.Placeholder" Required="localItem.Input.Required" TextFieldType="TextFieldType.Password" />
case InputType.SecretText:
<FluentTextField @ref="@_elementRefs[localItem]"
@bind-Value="localItem.Value"
Label="@localItem.Input.Label"
Placeholder="@localItem.Input.Placeholder"
Required="localItem.Input.Required"
TextFieldType="TextFieldType.Password"
AutoComplete="one-time-code"
Immediate="true" />
<ValidationMessage For="@(() => localItem.Value)" />
break;
case InputType.Select:
case InputType.Choice:
<FluentSelect TOption="SelectViewModel<string>"
@ref="@_elementRefs[localItem]"
@bind-Value="localItem.Value"
Label="@localItem.Input.Label"
Placeholder="@localItem.Input.Placeholder"
Expand All @@ -56,11 +73,20 @@
Position="SelectPosition.Below" />
<ValidationMessage For="@(() => localItem.Value)" />
break;
case InputType.Checkbox:
<FluentCheckbox @bind-Value="localItem.IsChecked" Label="@localItem.Input.Label" Placeholder="@localItem.Input.Placeholder" />
case InputType.Boolean:
<FluentCheckbox @ref="@_elementRefs[localItem]"
@bind-Value="localItem.IsChecked"
Label="@localItem.Input.Label"
Placeholder="@localItem.Input.Placeholder" />
break;
case InputType.Number:
<FluentNumberField @bind-Value="localItem.NumberValue" Label="@localItem.Input.Label" Placeholder="@localItem.Input.Placeholder" Required="localItem.Input.Required" />
<FluentNumberField TValue="int?"
@ref="@_elementRefs[localItem]"
@bind-Value="localItem.NumberValue"
Label="@localItem.Input.Label"
Placeholder="@localItem.Input.Placeholder"
Required="localItem.Input.Required"
Immediate="true" />
<ValidationMessage For="@(() => localItem.NumberValue)" />
break;
default:
Expand All @@ -70,11 +96,14 @@
</div>
}
</FluentStack>

@* Hidden submit is so the form is submitted when the user presses enter. *@
<button type="submit" style="display:none"></button>
</EditForm>
</FluentDialogBody>

<FluentDialogFooter>
<FluentButton Appearance="Appearance.Accent" OnClick="@OkAsync">
<FluentButton Appearance="Appearance.Accent" OnClick="@SubmitAsync">
@Dialog.Instance.Parameters.PrimaryAction
</FluentButton>
@if (!string.IsNullOrEmpty(Dialog.Instance.Parameters.SecondaryAction))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public partial class InteractionsInputDialog
private EditContext _editContext = default!;
private ValidationMessageStore _validationMessages = default!;
private List<InputViewModel> _inputDialogInputViewModels = default!;
private Dictionary<InputViewModel, FluentComponentBase> _elementRefs = default!;

protected override void OnInitialized()
{
Expand All @@ -29,6 +30,8 @@ protected override void OnInitialized()

_editContext.OnValidationRequested += (s, e) => ValidateModel();
_editContext.OnFieldChanged += (s, e) => ValidateField(e.FieldIdentifier);

_elementRefs = new();
}

protected override void OnParametersSet()
Expand All @@ -49,6 +52,31 @@ protected override void OnParametersSet()
}
}

protected override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// Focus the first input when the dialog loads.
if (_inputDialogInputViewModels.Count > 0 && _elementRefs.TryGetValue(_inputDialogInputViewModels[0], out var firstInputElement))
{
if (firstInputElement is FluentInputBase<string> textInput)
{
textInput.FocusAsync();
}
else if (firstInputElement is FluentInputBase<bool> boolInput)
{
boolInput.FocusAsync();
}
else if (firstInputElement is FluentInputBase<int?> numberInput)
{
numberInput.FocusAsync();
}
}
}

return Task.CompletedTask;
}

private void AddValidationErrorsFromModel()
{
for (var i = 0; i < Content.Inputs.Count; i++)
Expand Down Expand Up @@ -101,7 +129,7 @@ private static FieldIdentifier GetFieldIdentifier(InputViewModel inputModel)
{
var fieldName = inputModel.Input.InputType switch
{
InputType.Checkbox => nameof(inputModel.IsChecked),
InputType.Boolean => nameof(inputModel.IsChecked),
InputType.Number => nameof(inputModel.NumberValue),
_ => nameof(inputModel.Value)
};
Expand All @@ -111,11 +139,11 @@ private static FieldIdentifier GetFieldIdentifier(InputViewModel inputModel)
private static bool IsMissingRequiredValue(InputViewModel inputModel)
{
return inputModel.Input.Required &&
inputModel.Input.InputType != InputType.Checkbox &&
inputModel.Input.InputType != InputType.Boolean &&
string.IsNullOrWhiteSpace(inputModel.Value);
}

private async Task OkAsync()
private async Task SubmitAsync()
{
// The workflow is:
// 1. Validate the model that required fields are present.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,8 @@ public async Task<IDialogReference> ShowMessageBoxAsync(IDialogService dialogSer
Width = parameters.Width,
Height = parameters.Height,
AriaLabel = (content.Title ?? ""),
OnDialogResult = parameters.OnDialogResult
OnDialogResult = parameters.OnDialogResult,
PreventDismissOnOverlayClick = true
};
return await dialogService.ShowDialogAsync(typeof(MessageBox), content, dialogParameters);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Model/InputViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public InputViewModel(InteractionInput input)
public void SetInput(InteractionInput input)
{
Input = input;
if (input.InputType == InputType.Select && input.Options != null)
if (input.InputType == InputType.Choice && input.Options != null)
{
var optionsVM = input.Options
.Select(option => new SelectViewModel<string> { Id = option.Key, Name = option.Value, })
Expand Down
14 changes: 7 additions & 7 deletions src/Aspire.Hosting/ApplicationModel/IInteractionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public sealed class InteractionInput
public bool Required { get; init; }

/// <summary>
/// Gets or sets the options for the input. Only used by <see cref="InputType.Select"/> inputs.
/// Gets or sets the options for the input. Only used by <see cref="InputType.Choice"/> inputs.
/// </summary>
public IReadOnlyList<KeyValuePair<string, string>>? Options { get; init; }

Expand Down Expand Up @@ -151,17 +151,17 @@ public enum InputType
/// </summary>
Text,
/// <summary>
/// A password input.
/// A secure text input.
/// </summary>
Password,
SecretText,
/// <summary>
/// A select input.
/// A choice input. Selects from a list of options.
/// </summary>
Select,
Choice,
/// <summary>
/// A checkbox input.
/// A boolean input.
/// </summary>
Checkbox,
Boolean,
/// <summary>
/// A numeric input.
/// </summary>
Expand Down
6 changes: 3 additions & 3 deletions src/Aspire.Hosting/Dashboard/DashboardService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,9 @@ private static InputType MapInputType(ApplicationModel.InputType inputType)
return inputType switch
{
ApplicationModel.InputType.Text => InputType.Text,
ApplicationModel.InputType.Password => InputType.Password,
ApplicationModel.InputType.Select => InputType.Select,
ApplicationModel.InputType.Checkbox => InputType.Checkbox,
ApplicationModel.InputType.SecretText => InputType.SecretText,
ApplicationModel.InputType.Choice => InputType.Choice,
ApplicationModel.InputType.Boolean => InputType.Boolean,
ApplicationModel.InputType.Number => InputType.Number,
_ => throw new InvalidOperationException($"Unexpected input type: {inputType}"),
};
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting/Dashboard/DashboardServiceData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ await _interactionService.CompleteInteractionAsync(
var incomingValue = requestInput.Value;

// Ensure checkbox value is either true or false.
if (requestInput.InputType == Aspire.DashboardService.Proto.V1.InputType.Checkbox)
if (requestInput.InputType == Aspire.DashboardService.Proto.V1.InputType.Boolean)
{
incomingValue = (bool.TryParse(incomingValue, out var b) && b) ? "true" : "false";
}
Expand Down
6 changes: 3 additions & 3 deletions src/Aspire.Hosting/Dashboard/proto/dashboard_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -385,9 +385,9 @@ enum MessageIntent {
enum InputType {
INPUT_TYPE_UNSPECIFIED = 0;
INPUT_TYPE_TEXT = 1;
INPUT_TYPE_PASSWORD = 2;
INPUT_TYPE_SELECT = 3;
INPUT_TYPE_CHECKBOX = 4;
INPUT_TYPE_SECRET_TEXT = 2;
INPUT_TYPE_CHOICE = 3;
INPUT_TYPE_BOOLEAN = 4;
INPUT_TYPE_NUMBER = 5;
}

Expand Down
Loading