19 changed files with 795 additions and 0 deletions
@ -0,0 +1,17 @@ |
|||
using LINGYUN.Abp.ElsaNext.Studio.Blazor; |
|||
using LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor.Extensions; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpIdentityDomainModule), |
|||
typeof(AbpElsaNextStudioBlazorModule))] |
|||
public class AbpElsaNextStudioIdentityBlazorModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddIdentityUIHintHandlers(); |
|||
} |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
@using Elsa.Api.Client.Shared.UIHints.DropDown |
|||
@using Elsa.Studio.Models |
|||
@using LINGYUN.Abp.ElsaNext.Studio.Blazor.Components |
|||
@using Volo.Abp.Identity |
|||
@inject ILocalizer Localizer |
|||
|
|||
@inherits StudioComponentBase |
|||
@{ |
|||
var inputDescriptor = EditorContext.InputDescriptor; |
|||
var displayName = inputDescriptor.DisplayName; |
|||
var description = inputDescriptor.Description; |
|||
} |
|||
|
|||
<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()" /> |
|||
</ChildContent> |
|||
</ExpressionInput> |
|||
|
|||
@code { |
|||
private SelectListItem? _selectedItem; |
|||
|
|||
[Parameter] public DisplayInputEditorContext EditorContext { get; set; } = null!; |
|||
|
|||
[Inject] protected IIdentityUserRepository IdentityUserRepository { get; set; } = null!; |
|||
[Inject] protected IDialogService DialogService { get; set; } = null!; |
|||
|
|||
protected override async Task OnInitializedAsync() |
|||
{ |
|||
var selectedItem = await ResolveSelectedItemAsync(); |
|||
|
|||
if (selectedItem != null) |
|||
{ |
|||
await InvokeAsync(() => _selectedItem = selectedItem); |
|||
} |
|||
} |
|||
|
|||
private async Task OpenSelectDialogAsync() |
|||
{ |
|||
if (EditorContext.IsReadOnly) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var inputDescriptor = EditorContext.InputDescriptor; |
|||
var parameters = new DialogParameters<IdentityUserSelectDialog> |
|||
{ |
|||
{ x => x.Title, Localizer[inputDescriptor.DisplayName ?? inputDescriptor.Name] }, |
|||
{ x => x.SelectedValue, _selectedItem?.Value } |
|||
}; |
|||
var options = new DialogOptions |
|||
{ |
|||
CloseOnEscapeKey = true, |
|||
FullWidth = true, |
|||
MaxWidth = MaxWidth.Medium |
|||
}; |
|||
|
|||
var dialog = await DialogService.ShowAsync<IdentityUserSelectDialog>(string.Empty, parameters, options); |
|||
var result = await dialog.Result; |
|||
|
|||
if (result is { Canceled: false, Data: SelectListItem item }) |
|||
{ |
|||
await InvokeAsync(async () => |
|||
{ |
|||
_selectedItem = item; |
|||
await EditorContext.UpdateValueOrLiteralExpressionAsync(item.Value); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
private MudTextFieldExIcon[] GetAdornmentIcons() => |
|||
[ |
|||
new() |
|||
{ |
|||
Icon = Icons.Material.Filled.Search, |
|||
Tooltip = Localizer["Search"], |
|||
Disabled = EditorContext.IsReadOnly, |
|||
OnClick = OpenSelectDialogAsync |
|||
}, |
|||
new() |
|||
{ |
|||
Icon = Icons.Material.Filled.Clear, |
|||
Tooltip = Localizer["Clear"], |
|||
Visible = _selectedItem != null, |
|||
Disabled = EditorContext.IsReadOnly, |
|||
OnClick = ClearAsync |
|||
} |
|||
]; |
|||
|
|||
private async Task ClearAsync() |
|||
{ |
|||
await InvokeAsync(async () => |
|||
{ |
|||
_selectedItem = null; |
|||
await EditorContext.UpdateValueOrLiteralExpressionAsync(string.Empty); |
|||
}); |
|||
} |
|||
|
|||
private async Task<SelectListItem?> ResolveSelectedItemAsync() |
|||
{ |
|||
var value = EditorContext.GetLiteralValueOrDefault(); |
|||
|
|||
if (string.IsNullOrWhiteSpace(value)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
if (Guid.TryParse(value, out var userId)) |
|||
{ |
|||
var user = await IdentityUserRepository.FindAsync(userId); |
|||
|
|||
if (user != null) |
|||
{ |
|||
return new SelectListItem(user.Name ?? user.UserName, user.Id.ToString()); |
|||
} |
|||
} |
|||
|
|||
return new SelectListItem(value, value); |
|||
} |
|||
} |
|||
@ -0,0 +1,152 @@ |
|||
@using Elsa.Api.Client.Shared.UIHints.DropDown |
|||
@using System.Threading |
|||
@using Volo.Abp.Identity |
|||
@inject ILocalizer Localizer |
|||
|
|||
@implements IDisposable |
|||
|
|||
<MudDialog> |
|||
<TitleContent> |
|||
<MudText Typo="Typo.h6">@Title</MudText> |
|||
</TitleContent> |
|||
<DialogContent> |
|||
<MudStack Spacing="3"> |
|||
<MudTextField T="string" |
|||
Value="_searchText" |
|||
ValueChanged="OnSearchTextChangedAsync" |
|||
Immediate="true" |
|||
Placeholder="@Localizer["Search"]" |
|||
Adornment="Adornment.Start" |
|||
AdornmentIcon="@Icons.Material.Filled.Search" |
|||
Variant="Variant.Outlined" |
|||
Margin="Margin.Dense" /> |
|||
|
|||
<MudTable T="SelectListItem" |
|||
@ref="_table" |
|||
ServerData="LoadUsersAsync" |
|||
Dense="true" |
|||
Hover="true" |
|||
Breakpoint="Breakpoint.None" |
|||
OnRowClick="OnRowClickAsync"> |
|||
<HeaderContent> |
|||
<MudTh>@Localizer["Name"]</MudTh> |
|||
<MudTh Style="text-align:right" /> |
|||
</HeaderContent> |
|||
<RowTemplate> |
|||
<MudTd DataLabel="@Localizer["Name"]">@context.Text</MudTd> |
|||
<MudTd DataLabel="" Style="text-align:right"> |
|||
@if (context.Value == SelectedValue) |
|||
{ |
|||
<MudIcon Icon="@Icons.Material.Filled.Check" Color="Color.Success" Size="Size.Small" /> |
|||
} |
|||
else |
|||
{ |
|||
<MudButton Size="Size.Small" Variant="Variant.Text" Color="Color.Primary" OnClick="@(() => SelectAsync(context))"> |
|||
@Localizer["Select"] |
|||
</MudButton> |
|||
} |
|||
</MudTd> |
|||
</RowTemplate> |
|||
<NoRecordsContent> |
|||
<MudText Typo="Typo.body2">@Localizer["No results found"]</MudText> |
|||
</NoRecordsContent> |
|||
</MudTable> |
|||
</MudStack> |
|||
</DialogContent> |
|||
<DialogActions> |
|||
<MudButton Variant="Variant.Text" OnClick="CancelAsync">@Localizer["Cancel"]</MudButton> |
|||
</DialogActions> |
|||
</MudDialog> |
|||
|
|||
@code { |
|||
private const int DebounceIntervalMilliseconds = 350; |
|||
|
|||
private MudTable<SelectListItem>? _table; |
|||
private string? _searchText; |
|||
private CancellationTokenSource? _debounceCancellationTokenSource; |
|||
|
|||
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!; |
|||
|
|||
[Parameter] public string Title { get; set; } = string.Empty; |
|||
|
|||
[Parameter] public string? SelectedValue { get; set; } |
|||
|
|||
[Inject] protected IIdentityUserRepository IdentityUserRepository { get; set; } = null!; |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_debounceCancellationTokenSource?.Cancel(); |
|||
_debounceCancellationTokenSource?.Dispose(); |
|||
} |
|||
|
|||
private async Task OnSearchTextChangedAsync(string? searchValue) |
|||
{ |
|||
_searchText = searchValue; |
|||
|
|||
_debounceCancellationTokenSource?.Cancel(); |
|||
_debounceCancellationTokenSource?.Dispose(); |
|||
_debounceCancellationTokenSource = new CancellationTokenSource(); |
|||
|
|||
try |
|||
{ |
|||
await Task.Delay(DebounceIntervalMilliseconds, _debounceCancellationTokenSource.Token); |
|||
|
|||
var table = _table; |
|||
|
|||
if (table != null) |
|||
{ |
|||
await InvokeAsync(() => table.ReloadServerData()); |
|||
} |
|||
} |
|||
catch (OperationCanceledException) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
private async Task<TableData<SelectListItem>> LoadUsersAsync(TableState state, CancellationToken cancellationToken) |
|||
{ |
|||
var filter = string.IsNullOrWhiteSpace(_searchText) ? null : _searchText.Trim(); |
|||
|
|||
var users = await IdentityUserRepository.GetListAsync( |
|||
sorting: null, |
|||
maxResultCount: state.PageSize, |
|||
skipCount: state.Page * state.PageSize, |
|||
filter: filter, |
|||
cancellationToken: cancellationToken); |
|||
|
|||
var totalCount = await IdentityUserRepository.GetCountAsync( |
|||
filter: filter, |
|||
cancellationToken: cancellationToken); |
|||
|
|||
var items = users |
|||
.Select(user => new SelectListItem(user.Name ?? user.UserName, user.Id.ToString())) |
|||
.ToList(); |
|||
|
|||
return new TableData<SelectListItem> |
|||
{ |
|||
Items = items, |
|||
TotalItems = (int)totalCount |
|||
}; |
|||
} |
|||
|
|||
private async Task OnRowClickAsync(TableRowClickEventArgs<SelectListItem> args) |
|||
{ |
|||
await SelectAsync(args.Item); |
|||
} |
|||
|
|||
private Task SelectAsync(SelectListItem? item) |
|||
{ |
|||
if (item == null) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
MudDialog.Close(DialogResult.Ok(item)); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private void CancelAsync() |
|||
{ |
|||
MudDialog.Cancel(); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using Elsa.Studio.Extensions; |
|||
using LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor.Handlers; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor.Extensions; |
|||
|
|||
public static class ServiceCollectionExtensions |
|||
{ |
|||
public static IServiceCollection AddIdentityUIHintHandlers(this IServiceCollection services) |
|||
{ |
|||
return services.AddUIHintHandler<IdentityPickerHandler>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,25 @@ |
|||
using Elsa.Studio; |
|||
using Elsa.Studio.Contracts; |
|||
using Elsa.Studio.Models; |
|||
using LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor.Components; |
|||
using LINGYUN.Abp.ElsaNext.UIHints; |
|||
using Microsoft.AspNetCore.Components; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor.Handlers; |
|||
|
|||
public class IdentityPickerHandler : IUIHintHandler |
|||
{ |
|||
public bool GetSupportsUIHint(string uiHint) => uiHint is AbpElsaUIHints.UserPicker; |
|||
|
|||
public string UISyntax => WellKnownSyntaxNames.Object; |
|||
|
|||
public RenderFragment DisplayInputEditor(DisplayInputEditorContext context) |
|||
{ |
|||
return builder => |
|||
{ |
|||
builder.OpenComponent(0, typeof(IdentityUserPicker)); |
|||
builder.AddAttribute(1, nameof(IdentityUserPicker.EditorContext), context); |
|||
builder.CloseComponent(); |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Razor"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<AssemblyName>LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.ElsaNext.Studio.Identity.Blazor</PackageId> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Identity.Domain" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Studio.Blazor\LINGYUN.Abp.ElsaNext.Studio.Blazor.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,5 @@ |
|||
@using Microsoft.AspNetCore.Components.Web |
|||
@using Elsa.Studio.Components |
|||
@using Elsa.Studio.Localization |
|||
@using MudBlazor |
|||
@using MudExtensions |
|||
@ -0,0 +1,17 @@ |
|||
using LINGYUN.Abp.ElsaNext.Studio.Blazor; |
|||
using LINGYUN.Abp.ElsaNext.Studio.Saas.Blazor.Extensions; |
|||
using LINGYUN.Abp.Saas; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.Studio.Saas.Blazor; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpSaasDomainModule), |
|||
typeof(AbpElsaNextStudioBlazorModule))] |
|||
public class AbpElsaNextStudioSaasBlazorModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddSaasUIHintHandlers(); |
|||
} |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
@using Elsa.Api.Client.Shared.UIHints.DropDown |
|||
@using Elsa.Studio.Models |
|||
@using LINGYUN.Abp.ElsaNext.Studio.Blazor.Components |
|||
@using LINGYUN.Abp.Saas.Tenants |
|||
@inject ILocalizer Localizer |
|||
|
|||
@inherits StudioComponentBase |
|||
@{ |
|||
var inputDescriptor = EditorContext.InputDescriptor; |
|||
var displayName = inputDescriptor.DisplayName; |
|||
var description = inputDescriptor.Description; |
|||
} |
|||
|
|||
<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()" /> |
|||
</ChildContent> |
|||
</ExpressionInput> |
|||
|
|||
@code { |
|||
private SelectListItem? _selectedItem; |
|||
|
|||
[Parameter] public DisplayInputEditorContext EditorContext { get; set; } = null!; |
|||
|
|||
[Inject] protected ITenantRepository TenantRepository { get; set; } = null!; |
|||
[Inject] protected IDialogService DialogService { get; set; } = null!; |
|||
|
|||
protected override async Task OnInitializedAsync() |
|||
{ |
|||
var selectedItem = await ResolveSelectedItemAsync(); |
|||
|
|||
if (selectedItem != null) |
|||
{ |
|||
await InvokeAsync(() => _selectedItem = selectedItem); |
|||
} |
|||
} |
|||
|
|||
private async Task OpenSelectDialogAsync() |
|||
{ |
|||
if (EditorContext.IsReadOnly) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var inputDescriptor = EditorContext.InputDescriptor; |
|||
var parameters = new DialogParameters<TenantSelectDialog> |
|||
{ |
|||
{ x => x.Title, Localizer[inputDescriptor.DisplayName ?? inputDescriptor.Name] }, |
|||
{ x => x.SelectedValue, _selectedItem?.Value } |
|||
}; |
|||
var options = new DialogOptions |
|||
{ |
|||
CloseOnEscapeKey = true, |
|||
FullWidth = true, |
|||
MaxWidth = MaxWidth.Medium |
|||
}; |
|||
|
|||
var dialog = await DialogService.ShowAsync<TenantSelectDialog>(string.Empty, parameters, options); |
|||
var result = await dialog.Result; |
|||
|
|||
if (result is { Canceled: false, Data: SelectListItem item }) |
|||
{ |
|||
await InvokeAsync(async () => |
|||
{ |
|||
_selectedItem = item; |
|||
await EditorContext.UpdateValueOrLiteralExpressionAsync(item.Value); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
private MudTextFieldExIcon[] GetAdornmentIcons() => |
|||
[ |
|||
new() |
|||
{ |
|||
Icon = Icons.Material.Filled.Search, |
|||
Tooltip = Localizer["Search"], |
|||
Disabled = EditorContext.IsReadOnly, |
|||
OnClick = OpenSelectDialogAsync |
|||
}, |
|||
new() |
|||
{ |
|||
Icon = Icons.Material.Filled.Clear, |
|||
Tooltip = Localizer["Clear"], |
|||
Visible = _selectedItem != null, |
|||
Disabled = EditorContext.IsReadOnly, |
|||
OnClick = ClearAsync |
|||
} |
|||
]; |
|||
|
|||
private async Task ClearAsync() |
|||
{ |
|||
await InvokeAsync(async () => |
|||
{ |
|||
_selectedItem = null; |
|||
await EditorContext.UpdateValueOrLiteralExpressionAsync(string.Empty); |
|||
}); |
|||
} |
|||
|
|||
private async Task<SelectListItem?> ResolveSelectedItemAsync() |
|||
{ |
|||
var value = EditorContext.GetLiteralValueOrDefault(); |
|||
|
|||
if (string.IsNullOrWhiteSpace(value)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
if (Guid.TryParse(value, out var tenantId)) |
|||
{ |
|||
var tenant = await TenantRepository.FindAsync(tenantId); |
|||
|
|||
if (tenant != null) |
|||
{ |
|||
return new SelectListItem(tenant.Name, tenant.Id.ToString()); |
|||
} |
|||
} |
|||
|
|||
return new SelectListItem(value, value); |
|||
} |
|||
} |
|||
@ -0,0 +1,151 @@ |
|||
@using Elsa.Api.Client.Shared.UIHints.DropDown |
|||
@using System.Threading |
|||
@using LINGYUN.Abp.Saas.Tenants |
|||
@inject ILocalizer Localizer |
|||
|
|||
@implements IDisposable |
|||
|
|||
<MudDialog> |
|||
<TitleContent> |
|||
<MudText Typo="Typo.h6">@Title</MudText> |
|||
</TitleContent> |
|||
<DialogContent> |
|||
<MudStack Spacing="3"> |
|||
<MudTextField T="string" |
|||
Value="_searchText" |
|||
ValueChanged="OnSearchTextChangedAsync" |
|||
Immediate="true" |
|||
Placeholder="@Localizer["Search"]" |
|||
Adornment="Adornment.Start" |
|||
AdornmentIcon="@Icons.Material.Filled.Search" |
|||
Variant="Variant.Outlined" |
|||
Margin="Margin.Dense" /> |
|||
|
|||
<MudTable T="SelectListItem" |
|||
@ref="_table" |
|||
ServerData="LoadTenantsAsync" |
|||
Dense="true" |
|||
Hover="true" |
|||
Breakpoint="Breakpoint.None" |
|||
OnRowClick="OnRowClickAsync"> |
|||
<HeaderContent> |
|||
<MudTh>@Localizer["Name"]</MudTh> |
|||
<MudTh Style="text-align:right" /> |
|||
</HeaderContent> |
|||
<RowTemplate> |
|||
<MudTd DataLabel="@Localizer["Name"]">@context.Text</MudTd> |
|||
<MudTd DataLabel="" Style="text-align:right"> |
|||
@if (context.Value == SelectedValue) |
|||
{ |
|||
<MudIcon Icon="@Icons.Material.Filled.Check" Color="Color.Success" Size="Size.Small" /> |
|||
} |
|||
else |
|||
{ |
|||
<MudButton Size="Size.Small" Variant="Variant.Text" Color="Color.Primary" OnClick="@(() => SelectAsync(context))"> |
|||
@Localizer["Select"] |
|||
</MudButton> |
|||
} |
|||
</MudTd> |
|||
</RowTemplate> |
|||
<NoRecordsContent> |
|||
<MudText Typo="Typo.body2">@Localizer["No results found"]</MudText> |
|||
</NoRecordsContent> |
|||
</MudTable> |
|||
</MudStack> |
|||
</DialogContent> |
|||
<DialogActions> |
|||
<MudButton Variant="Variant.Text" OnClick="CancelAsync">@Localizer["Cancel"]</MudButton> |
|||
</DialogActions> |
|||
</MudDialog> |
|||
|
|||
@code { |
|||
private const int DebounceIntervalMilliseconds = 350; |
|||
|
|||
private MudTable<SelectListItem>? _table; |
|||
private string? _searchText; |
|||
private CancellationTokenSource? _debounceCancellationTokenSource; |
|||
|
|||
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!; |
|||
|
|||
[Parameter] public string Title { get; set; } = string.Empty; |
|||
|
|||
[Parameter] public string? SelectedValue { get; set; } |
|||
|
|||
[Inject] protected ITenantRepository TenantRepository { get; set; } = null!; |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_debounceCancellationTokenSource?.Cancel(); |
|||
_debounceCancellationTokenSource?.Dispose(); |
|||
} |
|||
|
|||
private async Task OnSearchTextChangedAsync(string? searchValue) |
|||
{ |
|||
_searchText = searchValue; |
|||
|
|||
_debounceCancellationTokenSource?.Cancel(); |
|||
_debounceCancellationTokenSource?.Dispose(); |
|||
_debounceCancellationTokenSource = new CancellationTokenSource(); |
|||
|
|||
try |
|||
{ |
|||
await Task.Delay(DebounceIntervalMilliseconds, _debounceCancellationTokenSource.Token); |
|||
|
|||
var table = _table; |
|||
|
|||
if (table != null) |
|||
{ |
|||
await InvokeAsync(() => table.ReloadServerData()); |
|||
} |
|||
} |
|||
catch (OperationCanceledException) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
private async Task<TableData<SelectListItem>> LoadTenantsAsync(TableState state, CancellationToken cancellationToken) |
|||
{ |
|||
var filter = string.IsNullOrWhiteSpace(_searchText) ? null : _searchText.Trim(); |
|||
|
|||
var tenants = await TenantRepository.GetListAsync( |
|||
filter: filter, |
|||
maxResultCount: state.PageSize, |
|||
skipCount: state.Page * state.PageSize, |
|||
cancellationToken: cancellationToken); |
|||
|
|||
var totalCount = await TenantRepository.GetCountAsync( |
|||
filter: filter, |
|||
cancellationToken: cancellationToken); |
|||
|
|||
var items = tenants |
|||
.Select(tenant => new SelectListItem(tenant.Name, tenant.Id.ToString())) |
|||
.ToList(); |
|||
|
|||
return new TableData<SelectListItem> |
|||
{ |
|||
Items = items, |
|||
TotalItems = (int)totalCount |
|||
}; |
|||
} |
|||
|
|||
private async Task OnRowClickAsync(TableRowClickEventArgs<SelectListItem> args) |
|||
{ |
|||
await SelectAsync(args.Item); |
|||
} |
|||
|
|||
private Task SelectAsync(SelectListItem? item) |
|||
{ |
|||
if (item == null) |
|||
{ |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
MudDialog.Close(DialogResult.Ok(item)); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private void CancelAsync() |
|||
{ |
|||
MudDialog.Cancel(); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using Elsa.Studio.Extensions; |
|||
using LINGYUN.Abp.ElsaNext.Studio.Saas.Blazor.Handlers; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.Studio.Saas.Blazor.Extensions; |
|||
|
|||
public static class ServiceCollectionExtensions |
|||
{ |
|||
public static IServiceCollection AddSaasUIHintHandlers(this IServiceCollection services) |
|||
{ |
|||
return services.AddUIHintHandler<TenantPickerHandler>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,25 @@ |
|||
using Elsa.Studio; |
|||
using Elsa.Studio.Contracts; |
|||
using Elsa.Studio.Models; |
|||
using LINGYUN.Abp.ElsaNext.Studio.Saas.Blazor.Components; |
|||
using LINGYUN.Abp.ElsaNext.UIHints; |
|||
using Microsoft.AspNetCore.Components; |
|||
|
|||
namespace LINGYUN.Abp.ElsaNext.Studio.Saas.Blazor.Handlers; |
|||
|
|||
public class TenantPickerHandler : IUIHintHandler |
|||
{ |
|||
public bool GetSupportsUIHint(string uiHint) => uiHint is AbpElsaUIHints.TenantPicker; |
|||
|
|||
public string UISyntax => WellKnownSyntaxNames.Object; |
|||
|
|||
public RenderFragment DisplayInputEditor(DisplayInputEditorContext context) |
|||
{ |
|||
return builder => |
|||
{ |
|||
builder.OpenComponent(0, typeof(TenantPicker)); |
|||
builder.AddAttribute(1, nameof(TenantPicker.EditorContext), context); |
|||
builder.CloseComponent(); |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Razor"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net10.0</TargetFramework> |
|||
<AssemblyName>LINGYUN.Abp.ElsaNext.Studio.Saas.Blazor</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.ElsaNext.Studio.Saas.Blazor</PackageId> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\saas\LINGYUN.Abp.Saas.Domain\LINGYUN.Abp.Saas.Domain.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.ElsaNext.Studio.Blazor\LINGYUN.Abp.ElsaNext.Studio.Blazor.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,5 @@ |
|||
@using Microsoft.AspNetCore.Components.Web |
|||
@using Elsa.Studio.Components |
|||
@using Elsa.Studio.Localization |
|||
@using MudBlazor |
|||
@using MudExtensions |
|||
@ -0,0 +1,7 @@ |
|||
namespace LINGYUN.Abp.ElsaNext.UIHints; |
|||
|
|||
public static class AbpElsaUIHints |
|||
{ |
|||
public const string TenantPicker = "tenant-picker"; |
|||
public const string UserPicker = "user-picker"; |
|||
} |
|||
Loading…
Reference in new issue