Browse Source

Merge branch 'dev' into feat-input-wrapper

pull/15741/head
Barış Can Yılmaz 4 years ago
committed by GitHub
parent
commit
81ecb4cac3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      docs/en/Modules/Cms-Kit/Blogging.md
  2. 8
      docs/en/Modules/Cms-Kit/Comments.md
  3. 6
      docs/en/Modules/Cms-Kit/Global-Resources.md
  4. 7
      docs/en/Modules/Cms-Kit/Index.md
  5. 18
      docs/en/Modules/Cms-Kit/Menus.md
  6. 8
      docs/en/Modules/Cms-Kit/Pages.md
  7. 8
      docs/en/Modules/Cms-Kit/Ratings.md
  8. 8
      docs/en/Modules/Cms-Kit/Reactions.md
  9. 8
      docs/en/Modules/Cms-Kit/Tags.md
  10. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Grid/AbpColumnTagHelper.cs
  11. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Grid/AbpColumnTagHelperService.cs
  12. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs
  13. 8
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs
  14. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/CmdHelper.cs
  15. 2
      framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs
  16. 41
      framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableStringToGuidConverter.cs
  17. 36
      framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToGuidConverter.cs
  18. 34
      framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleDocumentFilter.cs
  19. 23
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs
  20. 12
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs
  21. 85
      framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpStringToGuid_Tests.cs
  22. 1
      modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/en.json
  23. 4
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json
  24. 15
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaException.cs
  25. 13
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/SimpleMathsCaptchaGenerator.cs
  26. 2
      npm/ng-packs/package.json
  27. 14
      npm/ng-packs/packages/account-core/src/lib/tenant-box.service.ts
  28. 19
      npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts
  29. 4
      npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.ts
  30. 6
      npm/ng-packs/packages/account/src/lib/components/login/login.component.ts
  31. 2
      npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts
  32. 4
      npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts
  33. 6
      npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts
  34. 14
      npm/ng-packs/packages/account/src/lib/components/register/register.component.ts
  35. 10
      npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts
  36. 10
      npm/ng-packs/packages/components/chart.js/src/chart.component.ts
  37. 2
      npm/ng-packs/packages/components/chart.js/src/widget-utils.ts
  38. 4
      npm/ng-packs/packages/components/page/src/page-part.directive.ts
  39. 2
      npm/ng-packs/packages/components/page/src/page.component.html
  40. 14
      npm/ng-packs/packages/components/page/src/page.component.ts
  41. 6
      npm/ng-packs/packages/core/locale/src/utils/register-locale.ts
  42. 2
      npm/ng-packs/packages/core/package.json
  43. 10
      npm/ng-packs/packages/core/src/lib/abstracts/ng-model.component.ts
  44. 2
      npm/ng-packs/packages/core/src/lib/clients/http.client.ts
  45. 22
      npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts
  46. 6
      npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts
  47. 4
      npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts
  48. 58
      npm/ng-packs/packages/core/src/lib/directives/for.directive.ts
  49. 12
      npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts
  50. 15
      npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts
  51. 2
      npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts
  52. 20
      npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts
  53. 2
      npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts
  54. 6
      npm/ng-packs/packages/core/src/lib/models/auth.ts
  55. 38
      npm/ng-packs/packages/core/src/lib/models/dtos.ts
  56. 5
      npm/ng-packs/packages/core/src/lib/models/environment.ts
  57. 2
      npm/ng-packs/packages/core/src/lib/models/session.ts
  58. 6
      npm/ng-packs/packages/core/src/lib/providers/locale.provider.ts
  59. 15
      npm/ng-packs/packages/core/src/lib/services/config-state.service.ts
  60. 11
      npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts
  61. 17
      npm/ng-packs/packages/core/src/lib/services/environment.service.ts
  62. 5
      npm/ng-packs/packages/core/src/lib/services/http-wait.service.ts
  63. 10
      npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts
  64. 22
      npm/ng-packs/packages/core/src/lib/services/localization.service.ts
  65. 2
      npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts
  66. 4
      npm/ng-packs/packages/core/src/lib/services/permission.service.ts
  67. 6
      npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts
  68. 2
      npm/ng-packs/packages/core/src/lib/services/resource-wait.service.ts
  69. 7
      npm/ng-packs/packages/core/src/lib/services/rest.service.ts
  70. 5
      npm/ng-packs/packages/core/src/lib/services/router-events.service.ts
  71. 12
      npm/ng-packs/packages/core/src/lib/services/routes.service.ts
  72. 2
      npm/ng-packs/packages/core/src/lib/services/session-state.service.ts
  73. 8
      npm/ng-packs/packages/core/src/lib/services/subscription.service.ts
  74. 4
      npm/ng-packs/packages/core/src/lib/strategies/content-security.strategy.ts
  75. 10
      npm/ng-packs/packages/core/src/lib/strategies/content.strategy.ts
  76. 2
      npm/ng-packs/packages/core/src/lib/strategies/context.strategy.ts
  77. 9
      npm/ng-packs/packages/core/src/lib/strategies/cross-origin.strategy.ts
  78. 6
      npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts
  79. 4
      npm/ng-packs/packages/core/src/lib/tokens/localization.token.ts
  80. 3
      npm/ng-packs/packages/core/src/lib/tokens/set-token-response-to-storage.token.ts
  81. 20
      npm/ng-packs/packages/core/src/lib/utils/common-utils.ts
  82. 5
      npm/ng-packs/packages/core/src/lib/utils/date-utils.ts
  83. 10
      npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts
  84. 4
      npm/ng-packs/packages/core/src/lib/utils/factory-utils.ts
  85. 3
      npm/ng-packs/packages/core/src/lib/utils/internal-store-utils.ts
  86. 12
      npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts
  87. 2
      npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts
  88. 9
      npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts
  89. 28
      npm/ng-packs/packages/core/src/lib/utils/object-utils.ts
  90. 6
      npm/ng-packs/packages/core/src/lib/utils/route-utils.ts
  91. 6
      npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts
  92. 4
      npm/ng-packs/packages/core/src/lib/validators/range.validator.ts
  93. 2
      npm/ng-packs/packages/core/src/lib/validators/required.validator.ts
  94. 4
      npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts
  95. 2
      npm/ng-packs/packages/core/src/lib/validators/username.validator.ts
  96. 2
      npm/ng-packs/packages/core/testing/src/lib/services/mock-permission.service.ts
  97. 10
      npm/ng-packs/packages/core/testing/src/lib/services/mock-rest.service.ts
  98. 75
      npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html
  99. 14
      npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.ts
  100. 168
      npm/ng-packs/packages/identity/src/lib/components/users/users.component.html

6
docs/en/Modules/Cms-Kit/Blogging.md

@ -2,6 +2,12 @@
The blogging feature provides the necessary UI to manage and render blogs and blog posts.
## Enabling the Blogging Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## User Interface
### Menu Items

8
docs/en/Modules/Cms-Kit/Comments.md

@ -2,6 +2,12 @@
CMS kit provides a **comment** system to add the comment feature to any kind of resource, like blog posts, products, etc.
## Enabling the Comment Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## Options
The comment system provides a mechanism to group comment definitions by entity types. For example, if you want to use the comment system for blog posts and products, you need to define two entity types named `BlogPosts` and `Product`, and add comments under these entity types.
@ -16,7 +22,7 @@ Configure<CmsKitCommentOptions>(options =>
});
```
> If you're using the blog feature, the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above.
> If you're using the [Blogging Feature](Blogging.md), the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above.
`CmsKitCommentOptions` properties:

6
docs/en/Modules/Cms-Kit/Global-Resources.md

@ -2,6 +2,12 @@
CMS Kit Global Resources system allows to add global styles and scripts dynamically.
## Enabling the Global Resources Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## The User Interface
### Menu items

7
docs/en/Modules/Cms-Kit/Index.md

@ -16,11 +16,12 @@ The following features are currently available:
* Provides a [**global resources**](Global-Resources.md) system to add global styles and scripts dynamically.
* Provides a [**Dynamic Widget**](Dynamic-Widget.md) system to create dynamic widgets for page and blog posts.
Click to a feature to understand and learn how to use it.
> You can click on the any feature links above to understand and learn how to use it.
All features are individually usable. If you disable a feature, it completely disappears from your application, even from the database tables, by the help of the [Global Features](../../Global-Features.md) system.
All features are individually usable. If you disable a feature, it completely disappears from your application, even from the database tables, with the help of the [Global Features](../../Global-Features.md) system.
## Pre Requirements
- This module depends on [BlobStoring](../../Blob-Storing.md) module for keeping media content.
> Make sure `BlobStoring` module is installed and at least one provider is configured properly. For more information, check the [documentation](../../Blob-Storing.md).
@ -62,7 +63,7 @@ GlobalFeatureManager.Instance.Modules.CmsKit(cmsKit =>
This module follows the [module development best practices guide](https://docs.abp.io/en/abp/latest/Best-Practices/Index) and consists of several NuGet and NPM packages. See the guide if you want to understand the packages and relations between them.
CMS kit packages are designed for various usage scenarios. If you check the [CMS kit packages](https://www.nuget.org/packages?q=Volo.CmsKit), you will see that some packages have `Admin` and `Public` suffixes. The reason is that the module has two application layers, considering they might be used in different type of applications. These application layers uses a single domain layer.
CMS kit packages are designed for various usage scenarios. If you check the [CMS kit packages](https://www.nuget.org/packages?q=Volo.CmsKit), you will see that some packages have `Admin` and `Public` suffixes. The reason is that the module has two application layers, considering they might be used in different type of applications. These application layers uses a single domain layer:
- `Volo.CmsKit.Admin.*` packages contain the functionalities required by admin (back office) applications.
- `Volo.CmsKit.Public.*` packages contain the functionalities used in public websites where users read blog posts or leave comments.

18
docs/en/Modules/Cms-Kit/Menus.md

@ -1,7 +1,13 @@
# CMS Kit: Pages
# CMS Kit: Menus
CMS Kit Menu system allows to manage public menus dynamically.
## Enabling the Menu Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## The User Interface
### Menu items
@ -20,22 +26,18 @@ Menus page is used to manage dynamic public menus in the system.
![cms-kit-menus-page](../../images/cmskit-module-menus-page.png)
Created menus will be visible on public site.
The created menu items will be visible on the public-web side, as shown below:
![cms-kit-public-menus](../../images//cmskit-module-menus-public.png)
# Internals
## Internals
## Domain Layer
### Domain Layer
#### Aggregates
This module follows the [Entity Best Practices & Conventions](https://docs.abp.io/en/abp/latest/Best-Practices/Entities) guide.
##### Menus
- `MenuItem` (aggregate root): A Menu Item presents a single node at menu tree.
#### Repositories

8
docs/en/Modules/Cms-Kit/Pages.md

@ -2,6 +2,12 @@
CMS Kit Page system allows you to create dynamic pages by specifying URLs, which is the fundamental feature of a CMS.
## Enabling the Pages Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## The User Interface
### Menu items
@ -24,5 +30,5 @@ You can create or edit an existing page on this page.
![pages-edit](../../images/cmskit-module-pages-edit.png)
When you create a page, you can access the created page via `/pages/{slug}` URL.
When you create a page, you can access the created page via `/{slug}` URL.

8
docs/en/Modules/Cms-Kit/Ratings.md

@ -4,6 +4,12 @@ CMS kit provides a **rating** system to to add ratings feature to any kind of re
![ratings](../../images/cmskit-module-ratings.png)
## Enabling the Rating Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## Options
The rating system provides a mechanism to group ratings by entity types. For example, if you want to use the rating system for products, you need to define an entity type named `Product` and then add ratings under the defined entity type.
@ -17,7 +23,7 @@ Configure<CmsKitRatingOptions>(options =>
});
```
> If you're using the blog feature, the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above.
> If you're using the [Blogging Feature](Blogging.md), the ABP framework defines an entity type for the blog feature automatically. You can easily override or remove the predefined entity types in `Configure` method like shown above.
`CmsKitRatingOptions` properties:

8
docs/en/Modules/Cms-Kit/Reactions.md

@ -8,6 +8,12 @@ Reaction component allows users to react to your content via pre-defined icons/e
You can also customize the reaction icons shown in the reaction component.
## Enabling the Reaction Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## Options
Reaction system provides a mechanism to group reactions by entity types. For example, if you want to use the reaction system for products, you need to define an entity type named `Product`, and then add reactions under the defined entity type.
@ -32,7 +38,7 @@ Configure<CmsKitReactionOptions>(options =>
});
```
> If you're using the comment or blog features, the ABP framework defines predefined reactions for these features automatically.
> If you're using the [Comment](Comments.md) or [Blogging](Blogging.md) features, the ABP framework defines predefined reactions for these features automatically.
`CmsKitReactionOptions` properties:

8
docs/en/Modules/Cms-Kit/Tags.md

@ -2,6 +2,12 @@
CMS kit provides a **tag** system to tag any kind of resources, like a blog post.
## Enabling the Tag Management Feature
By default, CMS Kit features are disabled. Therefore, you need to enable the features you want, before starting to use it. You can use the [Global Feature](../../Global-Features.md) system to enable/disable CMS Kit features on development time. Alternatively, you can use the ABP Framework's [Feature System](https://docs.abp.io/en/abp/latest/Features) to disable a CMS Kit feature on runtime.
> Check the ["How to Install" section of the CMS Kit Module documentation](Index.md#how-to-install) to see how to enable/disable CMS Kit features on development time.
## Options
The tag system provides a mechanism to group tags by entity types. For example, if you want to use the tag system for blog posts and products, you need to define two entity types named `BlogPosts` and `Product` and add tags under these entity types.
@ -17,7 +23,7 @@ Configure<CmsKitTagOptions>(options =>
});
```
> If you're using the blog feature, the ABP framework defines an entity type for the blog feature automatically.
> If you're using the [Blogging Feature](Blogging.md), the ABP framework defines an entity type for the blog feature automatically.
`CmsKitTagOptions` properties:

4
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Grid/AbpColumnTagHelper.cs

@ -16,6 +16,8 @@ public class AbpColumnTagHelper : AbpTagHelper<AbpColumnTagHelper, AbpColumnTagH
public ColumnSize SizeXl { get; set; }
public ColumnSize SizeXxl { get; set;}
public ColumnSize Offset { get; set; }
public ColumnSize OffsetSm { get; set; }
@ -26,6 +28,8 @@ public class AbpColumnTagHelper : AbpTagHelper<AbpColumnTagHelper, AbpColumnTagH
public ColumnSize OffsetXl { get; set; }
public ColumnSize OffsetXxl { get; set; }
[HtmlAttributeName("order")]
public ColumnOrder ColumnOrder { get; set; }

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Grid/AbpColumnTagHelperService.cs

@ -24,6 +24,7 @@ public class AbpColumnTagHelperService : AbpTagHelperService<AbpColumnTagHelper>
ProcessSizeClass(context, output, TagHelper.SizeMd, "-md");
ProcessSizeClass(context, output, TagHelper.SizeLg, "-lg");
ProcessSizeClass(context, output, TagHelper.SizeXl, "-xl");
ProcessSizeClass(context, output, TagHelper.SizeXxl, "-xxl");
}
protected virtual void ProcessOffsetClasses(TagHelperContext context, TagHelperOutput output)
@ -33,6 +34,7 @@ public class AbpColumnTagHelperService : AbpTagHelperService<AbpColumnTagHelper>
ProcessOffsetClass(context, output, TagHelper.OffsetMd, "-md");
ProcessOffsetClass(context, output, TagHelper.OffsetLg, "-lg");
ProcessOffsetClass(context, output, TagHelper.OffsetXl, "-xl");
ProcessOffsetClass(context, output, TagHelper.OffsetXxl, "-xxl");
}
protected virtual void ProcessSizeClass(TagHelperContext context, TagHelperOutput output, ColumnSize size, string breakpoint)

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs

@ -20,6 +20,8 @@ public static class MvcCoreBuilderExtensions
options.JsonSerializerOptions.Converters.Add(new AbpStringToEnumFactory());
options.JsonSerializerOptions.Converters.Add(new AbpStringToBooleanConverter());
options.JsonSerializerOptions.Converters.Add(new AbpStringToGuidConverter());
options.JsonSerializerOptions.Converters.Add(new AbpNullableStringToGuidConverter());
options.JsonSerializerOptions.Converters.Add(new ObjectToInferredTypesConverter());
options.JsonSerializerOptions.TypeInfoResolver = new AbpDefaultJsonTypeInfoResolver(serviceProvider

8
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeThemeStep.cs

@ -83,7 +83,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
ChangeNamespaceAndKeyword(
context,
"/MyCompanyName.MyProjectName.Host/MyProjectNameServerModule.cs",
"/MyCompanyName.MyProjectName.Host/MyProjectNameHostModule.cs",
$"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}.Bundling",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling",
$"{defaultThemeName}ThemeBundles.Styles.Global",
@ -92,7 +92,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
ChangeNamespaceAndKeyword(
context,
"/MyCompanyName.MyProjectName.Host/MyProjectNameServerModule.cs",
"/MyCompanyName.MyProjectName.Host/MyProjectNameHostModule.cs",
$"Volo.Abp.AspNetCore.Mvc.UI.Theme.{defaultThemeName}",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic",
$"AbpAspNetCoreMvcUi{defaultThemeName}ThemeModule",
@ -209,7 +209,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
ChangeNamespaceAndKeyword(
context,
"/MyCompanyName.MyProjectName.Host/MyProjectNameServerModule.cs",
"/MyCompanyName.MyProjectName.Host/MyProjectNameHostModule.cs",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX.Bundling",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton.Bundling",
"LeptonXThemeBundles.Styles.Global",
@ -218,7 +218,7 @@ public class ChangeThemeStep : ProjectBuildPipelineStep
ChangeNamespaceAndKeyword(
context,
"/MyCompanyName.MyProjectName.Host/MyProjectNameServerModule.cs",
"/MyCompanyName.MyProjectName.Host/MyProjectNameHostModule.cs",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX",
"Volo.Abp.AspNetCore.Mvc.UI.Theme.Lepton",
"AbpAspNetCoreMvcUiLeptonXThemeModule",

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/CmdHelper.cs

@ -174,7 +174,7 @@ public class CmdHelper : ICmdHelper, ITransientDependency
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return delaySeconds == null ? "-c \"" + command + "\"" : "-c \"" + $"sleep {delaySeconds}s > /dev/null && " + command + "\"";
return delaySeconds == null ? "-c \"" + command + "\"" : "-c \"" + $"sleep {delaySeconds} > /dev/null && " + command + "\"";
}
//Windows default.

2
framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs

@ -22,6 +22,8 @@ public class AbpJsonSystemTextJsonModule : AbpModule
options.JsonSerializerOptions.Converters.Add(new AbpStringToEnumFactory());
options.JsonSerializerOptions.Converters.Add(new AbpStringToBooleanConverter());
options.JsonSerializerOptions.Converters.Add(new AbpStringToGuidConverter());
options.JsonSerializerOptions.Converters.Add(new AbpNullableStringToGuidConverter());
options.JsonSerializerOptions.Converters.Add(new ObjectToInferredTypesConverter());
options.JsonSerializerOptions.TypeInfoResolver = new AbpDefaultJsonTypeInfoResolver(serviceProvider

41
framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableStringToGuidConverter.cs

@ -0,0 +1,41 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Volo.Abp.Json.SystemTextJson.JsonConverters;
public class AbpNullableStringToGuidConverter : JsonConverter<Guid?>
{
private JsonSerializerOptions _writeJsonSerializerOptions;
public override Guid? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
var guidString = reader.GetString();
string[] formats = { "N", "D", "B", "P", "X" };
foreach (var format in formats)
{
if (Guid.TryParseExact(guidString, format, out var guid))
{
return guid;
}
}
}
if (reader.TryGetGuid(out var guid2))
{
return guid2;
}
return null;
}
public override void Write(Utf8JsonWriter writer, Guid? value, JsonSerializerOptions options)
{
_writeJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, this);
var entityConverter = (JsonConverter<Guid?>)_writeJsonSerializerOptions.GetConverter(typeof(Guid?));
entityConverter.Write(writer, value, _writeJsonSerializerOptions);
}
}

36
framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpStringToGuidConverter.cs

@ -0,0 +1,36 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Volo.Abp.Json.SystemTextJson.JsonConverters;
public class AbpStringToGuidConverter : JsonConverter<Guid>
{
private JsonSerializerOptions _writeJsonSerializerOptions;
public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
var guidString = reader.GetString();
string[] formats = { "N", "D", "B", "P", "X" };
foreach (var format in formats)
{
if (Guid.TryParseExact(guidString, format, out var guid))
{
return guid;
}
}
}
return reader.GetGuid();
}
public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options)
{
_writeJsonSerializerOptions ??= JsonSerializerOptionsHelper.Create(options, this);
var entityConverter = (JsonConverter<Guid>)_writeJsonSerializerOptions.GetConverter(typeof(Guid));
entityConverter.Write(writer, value, _writeJsonSerializerOptions);
}
}

34
framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleDocumentFilter.cs

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
@ -8,7 +10,9 @@ namespace Volo.Abp.Swashbuckle;
public class AbpSwashbuckleDocumentFilter : IDocumentFilter
{
protected string[] ActionUrlPrefixes = new[] {"Volo."};
protected virtual string[] ActionUrlPrefixes { get; set; } = new[] { "Volo." };
protected virtual string RegexConstraintPattern => @":regex\(([^()]*)\)";
public virtual void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
@ -17,7 +21,7 @@ public class AbpSwashbuckleDocumentFilter : IDocumentFilter
.Where(actionDescriptor => !string.IsNullOrEmpty(actionDescriptor.DisplayName) &&
ActionUrlPrefixes.Any(actionUrlPrefix => !actionDescriptor.DisplayName.Contains(actionUrlPrefix)))
.DistinctBy(actionDescriptor => actionDescriptor.AttributeRouteInfo?.Template)
.Select(actionDescriptor => actionDescriptor.AttributeRouteInfo?.Template?.EnsureStartsWith('/').Replace("?", ""))
.Select(RemoveRouteParameterConstraints)
.Where(actionUrl => !string.IsNullOrEmpty(actionUrl))
.ToList();
@ -25,4 +29,30 @@ public class AbpSwashbuckleDocumentFilter : IDocumentFilter
.Paths
.RemoveAll(path => !actionUrls.Contains(path.Key));
}
protected virtual string RemoveRouteParameterConstraints(ActionDescriptor actionDescriptor)
{
var route = actionDescriptor.AttributeRouteInfo?.Template?.EnsureStartsWith('/').Replace("?", "");
if (string.IsNullOrWhiteSpace(route))
{
return route;
}
route = Regex.Replace(route, RegexConstraintPattern, "");
while (route.Contains(':'))
{
var startIndex = route.IndexOf(":", StringComparison.Ordinal);
var endIndex = route.IndexOf("}", startIndex);
if (endIndex == -1)
{
break;
}
route = route.Remove(startIndex, (endIndex - startIndex));
}
return route;
}
}

23
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs

@ -49,6 +49,12 @@ public class ModelBindingController : AbpController
input.Time3.Value.Kind.ToString().ToLower() + "_" +
input.InnerModel.Time4.Kind.ToString().ToLower();
}
[HttpPost("Guid_Json_Test")]
public GuidJsonModel Guid_Json_Test([FromBody] GuidJsonModel input)
{
return input;
}
}
public class GetDateTimeKindModel
@ -68,3 +74,20 @@ public class GetDateTimeKindModel
public DateTime Time4 { get; set; }
}
}
public class GuidJsonModel
{
public Guid UserId { get; set; }
public Guid UserId2 { get; set; }
public Guid UserId3 { get; set; }
public Guid UserId4 { get; set; }
public Guid UserId5 { get; set; }
public Guid? TenantId { get; set; }
public Guid? TenantId2 { get; set; }
}

12
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs

@ -106,6 +106,18 @@ public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase
var resultAsString = await response.Content.ReadAsStringAsync();
resultAsString.ShouldBe($"local_{Kind.ToString().ToLower()}_{Kind.ToString().ToLower()}_local");
}
[Fact]
public async Task Guid_Json_Test()
{
var guid = Guid.NewGuid();
var json = $"{{\"UserId\":\"{guid:B}\",\"UserId2\":\"{guid:N}\",\"UserId3\":\"{guid:D}\",\"UserId4\":\"{guid:P}\",\"UserId5\":\"{guid:x}\",\"TenantId\":null,\"TenantId2\":\"\"}}";
var response = await Client.PostAsync("/api/model-Binding-test/Guid_Json_Test", new StringContent(json, Encoding.UTF8, MimeTypes.Application.Json));
response.StatusCode.ShouldBe(HttpStatusCode.OK);
var resultAsString = await response.Content.ReadAsStringAsync();
resultAsString.ShouldBe($"{{\"userId\":\"{guid:D}\",\"userId2\":\"{guid:D}\",\"userId3\":\"{guid:D}\",\"userId4\":\"{guid:D}\",\"userId5\":\"{guid:D}\",\"tenantId\":null,\"tenantId2\":null}}");
}
}
public class ModelBindingController_Utc_Tests : ModelBindingController_Tests

85
framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpStringToGuid_Tests.cs

@ -0,0 +1,85 @@
using System;
using System.Text.Json;
using Shouldly;
using Volo.Abp.Json.SystemTextJson.JsonConverters;
using Xunit;
namespace Volo.Abp.Json;
public class AbpStringToGuid_Tests
{
[Fact]
public void Test_Read()
{
var options = new JsonSerializerOptions()
{
Converters =
{
new AbpStringToGuidConverter(),
new AbpNullableStringToGuidConverter()
}
};
var guid = Guid.Parse("762DDB84-5225-4853-A566-FF0B3AF57585");
var testClass = JsonSerializer.Deserialize<TestClass>("{" +
$"\"Id\": \"{guid:N}\", " +
$"\"NullableId\": \"{guid:D}\", " +
$"\"NullableId2\": \"{guid:B}\", " +
$"\"NullableId3\": \"{guid:P}\", " +
$"\"NullableId4\": \"{guid:X}\", " +
"\"NullableId5\": \"\", " +
"\"NullableId6\": null}", options);
testClass.ShouldNotBeNull();
testClass.Id.ShouldBe(guid);
testClass.NullableId.ShouldBe(guid);
testClass.NullableId2.ShouldBe(guid);
testClass.NullableId3.ShouldBe(guid);
testClass.NullableId4.ShouldBe(guid);
testClass.NullableId5.ShouldBeNull();
testClass.NullableId6.ShouldBeNull();
}
[Fact]
public void Test_Write()
{
var options = new JsonSerializerOptions()
{
Converters =
{
new AbpStringToGuidConverter(),
new AbpNullableStringToGuidConverter()
}
};
var guid = Guid.Parse("762DDB84-5225-4853-A566-FF0B3AF57585");
var json = JsonSerializer.Serialize(new TestClass()
{
Id = guid,
NullableId = null,
NullableId2 = guid,
NullableId3 = null,
NullableId4 = guid,
NullableId5 = null,
NullableId6 = guid
}, options);
json.ShouldBe($"{{\"Id\":\"{guid:D}\",\"NullableId\":null,\"NullableId2\":\"{guid:D}\",\"NullableId3\":null,\"NullableId4\":\"{guid:D}\",\"NullableId5\":null,\"NullableId6\":\"{guid:D}\"}}");
}
class TestClass
{
public Guid Id { get; set; }
public Guid? NullableId { get; set; }
public Guid? NullableId2 { get; set; }
public Guid? NullableId3 { get; set; }
public Guid? NullableId4 { get; set; }
public Guid? NullableId5 { get; set; }
public Guid? NullableId6 { get; set; }
}
}

1
modules/audit-logging/src/Volo.Abp.AuditLogging.Domain.Shared/Volo/Abp/AuditLogging/Localization/en.json

@ -39,6 +39,7 @@
"ExtraProperties": "Extra properties",
"MaxDuration": "Max. Duration",
"MinDuration": "Min. Duration",
"MinMaxDuration": "Duration (Min. - Max.)",
"{0}Milliseconds": "{0} milliseconds",
"ExecutionTime": "Time",
"Parameters": "Parameters",

4
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json

@ -214,6 +214,8 @@
"Feature:TagEnableDescription": "CMS Kit's tag system that allows tagging entities such as BlogPost.",
"DeleteBlogPostMessage": "The blog will be deleted. Are you sure?",
"CaptchaCode": "Captcha code",
"CommentTextRequired": "Comment is required"
"CommentTextRequired": "Comment is required",
"CaptchaCodeErrorMessage" : "The answer you entered for the CAPTCHA was not correct. Please try again",
"CaptchaCodeMissingMessage": "The captcha code is missing!"
}
}

15
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/CaptchaException.cs

@ -1,15 +0,0 @@
using System.Runtime.Serialization;
using Volo.Abp;
namespace Volo.CmsKit.Public.Web.Security.Captcha;
public class CaptchaException : UserFriendlyException
{
public CaptchaException(string message) : base(message)
{
}
public CaptchaException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context)
{
}
}

13
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/SimpleMathsCaptchaGenerator.cs

@ -9,6 +9,9 @@ using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Volo.Abp;
using Volo.CmsKit.Localization;
using Microsoft.Extensions.Localization;
using Volo.Abp.DependencyInjection;
using Color = SixLabors.ImageSharp.Color;
using PointF = SixLabors.ImageSharp.PointF;
@ -17,6 +20,12 @@ namespace Volo.CmsKit.Public.Web.Security.Captcha;
public class SimpleMathsCaptchaGenerator : ISingletonDependency
{
private readonly IStringLocalizer<CmsKitResource> _localizer;
public SimpleMathsCaptchaGenerator(IStringLocalizer<CmsKitResource> localizer)
{
_localizer = localizer;
}
private static Dictionary<Guid, CaptchaRequest> Session { get; set; } = new Dictionary<Guid, CaptchaRequest>();
public CaptchaOutput Generate()
@ -74,7 +83,7 @@ public class SimpleMathsCaptchaGenerator : ISingletonDependency
var request = Session[requestId];
if (request.Output.Result != value)
{
throw new CaptchaException("The captcha code doesn't match text on the picture! Please try again.");
throw new UserFriendlyException(_localizer["CaptchaCodeErrorMessage"]);
}
}
@ -86,7 +95,7 @@ public class SimpleMathsCaptchaGenerator : ISingletonDependency
}
else
{
throw new CaptchaException("The captcha code is missing!");
throw new UserFriendlyException(_localizer["CaptchaCodeMissingMessage"]);
}
}

2
npm/ng-packs/package.json

@ -106,7 +106,7 @@
"jest-preset-angular": "^12.2.2",
"jsonc-parser": "^2.3.0",
"just-clone": "^6.1.1",
"just-compare": "^1.4.0",
"just-compare": "^2.3.0",
"lerna": "^4.0.0",
"lint-staged": "^13.0.3",
"ng-packagr": "15.0.1",

14
npm/ng-packs/packages/account-core/src/lib/tenant-box.service.ts

@ -12,22 +12,22 @@ import { finalize } from 'rxjs/operators';
export class TenantBoxService {
currentTenant$ = this.sessionState.getTenant$();
name: string;
name?: string;
isModalVisible: boolean;
isModalVisible!: boolean;
modalBusy: boolean;
modalBusy!: boolean;
constructor(
private toasterService: ToasterService,
private tenantService: AbpTenantService,
private sessionState: SessionStateService,
private configState: ConfigStateService,
) { }
) {}
onSwitch() {
const tenant = this.sessionState.getTenant();
this.name = tenant?.name;
this.name = tenant?.name || '';
this.isModalVisible = true;
}
@ -53,14 +53,14 @@ export class TenantBoxService {
});
}
private setTenant(tenant: CurrentTenantDto) {
private setTenant(tenant: CurrentTenantDto | null) {
this.sessionState.setTenant(tenant);
this.configState.refreshAppState();
}
private showError() {
this.toasterService.error('AbpUiMultiTenancy::GivenTenantIsNotAvailable', 'AbpUi::Error', {
messageLocalizationParams: [this.name],
messageLocalizationParams: [this.name || ''],
});
}
}

19
npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts

@ -1,7 +1,12 @@
import { ProfileService } from '@abp/ng.account.core/proxy';
import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared';
import { Component, Injector, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms';
import {
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
Validators,
} from '@angular/forms';
import { comparePasswords, Validation } from '@ngx-validate/core';
import { finalize } from 'rxjs/operators';
import { Account } from '../../models/account';
@ -19,14 +24,14 @@ const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword'];
export class ChangePasswordComponent
implements OnInit, Account.ChangePasswordComponentInputs, Account.ChangePasswordComponentOutputs
{
form: UntypedFormGroup;
form!: UntypedFormGroup;
inProgress: boolean;
inProgress?: boolean;
hideCurrentPassword: boolean;
hideCurrentPassword?: boolean;
mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => {
if (PASSWORD_FIELDS.indexOf(String(control.name)) < 0) return errors;
if (PASSWORD_FIELDS.indexOf(String(control?.name)) < 0) return errors;
return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch'));
};
@ -73,8 +78,8 @@ export class ChangePasswordComponent
this.inProgress = true;
this.profileService
.changePassword({
...(!this.hideCurrentPassword && { currentPassword: this.form.get('password').value }),
newPassword: this.form.get('newPassword').value,
...(!this.hideCurrentPassword && { currentPassword: this.form.get('password')?.value }),
newPassword: this.form.get('newPassword')?.value,
})
.pipe(finalize(() => (this.inProgress = false)))
.subscribe({

4
npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.ts

@ -10,7 +10,7 @@ import { finalize } from 'rxjs/operators';
export class ForgotPasswordComponent {
form: UntypedFormGroup;
inProgress: boolean;
inProgress?: boolean;
isEmailSent = false;
@ -27,7 +27,7 @@ export class ForgotPasswordComponent {
this.accountService
.sendPasswordResetCode({
email: this.form.get('email').value,
email: this.form.get('email')?.value,
appName: 'Angular',
})
.pipe(finalize(() => (this.inProgress = false)))

6
npm/ng-packs/packages/account/src/lib/components/login/login.component.ts

@ -14,9 +14,9 @@ const { maxLength, required } = Validators;
templateUrl: './login.component.html',
})
export class LoginComponent implements OnInit {
form: UntypedFormGroup;
form!: UntypedFormGroup;
inProgress: boolean;
inProgress?: boolean;
isSelfRegistrationEnabled = true;
@ -67,7 +67,7 @@ export class LoginComponent implements OnInit {
err.error?.error_description ||
err.error?.error.message ||
'AbpAccount::DefaultErrorMessage',
null,
'',
{ life: 7000 },
);
return throwError(err);

2
npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts

@ -26,7 +26,7 @@ export class ManageProfileComponent implements OnInit {
profile$ = this.manageProfileState.getProfile$();
hideChangePasswordTab: boolean;
hideChangePasswordTab?: boolean;
constructor(
protected profileService: ProfileService,

4
npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts

@ -27,11 +27,11 @@ export class PersonalSettingsHalfRowComponent {
public displayName: string;
public name: string;
public id: string;
public formGroup: UntypedFormGroup;
public formGroup!: UntypedFormGroup;
constructor(@Inject(EXTENSIONS_FORM_PROP) private propData: FormProp) {
this.displayName = propData.displayName;
this.name = propData.name;
this.id = propData.id;
this.id = propData.id || '';
}
}

6
npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts

@ -31,11 +31,11 @@ export class PersonalSettingsComponent
Account.PersonalSettingsComponentInputs,
Account.PersonalSettingsComponentOutputs
{
selected: ProfileDto;
selected?: ProfileDto;
form: UntypedFormGroup;
form!: UntypedFormGroup;
inProgress: boolean;
inProgress?: boolean;
constructor(
private fb: UntypedFormBuilder,

14
npm/ng-packs/packages/account/src/lib/components/register/register.component.ts

@ -15,9 +15,9 @@ const { maxLength, required, email } = Validators;
templateUrl: './register.component.html',
})
export class RegisterComponent implements OnInit {
form: UntypedFormGroup;
form!: UntypedFormGroup;
inProgress: boolean;
inProgress?: boolean;
isSelfRegistrationEnabled = true;
@ -48,7 +48,7 @@ export class RegisterComponent implements OnInit {
key: 'AbpAccount::SelfRegistrationDisabledMessage',
defaultValue: 'Self registration is disabled.',
},
null,
'',
{ life: 10000 },
);
return;
@ -69,9 +69,9 @@ export class RegisterComponent implements OnInit {
this.inProgress = true;
const newUser = {
userName: this.form.get('username').value,
password: this.form.get('password').value,
emailAddress: this.form.get('email').value,
userName: this.form.get('username')?.value,
password: this.form.get('password')?.value,
emailAddress: this.form.get('email')?.value,
appName: 'Angular',
} as RegisterDto;
@ -90,7 +90,7 @@ export class RegisterComponent implements OnInit {
err.error?.error_description ||
err.error?.error.message ||
'AbpAccount::DefaultErrorMessage',
null,
'',
{ life: 7000 },
);

10
npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts

@ -13,14 +13,14 @@ const PASSWORD_FIELDS = ['password', 'confirmPassword'];
templateUrl: './reset-password.component.html',
})
export class ResetPasswordComponent implements OnInit {
form: UntypedFormGroup;
form!: UntypedFormGroup;
inProgress = false;
isPasswordReset = false;
mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => {
if (PASSWORD_FIELDS.indexOf(String(control.name)) < 0) return errors;
if (PASSWORD_FIELDS.indexOf(String(control?.name)) < 0) return errors;
return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch'));
};
@ -58,9 +58,9 @@ export class ResetPasswordComponent implements OnInit {
this.accountService
.resetPassword({
userId: this.form.get('userId').value,
resetToken: this.form.get('resetToken').value,
password: this.form.get('password').value,
userId: this.form.get('userId')?.value,
resetToken: this.form.get('resetToken')?.value,
password: this.form.get('password')?.value,
})
.pipe(finalize(() => (this.inProgress = false)))
.subscribe(() => {

10
npm/ng-packs/packages/components/chart.js/src/chart.component.ts

@ -35,7 +35,7 @@ let Chart: any;
exportAs: 'abpChart',
})
export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges {
@Input() type: string;
@Input() type!: string;
@Input() data: any = {};
@ -43,9 +43,9 @@ export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges {
@Input() plugins: any[] = [];
@Input() width: string;
@Input() width?: string;
@Input() height: string;
@Input() height?: string;
@Input() responsive = true;
@ -53,7 +53,7 @@ export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges {
@Output() initialized = new EventEmitter<boolean>();
@ViewChild('canvas') canvas: ElementRef<HTMLCanvasElement>;
@ViewChild('canvas') canvas!: ElementRef<HTMLCanvasElement>;
chart: any;
@ -67,7 +67,7 @@ export class ChartComponent implements AfterViewInit, OnDestroy, OnChanges {
});
}
onCanvasClick(event) {
onCanvasClick(event: MouseEvent) {
if (this.chart) {
const element = this.chart.getElementsAtEventForMode(
event,

2
npm/ng-packs/packages/components/chart.js/src/widget-utils.ts

@ -1,4 +1,4 @@
export function getRandomBackgroundColor(count) {
export function getRandomBackgroundColor(count: number) {
const colors = [];
for (let i = 0; i < count; i++) {

4
npm/ng-packs/packages/components/page/src/page-part.directive.ts

@ -27,8 +27,8 @@ export const PAGE_RENDER_STRATEGY = new InjectionToken<PageRenderStrategy>('PAGE
@Directive({ selector: '[abpPagePart]' })
export class PagePartDirective implements OnInit, OnDestroy, OnChanges {
hasRendered = false;
type: string;
subscription: Subscription;
type!: string;
subscription!: Subscription;
@Input('abpPagePartContext') context: any;
@Input() set abpPagePart(type: string) {

2
npm/ng-packs/packages/components/page/src/page.component.html

@ -25,7 +25,7 @@
</ng-template>
<ng-template #defaultBreadcrumbTemplate>
<ng-container *ngIf="breadcrumbVisible">
<ng-container *ngIf="breadcrumb">
<div class="col-lg-auto ps-lg-0" *abpPagePart="pageParts.breadcrumb">
<abp-breadcrumb></abp-breadcrumb>
</div>

14
npm/ng-packs/packages/components/page/src/page.component.ts

@ -12,11 +12,11 @@ import {
encapsulation: ViewEncapsulation.None,
})
export class PageComponent {
@Input() title: string;
@Input() title?: string;
toolbarVisible = false;
_toolbarData: any;
@Input('toolbar') set toolbarData(val: any) {
@Input() set toolbar(val: any) {
this._toolbarData = val;
this.toolbarVisible = true;
}
@ -25,7 +25,7 @@ export class PageComponent {
return this._toolbarData;
}
@Input('breadcrumb') breadcrumbVisible = true;
@Input() breadcrumb = true;
pageParts = {
title: PageParts.title,
@ -33,16 +33,16 @@ export class PageComponent {
toolbar: PageParts.toolbar,
};
@ContentChild(PageTitleContainerComponent) customTitle: PageTitleContainerComponent;
@ContentChild(PageTitleContainerComponent) customTitle?: PageTitleContainerComponent;
@ContentChild(PageBreadcrumbContainerComponent)
customBreadcrumb: PageBreadcrumbContainerComponent;
@ContentChild(PageToolbarContainerComponent) customToolbar: PageToolbarContainerComponent;
customBreadcrumb?: PageBreadcrumbContainerComponent;
@ContentChild(PageToolbarContainerComponent) customToolbar?: PageToolbarContainerComponent;
get shouldRenderRow() {
return !!(
this.title ||
this.toolbarVisible ||
this.breadcrumbVisible ||
this.breadcrumb ||
this.customTitle ||
this.customBreadcrumb ||
this.customToolbar

6
npm/ng-packs/packages/core/locale/src/utils/register-locale.ts

@ -8,7 +8,7 @@ export interface LocaleErrorHandlerData {
locale: string;
}
let localeMap = {};
let localeMap = {} as { [key: string]: string };
export interface RegisterLocaleData {
cultureNameLocaleFileMap?: Record<string, string>;
@ -51,7 +51,7 @@ export function registerLocale(
};
}
const extraLocales = {};
const extraLocales = {} as { [key: string]: any };
export function storeLocaleData(data: any, localeId: string) {
extraLocales[localeId] = data;
}
@ -62,7 +62,7 @@ export async function defaultLocalErrorHandlerFn({ locale, resolve }: LocaleErro
return;
}
if (isDevMode) {
if (isDevMode()) {
console.error(
`Cannot find the ${locale} locale file. You can check how can add new culture at https://docs.abp.io/en/abp/latest/UI/Angular/Localization#adding-a-new-culture`,
);

2
npm/ng-packs/packages/core/package.json

@ -10,7 +10,7 @@
"@abp/utils": "~7.1.0-rc.1",
"angular-oauth2-oidc": "^15.0.1",
"just-clone": "^6.1.1",
"just-compare": "^1.4.0",
"just-compare": "^2.3.0",
"ts-toolbelt": "6.15.4",
"tslib": "^2.0.0"
},

10
npm/ng-packs/packages/core/src/lib/abstracts/ng-model.component.ts

@ -4,16 +4,16 @@ import { ControlValueAccessor } from '@angular/forms';
// Not an abstract class on purpose. Do not change!
@Component({ template: '' })
export class AbstractNgModelComponent<T = any, U = T> implements ControlValueAccessor {
protected _value: T;
protected _value!: T;
protected cdRef: ChangeDetectorRef;
onChange: (value: T) => void;
onTouched: () => void;
onChange?: (value: T) => void;
onTouched?: () => void;
@Input()
disabled: boolean;
disabled?: boolean;
@Input()
readonly: boolean;
readonly?: boolean;
@Input()
valueFn: (value: U, previousValue?: T) => T = value => value as any as T;

2
npm/ng-packs/packages/core/src/lib/clients/http.client.ts

@ -15,7 +15,7 @@ export class ExternalHttpClient extends HttpClient {
): Observable<any> {
if (typeof first === 'string') {
this.#setPlaceholderContext(options);
return super.request(first, url, options);
return super.request(first, url || '', options);
}
this.#setPlaceholderContext(first);

22
npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts

@ -17,8 +17,8 @@ import { TreeNode } from '../utils/tree-utils';
providers: [SubscriptionService],
})
export class DynamicLayoutComponent {
layout: Type<any>;
layoutKey: eLayoutType;
layout?: Type<any>;
layoutKey?: eLayoutType;
// TODO: Consider a shared enum (eThemeSharedComponents) for known layouts
readonly layouts = new Map([
@ -29,9 +29,9 @@ export class DynamicLayoutComponent {
isLayoutVisible = true;
private router: Router;
private route: ActivatedRoute;
private routes: RoutesService;
private router!: Router;
private route!: ActivatedRoute;
private routes!: RoutesService;
constructor(
injector: Injector,
@ -42,7 +42,7 @@ export class DynamicLayoutComponent {
@Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent,
) {
if (dynamicLayoutComponent) {
if (isDevMode) console.warn('DynamicLayoutComponent must be used only in AppComponent.');
if (isDevMode()) console.warn('DynamicLayoutComponent must be used only in AppComponent.');
return;
}
this.route = injector.get(ActivatedRoute);
@ -80,8 +80,10 @@ export class DynamicLayoutComponent {
if (this.layoutKey === expectedLayout) return;
const key = this.layouts.get(expectedLayout);
this.layout = this.getComponent(key)?.component;
this.layoutKey = expectedLayout;
if (key) {
this.layout = this.getComponent(key)?.component;
this.layoutKey = expectedLayout;
}
if(!this.layout){
this.showLayoutNotFoundError(expectedLayout);
}
@ -94,7 +96,7 @@ export class DynamicLayoutComponent {
}
console.warn(message);
}
private listenToLanguageChange() {
this.subscription.addOne(this.localizationService.languageChange$, () => {
@ -103,7 +105,7 @@ export class DynamicLayoutComponent {
});
}
private getComponent(key: string): ReplaceableComponents.ReplaceableComponent {
private getComponent(key: string): ReplaceableComponents.ReplaceableComponent | undefined {
return this.replaceableComponents.get(key);
}
}

6
npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts

@ -13,11 +13,11 @@ import { SubscriptionService } from '../services/subscription.service';
providers: [SubscriptionService],
})
export class ReplaceableRouteContainerComponent implements OnInit {
defaultComponent: Type<any>;
defaultComponent!: Type<any>;
componentKey: string;
componentKey!: string;
externalComponent: Type<any>;
externalComponent?: Type<any>;
constructor(
private route: ActivatedRoute,

4
npm/ng-packs/packages/core/src/lib/directives/debounce.directive.ts

@ -16,7 +16,9 @@ export class InputEventDebounceDirective implements OnInit {
constructor(private el: ElementRef, private subscription: SubscriptionService) {}
ngOnInit(): void {
const input$ = fromEvent(this.el.nativeElement, 'input').pipe(debounceTime(this.debounce));
const input$ = fromEvent<InputEvent>(this.el.nativeElement, 'input').pipe(
debounceTime(this.debounce),
);
this.subscription.addOne(input$, (event: Event) => {
this.debounceEvent.emit(event);

58
npm/ng-packs/packages/core/src/lib/directives/for.directive.ts

@ -38,32 +38,32 @@ class RecordView {
export class ForDirective implements OnChanges {
// eslint-disable-next-line @angular-eslint/no-input-rename
@Input('abpForOf')
items: any[];
items!: any[];
@Input('abpForOrderBy')
orderBy: string;
orderBy?: string;
@Input('abpForOrderDir')
orderDir: 'ASC' | 'DESC';
orderDir?: 'ASC' | 'DESC';
@Input('abpForFilterBy')
filterBy: string;
filterBy?: string;
@Input('abpForFilterVal')
filterVal: any;
@Input('abpForTrackBy')
trackBy;
trackBy?: TrackByFunction<any>;
@Input('abpForCompareBy')
compareBy: CompareFn;
compareBy?: CompareFn;
@Input('abpForEmptyRef')
emptyRef: TemplateRef<any>;
emptyRef?: TemplateRef<any>;
private differ: IterableDiffer<any>;
private differ!: IterableDiffer<any> | null;
private isShowEmptyRef: boolean;
private isShowEmptyRef!: boolean;
get compareFn(): CompareFn {
return this.compareBy || compare;
@ -83,22 +83,29 @@ export class ForDirective implements OnChanges {
const rw: RecordView[] = [];
changes.forEachOperation(
(record: IterableChangeRecord<any>, previousIndex: number, currentIndex: number) => {
(
record: IterableChangeRecord<any>,
previousIndex: number | null,
currentIndex: number | null,
) => {
if (record.previousIndex == null) {
const view = this.vcRef.createEmbeddedView(
this.tempRef,
new AbpForContext(null, -1, -1, this.items),
currentIndex,
currentIndex || 0,
);
rw.push(new RecordView(record, view));
} else if (currentIndex == null) {
} else if (currentIndex == null && previousIndex !== null) {
this.vcRef.remove(previousIndex);
} else {
const view = this.vcRef.get(previousIndex);
this.vcRef.move(view, currentIndex);
rw.push(new RecordView(record, view as EmbeddedViewRef<AbpForContext>));
if (previousIndex !== null) {
const view = this.vcRef.get(previousIndex);
if (view && currentIndex !== null) {
this.vcRef.move(view, currentIndex);
rw.push(new RecordView(record, view as EmbeddedViewRef<AbpForContext>));
}
}
}
},
);
@ -117,8 +124,10 @@ export class ForDirective implements OnChanges {
}
changes.forEachIdentityChange((record: IterableChangeRecord<any>) => {
const viewRef = this.vcRef.get(record.currentIndex) as EmbeddedViewRef<AbpForContext>;
viewRef.context.$implicit = record.item;
if (record.currentIndex !== null) {
const viewRef = this.vcRef.get(record.currentIndex) as EmbeddedViewRef<AbpForContext>;
viewRef.context.$implicit = record.item;
}
});
}
@ -152,10 +161,9 @@ export class ForDirective implements OnChanges {
}
private sortItems(items: any[]) {
if (this.orderBy) {
items.sort((a, b) =>
a[this.orderBy] > b[this.orderBy] ? 1 : a[this.orderBy] < b[this.orderBy] ? -1 : 0,
);
const orderBy = this.orderBy;
if (orderBy) {
items.sort((a, b) => (a[orderBy] > b[orderBy] ? 1 : a[orderBy] < b[orderBy] ? -1 : 0));
} else {
items.sort();
}
@ -166,13 +174,13 @@ export class ForDirective implements OnChanges {
if (!Array.isArray(items)) return;
const compareFn = this.compareFn;
const filterBy = this.filterBy;
if (
typeof this.filterBy !== 'undefined' &&
typeof filterBy !== 'undefined' &&
typeof this.filterVal !== 'undefined' &&
this.filterVal !== ''
) {
items = items.filter(item => compareFn(item[this.filterBy], this.filterVal));
items = items.filter(item => compareFn(item[filterBy], this.filterVal));
}
switch (this.orderDir) {

12
npm/ng-packs/packages/core/src/lib/directives/form-submit.directive.ts

@ -8,7 +8,7 @@ import {
Output,
Self,
} from '@angular/core';
import { UntypedFormControl, UntypedFormGroup, FormGroupDirective } from '@angular/forms';
import { FormGroupDirective, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { fromEvent } from 'rxjs';
import { debounceTime, filter } from 'rxjs/operators';
import { SubscriptionService } from '../services/subscription.service';
@ -26,8 +26,9 @@ export class FormSubmitDirective implements OnInit {
@Input()
debounce = 200;
// TODO: Remove unused input
@Input()
notValidateOnSubmit: string | boolean;
notValidateOnSubmit?: string | boolean;
@Input()
markAsDirtyWhenSubmit = true;
@ -41,11 +42,10 @@ export class FormSubmitDirective implements OnInit {
private host: ElementRef<HTMLFormElement>,
private cdRef: ChangeDetectorRef,
private subscription: SubscriptionService,
) { }
) {}
ngOnInit() {
this.subscription.addOne(this.formGroupDirective.ngSubmit, () => {
if (this.markAsDirtyWhenSubmit) {
this.markAsDirty();
}
@ -53,10 +53,10 @@ export class FormSubmitDirective implements OnInit {
this.executedNgSubmit = true;
});
const keyup$ = fromEvent(this.host.nativeElement as HTMLElement, 'keyup').pipe(
const keyup$ = fromEvent<KeyboardEvent>(this.host.nativeElement as HTMLElement, 'keyup').pipe(
debounceTime(this.debounce),
filter(event => !(event.target instanceof HTMLTextAreaElement)),
filter((event: KeyboardEvent) => event && event.key === 'Enter'),
filter(event => event && event.key === 'Enter'),
);
this.subscription.addOne(keyup$, () => {

15
npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts

@ -1,5 +1,4 @@
import {
ComponentRef,
Directive,
Injector,
Input,
@ -24,7 +23,7 @@ import { SubscriptionService } from '../services/subscription.service';
})
export class ReplaceableTemplateDirective implements OnInit, OnChanges {
@Input('abpReplaceableTemplate')
data: ReplaceableComponents.ReplaceableTemplateDirectiveInput<any, any>;
data!: ReplaceableComponents.ReplaceableTemplateDirectiveInput<any, any>;
providedData = {
inputs: {},
@ -140,11 +139,15 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges {
[key]: {
enumerable: true,
configurable: true,
get: () => this.data.inputs[key]?.value,
...(this.data.inputs[key]?.twoWay && {
get: () => this.data.inputs?.[key]?.value,
...(this.data.inputs?.[key]?.twoWay && {
set: (newValue: any) => {
this.data.inputs[key].value = newValue;
this.data.outputs[`${key}Change`](newValue);
if (this.data.inputs?.[key]) {
this.data.inputs[key].value = newValue;
}
if (this.data.outputs?.[`${key}Change`]) {
this.data.outputs[`${key}Change`](newValue);
}
},
}),
},

2
npm/ng-packs/packages/core/src/lib/directives/stop-propagation.directive.ts

@ -13,7 +13,7 @@ export class StopPropagationDirective implements OnInit {
constructor(private el: ElementRef, private subscription: SubscriptionService) {}
ngOnInit(): void {
this.subscription.addOne(fromEvent(this.el.nativeElement, 'click'), (event: MouseEvent) => {
this.subscription.addOne(fromEvent<MouseEvent>(this.el.nativeElement, 'click'), event => {
event.stopPropagation();
this.stopPropEvent.emit(event);
});

20
npm/ng-packs/packages/core/src/lib/handlers/routes.handler.ts

@ -12,16 +12,16 @@ export class RoutesHandler {
}
addRoutes() {
this.router?.config?.forEach(({ path = '', data }: RouteData) => {
if (!data?.routes) return;
if (Array.isArray(data.routes)) {
this.routes.add(data.routes);
return;
(this.router?.config as RouteData[])?.forEach(({ path = '', data }: RouteData) => {
const routes = data?.routes;
if (!routes) return;
if (Array.isArray(routes)) {
this.routes.add(routes);
} else {
const routesFlatten = flatRoutes([{ path, ...routes }], { path: '' });
this.routes.add(routesFlatten);
}
const routes = flatRoutes([{ path, ...data.routes }], { path: '' });
this.routes.add(routes);
});
}
}
@ -39,7 +39,7 @@ function flatRoutes(routes: RouteDef[], parent: any) {
acc.push(current, ...flatRoutes(children, current));
return acc;
}, []);
}, [] as ABP.Route[]);
}
type RouteDef = ABP.Route & { children: RouteDef[] };

2
npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts

@ -10,7 +10,7 @@ export class ApiInterceptor implements IApiInterceptor {
constructor(private httpWaitService: HttpWaitService) {}
getAdditionalHeaders(existingHeaders?: HttpHeaders) {
return existingHeaders;
return existingHeaders || new HttpHeaders();
}
intercept(request: HttpRequest<any>, next: HttpHandler) {

6
npm/ng-packs/packages/core/src/lib/models/auth.ts

@ -12,6 +12,8 @@ export type PipeToLoginFn = (
params: Pick<LoginParams, 'redirectUrl' | 'rememberMe'>,
injector: Injector,
) => UnaryFunction<any, any>;
export type SetTokenResponseToStorageFn<T = any> = (injector: Injector, tokenRes: T) => void;
/**
* @deprecated The interface should not be used anymore.
*/
export type SetTokenResponseToStorageFn<T = any> = (tokenRes: T) => void;
export type CheckAuthenticationStateFn = (injector: Injector) => void;

38
npm/ng-packs/packages/core/src/lib/models/dtos.ts

@ -1,17 +1,18 @@
import { ABP } from './common';
import { checkHasProp } from '../utils/common-utils';
export class ListResultDto<T> {
items?: T[];
constructor(initialValues: Partial<ListResultDto<T>> = {}) {
for (const key in initialValues) {
if (Object.prototype.hasOwnProperty.call(initialValues, key)) {
if (checkHasProp(initialValues, key)) {
this[key] = initialValues[key];
}
}
}
}
type ValueOf<T> = T[keyof T];
export class PagedResultDto<T> extends ListResultDto<T> {
totalCount?: number;
@ -25,11 +26,8 @@ export class LimitedResultRequestDto {
constructor(initialValues: Partial<LimitedResultRequestDto> = {}) {
for (const key in initialValues) {
if (
Object.prototype.hasOwnProperty.call(initialValues, key) &&
initialValues[key] !== undefined
) {
this[key] = initialValues[key];
if (checkHasProp(initialValues, key) && initialValues[key] !== undefined) {
this[key] = initialValues[key] as ValueOf<LimitedResultRequestDto>;
}
}
}
@ -56,7 +54,7 @@ export class EntityDto<TKey = string> {
constructor(initialValues: Partial<EntityDto<TKey>> = {}) {
for (const key in initialValues) {
if (Object.prototype.hasOwnProperty.call(initialValues, key)) {
if (checkHasProp(initialValues, key)) {
this[key] = initialValues[key];
}
}
@ -130,11 +128,11 @@ export class FullAuditedEntityWithUserDto<
}
export class ExtensibleObject {
extraProperties: ABP.Dictionary<any>;
extraProperties?: ABP.Dictionary<any>;
constructor(initialValues: Partial<ExtensibleObject> = {}) {
for (const key in initialValues) {
if (Object.prototype.hasOwnProperty.call(initialValues, key)) {
if (checkHasProp(initialValues, key)) {
this[key] = initialValues[key];
}
}
@ -142,7 +140,7 @@ export class ExtensibleObject {
}
export class ExtensibleEntityDto<TKey = string> extends ExtensibleObject {
id: TKey;
id?: TKey;
constructor(initialValues: Partial<ExtensibleEntityDto<TKey>> = {}) {
super(initialValues);
@ -152,7 +150,7 @@ export class ExtensibleEntityDto<TKey = string> extends ExtensibleObject {
export class ExtensibleCreationAuditedEntityDto<
TPrimaryKey = string,
> extends ExtensibleEntityDto<TPrimaryKey> {
creationTime: Date | string;
creationTime?: Date | string;
creatorId?: string;
constructor(initialValues: Partial<ExtensibleCreationAuditedEntityDto<TPrimaryKey>> = {}) {
@ -175,8 +173,8 @@ export class ExtensibleAuditedEntityWithUserDto<
TPrimaryKey = string,
TUserDto = any,
> extends ExtensibleAuditedEntityDto<TPrimaryKey> {
creator: TUserDto;
lastModifier: TUserDto;
creator?: TUserDto;
lastModifier?: TUserDto;
constructor(initialValues: Partial<ExtensibleAuditedEntityWithUserDto<TPrimaryKey>> = {}) {
super(initialValues);
@ -187,7 +185,7 @@ export class ExtensibleCreationAuditedEntityWithUserDto<
TPrimaryKey = string,
TUserDto = any,
> extends ExtensibleCreationAuditedEntityDto<TPrimaryKey> {
creator: TUserDto;
creator?: TUserDto;
constructor(
initialValues: Partial<ExtensibleCreationAuditedEntityWithUserDto<TPrimaryKey>> = {},
@ -199,9 +197,9 @@ export class ExtensibleCreationAuditedEntityWithUserDto<
export class ExtensibleFullAuditedEntityDto<
TPrimaryKey = string,
> extends ExtensibleAuditedEntityDto<TPrimaryKey> {
isDeleted: boolean;
isDeleted?: boolean;
deleterId?: string;
deletionTime: Date | string;
deletionTime?: Date | string;
constructor(initialValues: Partial<ExtensibleFullAuditedEntityDto<TPrimaryKey>> = {}) {
super(initialValues);
@ -212,9 +210,9 @@ export class ExtensibleFullAuditedEntityWithUserDto<
TPrimaryKey = string,
TUserDto = any,
> extends ExtensibleFullAuditedEntityDto<TPrimaryKey> {
creator: TUserDto;
lastModifier: TUserDto;
deleter: TUserDto;
creator?: TUserDto;
lastModifier?: TUserDto;
deleter?: TUserDto;
constructor(initialValues: Partial<ExtensibleFullAuditedEntityWithUserDto<TPrimaryKey>> = {}) {
super(initialValues);

5
npm/ng-packs/packages/core/src/lib/models/environment.ts

@ -17,9 +17,10 @@ export interface ApplicationInfo {
baseUrl?: string;
logoUrl?: string;
}
export interface ApiConfig {
export interface HasAdditional {
[key: string]: string;
}
export interface ApiConfig extends Partial<HasAdditional>{
url: string;
rootNamespace?: string;
}

2
npm/ng-packs/packages/core/src/lib/models/session.ts

@ -3,6 +3,6 @@ import { CurrentTenantDto } from '../proxy/volo/abp/asp-net-core/mvc/multi-tenan
export namespace Session {
export interface State {
language: string;
tenant: CurrentTenantDto;
tenant: CurrentTenantDto | null;
}
}

6
npm/ng-packs/packages/core/src/lib/providers/locale.provider.ts

@ -1,6 +1,7 @@
import { LOCALE_ID, Provider } from '@angular/core';
import { differentLocales } from '../constants/different-locales';
import { LocalizationService } from '../services/localization.service';
import { checkHasProp } from '../utils/common-utils';
export class LocaleId extends String {
constructor(private localizationService: LocalizationService) {
@ -9,7 +10,10 @@ export class LocaleId extends String {
toString(): string {
const { currentLang } = this.localizationService;
return differentLocales[currentLang] || currentLang;
if (checkHasProp(differentLocales, currentLang)) {
return differentLocales[currentLang];
}
return currentLang;
}
valueOf(): string {

15
npm/ng-packs/packages/core/src/lib/services/config-state.service.ts

@ -1,5 +1,5 @@
import { Inject, Injectable } from '@angular/core';
import { Observable, Subject, of } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { map, switchMap, take, tap } from 'rxjs/operators';
import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service';
import { AbpApplicationLocalizationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-localization.service';
@ -47,6 +47,9 @@ export class ConfigStateService {
private getLocalizationAndCombineWithAppState(
appState: ApplicationConfigurationDto,
): Observable<ApplicationConfigurationDto> {
if (!appState.localization.currentCulture.cultureName) {
throw new Error('culture name should defined');
}
return this.getlocalizationResource(appState.localization.currentCulture.cultureName).pipe(
map(result => ({ ...appState, localization: { ...appState.localization, ...result } })),
);
@ -77,11 +80,11 @@ export class ConfigStateService {
}
}
getOne$(key: string) {
getOne$<K extends keyof ApplicationConfigurationDto>(key: K) {
return this.store.sliceState(state => state[key]);
}
getOne(key: string) {
getOne<K extends keyof ApplicationConfigurationDto>(key: K) {
return this.store.state[key];
}
@ -138,7 +141,7 @@ export class ConfigStateService {
return keys.reduce((acc, key) => ({ ...acc, [key]: features.values[key] }), {});
}
getFeatures$(keys: string[]) {
getFeatures$(keys: string[]): Observable<{[key: string]: string} | undefined> {
return this.store.sliceState(({ features }) => {
if (!features?.values) return;
@ -164,7 +167,7 @@ export class ConfigStateService {
return keysFound.reduce((acc, key) => {
acc[key] = settings[key];
return acc;
}, {});
}, {} as Record<string, string>);
}
getSettings$(keyword?: string) {
@ -179,7 +182,7 @@ export class ConfigStateService {
return keysFound.reduce((acc, key) => {
acc[key] = settings[key];
return acc;
}, {});
}, {} as Record<string, string>);
}),
);
}

11
npm/ng-packs/packages/core/src/lib/services/dom-insertion.service.ts

@ -8,7 +8,7 @@ export class DomInsertionService {
insertContent<T extends HTMLScriptElement | HTMLStyleElement>(
contentStrategy: ContentStrategy<T>,
): T {
): T | undefined {
const hash = generateHash(contentStrategy.content);
if (this.inserted.has(hash)) return;
@ -20,10 +20,11 @@ export class DomInsertionService {
}
removeContent(element: HTMLScriptElement | HTMLStyleElement) {
const hash = generateHash(element.textContent);
this.inserted.delete(hash);
element.parentNode.removeChild(element);
if (element.textContent) {
const hash = generateHash(element.textContent);
this.inserted.delete(hash);
element.parentNode?.removeChild(element);
}
}
has(content: string): boolean {

17
npm/ng-packs/packages/core/src/lib/services/environment.service.ts

@ -4,10 +4,10 @@ import { map } from 'rxjs/operators';
import { Apis, Environment } from '../models/environment';
import { InternalStore } from '../utils/internal-store-utils';
const mapToApiUrl = (key: string) => (apis: Apis) =>
(apis[key] || apis.default).url || apis.default.url;
const mapToApiUrl = (key: string | undefined) => (apis: Apis) =>
((key && apis[key]) || apis.default).url || apis.default.url;
const mapToIssuer = (issuer: string) => {
const mapToIssuer = (issuer: string | undefined) => {
if (!issuer) {
return issuer;
}
@ -30,8 +30,8 @@ export class EnvironmentService {
return this.store.state;
}
getApiUrl(key: string) {
return mapToApiUrl(key)(this.store.state.apis);
getApiUrl(key: string | undefined) {
return mapToApiUrl(key)(this.store.state?.apis);
}
getApiUrl$(key: string) {
@ -43,11 +43,12 @@ export class EnvironmentService {
}
getIssuer() {
const issuer = this.store.state.oAuthConfig.issuer;
const issuer = this.store.state?.oAuthConfig?.issuer;
return mapToIssuer(issuer);
}
getIssuer$() {
return this.store.sliceState(state => state.oAuthConfig.issuer).pipe(map(mapToIssuer));
return this.store.sliceState(state => state?.oAuthConfig?.issuer).pipe(map(mapToIssuer));
}
}

5
npm/ng-packs/packages/core/src/lib/services/http-wait.service.ts

@ -86,7 +86,10 @@ export class HttpWaitService {
this.store.patch({ filteredRequests });
}
private applyFilter(requests: HttpRequest<any>[]) {
private applyFilter(requests: HttpRequest<any>[] | undefined) {
if (!requests) {
return [];
}
const { filteredRequests } = this.store.state;
return requests.filter(
({ method, url }) =>

10
npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { concat, Observable, of, throwError } from 'rxjs';
import { concat, Observable, of, pipe, throwError } from 'rxjs';
import { delay, retryWhen, shareReplay, take, tap } from 'rxjs/operators';
import { LoadingStrategy } from '../strategies';
import { ResourceWaitService } from './resource-wait.service';
@ -15,11 +15,13 @@ export class LazyLoadService {
load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Observable<Event> {
if (this.loaded.has(strategy.path)) return of(new CustomEvent('load'));
this.resourceWaitService.addResource(strategy.path);
const delayOperator = retryDelay ? pipe(delay(retryDelay)) : pipe();
const takeOp = retryTimes ? pipe(take(retryTimes)) : pipe();
return strategy.createStream().pipe(
retryWhen(error$ =>
concat(
error$.pipe(delay(retryDelay), take(retryTimes)),
throwError(new CustomEvent('error')),
error$.pipe(delayOperator, takeOp),
throwError(() => new CustomEvent('error')),
),
),
tap(() => {
@ -36,7 +38,7 @@ export class LazyLoadService {
if (!element) return false;
element.parentNode.removeChild(element);
element.parentNode?.removeChild(element);
this.loaded.delete(path);
return true;
}

22
npm/ng-packs/packages/core/src/lib/services/localization.service.ts

@ -90,15 +90,16 @@ export class LocalizationService {
Object.entries(remote).forEach(entry => {
const resourceName = entry[0];
const remoteTexts = entry[1];
let resource = local.get(resourceName) || {};
let resource = local?.get(resourceName) || {};
resource = { ...resource, ...remoteTexts };
local.set(resourceName, resource);
local?.set(resourceName, resource);
});
}
return local;
}),
filter(Boolean)
)
.subscribe(val => this.localizations$.next(val));
}
@ -134,6 +135,7 @@ export class LocalizationService {
lang => this.configState.getDeep('localization.currentCulture.cultureName') !== lang,
),
switchMap(lang => this.configState.refreshAppState().pipe(map(() => lang))),
filter(Boolean),
switchMap(lang => from(this.registerLocale(lang).then(() => lang))),
)
.subscribe(lang => this._languageChange$.next(lang));
@ -176,14 +178,14 @@ export class LocalizationService {
return this.getLocalization(this.configState.getAll(), key, ...interpolateParams);
}
localize(resourceName: string, key: string, defaultValue: string): Observable<string> {
localize(resourceName: string, key: string, defaultValue: string): Observable<string | null> {
return this.configState.getOne$('localization').pipe(
map(createLocalizer),
map(localize => localize(resourceName, key, defaultValue)),
);
}
localizeSync(resourceName: string, key: string, defaultValue: string): string {
localizeSync(resourceName: string, key: string, defaultValue: string): string | null {
const localization = this.configState.getOne('localization');
return createLocalizer(localization)(resourceName, key, defaultValue);
}
@ -210,7 +212,7 @@ export class LocalizationService {
...interpolateParams: string[]
) {
if (!key) key = '';
let defaultValue: string;
let defaultValue = '';
if (typeof key !== 'string') {
defaultValue = key.defaultValue;
@ -219,7 +221,7 @@ export class LocalizationService {
const keys = key.split('::') as string[];
const warn = (message: string) => {
if (isDevMode) console.warn(message);
if (isDevMode()) console.warn(message);
};
if (keys.length < 2) {
@ -263,7 +265,7 @@ export class LocalizationService {
}
}
function recursivelyMergeBaseResources(baseResourceName: string, source: ResourceDto) {
function recursivelyMergeBaseResources(baseResourceName: string, source: ResourceDto): ApplicationLocalizationResourceDto {
const item = source[baseResourceName];
if (item.baseResources.length === 0) {
@ -278,7 +280,7 @@ function recursivelyMergeBaseResources(baseResourceName: string, source: Resourc
}
function mergeResourcesWithBaseResource(resource: ResourceDto): ResourceDto {
const entities = Object.keys(resource).map(key => {
const entities: Array<[string, ApplicationLocalizationResourceDto]> = Object.keys(resource).map(key => {
const newValue = recursivelyMergeBaseResources(key, resource);
return [key, newValue];
});
@ -296,5 +298,5 @@ function combineLegacyandNewResources(
}, legacy);
}
type LegacyLanguageDto = Record<string, Record<string, string>>;
type ResourceDto = Record<string, ApplicationLocalizationResourceDto>;
export type LegacyLanguageDto = Record<string, Record<string, string>>;
export type ResourceDto = Record<string, ApplicationLocalizationResourceDto>;

2
npm/ng-packs/packages/core/src/lib/services/multi-tenancy.service.ts

@ -12,7 +12,7 @@ import { SessionStateService } from './session-state.service';
@Injectable({ providedIn: 'root' })
export class MultiTenancyService {
domainTenant: CurrentTenantDto = null;
domainTenant: CurrentTenantDto | null = null;
isTenantBoxVisible = true;

4
npm/ng-packs/packages/core/src/lib/services/permission.service.ts

@ -14,7 +14,7 @@ export class PermissionService {
);
}
getGrantedPolicy(key: string) {
getGrantedPolicy(key: string | undefined) {
const policies = this.getSnapshot();
return this.isPolicyGranted(key, policies);
}
@ -36,7 +36,7 @@ export class PermissionService {
);
}
protected isPolicyGranted(key: string, grantedPolicies: Record<string, boolean>) {
protected isPolicyGranted(key: string | undefined, grantedPolicies: Record<string, boolean>) {
if (!key) return true;
const orRegexp = /\|\|/g;

6
npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts

@ -23,7 +23,7 @@ export class ReplaceableComponentsService {
}
constructor(private ngZone: NgZone, private router: Router) {
this.store = new InternalStore([]);
this.store = new InternalStore([] as ReplaceableComponents.ReplaceableComponent[]);
}
add(replaceableComponent: ReplaceableComponents.ReplaceableComponent, reload?: boolean): void {
@ -44,11 +44,11 @@ export class ReplaceableComponentsService {
if (reload) reloadRoute(this.router, this.ngZone);
}
get(replaceableComponentKey: string): ReplaceableComponents.ReplaceableComponent {
get(replaceableComponentKey: string): ReplaceableComponents.ReplaceableComponent | undefined {
return this.replaceableComponents.find(component => component.key === replaceableComponentKey);
}
get$(replaceableComponentKey: string): Observable<ReplaceableComponents.ReplaceableComponent> {
get$(replaceableComponentKey: string): Observable<ReplaceableComponents.ReplaceableComponent | undefined> {
return this.replaceableComponents$.pipe(
map(components => components.find(component => component.key === replaceableComponentKey)),
);

2
npm/ng-packs/packages/core/src/lib/services/resource-wait.service.ts

@ -20,7 +20,7 @@ export class ResourceWaitService {
}
updateLoading$() {
return this.store.sliceUpdate(({ resources }) => !!resources.size);
return this.store.sliceUpdate(({ resources }) => !!resources?.size);
}
clearLoading() {

7
npm/ng-packs/packages/core/src/lib/services/rest.service.ts

@ -20,7 +20,7 @@ export class RestService {
protected httpErrorReporter: HttpErrorReporterService,
) {}
protected getApiFromStore(apiName: string): string {
protected getApiFromStore(apiName: string | undefined): string {
return this.environment.getApiUrl(apiName);
}
@ -51,13 +51,12 @@ export class RestService {
}
private getParams(params: Rest.Params, encoder?: HttpParameterCodec): HttpParams {
const filteredParams = Object.keys(params).reduce((acc, key) => {
const value = params[key];
const filteredParams = Object.entries(params).reduce((acc, [key, value]) => {
if (isUndefinedOrEmptyString(value)) return acc;
if (value === null && !this.options.sendNullsAsQueryParam) return acc;
acc[key] = value;
return acc;
}, {});
}, {} as any);
return encoder
? new HttpParams({ encoder, fromObject: filteredParams })
: new HttpParams({ fromObject: filteredParams });

5
npm/ng-packs/packages/core/src/lib/services/router-events.service.ts

@ -6,6 +6,7 @@ import {
NavigationStart,
Router,
RouterEvent,
Event
} from '@angular/router';
import { filter } from 'rxjs/operators';
@ -23,7 +24,7 @@ export class RouterEvents {
getEvents<T extends RouterEventConstructors>(...eventTypes: T) {
type FilteredRouterEvent = T extends Type<infer Ctor>[] ? Ctor : never;
const filterRouterEvents = (event: RouterEvent): event is FilteredRouterEvent =>
const filterRouterEvents = (event: Event): event is FilteredRouterEvent =>
eventTypes.some(type => event instanceof type);
return this.router.events.pipe(filter(filterRouterEvents));
@ -36,7 +37,7 @@ export class RouterEvents {
: never
: never;
const filterNavigationEvents = (event: RouterEvent): event is FilteredNavigationEvent =>
const filterNavigationEvents = (event: Event): event is FilteredNavigationEvent =>
navigationEventKeys.some(key => event instanceof NavigationEvent[key]);
return this.router.events.pipe(filter(filterNavigationEvents));

12
npm/ng-packs/packages/core/src/lib/services/routes.service.ts

@ -7,7 +7,7 @@ import { ConfigStateService } from './config-state.service';
import { PermissionService } from './permission.service';
// eslint-disable-next-line @typescript-eslint/ban-types
export abstract class AbstractTreeService<T extends object> {
export abstract class AbstractTreeService<T extends {[key: string | number | symbol]: any}> {
abstract id: string;
abstract parentId: string;
abstract hide: (item: T) => boolean;
@ -84,7 +84,7 @@ export abstract class AbstractTreeService<T extends object> {
}
find(predicate: (item: TreeNode<T>) => boolean, tree = this.tree): TreeNode<T> | null {
return tree.reduce(
return tree.reduce<TreeNode<T> | null>(
(acc, node) => (acc ? acc : predicate(node) ? node : this.find(predicate, node.children)),
null,
);
@ -119,9 +119,9 @@ export abstract class AbstractTreeService<T extends object> {
}
search(params: Partial<T>, tree = this.tree): TreeNode<T> | null {
const searchKeys = Object.keys(params);
const searchKeys = Object.keys(params) as Array<keyof Partial<T>>;
return tree.reduce(
return tree.reduce<TreeNode<T> | null>(
(acc, node) =>
acc
? acc
@ -147,7 +147,7 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
if (!Number.isInteger(a.order)) return 1;
if (!Number.isInteger(b.order)) return -1;
return a.order - b.order;
return (a.order as number) - (b.order as number);
};
constructor(protected injector: Injector) {
@ -170,7 +170,7 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
hasInvisibleChild(identifier: string): boolean {
const node = this.find(item => item[this.id] === identifier);
return node?.children?.some(child => child.invisible);
return node?.children?.some(child => child.invisible) || false;
}
/* istanbul ignore next */

2
npm/ng-packs/packages/core/src/lib/services/session-state.service.ts

@ -73,7 +73,7 @@ export class SessionStateService {
return this.store.sliceState(state => state.tenant);
}
setTenant(tenant: CurrentTenantDto) {
setTenant(tenant: CurrentTenantDto | null) {
if (compare(tenant, this.store.state.tenant)) return;
this.store.set({ ...this.store.state, tenant });

8
npm/ng-packs/packages/core/src/lib/services/subscription.service.ts

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import type { OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { Injectable } from '@angular/core';
import type { Observable, PartialObserver } from 'rxjs';
import { Subscription } from 'rxjs';
@Injectable()
export class SubscriptionService implements OnDestroy {
@ -33,7 +33,9 @@ export class SubscriptionService implements OnDestroy {
closeOne(subscription: Subscription | undefined | null) {
this.removeOne(subscription);
subscription.unsubscribe();
if (subscription) {
subscription.unsubscribe();
}
}
ngOnDestroy(): void {

4
npm/ng-packs/packages/core/src/lib/strategies/content-security.strategy.ts

@ -10,7 +10,9 @@ export class LooseContentSecurityStrategy extends ContentSecurityStrategy {
}
applyCSP(element: HTMLScriptElement | HTMLStyleElement) {
element.setAttribute('nonce', this.nonce);
if (this.nonce) {
element.setAttribute('nonce', this.nonce);
}
}
}

10
npm/ng-packs/packages/core/src/lib/strategies/content.strategy.ts

@ -1,5 +1,5 @@
import { ContentSecurityStrategy, CONTENT_SECURITY_STRATEGY } from './content-security.strategy';
import { DomStrategy, DOM_STRATEGY } from './dom.strategy';
import { CONTENT_SECURITY_STRATEGY, ContentSecurityStrategy } from './content-security.strategy';
import { DOM_STRATEGY, DomStrategy } from './dom.strategy';
export type ElementOptions<T extends HTMLScriptElement | HTMLStyleElement = any> = Partial<{
[key in keyof T]: T[key];
@ -19,7 +19,11 @@ export abstract class ContentStrategy<T extends HTMLScriptElement | HTMLStyleEle
const element = this.createElement();
if (this.options && Object.keys(this.options).length > 0) {
Object.keys(this.options).forEach(key => (element[key] = this.options[key]));
(Object.keys(this.options) as Array<keyof ElementOptions<T>>).forEach(key => {
if (this.options[key]) {
element[key] = (this.options as NonNullable<T>)[key];
}
});
}
this.contentSecurityStrategy.applyCSP(element);

2
npm/ng-packs/packages/core/src/lib/strategies/context.strategy.ts

@ -13,7 +13,7 @@ export class NoContextStrategy<
T extends Type<any> | TemplateRef<any> = any,
> extends ContextStrategy<T> {
constructor() {
super(undefined);
super(undefined as unknown as Partial<ContextType<T>>);
}
}

9
npm/ng-packs/packages/core/src/lib/strategies/cross-origin.strategy.ts

@ -1,9 +1,14 @@
export class CrossOriginStrategy {
constructor(public crossorigin: 'anonymous' | 'use-credentials', public integrity?: string) {}
constructor(
public crossorigin: 'anonymous' | 'use-credentials' | null,
public integrity?: string,
) {}
setCrossOrigin<T extends HTMLElement>(element: T) {
if (this.integrity) element.setAttribute('integrity', this.integrity);
element.setAttribute('crossorigin', this.crossorigin);
if (this.crossorigin) {
element.setAttribute('crossorigin', this.crossorigin);
}
}
}

6
npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts

@ -1,11 +1,11 @@
import { Observable, of } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { fromLazyLoad } from '../utils';
import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from './cross-origin.strategy';
import { DomStrategy, DOM_STRATEGY } from './dom.strategy';
import { CROSS_ORIGIN_STRATEGY, CrossOriginStrategy } from './cross-origin.strategy';
import { DOM_STRATEGY, DomStrategy } from './dom.strategy';
export abstract class LoadingStrategy<T extends HTMLScriptElement | HTMLLinkElement = any> {
element: T;
element!: T;
constructor(
public path: string,

4
npm/ng-packs/packages/core/src/lib/tokens/localization.token.ts

@ -4,10 +4,10 @@ import { ABP } from '../models/common';
export const LOCALIZATIONS = new InjectionToken('LOCALIZATIONS');
export function localizationContributor(localizations: ABP.Localization[]) {
export function localizationContributor(localizations?: ABP.Localization[]) {
if (localizations) {
localizations$.next([...localizations$.value, ...localizations]);
}
}
export const localizations$ = new BehaviorSubject([]);
export const localizations$ = new BehaviorSubject<ABP.Localization[]>([]);

3
npm/ng-packs/packages/core/src/lib/tokens/set-token-response-to-storage.token.ts

@ -1,6 +1,9 @@
import { InjectionToken } from '@angular/core';
import { SetTokenResponseToStorageFn } from '../models';
/**
* @deprecated The token should not be used anymore.
*/
export const SET_TOKEN_RESPONSE_TO_STORAGE_FN_KEY = new InjectionToken<SetTokenResponseToStorageFn>(
'SET_TOKEN_RESPONSE_TO_STORAGE_FN_KEY',
);

20
npm/ng-packs/packages/core/src/lib/utils/common-utils.ts

@ -7,34 +7,38 @@ export function isUndefinedOrEmptyString(value: unknown): boolean {
return value === undefined || value === '';
}
export function isNullOrUndefined(obj) {
export function isNullOrUndefined<T>(obj: T) {
return obj === null || obj === undefined;
}
export function isNullOrEmpty(obj){
export function isNullOrEmpty<T>(obj: T): boolean {
return obj === null || obj === undefined || obj === '';
}
export function exists(obj) {
export function exists<T>(obj: T): obj is T {
return !isNullOrUndefined(obj);
}
export function isObject(obj) {
export function isObject<T>(obj: T): boolean {
return obj instanceof Object;
}
export function isArray(obj) {
export function isArray<T>(obj: T): boolean {
return Array.isArray(obj);
}
export function isObjectAndNotArray(obj) {
export function isObjectAndNotArray<T>(obj: T): boolean {
return isObject(obj) && !isArray(obj);
}
export function isNode(obj) {
export function isNode<T>(obj: T): boolean {
return obj instanceof Node;
}
export function isObjectAndNotArrayNotNode(obj) {
export function isObjectAndNotArrayNotNode<T>(obj: T): boolean {
return isObjectAndNotArray(obj) && !isNode(obj);
}
export function checkHasProp<T>(object: T, key: string | keyof T): key is keyof T {
return Object.prototype.hasOwnProperty.call(object, key);
}

5
npm/ng-packs/packages/core/src/lib/utils/date-utils.ts

@ -13,8 +13,7 @@ export function getShortTimeFormat(configStateService: ConfigStateService) {
const dateTimeFormat = configStateService.getDeep(
'localization.currentCulture.dateTimeFormat',
) as DateTimeFormatDto;
return dateTimeFormat.shortTimePattern.replace('tt', 'a');
return dateTimeFormat?.shortTimePattern?.replace('tt', 'a');
}
export function getShortDateShortTimeFormat(configStateService: ConfigStateService) {
@ -22,5 +21,5 @@ export function getShortDateShortTimeFormat(configStateService: ConfigStateServi
'localization.currentCulture.dateTimeFormat',
) as DateTimeFormatDto;
return `${dateTimeFormat.shortDatePattern} ${dateTimeFormat.shortTimePattern.replace('tt', 'a')}`;
return `${dateTimeFormat.shortDatePattern} ${dateTimeFormat?.shortTimePattern?.replace('tt', 'a')}`;
}

10
npm/ng-packs/packages/core/src/lib/utils/environment-utils.ts

@ -24,19 +24,23 @@ export function getRemoteEnv(injector: Injector, environment: Partial<Environmen
httpErrorReporter.reportError(err);
return of(null);
}), // TODO: Consider get handle function from a provider
tap(env => environmentService.setState(mergeEnvironments(environment, env, remoteEnv))),
tap(env =>
environmentService.setState(
mergeEnvironments(environment, env || ({} as Environment), remoteEnv as RemoteEnv),
),
),
)
.toPromise();
}
function mergeEnvironments(
local: Partial<Environment>,
remote: any,
remote: Environment,
config: RemoteEnv,
): Environment {
switch (config.mergeStrategy) {
case 'deepmerge':
return deepMerge(local, remote);
return deepMerge(local, remote) as Environment;
case 'overwrite':
case null:
case undefined:

4
npm/ng-packs/packages/core/src/lib/utils/factory-utils.ts

@ -21,7 +21,7 @@ export class LazyModuleFactory<T> extends NgModuleFactory<T> {
create(parentInjector: Injector | null): NgModuleRef<T> {
const injector = Injector.create({
parent: parentInjector,
...(parentInjector && { parent: parentInjector }),
providers: this.moduleWithProviders.providers as StaticProvider[],
});
@ -35,7 +35,7 @@ export class LazyModuleFactory<T> extends NgModuleFactory<T> {
export function featuresFactory(
configState: ConfigStateService,
featureKeys: string[],
mapFn: (features) => any = features => features,
mapFn: (features: { [key: string]: string }) => any = features => features,
) {
return configState.getFeatures$(featureKeys).pipe(filter(Boolean), map(mapFn));
}

3
npm/ng-packs/packages/core/src/lib/utils/internal-store-utils.ts

@ -37,7 +37,8 @@ export class InternalStore<State> {
}
deepPatch(state: DeepPartial<State>) {
this.state$.next(deepMerge(this.state, state));
// TODO: Strict improve deepMerge
this.state$.next(deepMerge(this.state, state) as State);
this.update$.next(state);
}

12
npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts

@ -1,6 +1,6 @@
import { Observable, Observer } from 'rxjs';
import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from '../strategies/cross-origin.strategy';
import { DomStrategy, DOM_STRATEGY } from '../strategies/dom.strategy';
import { CROSS_ORIGIN_STRATEGY, CrossOriginStrategy } from '../strategies/cross-origin.strategy';
import { DOM_STRATEGY, DomStrategy } from '../strategies/dom.strategy';
export function fromLazyLoad<T extends Event>(
element: HTMLScriptElement | HTMLLinkElement,
@ -11,9 +11,9 @@ export function fromLazyLoad<T extends Event>(
domStrategy.insertElement(element);
return new Observable((observer: Observer<T>) => {
element.onload = (event: T) => {
element.onload = (event: Event) => {
clearCallbacks(element);
observer.next(event);
observer.next(event as T);
observer.complete();
};
@ -32,10 +32,10 @@ export function fromLazyLoad<T extends Event>(
});
}
function createErrorHandler(observer: Observer<Event>, element: HTMLElement) {
function createErrorHandler<T extends Event = Event>(observer: Observer<T>, element: HTMLElement) {
return function (event: Event | string) {
clearCallbacks(element);
element.parentNode.removeChild(element);
element.parentNode?.removeChild(element);
observer.error(event);
};
}

2
npm/ng-packs/packages/core/src/lib/utils/localization-utils.ts

@ -10,7 +10,7 @@ export function getLocaleDirection(locale: string): 'ltr' | 'rtl' {
}
export function createLocalizer(localization: ApplicationLocalizationConfigurationDto) {
return (resourceName: string, key: string, defaultValue: string) => {
return (resourceName: string, key: string, defaultValue: string | null) => {
if (resourceName === '_') return key;
const resource = localization?.values?.[resourceName];

9
npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts

@ -90,18 +90,21 @@ function replaceTenantNameWithinEnvironment(
);
}
if (environment.oAuthConfig.redirectUri) {
if (environment.oAuthConfig?.redirectUri) {
environment.oAuthConfig.redirectUri = environment.oAuthConfig.redirectUri.replace(
placeholder,
tenancyName,
);
}
environment.oAuthConfig.issuer = environment.oAuthConfig.issuer.replace(placeholder, tenancyName);
if(!environment.oAuthConfig) {
environment.oAuthConfig = {};
}
environment.oAuthConfig.issuer = (environment.oAuthConfig.issuer || '').replace(placeholder, tenancyName);
Object.keys(environment.apis).forEach(api => {
Object.keys(environment.apis[api]).forEach(key => {
environment.apis[api][key] = environment.apis[api][key].replace(placeholder, tenancyName);
environment.apis[api][key] = (environment.apis[api][key] || '').replace(placeholder, tenancyName);
});
});

28
npm/ng-packs/packages/core/src/lib/utils/object-utils.ts

@ -6,18 +6,25 @@ import {
isObject,
isObjectAndNotArrayNotNode,
} from './common-utils';
import { DeepPartial } from '../models';
export function deepMerge(target, source) {
export function deepMerge<T>(
target: DeepPartial<T> | T,
source: DeepPartial<T> | T,
): DeepPartial<T> | T {
if (isObjectAndNotArrayNotNode(target) && isObjectAndNotArrayNotNode(source)) {
return deepMergeRecursively(target, source);
} else if (isNullOrUndefined(target) && isNullOrUndefined(source)) {
return {};
return {} as T;
} else {
return exists(source) ? source : target;
return exists(source) ? (source as T) : target;
}
}
function deepMergeRecursively(target, source) {
function deepMergeRecursively<T>(
target: DeepPartial<T> | T,
source: DeepPartial<T> | T,
): DeepPartial<T> | T {
const shouldNotRecurse =
isNullOrUndefined(target) ||
isNullOrUndefined(source) || // at least one not defined
@ -33,14 +40,17 @@ function deepMergeRecursively(target, source) {
* we will prioritize source if it is a defined value.
*/
if (shouldNotRecurse) {
return exists(source) ? source : target;
return exists(source) ? (source as T) : target;
}
const keysOfTarget = Object.keys(target);
const keysOfSource = Object.keys(source);
const keysOfTarget = Object.keys(target as { [key: string]: any });
const keysOfSource = Object.keys(source as { [key: string]: any });
const uniqueKeys = new Set(keysOfTarget.concat(keysOfSource));
return [...uniqueKeys].reduce((retVal, key) => {
retVal[key] = deepMergeRecursively(target[key], source[key]);
(retVal as any)[key] = deepMergeRecursively(
(target as { [key: string]: any })[key],
(source as { [key: string]: any })[key],
);
return retVal;
}, {});
}, {} as T);
}

6
npm/ng-packs/packages/core/src/lib/utils/route-utils.ts

@ -1,11 +1,11 @@
import { NgZone } from '@angular/core';
import { PRIMARY_OUTLET, Router, UrlSegmentGroup } from '@angular/router';
import { PRIMARY_OUTLET, Router, UrlSegment, UrlSegmentGroup } from "@angular/router";
import { ABP } from '../models/common';
import { RoutesService } from '../services/routes.service';
import { noop } from './common-utils';
import { TreeNode } from './tree-utils';
export function findRoute(routesService: RoutesService, path: string): TreeNode<ABP.Route> {
export function findRoute(routesService: RoutesService, path: string): TreeNode<ABP.Route> | null {
const node = routesService.find(route => route.path === path);
return node || path === '/'
@ -14,7 +14,7 @@ export function findRoute(routesService: RoutesService, path: string): TreeNode<
}
export function getRoutePath(router: Router, url = router.url) {
const emptyGroup = { segments: [] } as UrlSegmentGroup;
const emptyGroup = { segments: [] as UrlSegment[] } as UrlSegmentGroup;
const primaryGroup = router.parseUrl(url).root.children[PRIMARY_OUTLET];
return '/' + (primaryGroup || emptyGroup).segments.map(({ path }) => path).join('/');

6
npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts

@ -62,7 +62,7 @@ export function createTreeNodeFilterCreator<T extends object>(
return (search: string) => {
const regex = new RegExp('.*' + search + '.*', 'i');
return function collectNodes(nodes: TreeNode<T>[], matches = []) {
return function collectNodes(nodes: TreeNode<T>[], matches: TreeNode<T>[] = []) {
for (const node of nodes) {
if (regex.test(mapperFn(node[key]))) matches.push(node);
@ -82,8 +82,8 @@ export type TreeNode<T extends object> = {
parent?: TreeNode<T>;
};
type NodeKey = number | string | symbol | undefined | null;
export type NodeKey = number | string | symbol | undefined | null;
type NodeValue<T extends object, F extends (...args: any) => any> = F extends undefined
export type NodeValue<T extends object, F extends (...args: any) => any> = F extends undefined
? TreeNode<T>
: ReturnType<F>;

4
npm/ng-packs/packages/core/src/lib/validators/range.validator.ts

@ -21,10 +21,10 @@ export function validateRange({ maximum = Infinity, minimum = 0 }: RangeOptions
};
}
function getMaxError(value: number, max: number, min: number): RangeError {
function getMaxError(value: number, max: number, min: number): RangeError | null {
return value > max ? { range: { max, min } } : null;
}
function getMinError(value: number, min: number, max: number): RangeError {
function getMinError(value: number, min: number, max: number): RangeError | null {
return value < min ? { range: { min, max } } : null;
}

2
npm/ng-packs/packages/core/src/lib/validators/required.validator.ts

@ -16,7 +16,7 @@ export function validateRequired({ allowEmptyStrings }: RequiredOptions = {}): V
return required;
}
function isValidRequired(value: any, allowEmptyStrings: boolean): boolean {
function isValidRequired(value: any, allowEmptyStrings: boolean | undefined): boolean {
if (value || value === 0 || value === false) return true;
if (allowEmptyStrings && value === '') return true;

4
npm/ng-packs/packages/core/src/lib/validators/string-length.validator.ts

@ -27,10 +27,10 @@ export function validateStringLength({
};
}
function getMaxLengthError(value: string, requiredLength: number): StringLengthError {
function getMaxLengthError(value: string, requiredLength: number): StringLengthError | null {
return value.length > requiredLength ? { maxlength: { requiredLength } } : null;
}
function getMinLengthError(value: string, requiredLength: number): StringLengthError {
function getMinLengthError(value: string, requiredLength: number): StringLengthError | null {
return value.length < requiredLength ? { minlength: { requiredLength } } : null;
}

2
npm/ng-packs/packages/core/src/lib/validators/username.validator.ts

@ -14,7 +14,7 @@ export interface UsernameOptions {
const onlyLetterAndNumberRegex = /^[a-zA-Z0-9]+$/;
export function validateUsername(
{ pattern }: UsernameOptions = { pattern: onlyLetterAndNumberRegex },
{ pattern = /.*/ }: UsernameOptions = { pattern: onlyLetterAndNumberRegex },
): ValidatorFn {
return (control: AbstractControl): UsernamePatternError | null => {
const isValid = isValidUserName(control.value, pattern);

2
npm/ng-packs/packages/core/testing/src/lib/services/mock-permission.service.ts

@ -27,7 +27,7 @@ export class MockPermissionService extends PermissionService {
const grantedPolicies = keys.reduce((policies, key) => {
policies[key] = true;
return policies;
}, {});
}, {} as { [key: string]: boolean });
this.configState['store'].deepPatch({ auth: { grantedPolicies } });
}

10
npm/ng-packs/packages/core/testing/src/lib/services/mock-rest.service.ts

@ -1,4 +1,10 @@
import { ABP, CORE_OPTIONS, EnvironmentService, RestService } from '@abp/ng.core';
import {
ABP,
CORE_OPTIONS,
EnvironmentService,
HttpErrorReporterService,
RestService,
} from '@abp/ng.core';
import { HttpClient } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
@ -12,7 +18,7 @@ export class MockRestService extends RestService {
protected http: HttpClient,
protected environment: EnvironmentService,
) {
super(options, http, environment, null);
super(options, http, environment, null as unknown as HttpErrorReporterService);
}
handleError(err: any): Observable<any> {

75
npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html

@ -1,48 +1,38 @@
<div id="identity-roles-wrapper" class="card">
<div class="card-header">
<div class="row">
<div class="col col-md-6">
<h5 class="card-title">{{ 'AbpIdentity::Roles' | abpLocalization }}</h5>
</div>
<div class="text-end col col-md-6">
<abp-page-toolbar [record]="data.items"></abp-page-toolbar>
</div>
<abp-page [title]="'AbpIdentity::Roles' | abpLocalization" [toolbar]="data.items">
<div id="identity-roles-wrapper" class="card">
<div class="card-body">
<abp-extensible-table
[data]="data.items"
[recordsTotal]="data.totalCount"
[list]="list"
></abp-extensible-table>
</div>
</div>
<div class="card-body">
<abp-extensible-table
[data]="data.items"
[recordsTotal]="data.totalCount"
[list]="list"
></abp-extensible-table>
</div>
</div>
<abp-modal [(visible)]="isModalVisible" [busy]="modalBusy">
<ng-template #abpHeader>
<h3>{{ (selected?.id ? 'AbpIdentity::Edit' : 'AbpIdentity::NewRole') | abpLocalization }}</h3>
</ng-template>
<abp-modal [(visible)]="isModalVisible" [busy]="modalBusy">
<ng-template #abpHeader>
<h3>{{ (selected?.id ? 'AbpIdentity::Edit' : 'AbpIdentity::NewRole') | abpLocalization }}</h3>
</ng-template>
<ng-template #abpBody>
<form [formGroup]="form" (ngSubmit)="save()" validateOnSubmit>
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
</form>
</ng-template>
<ng-template #abpBody>
<form [formGroup]="form" (ngSubmit)="save()" validateOnSubmit>
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
</form>
</ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" abpClose>
{{ 'AbpIdentity::Cancel' | abpLocalization }}
</button>
<abp-button iconClass="fa fa-check" [disabled]="form?.invalid" (click)="save()">{{
'AbpIdentity::Save' | abpLocalization
}}</abp-button>
</ng-template>
</abp-modal>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" abpClose>
{{ 'AbpIdentity::Cancel' | abpLocalization }}
</button>
<abp-button iconClass="fa fa-check" [disabled]="form?.invalid" (click)="save()">{{
'AbpIdentity::Save' | abpLocalization
}}</abp-button>
</ng-template>
</abp-modal>
<abp-permission-management
#abpPermissionManagement="abpPermissionManagement"
*abpReplaceableTemplate="
<abp-permission-management
#abpPermissionManagement="abpPermissionManagement"
*abpReplaceableTemplate="
{
inputs: {
providerName: { value: 'R' },
@ -55,6 +45,7 @@
};
let init = initTemplate
"
(abpInit)="init(abpPermissionManagement)"
>
</abp-permission-management>
(abpInit)="init(abpPermissionManagement)"
>
</abp-permission-management>
</abp-page>

14
npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.ts

@ -26,21 +26,21 @@ import { eIdentityComponents } from '../../enums/components';
export class RolesComponent implements OnInit {
data: PagedResultDto<IdentityRoleDto> = { items: [], totalCount: 0 };
form: UntypedFormGroup;
form!: UntypedFormGroup;
selected: IdentityRoleDto;
selected?: IdentityRoleDto;
isModalVisible: boolean;
isModalVisible!: boolean;
visiblePermissions = false;
providerKey: string;
providerKey?: string;
modalBusy = false;
permissionManagementKey = ePermissionManagementComponents.PermissionManagement;
onVisiblePermissionChange = event => {
onVisiblePermissionChange = (event: boolean) => {
this.visiblePermissions = event;
};
@ -82,7 +82,7 @@ export class RolesComponent implements OnInit {
if (!this.form.valid) return;
this.modalBusy = true;
const { id } = this.selected;
const { id } = this.selected || {};
(id
? this.service.update(id, { ...this.selected, ...this.form.value })
: this.service.create(this.form.value)
@ -118,7 +118,7 @@ export class RolesComponent implements OnInit {
}, 0);
}
sort(data) {
sort(data: any) {
const { prop, dir } = data.sorts[0];
this.list.sortKey = prop;
this.list.sortOrder = dir;

168
npm/ng-packs/packages/identity/src/lib/components/users/users.component.html

@ -1,96 +1,89 @@
<div id="identity-roles-wrapper" class="card">
<div class="card-header">
<div class="row">
<div class="col col-md-6">
<h5 class="card-title">{{ 'AbpIdentity::Users' | abpLocalization }}</h5>
</div>
<div class="text-end col col-md-6">
<abp-page-toolbar [record]="data.items"></abp-page-toolbar>
</div>
</div>
</div>
<div class="card-body">
<div id="data-tables-table-filter" class="data-tables-filter">
<div class="input-group">
<input
type="search"
class="form-control"
[placeholder]="'AbpUi::PagerSearch' | abpLocalization"
[(ngModel)]="list.filter"
/>
<abp-page [title]="'AbpIdentity::Users' | abpLocalization" [toolbar]="data.items">
<div id="identity-roles-wrapper" class="card">
<div class="card-body">
<div id="data-tables-table-filter" class="data-tables-filter mb-3">
<div class="input-group">
<input
type="search"
class="form-control"
[placeholder]="'AbpUi::PagerSearch' | abpLocalization"
[(ngModel)]="list.filter"
/>
</div>
</div>
</div>
<abp-extensible-table
[data]="data.items"
[recordsTotal]="data.totalCount"
[list]="list"
></abp-extensible-table>
<abp-extensible-table
[data]="data.items"
[recordsTotal]="data.totalCount"
[list]="list"
></abp-extensible-table>
</div>
</div>
</div>
<abp-modal [(visible)]="isModalVisible" [busy]="modalBusy">
<ng-template #abpHeader>
<h3>{{ (selected?.id ? 'AbpIdentity::Edit' : 'AbpIdentity::NewUser') | abpLocalization }}</h3>
</ng-template>
<abp-modal [(visible)]="isModalVisible" [busy]="modalBusy">
<ng-template #abpHeader>
<h3>{{ (selected?.id ? 'AbpIdentity::Edit' : 'AbpIdentity::NewUser') | abpLocalization }}</h3>
</ng-template>
<ng-template #abpBody>
<ng-template #loaderRef
><div class="text-center"><i class="fa fa-pulse fa-spinner"></i></div
></ng-template>
<ng-template #abpBody>
<ng-template #loaderRef
>
<div class="text-center"><i class="fa fa-pulse fa-spinner"></i></div
>
</ng-template>
<form *ngIf="form; else loaderRef" [formGroup]="form" (ngSubmit)="save()">
<ul ngbNav #nav="ngbNav" class="nav-tabs">
<li ngbNavItem>
<a ngbNavLink>{{ 'AbpIdentity::UserInformations' | abpLocalization }}</a>
<ng-template ngbNavContent>
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
</ng-template>
</li>
<form *ngIf="form; else loaderRef" [formGroup]="form" (ngSubmit)="save()">
<ul ngbNav #nav="ngbNav" class="nav-tabs">
<li ngbNavItem>
<a ngbNavLink>{{ 'AbpIdentity::UserInformations' | abpLocalization }}</a>
<ng-template ngbNavContent>
<abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
</ng-template>
</li>
<li ngbNavItem>
<a ngbNavLink>{{ 'AbpIdentity::Roles' | abpLocalization }}</a>
<ng-template ngbNavContent>
<div
*ngFor="let roleGroup of roleGroups; let i = index; trackBy: trackByFn"
class="form-check mb-2"
>
<abp-checkbox
*abpReplaceableTemplate="{
inputs:{
checkboxId:'roles-' + i,
label:roles[i].name,
formControl:roleGroup.controls[roles[i].name]
},
componentKey: inputKey
}"
[checkboxId]="'roles-' + i"
[formControl]="roleGroup.controls[roles[i].name]"
[label]="roles[i].name"
<li ngbNavItem>
<a ngbNavLink>{{ 'AbpIdentity::Roles' | abpLocalization }}</a>
<ng-template ngbNavContent>
<div
*ngFor="let roleGroup of roleGroups; let i = index; trackBy: trackByFn"
class="form-check mb-2"
>
</abp-checkbox>
</div>
</ng-template>
</li>
</ul>
<div class="mt-2 fade-in-top" [ngbNavOutlet]="nav"></div>
</form>
</ng-template>
<abp-checkbox
*abpReplaceableTemplate="{
inputs:{
checkboxId:'roles-' + i,
label:roles[i].name,
formControl:roleGroup.controls[roles[i].name]
},
componentKey: inputKey
}"
[checkboxId]="'roles-' + i"
[formControl]="roleGroup.controls[roles[i].name]"
[label]="roles[i].name"
>
</abp-checkbox>
</div>
</ng-template>
</li>
</ul>
<div class="mt-2 fade-in-top" [ngbNavOutlet]="nav"></div>
</form>
</ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" abpClose>
{{ 'AbpIdentity::Cancel' | abpLocalization }}
</button>
<abp-button iconClass="fa fa-check" [disabled]="form?.invalid" (click)="save()">{{
'AbpIdentity::Save' | abpLocalization
}}</abp-button>
</ng-template>
</abp-modal>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" abpClose>
{{ 'AbpIdentity::Cancel' | abpLocalization }}
</button>
<abp-button iconClass="fa fa-check" [disabled]="form?.invalid" (click)="save()">{{
'AbpIdentity::Save' | abpLocalization
}}</abp-button>
</ng-template>
</abp-modal>
<abp-permission-management
#abpPermissionManagement="abpPermissionManagement"
*abpReplaceableTemplate="
<abp-permission-management
#abpPermissionManagement="abpPermissionManagement"
*abpReplaceableTemplate="
{
inputs: {
providerName: { value: 'U' },
@ -102,7 +95,8 @@
};
let init = initTemplate
"
[entityDisplayName]="entityDisplayName"
(abpInit)="init(abpPermissionManagement)"
>
</abp-permission-management>
[entityDisplayName]="entityDisplayName"
(abpInit)="init(abpPermissionManagement)"
>
</abp-permission-management>
</abp-page>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save