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( [Input(
Description = "The recipients email addresses.", Description = "The recipients email addresses.",
UIHint = AbpElsaUIHints.UserPicker)] UIHint = AbpElsaUIHints.UserPickerMultiple)]
public Input<ICollection<Guid>> To { get; set; } = null!; public Input<ICollection<Guid>> To { get; set; } = null!;
[Input( [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.Api.Client.Shared.UIHints.DropDown
@using Elsa.Studio.Models @using Elsa.Studio.Models
@using LINGYUN.Abp.ElsaNext.Studio.Blazor.Components @using LINGYUN.Abp.ElsaNext.Studio.Blazor.Components
@using System.Text.Json
@using Volo.Abp.Identity @using Volo.Abp.Identity
@inject ILocalizer Localizer @inject ILocalizer Localizer
@ -13,33 +14,41 @@
<ExpressionInput EditorContext="@EditorContext"> <ExpressionInput EditorContext="@EditorContext">
<ChildContent> <ChildContent>
<MudTextFieldEx Value="@(_selectedItem?.Text ?? string.Empty)" <MudTextFieldEx Value="@SelectedText"
Label="@Localizer[displayName]" Label="@Localizer[displayName]"
HelperText="@Localizer[description]" HelperText="@Localizer[description]"
Variant="Variant.Outlined" Variant="Variant.Outlined"
Margin="Margin.Dense" Margin="Margin.Dense"
ReadOnly="true" ReadOnly="true"
Disabled="EditorContext.IsReadOnly" Disabled="EditorContext.IsReadOnly"
OnFieldClick="@(_ => OpenSelectDialogAsync())" OnFieldClick="@(_ => OpenSelectDialogAsync())"
AdornmentIcons="GetAdornmentIcons()" /> AdornmentIcons="GetAdornmentIcons()" />
</ChildContent> </ChildContent>
</ExpressionInput> </ExpressionInput>
@code { @code {
private SelectListItem? _selectedItem; private readonly List<SelectListItem> _selectedItems = [];
[Parameter] public bool AllowMultiple { get; set; }
[Parameter] public DisplayInputEditorContext EditorContext { get; set; } = null!; [Parameter] public DisplayInputEditorContext EditorContext { get; set; } = null!;
[Inject] protected IIdentityUserRepository IdentityUserRepository { get; set; } = null!; [Inject] protected IIdentityUserRepository IdentityUserRepository { get; set; } = null!;
[Inject] protected IDialogService DialogService { 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() 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> var parameters = new DialogParameters<IdentityUserSelectDialog>
{ {
{ x => x.Title, Localizer[inputDescriptor.DisplayName ?? inputDescriptor.Name] }, { 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 var options = new DialogOptions
{ {
@ -66,12 +77,31 @@
var dialog = await DialogService.ShowAsync<IdentityUserSelectDialog>(string.Empty, parameters, options); var dialog = await DialogService.ShowAsync<IdentityUserSelectDialog>(string.Empty, parameters, options);
var result = await dialog.Result; 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 () => await InvokeAsync(async () =>
{ {
_selectedItem = item; _selectedItems.Clear();
await EditorContext.UpdateValueOrLiteralExpressionAsync(item.Value); _selectedItems.AddRange(selectedItems);
await EditorContext.UpdateValueOrObjectExpressionAsync(_selectedItems.Select(x => x.Value).ToList());
}); });
} }
} }
@ -89,7 +119,7 @@
{ {
Icon = Icons.Material.Filled.Clear, Icon = Icons.Material.Filled.Clear,
Tooltip = Localizer["Clear"], Tooltip = Localizer["Clear"],
Visible = _selectedItem != null, Visible = _selectedItems.Count > 0,
Disabled = EditorContext.IsReadOnly, Disabled = EditorContext.IsReadOnly,
OnClick = ClearAsync OnClick = ClearAsync
} }
@ -99,18 +129,33 @@
{ {
await InvokeAsync(async () => await InvokeAsync(async () =>
{ {
_selectedItem = null; _selectedItems.Clear();
await EditorContext.UpdateValueOrLiteralExpressionAsync(string.Empty);
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(); var value = EditorContext.GetLiteralValueOrDefault();
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
return null; return [];
} }
if (Guid.TryParse(value, out var userId)) if (Guid.TryParse(value, out var userId))
@ -119,10 +164,63 @@
if (user != null) 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" Dense="true"
Hover="true" Hover="true"
Breakpoint="Breakpoint.None" Breakpoint="Breakpoint.None"
MultiSelection="@AllowMultiple"
SelectedItems="_selectedItems"
SelectedItemsChanged="OnSelectedItemsChanged"
OnRowClick="OnRowClickAsync"> OnRowClick="OnRowClickAsync">
<HeaderContent> <HeaderContent>
<MudTh>@Localizer["Name"]</MudTh> <MudTh>@Localizer["Name"]</MudTh>
@ -35,7 +38,14 @@
<RowTemplate> <RowTemplate>
<MudTd DataLabel="@Localizer["Name"]">@context.Text</MudTd> <MudTd DataLabel="@Localizer["Name"]">@context.Text</MudTd>
<MudTd DataLabel="" Style="text-align:right"> <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" /> <MudIcon Icon="@Icons.Material.Filled.Check" Color="Color.Success" Size="Size.Small" />
} }
@ -54,6 +64,10 @@
</MudStack> </MudStack>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
@if (AllowMultiple)
{
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ConfirmAsync">@Localizer["Select"]</MudButton>
}
<MudButton Variant="Variant.Text" OnClick="CancelAsync">@Localizer["Cancel"]</MudButton> <MudButton Variant="Variant.Text" OnClick="CancelAsync">@Localizer["Cancel"]</MudButton>
</DialogActions> </DialogActions>
</MudDialog> </MudDialog>
@ -63,14 +77,19 @@
private MudTable<SelectListItem>? _table; private MudTable<SelectListItem>? _table;
private string? _searchText; private string? _searchText;
private HashSet<SelectListItem> _selectedItems = [];
private CancellationTokenSource? _debounceCancellationTokenSource; private CancellationTokenSource? _debounceCancellationTokenSource;
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!; [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!;
[Parameter] public string Title { get; set; } = string.Empty; [Parameter] public string Title { get; set; } = string.Empty;
[Parameter] public bool AllowMultiple { get; set; }
[Parameter] public string? SelectedValue { get; set; } [Parameter] public string? SelectedValue { get; set; }
[Parameter] public IReadOnlyCollection<string> SelectedValues { get; set; } = [];
[Inject] protected IIdentityUserRepository IdentityUserRepository { get; set; } = null!; [Inject] protected IIdentityUserRepository IdentityUserRepository { get; set; } = null!;
public void Dispose() public void Dispose()
@ -119,9 +138,23 @@
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
var items = users 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(); .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> return new TableData<SelectListItem>
{ {
Items = items, Items = items,
@ -129,9 +162,43 @@
}; };
} }
private void OnSelectedItemsChanged(HashSet<SelectListItem> selectedItems)
{
_selectedItems = selectedItems;
}
private async Task OnRowClickAsync(TableRowClickEventArgs<SelectListItem> args) 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) 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.Contracts;
using Elsa.Studio.Models; using Elsa.Studio.Models;
using LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor.Components; 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 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 string UISyntax => WellKnownSyntaxNames.Object;
public RenderFragment DisplayInputEditor(DisplayInputEditorContext context) public RenderFragment DisplayInputEditor(DisplayInputEditorContext context)
{ {
var allowMultiple = context.InputDescriptor.UIHint == AbpElsaUIHints.UserPickerMultiple;
return builder => return builder =>
{ {
builder.OpenComponent(0, typeof(IdentityUserPicker)); builder.OpenComponent(0, typeof(IdentityUserPicker));
builder.AddAttribute(1, nameof(IdentityUserPicker.EditorContext), context); builder.AddAttribute(1, nameof(IdentityUserPicker.EditorContext), context);
builder.AddAttribute(2, nameof(IdentityUserPicker.AllowMultiple), true);
builder.CloseComponent(); builder.CloseComponent();
}; };
} }

Loading…
Cancel
Save