Browse Source

Merge remote-tracking branch 'abpframework/dev' into docs

pull/3113/head
liangshiwei 6 years ago
parent
commit
b6ae63f70f
  1. 4
      docs/en/Authorization.md
  2. 7
      docs/en/Customizing-Application-Modules-Guide.md
  3. 3
      docs/en/Modules/Account.md
  4. 3
      docs/en/Themes/Basic.md
  5. 2
      docs/en/Tutorials/Part-1.md
  6. 1
      docs/en/Tutorials/Part-2.md
  7. 0
      docs/en/Tutorials/Part-3.md
  8. 34
      docs/en/UI/Angular/Component-Replacement.md
  9. 290
      docs/en/UI/Angular/Config-State.md
  10. 11
      docs/en/UI/Angular/Custom-Setting-Page.md
  11. BIN
      docs/en/UI/Angular/images/custom-settings.png
  12. 2
      docs/en/UI/AspNetCore/Bundling-Minification.md
  13. 434
      docs/en/UI/AspNetCore/Customization-User-Interface.md
  14. 3
      docs/en/UI/AspNetCore/Layout-Hooks.md
  15. 3
      docs/en/UI/AspNetCore/Navigation-Menu.md
  16. 76
      docs/en/UI/AspNetCore/Tag-Helpers/Buttons.md
  17. 27
      docs/en/UI/AspNetCore/Tag-Helpers/Index.md
  18. BIN
      docs/en/UI/AspNetCore/Tag-Helpers/fa-address-card.png
  19. 2
      docs/en/UI/AspNetCore/Theming.md
  20. 3
      docs/en/UI/AspNetCore/Toolbars.md
  21. 19
      docs/en/docs-nav.json
  22. BIN
      docs/en/images/bookstore-added-brand-files.png
  23. BIN
      docs/en/images/bookstore-added-logo.png
  24. BIN
      docs/en/images/bookstore-added-role-js-file.png
  25. BIN
      docs/en/images/bookstore-brand-area-highlighted.png
  26. BIN
      docs/en/images/bookstore-global-css-file.png
  27. BIN
      docs/en/images/bookstore-google-analytics-view-component.png
  28. BIN
      docs/en/images/bookstore-menus-highlighted.png
  29. BIN
      docs/en/images/bookstore-notification-icon-on-toolbar.png
  30. BIN
      docs/en/images/bookstore-notification-view-component.png
  31. BIN
      docs/en/images/bookstore-toolbar-highlighted.png
  32. BIN
      docs/en/images/overriding-login-cshtml.png
  33. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/BasicTheme.cs
  34. 198
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularModuleSourceCodeAdder.cs
  35. 19
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs
  36. 62
      framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json
  37. 26
      npm/ng-packs/package.json
  38. 4
      npm/ng-packs/packages/account/package.json
  39. 4
      npm/ng-packs/packages/core/src/lib/states/config.state.ts
  40. 2
      npm/ng-packs/packages/feature-management/package.json
  41. 6
      npm/ng-packs/packages/identity/package.json
  42. 2
      npm/ng-packs/packages/permission-management/package.json
  43. 4
      npm/ng-packs/packages/setting-management/package.json
  44. 6
      npm/ng-packs/packages/tenant-management/package.json
  45. 2
      npm/ng-packs/packages/theme-basic/package.json
  46. 2
      npm/ng-packs/packages/theme-shared/package.json
  47. 10
      samples/BookStore-Angular-MongoDb/angular/package.json
  48. 2
      templates/app/angular/package.json
  49. 10
      templates/module/angular/package.json
  50. 2
      templates/module/angular/projects/my-project-name-config/package.json
  51. 2
      templates/module/angular/projects/my-project-name/package.json
  52. 6
      templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/sl.json
  53. 6
      templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hans.json
  54. 6
      templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hant.json

4
docs/en/Authorization.md

@ -204,6 +204,10 @@ If you define and register a policy to the ASP.NET Core authorization system wit
See [policy based authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) document to learn how to define a custom policy.
### Changing Permission Definitions of a Depended Module
A class deriving from the `PermissionDefinitionProvider` (just like the example above) can also get existing permission definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions.
## IAuthorizationService
ASP.NET Core provides the `IAuthorizationService` that can be used to check for authorization. Once you inject, you can use it in your code to conditionally control the authorization.

7
docs/en/Customizing-Application-Modules-Guide.md

@ -53,3 +53,10 @@ This section suggests some approaches if you decided to use pre-built applicatio
* [Overriding Services](Customizing-Application-Modules-Overriding-Services.md)
* [Overriding the User Interface](Customizing-Application-Modules-Overriding-User-Interface.md)
### See Also
Also, see the following documents:
* See [the localization document](Localization.md) to learn how to extend existing localization resources.
* See [the settings document](Settings.md) to learn how to change setting definitions of a depended module.
* See [the authorization document](Authorization.md) to learn how to change permission definitions of a depended module.

3
docs/en/Modules/Account.md

@ -0,0 +1,3 @@
# Account Module
TODO

3
docs/en/Themes/Basic.md

@ -0,0 +1,3 @@
## Basic Theme
TODO

2
docs/en/Tutorials/Part-1.md

@ -727,7 +727,7 @@ $(function () {
* `abp.libs.datatables.createAjax` is a helper function to adapt ABP's dynamic JavaScript API proxies to [Datatable](https://datatables.net/)'s format.
* `abp.libs.datatables.normalizeConfiguration` is another helper function. There's no requirement to use it, but it simplifies the [Datatables](https://datatables.net/) configuration by providing conventional values for missing options.
* `acme.bookStore.book.getList` is the function to get list of books (as described in [dynamic JavaScript proxies](#Dynamic JavaScript proxies)).
* `acme.bookStore.book.getList` is the function to get list of books (as described in [dynamic JavaScript proxies](#dynamic-javascript-proxies)).
* See [Datatables documentation](https://datatables.net/manual/) for all configuration options.
It's end of this part. The final UI of this work is shown as below:

1
docs/en/Tutorials/Part-2.md

@ -641,7 +641,6 @@ Open `book-list.component.html` file in `books\book-list` folder and replace the
</div>
<div class="card-body">
<abp-table
*ngIf="[150, 0] as columnWidths"
[value]="books$ | async"
[abpLoading]="loading"
[headerTemplate]="tableHeader"

0
docs/en/Tutorials/part-3.md → docs/en/Tutorials/Part-3.md

34
docs/en/UI/Angular/Component-Replacement.md

@ -31,18 +31,22 @@ export class AppComponent {
## Available Replaceable Components
| Component key | Description |
|----------------------------------------------------|-------------------------|
| Account.LoginComponent | Login page |
| Account.RegisterComponent | Register page |
| Account.ManageProfileComponent | Manage Profile page |
| Account.AuthWrapperComponent | This component wraps register and login pages |
| Account.ChangePasswordComponent | Change password form |
| Account.PersonalSettingsComponent | Personal settings form |
| Account.TenantBoxComponentInputs | Tenant changing box |
| FeatureManagement.FeatureManagementComponent | Features modal |
| Identity.UsersComponent | Users page |
| Identity.RolesComponent | Roles page |
| PermissionManagement.PermissionManagementComponent | Permissions modal |
| SettingManagement.SettingManagementComponent | Setting Management page |
| TenantManagement.TenantsComponent | Tenants page |
| Component key | Description |
| -------------------------------------------------- | --------------------------------------------- |
| Account.LoginComponent | Login page |
| Account.RegisterComponent | Register page |
| Account.ManageProfileComponent | Manage Profile page |
| Account.AuthWrapperComponent | This component wraps register and login pages |
| Account.ChangePasswordComponent | Change password form |
| Account.PersonalSettingsComponent | Personal settings form |
| Account.TenantBoxComponentInputs | Tenant changing box |
| FeatureManagement.FeatureManagementComponent | Features modal |
| Identity.UsersComponent | Users page |
| Identity.RolesComponent | Roles page |
| PermissionManagement.PermissionManagementComponent | Permissions modal |
| SettingManagement.SettingManagementComponent | Setting Management page |
| TenantManagement.TenantsComponent | Tenants page |
## What's Next?
- [Custom Setting Page](./Custom-Setting-Page.md)

290
docs/en/UI/Angular/Config-State.md

@ -0,0 +1,290 @@
# Config State
`ConfigStateService` is a singleton service, i.e. provided in root level of your application, and is actually a façade for interacting with application configuration state in the `Store`.
## Before Use
In order to use the `ConfigStateService` you must inject it in your class as a dependency.
```js
import { ConfigStateService } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
constructor(private config: ConfigStateService) {}
}
```
You do not have to provide the `ConfigStateService` at module or component/directive level, because it is already **provided in root**.
## Selector Methods
`ConfigStateService` has numerous selector methods which allow you to get a specific configuration or all configurations from the `Store`.
### How to Get All Configurations From the Store
You can use the `getAll` method of `ConfigStateService` to get all of the configuration object from the store. It is used as follows:
```js
// this.config is instance of ConfigStateService
const config = this.config.getAll();
```
### How to Get a Specific Configuration From the Store
You can use the `getOne` method of `ConfigStateService` to get a specific configuration property from the store. For that, the property name should be passed to the method as parameter.
```js
// this.config is instance of ConfigStateService
const currentUser = this.config.getOne("currentUser");
```
On occasion, you will probably want to be more specific than getting just the current user. For example, here is how you can get the `tenantId`:
```js
const tenantId = this.config.getDeep("currentUser.tenantId");
```
or by giving an array of keys as parameter:
```js
const tenantId = this.config.getDeep(["currentUser", "tenantId"]);
```
FYI, `getDeep` is able to do everything `getOne` does. Just keep in mind that `getOne` is slightly faster.
#### Config State Properties
Please refer to `Config.State` type for all the properties you can get with `getOne` and `getDeep`. It can be found in the [config.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L7).
### How to Get the Application Information From the Store
The `getApplicationInfo` method is used to get the application information from the environment variables stored as the config state. This is how you can use it:
```js
// this.config is instance of ConfigStateService
const appInfo = this.config.getApplicationInfo();
```
This method never returns `undefined` or `null` and returns an empty object literal (`{}`) instead. In other words, you will never get an error when referring to the properties of `appInfo` above.
#### Application Information Properties
Please refer to `Config.Application` type for all the properties you can get with `getApplicationInfo`. It can be found in the [config.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L21).
### How to Get API URL From the Store
The `getApplicationInfo` method is used to get a specific API URL from the environment variables stored as the config state. This is how you can use it:
```js
// this.config is instance of ConfigStateService
const apiUrl = this.config.getApiUrl();
// environment.apis.default.url
const searchUrl = this.config.getApiUrl("search");
// environment.apis.search.url
```
This method returns the `url` of a specific API based on the key given as its only parameter. If there is no key, `'default'` is used.
### How to Get All Settings From the Store
You can use the `getSettings` method of `ConfigStateService` to get all of the settings object from the configuration state. Here is how you get all settings:
```js
// this.config is instance of ConfigStateService
const settings = this.config.getSettings();
```
In addition, the method lets you search settings by **passing a keyword** to it.
```js
const localizationSettings = this.config.getSettings("Localization");
/*
{
'Abp.Localization.DefaultLanguage': 'en'
}
*/
```
Beware though, **settings search is case sensitive**.
### How to Get a Specific Setting From the Store
You can use the `getSetting` method of `ConfigStateService` to get a specific setting from the configuration state. Here is an example:
```js
// this.config is instance of ConfigStateService
const defaultLang = this.config.getSetting("Abp.Localization.DefaultLanguage");
// 'en'
```
### How to Get a Specific Permission From the Store
You can use the `getGrantedPolicy` method of `ConfigStateService` to get a specific permission from the configuration state. For that, you should pass a policy key as parameter to the method.
```js
// this.config is instance of ConfigStateService
const hasIdentityPermission = this.config.getGrantedPolicy("Abp.Identity");
// true
```
You may also **combine policy keys** to fine tune your selection:
```js
// this.config is instance of ConfigStateService
const hasIdentityAndAccountPermission = this.config.getGrantedPolicy(
"Abp.Identity && Abp.Account"
);
// false
const hasIdentityOrAccountPermission = this.config.getGrantedPolicy(
"Abp.Identity || Abp.Account"
);
// true
```
Please consider the following **rules** when creating your permission selectors:
- Maximum 2 keys can be combined.
- `&&` operator looks for both keys.
- `||` operator looks for either key.
- Empty string `''` as key will return `true`
- Using an operator without a second key will return `false`
### How to Get Translations From the Store
The `getLocalization` method of `ConfigStateService` is used for translations. Here are some examples:
```js
// this.config is instance of ConfigStateService
const identity = this.config.getLocalization("AbpIdentity::Identity");
// 'identity'
const notFound = this.config.getLocalization("AbpIdentity::IDENTITY");
// 'AbpIdentity::IDENTITY'
const defaultValue = this.config.getLocalization({
key: "AbpIdentity::IDENTITY",
defaultValue: "IDENTITY"
});
// 'IDENTITY'
```
Please check out the [localization documentation](./Localization.md) for details.
## Dispatch Methods
`ConfigStateService` has several dispatch methods which allow you to conveniently dispatch predefined actions to the `Store`.
### How to Get Application Configuration From Server
The `dispatchGetAppConfiguration` triggers a request to an endpoint that responds with the application state and then places this response to the `Store` as configuration state.
```js
// this.config is instance of ConfigStateService
this.config.dispatchGetAppConfiguration();
// returns a state stream which emits after dispatch action is complete
```
Note that **you do not have to call this method at application initiation**, because the application configuration is already being received from the server at start.
### How to Patch Route Configuration
The `dispatchPatchRouteByName` finds a route by its name and replaces its configuration in the `Store` with the new configuration passed as the second parameter.
```js
// this.config is instance of ConfigStateService
const newRouteConfig: Partial<ABP.Route> = {
name: "Home",
path: "home",
children: [
{
name: "Dashboard",
path: "dashboard"
}
]
};
this.config.dispatchPatchRouteByName("::Menu:Home", newRouteConfig);
// returns a state stream which emits after dispatch action is complete
```
### How to Add a New Route Configuration
The `dispatchAddRoute` adds a new route to the configuration state in the `Store`. For this, the route config should be passed as the parameter of the method.
```js
// this.config is instance of ConfigStateService
const newRoute: ABP.Route = {
name: "My New Page",
iconClass: "fa fa-dashboard",
path: "page",
invisible: false,
order: 2,
requiredPolicy: "MyProjectName::MyNewPage"
};
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
The `newRoute` will be placed as at root level, i.e. without any parent routes and its url will be stored as `'/path'`.
If you want **to add a child route, you can do this:**
```js
// this.config is instance of ConfigStateService
const newRoute: ABP.Route = {
parentName: "AbpAccount::Login",
name: "My New Page",
iconClass: "fa fa-dashboard",
path: "page",
invisible: false,
order: 2,
requiredPolicy: "MyProjectName::MyNewPage"
};
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
The `newRoute` will then be placed as a child of the parent route named `'AbpAccount::Login'` and its url will be set as `'/account/login/page'`.
#### Route Configuration Properties
Please refer to `ABP.Route` type for all the properties you can pass to `dispatchSetEnvironment` in its parameter. It can be found in the [common.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/common.ts#L27).
### How to Set the Environment
The `dispatchSetEnvironment` places environment variables passed to it in the `Store` under the configuration state. Here is how it is used:
```js
// this.config is instance of ConfigStateService
this.config.dispatchSetEnvironment({
/* environment properties here */
});
// returns a state stream which emits after dispatch action is complete
```
Note that **you do not have to call this method at application initiation**, because the environment variables are already being stored at start.
#### Environment Properties
Please refer to `Config.Environment` type for all the properties you can pass to `dispatchSetEnvironment` as parameter. It can be found in the [config.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L13).

11
docs/en/UI/Angular/AddingSettingTab.md → docs/en/UI/Angular/Custom-Setting-Page.md

@ -1,6 +1,6 @@
## Creating a Settings Tab
# Custom Setting Page
There are several settings tabs from different modules. You can add custom settings tabs to your project in 3 steps.
There are several settings tabs from different modules. You can add custom settings page to your project in 3 steps.
1. Create a Component
@ -11,12 +11,11 @@ import { Component } from '@angular/core';
@Component({
selector: 'app-your-custom-settings',
template: `
your-custom-settings works! mySetting: {%{{{ mySetting$ | async }}}%}
custom-settings works!
`,
})
export class YourCustomSettingsComponent {
@Select(ConfigState.getSetting('MyProjectName.MySetting1')) // Gets a setting. MyProjectName.MySetting1 is a setting key.
mySetting$: Observable<string>; // The selected setting is set to the mySetting variable as Observable.
// Your component logic
}
```
@ -38,6 +37,6 @@ ngOnInit() {
}
```
Open the `setting-management` page to see the changes:
Navigate to `/setting-management` route to see the changes:
![Custom Settings Tab](./images/custom-settings.png)

BIN
docs/en/UI/Angular/images/custom-settings.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

2
docs/en/UI/AspNetCore/Bundling-Minification.md

@ -143,7 +143,7 @@ This time, no file defined in the tag helper definition because the bundle files
### Configuring An Existing Bundle
ABP supports [modularity](../Module-Development-Basics.md) for bundling as well. A module can modify an existing bundle that is created by a dependant module. Example:
ABP supports [modularity](../Module-Development-Basics.md) for bundling as well. A module can modify an existing bundle that is created by a depended module. Example:
````C#
[DependsOn(typeof(MyWebModule))]

434
docs/en/UI/AspNetCore/Customization-User-Interface.md

@ -2,7 +2,17 @@
This document explains how to override the user interface of a depended [application module](../../Modules/Index.md) for ASP.NET Core MVC / Razor Page applications.
## Overriding a Page Model
## Overriding a Page
This section covers the [Razor Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/) development, which is the recommended approach to create server rendered user interface for ASP.NET Core. Pre-built modules typically uses the Razor Pages approach instead of the classic MVC pattern (next sections will cover the MVC pattern too).
You typically have three kind of override requirement for a page:
* Overriding **only the Page Model** (C#) side to perform additional logic without changing the page UI.
* Overring **only the Razor Page** (.chtml file) to change the UI without changing the c# behind the page.
* **Completely overriding** the page.
### Overriding a Page Model (C#)
````csharp
using System.Threading.Tasks;
@ -36,5 +46,425 @@ namespace Acme.BookStore.Web.Pages.Identity.Users
}
````
This class replaces `EditModalModel` for the users and overrides the `OnPostAsync` method.
* This class inherits from and replaces the `EditModalModel` for the users and overrides the `OnPostAsync` method to perform additional logic before and after the underlying code.
* It uses `ExposeServices` and `Dependency` attributes to replace the class.
### Overriding a Razor Page (.CSHTML)
Overriding a `.cshtml` file (razor page, razor view, view component... etc.) is possible through the [Virtual File System](../../Virtual-File-System.md).
Virtual File system allows us to **embed resources into assemblies**. In this way, pre-built modules define the razor pages inside their NuGet packages. When you depend a module, you can override any file added to the virtual file system by that module, including pages/views.
#### Example
This example overrides the **login page** UI defined by the [Account Module](../../Modules/Account.md).
Physical files override the embedded files defined in the same location. The account module defines a `Login.cshtml` file under the `Pages/Account` folder. So, you can override it by creating a file in the same path:
![overriding-login-cshtml](../../images/overriding-login-cshtml.png)
You typically want to copy the original `.cshtml` file of the module, then make the necessary changes. You can find the original file [here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml). Do not copy the `Login.cshtml.cs` file which is the code behind file for the razor page and we don't want to override it yet (see the next section).
That's all, you can change the file content however you like.
### Completely Overriding a Razor Page
You may want to completely override a page; the razor and the c# file related to the page.
In such a case;
1. Override the C# page model class just like described above, but don't replace the existing page model class.
2. Override the Razor Page just described above, but also change the @model directive to point your new page model.
#### Example
This example overrides the **login page** defined by the [Account Module](../../Modules/Account.md).
Create a page model class deriving from the ` LoginModel ` (defined in the ` Volo.Abp.Account.Web.Pages.Account ` namespace):
````csharp
public class MyLoginModel : LoginModel
{
public MyLoginModel(
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions
) : base(
schemeProvider,
accountOptions)
{
}
public override Task<IActionResult> OnPostAsync(string action)
{
//TODO: Add logic
return base.OnPostAsync(action);
}
//TODO: Add new methods and properties...
}
````
You can override any method or add new properties/methods if needed.
> Notice that we didn't use `[Dependency(ReplaceServices = true)]` or `[ExposeServices(typeof(LoginModel))]` since we don't want to replace the existing class in the dependency injection, we define a new one.
Copy `Login.cshtml` file into your solution as just described above. Change the **@model** directive to point to the `MyLoginModel`:
````xml
@page
...
@model Acme.BookStore.Web.Pages.Account.MyLoginModel
...
````
That's all! Make any change in the view and run your application.
#### Replacing Page Model Without Inheritance
You don't have to inherit from the original page model class (like done in the previous example). Instead, you can completely **re-implement** the page yourself. In this case, just derive from `PageModel`, `AbpPageModel` or any suitable base class you need.
## Overriding a View Component
The ABP Framework, pre-built themes and modules define some **re-usable view components**. These view components can be replaced just like a page described above.
### Example
The screenshot below was taken from the **basic theme** comes with the application startup template.
![bookstore-brand-area-highlighted](../../images/bookstore-brand-area-highlighted.png)
[The basic theme](../../Themes/Basic.md) defines some view components for the layout. For example, the highlighted area with the red rectangle above is called **Brand component**. You probably want to customize this component by adding your **own application logo**. Let's see how to do it.
First, create your logo and place under a folder in your web application. We used `wwwroot/logos/bookstore-logo.png` path. Then copy the Brand component's view ([from here](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/Default.cshtml)) from the basic theme files under the `Themes/Basic/Components/Brand` folder. The result should be similar the picture below:
![bookstore-added-brand-files](../../images/bookstore-added-brand-files.png)
Then change the `Default.cshtml` as you like. Example content can be like that:
````xml
<a href="/">
<img src="~/logos/bookstore-logo.png" width="250" height="60"/>
</a>
````
Now, you can run the application to see the result:
![bookstore-added-logo](../../images/bookstore-added-logo.png)
If you need, you can also replace [the code behind c# class](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/MainNavbarBrandViewComponent.cs) of the component just using the dependency injection system.
### Overriding the Theme
Just as explained above, you can replace any component, layout or c# class of the used theme. See the [theming document](Theming.md) for more information on the theming system.
## Overriding Static Resources
Overriding a static embedded resource (like JavaScript, Css or image files) of a module is pretty easy. Just place a file in the same path in your solution and let the Virtual File System to handle it.
## Manipulating the Bundles
The [Bundling & Minification](Bundling-Minification.md) system provides an **extensible and dynamic** system to create **script** and **style** bundles. It allows you to extend and manipulate the existing bundles.
### Example: Add a Global CSS File
For example, ABP Framework defines a **global style bundle** which is added to every page (actually, added to the layout by the themes). Let's add a **custom style file** to the end of the bundle files, so we can override any global style.
First, create a CSS file and locate it in a folder inside the `wwwroot`:
![bookstore-global-css-file](../../images/bookstore-global-css-file.png)
Define some custom CSS rules inside the file. Example:
````css
.card-title {
color: orange;
font-size: 2em;
text-decoration: underline;
}
.btn-primary {
background-color: red;
}
````
Then add this file to the standard global style bundle in the `ConfigureServices` method of your [module](../../Module-Development-Basics.md):
````csharp
Configure<AbpBundlingOptions>(options =>
{
options.StyleBundles.Configure(
StandardBundles.Styles.Global, //The bundle name!
bundleConfiguration =>
{
bundleConfiguration.AddFiles("/styles/my-global-styles.css");
}
);
});
````
#### The Global Script Bundle
Just like the `StandardBundles.Styles.Global`, there is a `StandardBundles.Scripts.Global` that you can add files or manipulate the existing ones.
### Example: Manipulate the Bundle Files
The example above adds a new file to the bundle. You can do more if you create a **bundle contributor** class. Example:
````csharp
public class MyGlobalStyleBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.Clear();
context.Files.Add("/styles/my-global-styles.css");
}
}
````
Then you can add the contributor to an existing bundle:
````csharp
Configure<AbpBundlingOptions>(options =>
{
options.StyleBundles.Configure(
StandardBundles.Styles.Global,
bundleConfiguration =>
{
bundleConfiguration.AddContributors(typeof(MyGlobalStyleBundleContributor));
}
);
});
````
It is not a good idea to clear all CSS files. In a real world scenario, you can find and replace a specific file with your own file.
### Example: Add a JavaScript File for a Specific Page
The examples above works with the global bundle added to the layout. What if you want to add a CSS/JavaScript file (or replace a file) for a specific page defines inside a depended module?
Assume that you want to run a **JavaScript code** once the user enters to the **Role Management** page of the Identity Module.
First, create a standard JavaScript file under the `wwwroot`, `Pages` or `Views` folder (ABP support to add static resources inside these folders by default). We prefer the `Pages/Identity/Roles` folder to follow the conventions:
![bookstore-added-role-js-file](../../images/bookstore-added-role-js-file.png)
Content of the file is simple:
````js
$(function() {
abp.log.info('My custom role script file has been loaded!');
});
````
Then add this file to the bundle of the role management page:
````csharp
Configure<AbpBundlingOptions>(options =>
{
options.ScriptBundles
.Configure(
typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName,
bundleConfig =>
{
bundleConfig.AddFiles("/Pages/Identity/Roles/my-role-script.js");
});
});
````
`typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName` is the safe way to get the bundle name for the role management page.
> Notice that not every page defines such page bundles. They define only if needed.
In addition to adding new CSS/JavaScript file to a page, you also can replace the existing one (by defining a bundle contributor).
## Layout Customization
Layouts are defined by the theme ([see the theming](Theming.md)) by design. They are not included in a downloaded application solution. In this way you can easily **upgrade** the theme and get new features. You can not **directly change** the layout code in your application unless you replace it by your own layout (will be explained in the next sections).
There are some common ways to **customize the layout** described in the next sections.
### Menu Contributors
There are two **standard menus** defined by the ABP Framework:
![bookstore-menus-highlighted](../../images/bookstore-menus-highlighted.png)
* `StandardMenus.Main`: The main menu of the application.
* `StandardMenus.User`: The user menu (generally at the top right of the screen).
Rendering the menus is a responsibility of the theme, but **menu items** are determined by the modules and your application code. Just implement the `IMenuContributor` interface and **manipulate the menu items** in the `ConfigureMenuAsync` method.
Menu contributors are executed whenever need to render the menu. There is already a menu contributor defined in the **application startup template**, so you can take it as an example and improve if necessary. See the [navigation menu](Navigation-Menu.md) document for more.
### Toolbar Contributors
[Toolbar system](Toolbars.md) is used to define **toolbars** on the user interface. Modules (or your application) can add **items** to a toolbar, then the theme renders the toolbar on the **layout**.
There is only one **standard toolbar** (named "Main" - defined as a constant: `StandardToolbars.Main`). For the basic theme, it is rendered as shown below:![bookstore-toolbar-highlighted](../../images/bookstore-toolbar-highlighted.png)
In the screenshot above, there are two items added to the main toolbar: Language switch component & user menu. You can add your own items here.
#### Example: Add a Notification Icon
In this example, we will add a **notification (bell) icon** to the left of the language switch item. A item in the toolbar should be a **view component**. So, first, create a new view component in your project:
![bookstore-notification-view-component](../../images/bookstore-notification-view-component.png)
**NotificationViewComponent.cs**
````csharp
public class NotificationViewComponent : AbpViewComponent
{
public async Task<IViewComponentResult> InvokeAsync()
{
return View("/Pages/Shared/Components/Notification/Default.cshtml");
}
}
````
**Default.cshtml**
````xml
<div id="MainNotificationIcon" style="color: white; margin: 8px;">
<i class="far fa-bell"></i>
</div>
````
Now, we can create a class implementing the `IToolbarContributor` interface:
````csharp
public class MyToolbarContributor : IToolbarContributor
{
public Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
{
if (context.Toolbar.Name == StandardToolbars.Main)
{
context.Toolbar.Items
.Insert(0, new ToolbarItem(typeof(NotificationViewComponent)));
}
return Task.CompletedTask;
}
}
````
This class adds the `NotificationViewComponent` as the first item in the `Main` toolbar.
Finally, you need to add this contributor to the `AbpToolbarOptions`, in the `ConfigureServices` of your module:
````csharp
Configure<AbpToolbarOptions>(options =>
{
options.Contributors.Add(new MyToolbarContributor());
});
````
That's all, you will see the notification icon on the toolbar when you run the application:
![bookstore-notification-icon-on-toolbar](../../images/bookstore-notification-icon-on-toolbar.png)
`NotificationViewComponent` in this sample simply returns a view without any data. In real life, you probably want to **query database** (or call an HTTP API) to get notifications and pass to the view. If you need, you can add a `JavaScript` or `CSS` file to the global bundle (as described before) for your toolbar item.
See the [toolbars document](Toolbars.md) for more about the toolbar system.
### Layout Hooks
[Layout Hooks](Layout-Hooks.md) system allows you to **add code** at some specific parts of the layout. All layouts of all themes should implement these hooks. Then you can then add a **view component** into a hook point.
#### Example: Add Google Analytics Script
Assume that you need to add the Google Analytics script to the layout (that will be available for all the pages). First, **create a view component** in your project:
![bookstore-google-analytics-view-component](../../images/bookstore-google-analytics-view-component.png)
**NotificationViewComponent.cs**
````csharp
public class GoogleAnalyticsViewComponent : AbpViewComponent
{
public IViewComponentResult Invoke()
{
return View("/Pages/Shared/Components/GoogleAnalytics/Default.cshtml");
}
}
````
**Default.cshtml**
````html
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-xxxxxx-1', 'auto');
ga('send', 'pageview');
</script>
````
Change `UA-xxxxxx-1` with your own code.
You can then add this component to any of the hook points in the `ConfigureServices` of your module:
````csharp
Configure<AbpLayoutHookOptions>(options =>
{
options.Add(
LayoutHooks.Head.Last, //The hook name
typeof(GoogleAnalyticsViewComponent) //The component to add
);
});
````
Now, the GA code will be inserted in the `head` of the page as the last item. You (or the modules you are using) can add multiple items to the same hook. All of them will be added to the layout.
The configuration above adds the `GoogleAnalyticsViewComponent` to all layouts. You may want to only add to a specific layout:
````csharp
Configure<AbpLayoutHookOptions>(options =>
{
options.Add(
LayoutHooks.Head.Last,
typeof(GoogleAnalyticsViewComponent),
layout: StandardLayouts.Application //Set the layout to add
);
});
````
See the layouts section below to learn more about the layout system.
### Layouts
Layout system allows themes to define standard, named layouts and allows any page to select a proper layout for its purpose. There are three pre-defined layouts:
* "**Application**": The main (and the default) layout for an application. It typically contains header, menu (sidebar), footer, toolbar... etc.
* "**Account**": This layout is used by login, register and other similar pages. It is used for the pages under the `/Pages/Account` folder by default.
* "**Empty**": Empty and minimal layout.
These names are defined in the `StandardLayouts` class as constants. You can definitely create your own layouts, but these are standard layout names and implemented by all the themes out of the box.
#### Layout Location
You can find the layout files [here](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts) for the basic theme. You can take them as references to build your own layouts or you can override them if necessary.
#### ITheme
ABP Framework uses the `ITheme` service to get the layout location by the layout name. You can replace this service to dynamically select the layout location.
#### IThemeManager
`IThemeManager` is used to obtain the current theme and get the layout path. Any page can determine the layout of its own. Example:
````html
@using Volo.Abp.AspNetCore.Mvc.UI.Theming
@inject IThemeManager ThemeManager
@{
Layout = ThemeManager.CurrentTheme.GetLayout(StandardLayouts.Empty);
}
````
This page will use the empty layout. You use `ThemeManager.CurrentTheme.GetEmptyLayout();` extension method as a shortcut.
If you want to set the layout for all the pages under a specific folder, then write the code above in a `_ViewStart.cshtml` file under that folder.

3
docs/en/UI/AspNetCore/Layout-Hooks.md

@ -0,0 +1,3 @@
# Layout Hooks
TODO

3
docs/en/UI/AspNetCore/Navigation-Menu.md

@ -0,0 +1,3 @@
# Navigation Menu
TODO

76
docs/en/UI/AspNetCore/Tag-Helpers/Buttons.md

@ -1,30 +1,26 @@
# Buttons
ABP framework has a special Tag Helper to create bootstrap button easily.
## Introduction
`<abp-button>`
`abp-button` is the main element to create buttons.
## Attributes
Basic usage:
`<abp-button>` has 7 different attribute.
````xml
<abp-button button-type="Primary">Click Me</abp-button>
````
* [`button-type`](#button-type)
* [`size`](#size)
* [`busy-text`](#busy-text)
* [`text`](#text)
* [`icon`](#icon)
* [`disabled`](#disabled)
* [`icon-type`](#icon-type)
## Demo
### `button-type`
See the [buttons demo page](https://bootstrap-taghelpers.abp.io/Components/Buttons) to see it in action.
`button-type` is a selectable parameter. It's default value is `Default`.
## Attributes
`<abp-button button-type="Primary">Button</abp-button>`
### button-type
You can choose one of the button type listed below.
A value indicates the main style/type of the button. Should be one of the following values:
* `Default`
* `Default` (default value)
* `Primary`
* `Secondary`
* `Success`
@ -43,15 +39,11 @@ You can choose one of the button type listed below.
* `Outline_Dark`
* `Link`
### `size`
`size` is a selectable parameter. It's default value is `Default`.
### size
`<abp-button size="Default">Button</abp-button>`
A value indicates the size of the button. Should be one of the following values:
You can choose one of the size type listed below.
* `Default`
* `Default` (default value)
* `Small`
* `Medium`
* `Large`
@ -60,35 +52,35 @@ You can choose one of the size type listed below.
* `Block_Medium`
* `Block_Large`
### `busy-text`
`busy-text` is a string parameter. It shows the text while the button is busy.
### `text`
### busy-text
`text` is a string parameter that displaying on button.
A text that is shown when the button is busy.
### `icon`
### text
`icon` is a string parameter. It is depending to [`icon-type`](#`icon-type`). For default, we use [Font Awesome](https://fontawesome.com/) for icons. To use it, you need to set `icon` parameter as a icon name.
The text of the button. This is a shortcut if you simply want to set a text to the button. Example:
##### Example
````xml
<abp-button button-type="Primary" text="Click Me" />
````
[fa-address-card](https://fontawesome.com/icons/address-card): ![fa-address-card](fa-address-card.png "Address Card")
In this case, you can use a self-closing tag to make it shorter.
`<abp-button icon="address-card" text="Address" />`
### icon
> Don't forget: You dont need to write prefix! It will add automatically "fa" prefix for [Font Awesome](https://fontawesome.com/) icons while you did not change `icon-type`.
Used to set an icon for the button. It works with the [Font Awesome](https://fontawesome.com/) icon classes by default. Example:
### `disabled`
````xml
<abp-button icon="address-card" text="Address" />
````
`disabled` is a boolean parameter. If you set it `true`, your button will be disabled.
##### icon-type
### `icon-type`
If you don't want to use font-awesome, you have two options:
`icon-type` is a selectable parameter. It's default value is `FontAwesome`. You can create your own icon type provider and change it.
1. Set `icon-type` to `Other` and write the CSS class of the font icon you're using.
2. If you don't use a font icon use the opening and closing tags manually and write any code inside the tags.
You can choose one of the icon type listed below.
### disabled
* `FontAwesome`
* `Other`
Set `true` to make the button initially disabled.

27
docs/en/UI/AspNetCore/Tag-Helpers/Index.md

@ -1,3 +1,26 @@
## ABP Tag Helpers
# ABP Tag Helpers
"ABP tag helpers" documentation is creating now. You can see a [demo of components](http://bootstrap-taghelpers.abp.io/) for now.
ABP Framework defines a set of **tag helper components** to simply the user interface development for ASP.NET Core (MVC / Razor Pages) applications.
## Bootstrap Component Wrappers
Most of the tag helpers are [Bootstrap](https://getbootstrap.com/) (v4+) wrappers. Coding bootstrap is not so easy, not so type-safe and contains too much repetitive HTML tags. ABP Tag Helpers makes it **easier** and **type safe**.
We don't aim to wrap bootstrap components 100%. Writing **native bootstrap style code** is still possible (actually, tag helpers generates native bootstrap code in the end), but we suggest to use the tag helpers wherever possible.
ABP Framework also adds some **useful features** to the standard bootstrap components.
Here, the list of components those are wrapped by the ABP Framework:
* [Buttons](Buttons.md)
* ...
> Until all the tag helpers are documented, you can visit https://bootstrap-taghelpers.abp.io/ to see them with live samples.
## Form Elements
See [demo](https://bootstrap-taghelpers.abp.io/Components/FormElements).
## Dynamic Inputs
See [demo](https://bootstrap-taghelpers.abp.io/Components/DynamicForms).

BIN
docs/en/UI/AspNetCore/Tag-Helpers/fa-address-card.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

2
docs/en/UI/AspNetCore/Theming.md

@ -1,3 +1,3 @@
# Theming
# ASP.NET Core MVC / Razor Pages Theming
TODO

3
docs/en/UI/AspNetCore/Toolbars.md

@ -0,0 +1,3 @@
# Toolbars
TODO

19
docs/en/docs-nav.json

@ -293,16 +293,7 @@
},
{
"text": "Tag Helpers",
"items": [
{
"text": "Live Demo",
"path": "UI/AspNetCore/Tag-Helpers/Index.md"
},
{
"text": "Buttons",
"path": "UI/AspNetCore/Tag-Helpers/Buttons.md"
}
]
"path": "UI/AspNetCore/Tag-Helpers/Index.md"
},
{
"text": "Widgets",
@ -325,9 +316,17 @@
"text": "Permission Management",
"path": "UI/Angular/Permission-Management.md"
},
{
"text": "Config State",
"path": "UI/Angular/Config-State.md"
},
{
"text": "Component Replacement",
"path": "UI/Angular/Component-Replacement.md"
},
{
"text": "Custom Setting Page",
"path": "UI/Angular/Custom-Setting-Page.md"
}
]
}

BIN
docs/en/images/bookstore-added-brand-files.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/images/bookstore-added-logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
docs/en/images/bookstore-added-role-js-file.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/en/images/bookstore-brand-area-highlighted.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
docs/en/images/bookstore-global-css-file.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/en/images/bookstore-google-analytics-view-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/en/images/bookstore-menus-highlighted.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/en/images/bookstore-notification-icon-on-toolbar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
docs/en/images/bookstore-notification-view-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
docs/en/images/bookstore-toolbar-highlighted.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/en/images/overriding-login-cshtml.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/BasicTheme.cs

@ -8,7 +8,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic
{
public const string Name = "Basic";
public string GetLayout(string name, bool fallbackToDefault = true)
public virtual string GetLayout(string name, bool fallbackToDefault = true)
{
switch (name)
{

198
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AngularModuleSourceCodeAdder.cs

@ -0,0 +1,198 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.ProjectModification
{
public class AngularModuleSourceCodeAdder : ITransientDependency
{
public async Task AddAsync(string solutionFilePath, string angularPath)
{
var angularProjectsPath = Path.Combine(angularPath, "projects");
var projects = await CopyAndGetNamesOfAngularProjectsAsync(solutionFilePath, angularProjectsPath);
if (!projects.Any())
{
return;
}
await ReplaceProjectNamesAndCopySourCodeRequirementsToProjectsAsync(angularProjectsPath, projects);
await RemoveRedundantFilesAsync(angularProjectsPath, projects);
await AddPathsToTsConfigAsync(angularPath, angularProjectsPath, projects);
await AddProjectToAngularJsonAsync(angularPath, projects);
}
private async Task AddProjectToAngularJsonAsync(string angularPath, List<string> projects)
{
var angularJsonFilePath = Path.Combine(angularPath, "angular.json");
var fileContent = File.ReadAllText(angularJsonFilePath);
var json = JObject.Parse(fileContent);
var projectsJobject = (JObject)json["projects"];
foreach (var project in projects)
{
projectsJobject.Add(project, new JObject(
new JProperty("projectType", "library"),
new JProperty("root", $"projects/{project}"),
new JProperty("sourceRoot", $"projects/{project}/src"),
new JProperty("prefix", "lib"),
new JProperty("architect", new JObject(
new JProperty("build", new JObject(
new JProperty("builder", "@angular-devkit/build-ng-package:build"),
new JProperty("options", new JObject(
new JProperty("tsConfig", $"projects/{project}/tsconfig.lib.json"),
new JProperty("project", $"projects/{project}/ng-package.json")
)),
new JProperty("configurations", new JObject(
new JProperty("production", new JObject(
new JProperty("tsConfig", $"projects/{project}/tsconfig.lib.prod.json")))
)))),
new JProperty("test", new JObject(
new JProperty("builder", "@angular-devkit/build-angular:karma"),
new JProperty("options", new JObject(
new JProperty("main", $"projects/{project}/src/test.ts"),
new JProperty("tsConfig", $"projects/{project}/tsconfig.spec.json"),
new JProperty("karmaConfig", $"projects/{project}/karma.conf.js")
)
))),
new JProperty("lint", new JObject(
new JProperty("builder", "@angular-devkit/build-angular:tslint"),
new JProperty("options", new JObject(
new JProperty("tsConfig", new JArray(new JValue($"projects/{project}/tsconfig.lib.json"), new JValue($"projects/{project}/tsconfig.spec.json"))),
new JProperty("exclude", new JArray(new JValue("**/node_modules/**")))
))
))
))
));
}
File.WriteAllText(angularJsonFilePath, json.ToString(Formatting.Indented));
}
private async Task AddPathsToTsConfigAsync(string angularPath, string angularProjectsPath, List<string> projects)
{
var tsConfigPath = Path.Combine(angularPath, "tsconfig.json");
var fileContent = File.ReadAllText(tsConfigPath);
var tsConfigAsJson = JObject.Parse(fileContent);
var compilerOptions = (JObject)tsConfigAsJson["compilerOptions"];
foreach (var project in projects)
{
var projectPackageName = await GetProjectPackageNameAsync(angularProjectsPath, project);
var property = new JProperty($"{projectPackageName}",
new JArray(new object[] { $"projects/{project}/src/public-api.ts" })
);
var property2 = new JProperty($"{projectPackageName}/*",
new JArray(new object[] { $"projects/{project}/src/lib/*" })
);
if (compilerOptions["paths"] == null)
{
compilerOptions.Add("paths", new JObject());
}
((JObject)compilerOptions["paths"]).Add(property);
((JObject)compilerOptions["paths"]).Add(property2);
}
File.WriteAllText(tsConfigPath, tsConfigAsJson.ToString(Formatting.Indented));
}
private async Task<string> GetProjectPackageNameAsync(string angularProjectsPath, string project)
{
var packageJsonPath = Path.Combine(angularProjectsPath, project, "package.json");
var fileContent = File.ReadAllText(packageJsonPath);
return (string)JObject.Parse(fileContent)["name"];
}
private async Task RemoveRedundantFilesAsync(string angularProjectsPath, List<string> projects)
{
foreach (var project in projects)
{
var jestConfigPath = Path.Combine(angularProjectsPath, project, "jest.config.js");
File.Delete(jestConfigPath);
var testPath = Path.Combine(angularProjectsPath, project, "src", "test.ts");
File.Delete(testPath);
}
}
private async Task ReplaceProjectNamesAndCopySourCodeRequirementsToProjectsAsync(string angularProjectsPath, List<string> projects)
{
foreach (var project in projects)
{
var sourceCodeReqFolder = Path.Combine(angularProjectsPath, project, "source-code-requirements");
Directory.CreateDirectory(sourceCodeReqFolder);
var filesUnderSourceCodeReqFolder = Directory.GetFiles(sourceCodeReqFolder);
foreach (var fileUnderSourceCodeReqFolder in filesUnderSourceCodeReqFolder)
{
var newDest = Path.Combine(angularProjectsPath, project, Path.GetFileName(fileUnderSourceCodeReqFolder));
File.Move(fileUnderSourceCodeReqFolder, newDest);
var fileContent = File.ReadAllText(newDest);
fileContent = fileContent.Replace("{{project-name}}", project);
fileContent = fileContent.Replace("{{library-name-kebab-case}}", project);
File.WriteAllText(newDest, fileContent);
}
}
}
private async Task<List<string>> CopyAndGetNamesOfAngularProjectsAsync(string solutionFilePath, string angularProjectsPath)
{
var projects = new List<string>();
if (!Directory.Exists(angularProjectsPath))
{
Directory.CreateDirectory(angularProjectsPath);
}
var angularPathsInDownloadedSourceCode = Directory.GetDirectories(Path.Combine(Path.Combine(Path.GetDirectoryName(solutionFilePath), "modules"))).Select(p => Path.Combine(p, "angular"))
.Where(Directory.Exists);
foreach (var folder in angularPathsInDownloadedSourceCode)
{
var projectsInFolder = Directory.GetDirectories(folder);
foreach (var projectInFolder in projectsInFolder)
{
var projectName = Path.GetFileName(projectInFolder.TrimEnd('\\').TrimEnd('/'));
var destDirName = Path.Combine(angularProjectsPath, projectName);
if (Directory.Exists(destDirName))
{
Directory.Delete(projectInFolder, true);
continue;
}
projects.Add(projectName);
Directory.Move(projectInFolder, destDirName);
}
}
return projects;
}
}
}

19
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs

@ -30,6 +30,7 @@ namespace Volo.Abp.Cli.ProjectModification
public SourceCodeDownloadService SourceCodeDownloadService { get; }
public SolutionFileModifier SolutionFileModifier { get; }
public NugetPackageToLocalReferenceConverter NugetPackageToLocalReferenceConverter { get; }
public AngularModuleSourceCodeAdder AngularModuleSourceCodeAdder { get; }
public SolutionModuleAdder(
IJsonSerializer jsonSerializer,
@ -42,7 +43,8 @@ namespace Volo.Abp.Cli.ProjectModification
IRemoteServiceExceptionHandler remoteServiceExceptionHandler,
SourceCodeDownloadService sourceCodeDownloadService,
SolutionFileModifier solutionFileModifier,
NugetPackageToLocalReferenceConverter nugetPackageToLocalReferenceConverter)
NugetPackageToLocalReferenceConverter nugetPackageToLocalReferenceConverter,
AngularModuleSourceCodeAdder angularModuleSourceCodeAdder)
{
JsonSerializer = jsonSerializer;
ProjectNugetPackageAdder = projectNugetPackageAdder;
@ -55,6 +57,7 @@ namespace Volo.Abp.Cli.ProjectModification
SourceCodeDownloadService = sourceCodeDownloadService;
SolutionFileModifier = solutionFileModifier;
NugetPackageToLocalReferenceConverter = nugetPackageToLocalReferenceConverter;
AngularModuleSourceCodeAdder = angularModuleSourceCodeAdder;
Logger = NullLogger<SolutionModuleAdder>.Instance;
}
@ -83,11 +86,25 @@ namespace Volo.Abp.Cli.ProjectModification
await DownloadSourceCodesToSolutionFolder(module, modulesFolderInSolution, version);
await SolutionFileModifier.AddModuleToSolutionFileAsync(module, solutionFile);
await NugetPackageToLocalReferenceConverter.Convert(module, solutionFile);
await HandleAngularProject(module, solutionFile);
}
ModifyDbContext(projectFiles, module, startupProject, skipDbMigrations);
}
private async Task HandleAngularProject(ModuleWithMastersInfo module, string solutionFilePath)
{
var angularPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(solutionFilePath)), "angular");
if (!Directory.Exists(angularPath))
{
return;
}
await AngularModuleSourceCodeAdder.AddAsync(solutionFilePath, angularPath);
}
private async Task DownloadSourceCodesToSolutionFolder(ModuleWithMastersInfo module, string modulesFolderInSolution, string version = null)
{
await SourceCodeDownloadService.DownloadAsync(

62
framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json

@ -0,0 +1,62 @@
{
"culture": "ar",
"texts": {
"InternalServerErrorMessage": "حدث خطأ داخلي أثناء طلبك!",
"ValidationErrorMessage": "طلبك غير صحيح!",
"ValidationNarrativeErrorMessageTitle": "تم الكشف عن الأخطاء التالية أثناء التحقق .",
"DefaultErrorMessage": "حدث خطأ!",
"DefaultErrorMessageDetail": "لم يتم إرسال تفاصيل الخطأ بواسطة الخادم.",
"DefaultErrorMessage401": "أنت غير مصدق!",
"DefaultErrorMessage401Detail": "يجب عليك تسجيل الدخول لأداء هذه العملية.",
"DefaultErrorMessage403": "أنك غير مخول!",
"DefaultErrorMessage403Detail": "لا يسمح لك بإجراء هذه العملية!",
"DefaultErrorMessage404": "المورد غير موجود!",
"DefaultErrorMessage404Detail": "لم يتم العثور على المورد المطلوب على الخادم!",
"EntityNotFoundErrorMessage": "لا يوجد كيان {0} بالمعرف = {1}!",
"Languages": "اللغات",
"Error": "خطأ",
"AreYouSure": "هل أنت متأكد؟",
"Cancel": "إلغاء",
"Yes": "نعم",
"No": "لا",
"Ok": "حسنا",
"Close": "أغلق",
"Save": "حفظ",
"SavingWithThreeDot": "حفظ...",
"Actions": "أجراءات",
"Delete": "حذف",
"Edit": "تعديل",
"Refresh": "تحديث",
"Language": "لغة",
"LoadMore": "تحميل المزيد",
"ProcessingWithThreeDot": "معالجة...",
"LoadingWithThreeDot": "جار التحميل...",
"Welcome": "أهلا بك",
"Login": "تسجيل الدخول",
"Register": "تسجيل",
"Logout": "تسجيل خروج",
"Submit": "إرسال",
"Back": "عودة",
"PagerSearch": "بحث",
"PagerNext": "التالى",
"PagerPrevious": "السابق",
"PagerFirst": "الأول",
"PagerLast": "الاخير",
"PagerInfo": "عرض _START_ الي _END_ من _TOTAL_ إدخالات",
"PagerInfoEmpty": "عرض 0 الي 0 من 0 إدخالات",
"PagerInfoFiltered": "(تمت تصفيته من _MAX_ مجموع الإدخالات)",
"NoDataAvailableInDatatable": "لا توجد بيانات متاحة في الجدول",
"PagerShowMenuEntries": "عرض _MENU_ إدخالات",
"DatatableActionDropdownDefaultText": "أجراءات",
"ChangePassword": "تغيير كلمة السر",
"PersonalInfo": "ملفي الشخصي",
"AreYouSureYouWantToCancelEditingWarningMessage": "لم تحفظ التغييرات.",
"UnhandledException": "استثناء غير معالج!",
"401Message": "غير مصرح",
"403Message": "ممنوع",
"404Message": "الصفحة غير موجودة",
"500Message": "خطأ في الخادم الداخلي",
"GoHomePage": "اذهب الى الصفحة الرئيسية",
"GoBack": "عد"
}
}

26
npm/ng-packs/package.json

@ -21,19 +21,19 @@
"generate:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
},
"devDependencies": {
"@abp/ng.account": "^2.1.0",
"@abp/ng.account.config": "^2.1.0",
"@abp/ng.core": "^2.1.0",
"@abp/ng.feature-management": "^2.1.0",
"@abp/ng.identity": "^2.1.0",
"@abp/ng.identity.config": "^2.1.0",
"@abp/ng.permission-management": "^2.1.0",
"@abp/ng.setting-management": "^2.1.0",
"@abp/ng.setting-management.config": "^2.1.0",
"@abp/ng.tenant-management": "^2.1.0",
"@abp/ng.tenant-management.config": "^2.1.0",
"@abp/ng.theme.basic": "^2.1.0",
"@abp/ng.theme.shared": "^2.1.0",
"@abp/ng.account": "~2.1.0",
"@abp/ng.account.config": "~2.1.0",
"@abp/ng.core": "~2.1.0",
"@abp/ng.feature-management": "~2.1.0",
"@abp/ng.identity": "~2.1.0",
"@abp/ng.identity.config": "~2.1.0",
"@abp/ng.permission-management": "~2.1.0",
"@abp/ng.setting-management": "~2.1.0",
"@abp/ng.setting-management.config": "~2.1.0",
"@abp/ng.tenant-management": "~2.1.0",
"@abp/ng.tenant-management.config": "~2.1.0",
"@abp/ng.theme.basic": "~2.1.0",
"@abp/ng.theme.shared": "~2.1.0",
"@angular-builders/jest": "^8.2.0",
"@angular-devkit/build-angular": "~0.803.21",
"@angular-devkit/build-ng-packagr": "~0.803.21",

4
npm/ng-packs/packages/account/package.json

@ -7,8 +7,8 @@
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.account.config": "^2.2.0",
"@abp/ng.theme.shared": "^2.2.0"
"@abp/ng.account.config": "~2.2.0",
"@abp/ng.theme.shared": "~2.2.0"
},
"publishConfig": {
"access": "public"

4
npm/ng-packs/packages/core/src/lib/states/config.state.ts

@ -156,7 +156,7 @@ export class ConfigState {
const selector = createSelector([ConfigState], (state: Config.State) => {
if (!state.localization) return defaultValue || key;
const { defaultResourceName } = state.environment.localization;
const defaultResourceName = snq(() => state.environment.localization.defaultResourceName);
if (keys[0] === '') {
if (!defaultResourceName) {
throw new Error(
@ -170,7 +170,7 @@ export class ConfigState {
);
}
keys[0] = snq(() => defaultResourceName);
keys[0] = defaultResourceName;
}
let localization = (keys as any).reduce((acc, val) => {

2
npm/ng-packs/packages/feature-management/package.json

@ -7,7 +7,7 @@
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.theme.shared": "^2.2.0"
"@abp/ng.theme.shared": "~2.2.0"
},
"publishConfig": {
"access": "public"

6
npm/ng-packs/packages/identity/package.json

@ -7,9 +7,9 @@
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.identity.config": "^2.2.0",
"@abp/ng.permission-management": "^2.2.0",
"@abp/ng.theme.shared": "^2.2.0"
"@abp/ng.identity.config": "~2.2.0",
"@abp/ng.permission-management": "~2.2.0",
"@abp/ng.theme.shared": "~2.2.0"
},
"publishConfig": {
"access": "public"

2
npm/ng-packs/packages/permission-management/package.json

@ -7,7 +7,7 @@
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.theme.shared": "^2.2.0"
"@abp/ng.theme.shared": "~2.2.0"
},
"publishConfig": {
"access": "public"

4
npm/ng-packs/packages/setting-management/package.json

@ -7,8 +7,8 @@
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.setting-management.config": "^2.2.0",
"@abp/ng.theme.shared": "^2.2.0"
"@abp/ng.setting-management.config": "~2.2.0",
"@abp/ng.theme.shared": "~2.2.0"
},
"publishConfig": {
"access": "public"

6
npm/ng-packs/packages/tenant-management/package.json

@ -7,9 +7,9 @@
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.feature-management": "^2.2.0",
"@abp/ng.tenant-management.config": "^2.2.0",
"@abp/ng.theme.shared": "^2.2.0"
"@abp/ng.feature-management": "~2.2.0",
"@abp/ng.tenant-management.config": "~2.2.0",
"@abp/ng.theme.shared": "~2.2.0"
},
"publishConfig": {
"access": "public"

2
npm/ng-packs/packages/theme-basic/package.json

@ -7,7 +7,7 @@
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.theme.shared": "^2.2.0"
"@abp/ng.theme.shared": "~2.2.0"
},
"publishConfig": {
"access": "public"

2
npm/ng-packs/packages/theme-shared/package.json

@ -7,7 +7,7 @@
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.core": "^2.2.0",
"@abp/ng.core": "~2.2.0",
"@fortawesome/fontawesome-free": "^5.12.1",
"@ng-bootstrap/ng-bootstrap": "^5.3.0",
"@ngx-validate/core": "^0.0.7",

10
samples/BookStore-Angular-MongoDb/angular/package.json

@ -13,11 +13,11 @@
},
"private": true,
"dependencies": {
"@abp/ng.account": "^2.1.0",
"@abp/ng.identity": "^2.1.0",
"@abp/ng.setting-management": "^2.1.0",
"@abp/ng.tenant-management": "^2.1.0",
"@abp/ng.theme.basic": "^2.1.0",
"@abp/ng.account": "~2.2.0",
"@abp/ng.identity": "~2.2.0",
"@abp/ng.setting-management": "~2.2.0",
"@abp/ng.tenant-management": "~2.2.0",
"@abp/ng.theme.basic": "~2.2.0",
"@angular/animations": "~8.2.14",
"@angular/common": "~8.2.14",
"@angular/compiler": "~8.2.14",

2
templates/app/angular/package.json

@ -1,5 +1,5 @@
{
"name": "ng9-abp",
"name": "MyProjectName",
"version": "0.0.0",
"scripts": {
"ng": "ng",

10
templates/module/angular/package.json

@ -12,11 +12,11 @@
},
"private": true,
"dependencies": {
"@abp/ng.account": "^2.2.0",
"@abp/ng.theme.basic": "^2.2.0",
"@abp/ng.identity": "^2.2.0",
"@abp/ng.tenant-management": "^2.2.0",
"@abp/ng.setting-management": "^2.2.0",
"@abp/ng.account": "~2.2.0",
"@abp/ng.theme.basic": "~2.2.0",
"@abp/ng.identity": "~2.2.0",
"@abp/ng.tenant-management": "~2.2.0",
"@abp/ng.setting-management": "~2.2.0",
"@angular/animations": "~8.2.14",
"@angular/common": "~8.2.14",
"@angular/compiler": "~8.2.14",

2
templates/module/angular/projects/my-project-name-config/package.json

@ -2,6 +2,6 @@
"name": "my-project-name.config",
"version": "0.0.1",
"peerDependencies": {
"@abp/ng.core": ">=2.2.0"
"@abp/ng.core": "~2.2.0"
}
}

2
templates/module/angular/projects/my-project-name/package.json

@ -2,7 +2,7 @@
"name": "my-project-name",
"version": "0.0.1",
"dependencies": {
"@abp/ng.theme.shared": "^2.2.0",
"@abp/ng.theme.shared": "~2.2.0",
"my-project-name.config": "^0.0.1"
}
}

6
templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/sl.json

@ -0,0 +1,6 @@
{
"culture": "sl",
"texts": {
"ManageYourProfile": "Upravljajte svojim profilom"
}
}

6
templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hans.json

@ -0,0 +1,6 @@
{
"culture": "zh-Hans",
"texts": {
"ManageYourProfile": "管理个人资料"
}
}

6
templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/zh-Hant.json

@ -0,0 +1,6 @@
{
"culture": "zh-Hant",
"texts": {
"ManageYourProfile": "管理個人資料"
}
}
Loading…
Cancel
Save