diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Notifications/LINGYUN/Abp/ElsaNext/Notifications/Activities/SendNotification.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Notifications/LINGYUN/Abp/ElsaNext/Notifications/Activities/SendNotification.cs index c4d4ea9cb..49413a231 100644 --- a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Notifications/LINGYUN/Abp/ElsaNext/Notifications/Activities/SendNotification.cs +++ b/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> To { get; set; } = null!; [Input( diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Components/IdentityUserPicker.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Components/IdentityUserPicker.razor index c876a1083..d1b2854cf 100644 --- a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Components/IdentityUserPicker.razor +++ b/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 @@ - + @code { - private SelectListItem? _selectedItem; + private readonly List _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 { { 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(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 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()); + } + else + { + await EditorContext.UpdateValueOrLiteralExpressionAsync(string.Empty); + } }); } - private async Task ResolveSelectedItemAsync() + private async Task> ResolveSelectedItemsAsync() + { + return AllowMultiple + ? await ResolveSelectedUsersAsync() + : await ResolveSelectedUserAsync(); + } + + private async Task> 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> 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 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 ParseSelectedValues(string? value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return []; + } + + var trimmed = value.Trim(); + + try + { + if (trimmed.StartsWith('[')) + { + return JsonSerializer.Deserialize>(trimmed) ?? []; + } + + if (trimmed.StartsWith('"')) + { + var single = JsonSerializer.Deserialize(trimmed); + return string.IsNullOrWhiteSpace(single) ? [] : [single]; + } + } + catch (JsonException) + { + } + + return trimmed.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); } } diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Components/IdentityUserSelectDialog.razor b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Components/IdentityUserSelectDialog.razor index 2eee7e131..7af350a8a 100644 --- a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Components/IdentityUserSelectDialog.razor +++ b/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"> @Localizer["Name"] @@ -35,7 +38,14 @@ @context.Text - @if (context.Value == SelectedValue) + @if (AllowMultiple) + { + @if (_selectedItems.Contains(context)) + { + + } + } + else if (context.Value == SelectedValue) { } @@ -54,6 +64,10 @@ + @if (AllowMultiple) + { + @Localizer["Select"] + } @Localizer["Cancel"] @@ -63,14 +77,19 @@ private MudTable? _table; private string? _searchText; + private HashSet _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 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(_selectedItems); + + foreach (var item in items.Where(x => SelectedValues.Contains(x.Value))) + { + selectedItems.Add(item); + } + + _selectedItems = selectedItems; + } + return new TableData { Items = items, @@ -129,9 +162,43 @@ }; } + private void OnSelectedItemsChanged(HashSet selectedItems) + { + _selectedItems = selectedItems; + } + private async Task OnRowClickAsync(TableRowClickEventArgs 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(_selectedItems); + + if (!selectedItems.Remove(item)) + { + selectedItems.Add(item); + } + + _selectedItems = selectedItems; + } + + private Task ConfirmAsync() + { + MudDialog.Close(DialogResult.Ok>(_selectedItems.ToList())); + return Task.CompletedTask; } private Task SelectAsync(SelectListItem? item) diff --git a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Handlers/IdentityPickerHandler.cs b/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Handlers/IdentityPickerHandler.cs index c02377f42..5b2455122 100644 --- a/aspnet-core/modules/elsa/LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor/Handlers/IdentityPickerHandler.cs +++ b/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(); }; }