Browse Source

feat: Reconstruct the user multi-select component

pull/1568/head
colin 3 weeks ago
parent
commit
7184f6d7c5
  1. 2
      aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Notifications/LINGYUN/Abp/ElsaNext/Notifications/Activities/SendNotification.cs
  2. 146
      aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Components/IdentityUserPicker.razor
  3. 73
      aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Components/IdentityUserSelectDialog.razor
  4. 7
      aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Handlers/IdentityPickerHandler.cs

2
aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Notifications/LINGYUN/Abp/ElsaNext/Notifications/Activities/SendNotification.cs

@ -61,7 +61,7 @@ public class SendNotification : CodeActivity
[Input(
Description = "The recipients email addresses.",
UIHint = AbpElsaUIHints.UserPicker)]
UIHint = AbpElsaUIHints.UserPickerMultiple)]
public Input<ICollection<Guid>> To { get; set; } = null!;
[Input(

146
aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Components/IdentityUserPicker.razor

@ -1,6 +1,7 @@
@using Elsa.Api.Client.Shared.UIHints.DropDown
@using Elsa.Studio.Models
@using LINGYUN.Abp.ElsaNext.Studio.Blazor.Components
@using System.Text.Json
@using Volo.Abp.Identity
@inject ILocalizer Localizer
@ -13,33 +14,41 @@
<ExpressionInput EditorContext="@EditorContext">
<ChildContent>
<MudTextFieldEx Value="@(_selectedItem?.Text ?? string.Empty)"
Label="@Localizer[displayName]"
HelperText="@Localizer[description]"
Variant="Variant.Outlined"
Margin="Margin.Dense"
ReadOnly="true"
Disabled="EditorContext.IsReadOnly"
OnFieldClick="@(_ => OpenSelectDialogAsync())"
AdornmentIcons="GetAdornmentIcons()" />
<MudTextFieldEx Value="@SelectedText"
Label="@Localizer[displayName]"
HelperText="@Localizer[description]"
Variant="Variant.Outlined"
Margin="Margin.Dense"
ReadOnly="true"
Disabled="EditorContext.IsReadOnly"
OnFieldClick="@(_ => OpenSelectDialogAsync())"
AdornmentIcons="GetAdornmentIcons()" />
</ChildContent>
</ExpressionInput>
@code {
private SelectListItem? _selectedItem;
private readonly List<SelectListItem> _selectedItems = [];
[Parameter] public bool AllowMultiple { get; set; }
[Parameter] public DisplayInputEditorContext EditorContext { get; set; } = null!;
[Inject] protected IIdentityUserRepository IdentityUserRepository { get; set; } = null!;
[Inject] protected IDialogService DialogService { get; set; } = null!;
private string SelectedText => string.Join(", ", _selectedItems.Select(x => x.Text));
protected override async Task OnInitializedAsync()
{
var selectedItem = await ResolveSelectedItemAsync();
var selectedItems = await ResolveSelectedItemsAsync();
if (selectedItem != null)
if (selectedItems.Count > 0)
{
await InvokeAsync(() => _selectedItem = selectedItem);
await InvokeAsync(() =>
{
_selectedItems.Clear();
_selectedItems.AddRange(selectedItems);
});
}
}
@ -54,7 +63,9 @@
var parameters = new DialogParameters<IdentityUserSelectDialog>
{
{ x => x.Title, Localizer[inputDescriptor.DisplayName ?? inputDescriptor.Name] },
{ x => x.SelectedValue, _selectedItem?.Value }
{ x => x.AllowMultiple, AllowMultiple },
{ x => x.SelectedValue, _selectedItems.FirstOrDefault()?.Value },
{ x => x.SelectedValues, _selectedItems.Select(x => x.Value).ToArray() }
};
var options = new DialogOptions
{
@ -66,12 +77,31 @@
var dialog = await DialogService.ShowAsync<IdentityUserSelectDialog>(string.Empty, parameters, options);
var result = await dialog.Result;
if (result is { Canceled: false, Data: SelectListItem item })
if (result is not { Canceled: false })
{
return;
}
if (result.Data is SelectListItem selectedItem)
{
await InvokeAsync(async () =>
{
_selectedItems.Clear();
_selectedItems.Add(selectedItem);
await EditorContext.UpdateValueOrLiteralExpressionAsync(selectedItem.Value);
});
return;
}
if (result.Data is IReadOnlyList<SelectListItem> selectedItems)
{
await InvokeAsync(async () =>
{
_selectedItem = item;
await EditorContext.UpdateValueOrLiteralExpressionAsync(item.Value);
_selectedItems.Clear();
_selectedItems.AddRange(selectedItems);
await EditorContext.UpdateValueOrObjectExpressionAsync(_selectedItems.Select(x => x.Value).ToList());
});
}
}
@ -89,7 +119,7 @@
{
Icon = Icons.Material.Filled.Clear,
Tooltip = Localizer["Clear"],
Visible = _selectedItem != null,
Visible = _selectedItems.Count > 0,
Disabled = EditorContext.IsReadOnly,
OnClick = ClearAsync
}
@ -99,18 +129,33 @@
{
await InvokeAsync(async () =>
{
_selectedItem = null;
await EditorContext.UpdateValueOrLiteralExpressionAsync(string.Empty);
_selectedItems.Clear();
if (AllowMultiple)
{
await EditorContext.UpdateValueOrObjectExpressionAsync(new List<string>());
}
else
{
await EditorContext.UpdateValueOrLiteralExpressionAsync(string.Empty);
}
});
}
private async Task<SelectListItem?> ResolveSelectedItemAsync()
private async Task<IReadOnlyList<SelectListItem>> ResolveSelectedItemsAsync()
{
return AllowMultiple
? await ResolveSelectedUsersAsync()
: await ResolveSelectedUserAsync();
}
private async Task<IReadOnlyList<SelectListItem>> ResolveSelectedUserAsync()
{
var value = EditorContext.GetLiteralValueOrDefault();
if (string.IsNullOrWhiteSpace(value))
{
return null;
return [];
}
if (Guid.TryParse(value, out var userId))
@ -119,10 +164,63 @@
if (user != null)
{
return new SelectListItem(user.Name ?? user.UserName, user.Id.ToString());
return [new SelectListItem(user.Name ?? user.UserName, user.Id.ToString())];
}
}
return new SelectListItem(value, value);
return [new SelectListItem(value, value)];
}
private async Task<IReadOnlyList<SelectListItem>> ResolveSelectedUsersAsync()
{
var values = ParseSelectedValues(EditorContext.GetObjectValueOrDefault());
if (values.Count == 0)
{
return [];
}
var userIds = values.Where(x => Guid.TryParse(x, out _)).Select(Guid.Parse).ToList();
List<IdentityUser> users = [];
if (userIds.Count > 0)
{
users = await IdentityUserRepository.GetListByIdsAsync(userIds);
}
var names = users.ToDictionary(x => x.Id.ToString(), x => x.Name ?? x.UserName, StringComparer.OrdinalIgnoreCase);
return values
.Select(value => new SelectListItem(names.TryGetValue(value, out var name) ? name : value, value))
.ToList();
}
private static IReadOnlyList<string> ParseSelectedValues(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return [];
}
var trimmed = value.Trim();
try
{
if (trimmed.StartsWith('['))
{
return JsonSerializer.Deserialize<List<string>>(trimmed) ?? [];
}
if (trimmed.StartsWith('"'))
{
var single = JsonSerializer.Deserialize<string>(trimmed);
return string.IsNullOrWhiteSpace(single) ? [] : [single];
}
}
catch (JsonException)
{
}
return trimmed.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
}
}

73
aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Components/IdentityUserSelectDialog.razor

@ -27,6 +27,9 @@
Dense="true"
Hover="true"
Breakpoint="Breakpoint.None"
MultiSelection="@AllowMultiple"
SelectedItems="_selectedItems"
SelectedItemsChanged="OnSelectedItemsChanged"
OnRowClick="OnRowClickAsync">
<HeaderContent>
<MudTh>@Localizer["Name"]</MudTh>
@ -35,7 +38,14 @@
<RowTemplate>
<MudTd DataLabel="@Localizer["Name"]">@context.Text</MudTd>
<MudTd DataLabel="" Style="text-align:right">
@if (context.Value == SelectedValue)
@if (AllowMultiple)
{
@if (_selectedItems.Contains(context))
{
<MudIcon Icon="@Icons.Material.Filled.Check" Color="Color.Success" Size="Size.Small" />
}
}
else if (context.Value == SelectedValue)
{
<MudIcon Icon="@Icons.Material.Filled.Check" Color="Color.Success" Size="Size.Small" />
}
@ -54,6 +64,10 @@
</MudStack>
</DialogContent>
<DialogActions>
@if (AllowMultiple)
{
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ConfirmAsync">@Localizer["Select"]</MudButton>
}
<MudButton Variant="Variant.Text" OnClick="CancelAsync">@Localizer["Cancel"]</MudButton>
</DialogActions>
</MudDialog>
@ -63,14 +77,19 @@
private MudTable<SelectListItem>? _table;
private string? _searchText;
private HashSet<SelectListItem> _selectedItems = [];
private CancellationTokenSource? _debounceCancellationTokenSource;
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!;
[Parameter] public string Title { get; set; } = string.Empty;
[Parameter] public bool AllowMultiple { get; set; }
[Parameter] public string? SelectedValue { get; set; }
[Parameter] public IReadOnlyCollection<string> SelectedValues { get; set; } = [];
[Inject] protected IIdentityUserRepository IdentityUserRepository { get; set; } = null!;
public void Dispose()
@ -119,9 +138,23 @@
cancellationToken: cancellationToken);
var items = users
.Select(user => new SelectListItem(user.Name ?? user.UserName, user.Id.ToString()))
.Select(user => new SelectListItem(
user.Name.IsNullOrWhiteSpace() ? user.UserName : user.Name,
user.Id.ToString()))
.ToList();
if (AllowMultiple && SelectedValues.Count > 0)
{
var selectedItems = new HashSet<SelectListItem>(_selectedItems);
foreach (var item in items.Where(x => SelectedValues.Contains(x.Value)))
{
selectedItems.Add(item);
}
_selectedItems = selectedItems;
}
return new TableData<SelectListItem>
{
Items = items,
@ -129,9 +162,43 @@
};
}
private void OnSelectedItemsChanged(HashSet<SelectListItem> selectedItems)
{
_selectedItems = selectedItems;
}
private async Task OnRowClickAsync(TableRowClickEventArgs<SelectListItem> args)
{
await SelectAsync(args.Item);
if (!AllowMultiple)
{
await SelectAsync(args.Item);
return;
}
ToggleSelection(args.Item);
}
private void ToggleSelection(SelectListItem? item)
{
if (item == null)
{
return;
}
var selectedItems = new HashSet<SelectListItem>(_selectedItems);
if (!selectedItems.Remove(item))
{
selectedItems.Add(item);
}
_selectedItems = selectedItems;
}
private Task ConfirmAsync()
{
MudDialog.Close(DialogResult.Ok<IReadOnlyList<SelectListItem>>(_selectedItems.ToList()));
return Task.CompletedTask;
}
private Task SelectAsync(SelectListItem? item)

7
aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Handlers/IdentityPickerHandler.cs

@ -1,4 +1,4 @@
using Elsa.Studio;
using Elsa.Studio;
using Elsa.Studio.Contracts;
using Elsa.Studio.Models;
using LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor.Components;
@ -9,16 +9,19 @@ namespace LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor.Handlers;
public class IdentityPickerHandler : IUIHintHandler
{
public bool GetSupportsUIHint(string uiHint) => uiHint is AbpElsaUIHints.UserPicker;
public bool GetSupportsUIHint(string uiHint) => uiHint is AbpElsaUIHints.UserPicker or AbpElsaUIHints.UserPickerMultiple;
public string UISyntax => WellKnownSyntaxNames.Object;
public RenderFragment DisplayInputEditor(DisplayInputEditorContext context)
{
var allowMultiple = context.InputDescriptor.UIHint == AbpElsaUIHints.UserPickerMultiple;
return builder =>
{
builder.OpenComponent(0, typeof(IdentityUserPicker));
builder.AddAttribute(1, nameof(IdentityUserPicker.EditorContext), context);
builder.AddAttribute(2, nameof(IdentityUserPicker.AllowMultiple), true);
builder.CloseComponent();
};
}

Loading…
Cancel
Save