diff --git a/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs new file mode 100644 index 0000000000..32ae99f3f7 --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs @@ -0,0 +1,324 @@ +using Blazorise; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; +using Volo.Abp.Reflection; + +namespace Volo.Abp.BlazoriseUI +{ + public static class BlazoriseUiObjectExtensionPropertyInfoExtensions + { + private static readonly HashSet NumberTypes = new HashSet { + typeof(int), + typeof(long), + typeof(byte), + typeof(sbyte), + typeof(short), + typeof(ushort), + typeof(uint), + typeof(long), + typeof(ulong), + typeof(float), + typeof(double), + typeof(decimal), + typeof(int?), + typeof(long?), + typeof(byte?), + typeof(sbyte?), + typeof(short?), + typeof(ushort?), + typeof(uint?), + typeof(long?), + typeof(ulong?), + typeof(float?), + typeof(double?), + typeof(decimal?) + }; + + private static readonly HashSet TextEditSupportedAttributeTypes = new HashSet { + typeof(EmailAddressAttribute), + typeof(UrlAttribute), + typeof(PhoneAttribute) + }; + + public static string GetDateEditInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property) + { + if (property.IsDate()) + { + return "{0:yyyy-MM-dd}"; + } + + if (property.IsDateTime()) + { + return "{0:yyyy-MM-ddTHH:mm}"; + } + + return null; + } + + public static string GetTextInputValueOrNull(this IBasicObjectExtensionPropertyInfo property, object value) + { + if (value == null) + { + return null; + } + + if (TypeHelper.IsFloatingType(property.Type)) + { + return value.ToString()?.Replace(',', '.'); + } + + return value.ToString(); + } + + public static T GetInputValueOrDefault(this IBasicObjectExtensionPropertyInfo property, object value) + { + if (value == null) + { + return default; + } + + return (T)value; + } + + public static bool GetBooleanInputValueOrDefault(this IBasicObjectExtensionPropertyInfo property, object value) + { + if (value == null) + { + return false; + } + + return (bool)value; + } + + public static DateTime? GetDateInputValueOrDefault(this IBasicObjectExtensionPropertyInfo property, object value) + { + if (value == null) + { + return default; + } + + return (DateTime?)value; + } + + public static TimeSpan? GetTimeInputValueOrDefault(this IBasicObjectExtensionPropertyInfo property, object value) + { + if (value == null) + { + return default; + } + + return (TimeSpan?)value; + } + + public static TextInputMode GetTextInputMode(this ObjectExtensionPropertyInfo propertyInfo) + { + foreach (var attribute in propertyInfo.Attributes) + { + var textRoleByAttribute = GetTextInputModeFromAttributeOrNull(attribute); + if (textRoleByAttribute != null) + { + return textRoleByAttribute.Value; + } + } + + return GetTextInputModeFromTypeOrNull(propertyInfo.Type) + ?? TextInputMode.None; //default + } + + private static TextInputMode? GetTextInputModeFromTypeOrNull(Type type) + { + if (TypeHelper.IsFloatingType(type)) + { + return TextInputMode.Decimal; + } + + if (NumberTypes.Contains(type)) + { + return TextInputMode.Numeric; + } + + return null; + } + + private static TextInputMode? GetTextInputModeFromAttributeOrNull(Attribute attribute) + { + if (attribute is EmailAddressAttribute) + { + return TextInputMode.Email; + } + + if (attribute is UrlAttribute) + { + return TextInputMode.Url; + } + + //if (attribute is HiddenInputAttribute) + //{ + // return "hidden"; + //} + + if (attribute is PhoneAttribute) + { + return TextInputMode.Tel; + } + + if (attribute is DataTypeAttribute dataTypeAttribute) + { + switch (dataTypeAttribute.DataType) + { + case DataType.EmailAddress: + return TextInputMode.Email; + case DataType.Url: + return TextInputMode.Url; + case DataType.PhoneNumber: + return TextInputMode.Tel; + } + } + + return null; + } + + public static TextRole GetTextRole(this ObjectExtensionPropertyInfo propertyInfo) + { + foreach (var attribute in propertyInfo.Attributes) + { + var textRoleByAttribute = GetTextRoleFromAttributeOrNull(attribute); + if (textRoleByAttribute != null) + { + return textRoleByAttribute.Value; + } + } + + return TextRole.Text; //default + } + + private static TextRole? GetTextRoleFromAttributeOrNull(Attribute attribute) + { + if (attribute is EmailAddressAttribute) + { + return TextRole.Email; + } + + if (attribute is UrlAttribute) + { + return TextRole.Url; + } + + //if (attribute is HiddenInputAttribute) + //{ + // return "hidden"; + //} + + //if (attribute is PhoneAttribute) + //{ + // return TextRole.Tel; + //} + + if (attribute is DataTypeAttribute dataTypeAttribute) + { + switch (dataTypeAttribute.DataType) + { + case DataType.Password: + return TextRole.Password; + case DataType.EmailAddress: + return TextRole.Email; + case DataType.Url: + return TextRole.Url; + } + } + + return null; + } + + public static Type GetInputType(this ObjectExtensionPropertyInfo propertyInfo) + { + foreach (var attribute in propertyInfo.Attributes) + { + var inputTypeByAttribute = GetInputTypeFromAttributeOrNull(attribute); + if (inputTypeByAttribute != null) + { + return inputTypeByAttribute; + } + } + return GetInputTypeFromTypeOrNull(propertyInfo.Type) + ?? typeof(TextEdit); //default + } + + private static Type GetInputTypeFromAttributeOrNull(Attribute attribute) + { + var hasTextEditSupport = TextEditSupportedAttributeTypes.Any(t => t == attribute.GetType()); + + if (hasTextEditSupport) + { + return typeof(TextEdit); + } + + //if (attribute is EmailAddressAttribute) + //{ + // return TextInputMode.Email; + //} + + //if (attribute is UrlAttribute) + //{ + // return TextInputMode.Url; + //} + + //if (attribute is HiddenInputAttribute) + //{ + // return "hidden"; + //} + + //if (attribute is PhoneAttribute) + //{ + // return TextInputMode.Tel; + //} + + if (attribute is DataTypeAttribute dataTypeAttribute) + { + switch (dataTypeAttribute.DataType) + { + case DataType.Password: + return typeof(TextEdit); + case DataType.Date: + return typeof(DateEdit<>); + case DataType.Time: + return typeof(TimeEdit<>); + case DataType.EmailAddress: + return typeof(TextEdit); + case DataType.Url: + return typeof(TextEdit); + case DataType.PhoneNumber: + return typeof(TextEdit); + case DataType.DateTime: + return typeof(DateEdit<>); + } + } + + return null; + } + + private static Type GetInputTypeFromTypeOrNull(Type type) + { + if (type == typeof(bool)) + { + return typeof(Check<>); + } + + if (type == typeof(DateTime)) + { + return typeof(DateEdit<>); + } + + if (NumberTypes.Contains(type)) + { + return typeof(TextEdit); + } + + return null; + } + } +} diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor new file mode 100644 index 0000000000..1498f2a885 --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor @@ -0,0 +1,62 @@ +@typeparam TEntityType +@typeparam TResourceType +@using Volo.Abp.ObjectExtending +@using Volo.Abp.Localization +@using Volo.Abp.Data + +@foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties()) +{ + if (!propertyInfo.Name.EndsWith("_Text")) + { + if (propertyInfo.Type.IsEnum) + { + + } + else if (!propertyInfo.Lookup.Url.IsNullOrEmpty()) + { + + + } + else + { + var inputType = propertyInfo.GetInputType(); + if (inputType == typeof(TextEdit)) + { + + + + } + else if (inputType == typeof(Check<>)) + { + + @propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory) + + } + else if (inputType == typeof(TimeEdit<>)) + { + + @propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)--> + + + + + + + } + else if (inputType == typeof(DateEdit<>)) + { + + @propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory) + + + + + + + } + } + } +} diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs new file mode 100644 index 0000000000..ae461c7f8f --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using System; +using Volo.Abp.AspNetCore.Components.WebAssembly; +using Volo.Abp.Data; + +namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending +{ + public partial class ExtensionProperties : ComponentBase + where TEntityType : IHasExtraProperties + { + [Inject] + public IStringLocalizerFactory StringLocalizerFactory { get; set; } + + [Parameter] + public AbpBlazorMessageLocalizerHelper LH { get; set; } + + [Parameter] + public TEntityType Entity { get; set; } + + protected void TextChanged(string value, string propertyName) + { + Entity.SetProperty(propertyName, value); + } + + protected void CheckedChanged(bool value, string propertyName) + { + Entity.SetProperty(propertyName, value); + } + + protected void TimeChanged(TimeSpan? value, string propertyName) + { + Entity.SetProperty(propertyName, value); + } + + protected void DateTimeChanged(DateTime? value, string propertyName) + { + Entity.SetProperty(propertyName, value); + } + } +} diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor new file mode 100644 index 0000000000..637a9a94ee --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor @@ -0,0 +1,16 @@ +@typeparam TEntity +@typeparam TResourceType +@using Abp.Localization +@using Blazorise.Components + + + @PropertyInfo.GetLocalizedDisplayName(StringLocalizerFactory) + + + diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor.cs new file mode 100644 index 0000000000..a33ba5c08f --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor.cs @@ -0,0 +1,92 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending +{ + public partial class LookupExtensionProperty : ComponentBase + where TEntity : IHasExtraProperties + { + protected List> lookupItems; + + [Inject] + public IStringLocalizerFactory StringLocalizerFactory { get; set; } + + [Inject] + public HttpClient Client { get; set; } + + [Parameter] + public TEntity Entity { get; set; } + + [Parameter] + public ObjectExtensionPropertyInfo PropertyInfo { get; set; } + + public object SelectedValue + { + get + { + return Entity.GetProperty(PropertyInfo.Name); + } + set + { + Entity.SetProperty(PropertyInfo.Name, value); + UpdateLookupTextProperty(value); + } + } + + + public LookupExtensionProperty() + { + lookupItems = new List>(); + } + + protected override async Task OnInitializedAsync() + { + lookupItems = await GetLookupItemsAsync(string.Empty); + } + + protected virtual void UpdateLookupTextProperty(object value) + { + var lookupPropertyName = $"{PropertyInfo.Name}_Text"; + var selectedItemText = lookupItems.SingleOrDefault(t => t.Value.Equals(value)).Text; + Entity.SetProperty(lookupPropertyName, selectedItemText); + } + + protected virtual async Task>> GetLookupItemsAsync(string filter) + { + var selectItems = new List>(); + var url = PropertyInfo.Lookup.Url; + if (!filter.IsNullOrEmpty()) + { + url += $"{PropertyInfo.Lookup.FilterParamName}={filter.Trim()}"; + } + + var responseStream = await Client.GetStreamAsync(url); + + var document = await JsonDocument.ParseAsync(responseStream); + var itemsArrayProp = document.RootElement.GetProperty(PropertyInfo.Lookup.ResultListPropertyName); + foreach (var item in itemsArrayProp.EnumerateArray()) + { + selectItems.Add(new SelectItem + { + Text = item.GetProperty(PropertyInfo.Lookup.DisplayPropertyName).GetString(), + Value = JsonSerializer.Deserialize(item.GetProperty(PropertyInfo.Lookup.ValuePropertyName).GetRawText(), PropertyInfo.Type) + }); + } + + return selectItems; + } + + protected virtual async Task SearchFilterChangedAsync(string filter) + { + lookupItems = await GetLookupItemsAsync(filter); + } + } +} diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor new file mode 100644 index 0000000000..875599725b --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor @@ -0,0 +1,13 @@ +@typeparam TEntity +@typeparam TResourceType +@using Abp.Localization + + + @PropertyInfo.GetLocalizedDisplayName(StringLocalizerFactory) + + diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor.cs new file mode 100644 index 0000000000..6afc4f5254 --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor.cs @@ -0,0 +1,84 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using System; +using System.Collections.Generic; +using Volo.Abp.Data; +using Volo.Abp.Localization; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending +{ + public partial class SelectExtensionProperty : ComponentBase + where TEntity : IHasExtraProperties + { + [Inject] + public IStringLocalizerFactory StringLocalizerFactory { get; set; } + + [Parameter] + public TEntity Entity { get; set; } + + [Parameter] + public ObjectExtensionPropertyInfo PropertyInfo { get; set; } + + public int SelectedValue + { + get + { + return Entity.GetProperty(PropertyInfo.Name); + } + set + { + Entity.SetProperty(PropertyInfo.Name, value); + } + } + + protected virtual IEnumerable> GetSelectItemsFromEnum() + { + var isNullableType = Nullable.GetUnderlyingType(PropertyInfo.Type) != null; + var enumType = PropertyInfo.Type; + + if (isNullableType) + { + enumType = Nullable.GetUnderlyingType(PropertyInfo.Type); + yield return new SelectItem(); + } + + foreach (var enumValue in enumType.GetEnumValues()) + { + var memberName = enumType.GetEnumName(enumValue); + var localizedMemberName = AbpInternalLocalizationHelper.LocalizeWithFallback( + new[] + { + // containerLocalizer, + StringLocalizerFactory.CreateDefaultOrNull() + }, + new[] + { + $"Enum:{enumType.Name}.{memberName}", + $"{enumType.Name}.{memberName}", + memberName + }, + memberName + ); + + yield return new SelectItem + { + Value = (int)enumValue, + Text = localizedMemberName + }; + } + } + + + protected virtual void SelectedValueChanged(int value) + { + SelectedValue = value; + } + } + + public class SelectItem + { + public string Text { get; set; } + public TValue Value { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor new file mode 100644 index 0000000000..3c2f35094f --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor @@ -0,0 +1,16 @@ +@typeparam TEntity +@typeparam TResourceType +@using Volo.Abp.BlazoriseUI +@using Volo.Abp.Localization + +@if (PropertyInfo != null && Entity != null) +{ + + @PropertyInfo.GetLocalizedDisplayName(StringLocalizerFactory) + + + + + + +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor.cs new file mode 100644 index 0000000000..0a1ceacf03 --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending +{ + public partial class TextExtensionProperty : ComponentBase + where TEntity : IHasExtraProperties + { + [Inject] + public IStringLocalizerFactory StringLocalizerFactory { get; set; } + + [Parameter] + public TEntity Entity { get; set; } + + [Parameter] + public ObjectExtensionPropertyInfo PropertyInfo { get; set; } + + + protected string Value + { + get + { + return PropertyInfo.GetTextInputValueOrNull(Entity.GetProperty(PropertyInfo.Name)); + } + set + { + Entity.SetProperty(PropertyInfo.Name, value); + } + } + } +} diff --git a/framework/src/Volo.Abp.BlazoriseUI/ObjectExtensionPropertyInfoBlazorExtensions.cs b/framework/src/Volo.Abp.BlazoriseUI/ObjectExtensionPropertyInfoBlazorExtensions.cs new file mode 100644 index 0000000000..b9c4613161 --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/ObjectExtensionPropertyInfoBlazorExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Volo.Abp.ObjectExtending +{ + public static class ObjectExtensionPropertyInfoBlazorExtensions + { + private static readonly Type[] DateTimeTypes = + { + typeof(DateTime), + typeof(DateTime?), + typeof(DateTimeOffset), + typeof(DateTimeOffset?) + }; + + public static bool IsDate(this IBasicObjectExtensionPropertyInfo property) + { + return DateTimeTypes.Contains(property.Type) && + property.GetDataTypeOrNull() == DataType.Date; + } + + public static bool IsDateTime(this IBasicObjectExtensionPropertyInfo property) + { + return DateTimeTypes.Contains(property.Type) && + !property.IsDate(); + } + + public static DataType? GetDataTypeOrNull(this IBasicObjectExtensionPropertyInfo property) + { + return property + .Attributes + .OfType() + .FirstOrDefault()?.DataType; + } + } +} diff --git a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj index 9405337b89..9bd0c7e8f0 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj +++ b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj @@ -15,6 +15,6 @@ + - diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs index 7cdeb0295b..ab9b7764bd 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs @@ -3,7 +3,10 @@ using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing; using Volo.Abp.AutoMapper; using Volo.Abp.BlazoriseUI; using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectExtending.Modularity; using Volo.Abp.PermissionManagement.Blazor; +using Volo.Abp.Threading; using Volo.Abp.UI.Navigation; namespace Volo.Abp.Identity.Blazor @@ -16,6 +19,8 @@ namespace Volo.Abp.Identity.Blazor )] public class AbpIdentityBlazorModule : AbpModule { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAutoMapperObjectMapper(); @@ -35,5 +40,27 @@ namespace Volo.Abp.Identity.Blazor options.AdditionalAssemblies.Add(typeof(AbpIdentityBlazorModule).Assembly); }); } + + public override void PostConfigureServices(ServiceConfigurationContext context) + { + OneTimeRunner.Run(() => + { + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToUi( + IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.Role, + createFormTypes: new[] { typeof(IdentityRoleCreateDto) }, + editFormTypes: new[] { typeof(IdentityRoleUpdateDto) } + ); + + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToUi( + IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.User, + createFormTypes: new[] { typeof(IdentityUserCreateDto) }, + editFormTypes: new[] { typeof(IdentityUserUpdateDto) } + ); + }); + } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor index a97b7b9632..6cd6575410 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor @@ -7,6 +7,7 @@ @using Microsoft.Extensions.Localization @using Volo.Abp.Identity.Localization @using Volo.Abp.AspNetCore.Components.WebAssembly.Theming +@using Volo.Abp.BlazoriseUI.Components.ObjectExtending @inject AbpBlazorMessageLocalizerHelper LH @inherits AbpCrudPageBase @@ -53,6 +54,7 @@ + @L["DisplayName:IsDefault"] @@ -91,6 +93,7 @@ + @L["DisplayName:IsDefault"]