Browse Source

Merge branch 'dev' of https://github.com/abpframework/abp into dev

pull/2523/head
Halil İbrahim Kalkan 6 years ago
parent
commit
80eb55bef8
  1. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs
  2. 31
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs
  3. 51
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController.cs
  4. 58
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController_Tests.cs
  5. 8
      npm/ng-packs/README.md
  6. 11
      npm/ng-packs/package.json
  7. 5
      npm/ng-packs/packages/account-config/package.json
  8. 5
      npm/ng-packs/packages/account/package.json
  9. 38
      npm/ng-packs/packages/account/src/lib/account-routing.module.ts
  10. 4
      npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html
  11. 9
      npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts
  12. 34
      npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.html
  13. 41
      npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts
  14. 118
      npm/ng-packs/packages/account/src/lib/components/login/login.component.html
  15. 28
      npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.html
  16. 19
      npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts
  17. 87
      npm/ng-packs/packages/account/src/lib/components/register/register.component.html
  18. 20
      npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts
  19. 29
      npm/ng-packs/packages/account/src/lib/models/account.ts
  20. 1
      npm/ng-packs/packages/account/src/lib/models/index.ts
  21. 5
      npm/ng-packs/packages/core/package.json
  22. 1
      npm/ng-packs/packages/core/src/lib/actions/index.ts
  23. 6
      npm/ng-packs/packages/core/src/lib/actions/replaceable-components.actions.ts
  24. 1
      npm/ng-packs/packages/core/src/lib/components/index.ts
  25. 39
      npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts
  26. 26
      npm/ng-packs/packages/core/src/lib/core.module.ts
  27. 2
      npm/ng-packs/packages/core/src/lib/directives/index.ts
  28. 12
      npm/ng-packs/packages/core/src/lib/directives/init.directive.ts
  29. 164
      npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts
  30. 13
      npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts
  31. 8
      npm/ng-packs/packages/core/src/lib/models/common.ts
  32. 3
      npm/ng-packs/packages/core/src/lib/models/index.ts
  33. 47
      npm/ng-packs/packages/core/src/lib/models/replaceable-components.ts
  34. 3
      npm/ng-packs/packages/core/src/lib/states/index.ts
  35. 50
      npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts
  36. 41
      npm/ng-packs/packages/core/src/lib/tests/replaceable-components.state.spec.ts
  37. 66
      npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts
  38. 175
      npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts
  39. 5
      npm/ng-packs/packages/feature-management/package.json
  40. 6
      npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts
  41. 12
      npm/ng-packs/packages/feature-management/src/lib/models/feature-management.ts
  42. 5
      npm/ng-packs/packages/identity-config/package.json
  43. 5
      npm/ng-packs/packages/identity/package.json
  44. 34
      npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html
  45. 4
      npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.ts
  46. 23
      npm/ng-packs/packages/identity/src/lib/components/users/users.component.html
  47. 4
      npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts
  48. 35
      npm/ng-packs/packages/identity/src/lib/identity-routing.module.ts
  49. 5
      npm/ng-packs/packages/permission-management/package.json
  50. 72
      npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts
  51. 13
      npm/ng-packs/packages/permission-management/src/lib/models/permission-management.ts
  52. 5
      npm/ng-packs/packages/setting-management-config/package.json
  53. 5
      npm/ng-packs/packages/setting-management/package.json
  54. 18
      npm/ng-packs/packages/setting-management/src/lib/setting-management-routing.module.ts
  55. 5
      npm/ng-packs/packages/tenant-management-config/package.json
  56. 5
      npm/ng-packs/packages/tenant-management/package.json
  57. 15
      npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html
  58. 4
      npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts
  59. 25
      npm/ng-packs/packages/tenant-management/src/lib/tenant-management-routing.module.ts
  60. 5
      npm/ng-packs/packages/theme-basic/package.json
  61. 5
      npm/ng-packs/packages/theme-shared/package.json
  62. 679
      npm/ng-packs/yarn.lock
  63. 23
      templates/module/angular/projects/my-project-name/src/lib/my-project-name-routing.module.ts

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs

@ -5,6 +5,7 @@ using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc.Features;
using Volo.Abp.AspNetCore.Mvc.ModelBinding;
using Volo.Abp.AspNetCore.Mvc.Response;
using Volo.Abp.AspNetCore.Mvc.Uow;
using Volo.Abp.AspNetCore.Mvc.Validation;
@ -28,6 +29,7 @@ namespace Volo.Abp.AspNetCore.Mvc
private static void AddFilters(MvcOptions options)
{
options.Filters.AddService(typeof(AbpAuditActionFilter));
options.Filters.AddService(typeof(AbpNoContentActionFilter));
options.Filters.AddService(typeof(AbpFeatureActionFilter));
options.Filters.AddService(typeof(AbpValidationActionFilter));
options.Filters.AddService(typeof(AbpUowActionFilter));

31
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs

@ -0,0 +1,31 @@
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.Response
{
public class AbpNoContentActionFilter : IAsyncActionFilter, ITransientDependency
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ActionDescriptor.IsControllerAction())
{
await next();
return;
}
await next();
if (context.HttpContext.Response.StatusCode == (int)HttpStatusCode.OK)
{
var returnType = context.ActionDescriptor.GetReturnType();
if (returnType == typeof(Task) || returnType == typeof(void))
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.NoContent;
}
}
}
}
}

51
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController.cs

@ -0,0 +1,51 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Volo.Abp.AspNetCore.Mvc.Response
{
[Route("api/NoContent-Test")]
public class NoContentTestController : AbpController
{
[HttpGet]
[Route("TestMethod")]
public void TestMethod()
{
}
[HttpGet]
[Route("TestMethodWithReturn")]
public string TestMethodWithReturn()
{
return "TestReturn";
}
[HttpGet]
[Route("TestCustomHttpStatusCodeMethod")]
public void TestCustomHttpStatusCodeMethod()
{
Response.Redirect("/");
}
[HttpGet]
[Route("TestAsyncMethod")]
public async Task TestAsyncMethod()
{
await Task.CompletedTask;
}
[HttpGet]
[Route("TestAsyncMethodWithReturn")]
public async Task<string> TestAsyncMethodWithReturn()
{
return await Task.FromResult("TestReturn");
}
[HttpGet]
[Route("TestAsyncCustomHttpStatusCodeMethod")]
public async Task TestAsyncCustomHttpStatusCodeMethod()
{
Response.Redirect("/");
await Task.CompletedTask;
}
}
}

58
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Response/NoContentTestController_Tests.cs

@ -0,0 +1,58 @@
using System.Net;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.Response
{
public class NoContentTestController_Tests : AspNetCoreMvcTestBase
{
[Fact]
public async Task Should_Set_No_Content_For_Void_Action()
{
var result = await GetResponseAsync("/api/NoContent-Test/TestMethod", HttpStatusCode.NoContent)
.ConfigureAwait(false);
result.StatusCode.ShouldBe(HttpStatusCode.NoContent);
}
[Fact]
public async Task Should_Not_Set_No_Content_For_Not_Void_Action()
{
var result = await GetResponseAsync("/api/NoContent-Test/TestMethodWithReturn")
.ConfigureAwait(false);
result.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact]
public async Task Should_Not_Set_No_Content_For_Custom_Http_Status_Code_Action()
{
var result = await GetResponseAsync("/api/NoContent-Test/TestCustomHttpStatusCodeMethod", HttpStatusCode.Redirect)
.ConfigureAwait(false);
result.StatusCode.ShouldBe(HttpStatusCode.Redirect);
}
[Fact]
public async Task Should_Set_No_Content_For_Task_Action()
{
var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncMethod", HttpStatusCode.NoContent)
.ConfigureAwait(false);
result.StatusCode.ShouldBe(HttpStatusCode.NoContent);
}
[Fact]
public async Task Should_Not_Set_No_Content_For_Not_Task_Action()
{
var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncMethodWithReturn")
.ConfigureAwait(false);
result.StatusCode.ShouldBe(HttpStatusCode.OK);
}
[Fact]
public async Task Should_Not_Set_No_Content_For_Custom_Http_Status_Code_Async_Action()
{
var result = await GetResponseAsync("/api/NoContent-Test/TestAsyncCustomHttpStatusCodeMethod", HttpStatusCode.Redirect)
.ConfigureAwait(false);
result.StatusCode.ShouldBe(HttpStatusCode.Redirect);
}
}
}

8
npm/ng-packs/README.md

@ -1,4 +1,10 @@
<h1> Abp Ng Packages </h1>
# Abp Ng Packages
<a href="https://github.com/abpframework/abp/actions?query=workflow%3AAngular">![action badge](https://img.shields.io/github/workflow/status/abpframework/abp/Angular)</a>
<a href="https://github.com/abpframework/abp/labels/ui-angular">![issues](https://img.shields.io/github/issues/abpframework/abp/ui-angular)</a>
<a href="https://github.com/abpframework/abp/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+label%3Aui-angular">![pull requests](https://img.shields.io/github/issues-pr-raw/abpframework/abp/ui-angular)</a>
<a href="https://npmjs.org/package/@abp/ng.core">![npm](https://img.shields.io/npm/dm/@abp/ng.core)</a>
![npm version](https://img.shields.io/npm/v/@abp/ng.core?label=version)
## Getting started

11
npm/ng-packs/package.json

@ -1,4 +1,9 @@
{
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"scripts": {
"ng": "ng",
"abpng": "abpng",
@ -30,11 +35,11 @@
"@abp/ng.theme.basic": "^1.1.1",
"@abp/ng.theme.shared": "^1.1.1",
"@angular-builders/jest": "^8.2.0",
"@angular-devkit/build-angular": "~0.803.20",
"@angular-devkit/build-ng-packagr": "~0.803.20",
"@angular-devkit/build-angular": "~0.803.21",
"@angular-devkit/build-ng-packagr": "~0.803.21",
"@angular/animations": "~8.2.14",
"@angular/cdk": "^8.2.3",
"@angular/cli": "~8.3.18",
"@angular/cli": "~8.3.21",
"@angular/common": "~8.2.14",
"@angular/compiler": "~8.2.14",
"@angular/compiler-cli": "~8.2.14",

5
npm/ng-packs/packages/account-config/package.json

@ -1,6 +1,11 @@
{
"name": "@abp/ng.account.config",
"version": "1.1.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"publishConfig": {
"access": "public"
}

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

@ -1,6 +1,11 @@
{
"name": "@abp/ng.account",
"version": "1.1.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.account.config": "^1.1.1",
"@abp/ng.theme.shared": "^1.1.1"

38
npm/ng-packs/packages/account/src/lib/account-routing.module.ts

@ -1,4 +1,9 @@
import { DynamicLayoutComponent } from '@abp/ng.core';
import {
DynamicLayoutComponent,
AuthGuard,
ReplaceableComponents,
ReplaceableRouteContainerComponent,
} from '@abp/ng.core';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './components/login/login.component';
@ -11,11 +16,36 @@ const routes: Routes = [
path: '',
component: DynamicLayoutComponent,
children: [
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent },
{
path: 'login',
component: ReplaceableRouteContainerComponent,
data: {
replaceableComponent: {
key: 'Account.LoginComponent',
defaultComponent: LoginComponent,
} as ReplaceableComponents.RouteData<LoginComponent>,
},
},
{
path: 'register',
component: ReplaceableRouteContainerComponent,
data: {
replaceableComponent: {
key: 'Account.RegisterComponent',
defaultComponent: RegisterComponent,
} as ReplaceableComponents.RouteData<RegisterComponent>,
},
},
{
path: 'manage-profile',
component: ManageProfileComponent,
component: ReplaceableRouteContainerComponent,
canActivate: [AuthGuard],
data: {
replaceableComponent: {
key: 'Account.ManageProfileComponent',
defaultComponent: ManageProfileComponent,
} as ReplaceableComponents.RouteData<ManageProfileComponent>,
},
},
],
},

4
npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html

@ -1,6 +1,8 @@
<div class="row">
<div class="mx-auto col col-md-5">
<abp-tenant-box></abp-tenant-box>
<abp-tenant-box
*abpReplaceableTemplate="{ componentKey: 'Account.TenantBoxComponent' }"
></abp-tenant-box>
<div class="abp-account-container">
<div class="card mt-3 shadow-sm rounded">

9
npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts

@ -1,13 +1,16 @@
import { Component, Input, TemplateRef } from '@angular/core';
import { Account } from '../../models/account';
@Component({
selector: 'abp-auth-wrapper',
templateUrl: './auth-wrapper.component.html',
exportAs: 'abpAuthWrapper',
})
export class AuthWrapperComponent {
export class AuthWrapperComponent
implements Account.AuthWrapperComponentInputs, Account.AuthWrapperComponentOutputs {
@Input()
mainContentRef: TemplateRef<any>;
readonly mainContentRef: TemplateRef<any>;
@Input()
cancelContentRef: TemplateRef<any>;
readonly cancelContentRef: TemplateRef<any>;
}

34
npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.html

@ -1,17 +1,41 @@
<form [formGroup]="form" (ngSubmit)="onSubmit()" [mapErrorsFn]="mapErrorsFn" validateOnSubmit>
<div class="form-group">
<label for="current-password">{{ 'AbpIdentity::DisplayName:CurrentPassword' | abpLocalization }}</label
<label for="current-password">{{
'AbpIdentity::DisplayName:CurrentPassword' | abpLocalization
}}</label
><span> * </span
><input type="password" id="current-password" class="form-control" formControlName="password" autofocus />
><input
type="password"
id="current-password"
class="form-control"
formControlName="password"
autofocus
autocomplete="current-password"
/>
</div>
<div class="form-group">
<label for="new-password">{{ 'AbpIdentity::DisplayName:NewPassword' | abpLocalization }}</label
><span> * </span><input type="password" id="new-password" class="form-control" formControlName="newPassword" />
><span> * </span
><input
type="password"
id="new-password"
class="form-control"
formControlName="newPassword"
autocomplete="new-password"
/>
</div>
<div class="form-group">
<label for="confirm-new-password">{{ 'AbpIdentity::DisplayName:NewPasswordConfirm' | abpLocalization }}</label
<label for="confirm-new-password">{{
'AbpIdentity::DisplayName:NewPasswordConfirm' | abpLocalization
}}</label
><span> * </span
><input type="password" id="confirm-new-password" class="form-control" formControlName="repeatNewPassword" />
><input
type="password"
id="confirm-new-password"
class="form-control"
formControlName="repeatNewPassword"
autocomplete="new-password"
/>
</div>
<abp-button
iconClass="fa fa-check"

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

@ -6,6 +6,7 @@ import { comparePasswords, Validation, PasswordRules, validatePassword } from '@
import { Store } from '@ngxs/store';
import snq from 'snq';
import { finalize } from 'rxjs/operators';
import { Account } from '../../models/account';
const { minLength, required, maxLength } = Validators;
@ -14,8 +15,10 @@ const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword'];
@Component({
selector: 'abp-change-password-form',
templateUrl: './change-password.component.html',
exportAs: 'abpChangePasswordForm',
})
export class ChangePasswordComponent implements OnInit {
export class ChangePasswordComponent
implements OnInit, Account.ChangePasswordComponentInputs, Account.ChangePasswordComponentOutputs {
form: FormGroup;
inProgress: boolean;
@ -26,7 +29,11 @@ export class ChangePasswordComponent implements OnInit {
return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch'));
};
constructor(private fb: FormBuilder, private store: Store, private toasterService: ToasterService) {}
constructor(
private fb: FormBuilder,
private store: Store,
private toasterService: ToasterService,
) {}
ngOnInit(): void {
const passwordRules: ABP.Dictionary<string> = this.store.selectSnapshot(
@ -61,12 +68,24 @@ export class ChangePasswordComponent implements OnInit {
newPassword: [
'',
{
validators: [required, validatePassword(passwordRulesArr), minLength(requiredLength), maxLength(32)],
validators: [
required,
validatePassword(passwordRulesArr),
minLength(requiredLength),
maxLength(32),
],
},
],
repeatNewPassword: [
'',
{ validators: [required, validatePassword(passwordRulesArr), minLength(requiredLength), maxLength(32)] },
{
validators: [
required,
validatePassword(passwordRulesArr),
minLength(requiredLength),
maxLength(32),
],
},
],
},
{
@ -89,12 +108,18 @@ export class ChangePasswordComponent implements OnInit {
.subscribe({
next: () => {
this.form.reset();
this.toasterService.success('AbpAccount::PasswordChangedMessage', 'Success', { life: 5000 });
this.toasterService.success('AbpAccount::PasswordChangedMessage', 'Success', {
life: 5000,
});
},
error: err => {
this.toasterService.error(snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'), 'Error', {
life: 7000,
});
this.toasterService.error(
snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'),
'Error',
{
life: 7000,
},
);
},
});
}

118
npm/ng-packs/packages/account/src/lib/components/login/login.component.html

@ -1,50 +1,74 @@
<abp-auth-wrapper [mainContentRef]="mainContentRef" [cancelContentRef]="cancelContentRef">
<ng-template #mainContentRef>
<h4>{{ 'AbpAccount::Login' | abpLocalization }}</h4>
<strong>
{{ 'AbpAccount::AreYouANewUser' | abpLocalization }}
<a class="text-decoration-none" routerLink="/account/register">{{ 'AbpAccount::Register' | abpLocalization }}</a>
</strong>
<form [formGroup]="form" (ngSubmit)="onSubmit()" validateOnSubmit class="mt-4">
<div class="form-group">
<label for="login-input-user-name-or-email-address">{{
'AbpAccount::UserNameOrEmailAddress' | abpLocalization
}}</label>
<abp-auth-wrapper
*abpReplaceableTemplate="{
componentKey: 'Account.AuthWrapperComponent',
inputs: {
mainContentRef: { value: mainContentRef },
cancelContentRef: { value: cancelContentRef }
}
}"
[mainContentRef]="mainContentRef"
[cancelContentRef]="cancelContentRef"
>
</abp-auth-wrapper>
<ng-template #mainContentRef>
<h4>{{ 'AbpAccount::Login' | abpLocalization }}</h4>
<strong>
{{ 'AbpAccount::AreYouANewUser' | abpLocalization }}
<a class="text-decoration-none" routerLink="/account/register">{{
'AbpAccount::Register' | abpLocalization
}}</a>
</strong>
<form [formGroup]="form" (ngSubmit)="onSubmit()" validateOnSubmit class="mt-4">
<div class="form-group">
<label for="login-input-user-name-or-email-address">{{
'AbpAccount::UserNameOrEmailAddress' | abpLocalization
}}</label>
<input
class="form-control"
type="text"
id="login-input-user-name-or-email-address"
formControlName="username"
autocomplete="username"
autofocus
/>
</div>
<div class="form-group">
<label for="login-input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label>
<input
class="form-control"
type="password"
id="login-input-password"
formControlName="password"
autocomplete="current-password"
/>
</div>
<div class="form-check" validationTarget validationStyle>
<label class="form-check-label" for="login-input-remember-me">
<input
class="form-control"
type="text"
id="login-input-user-name-or-email-address"
formControlName="username"
autofocus
class="form-check-input"
type="checkbox"
id="login-input-remember-me"
formControlName="remember"
/>
</div>
<div class="form-group">
<label for="login-input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label>
<input class="form-control" type="password" id="login-input-password" formControlName="password" />
</div>
<div class="form-check" validationTarget validationStyle>
<label class="form-check-label" for="login-input-remember-me">
<input class="form-check-input" type="checkbox" id="login-input-remember-me" formControlName="remember" />
{{ 'AbpAccount::RememberMe' | abpLocalization }}
</label>
</div>
<abp-button
[loading]="inProgress"
buttonType="submit"
name="Action"
buttonClass="btn-block btn-lg mt-3 btn btn-primary"
>
{{ 'AbpAccount::Login' | abpLocalization }}
</abp-button>
</form>
</ng-template>
<ng-template #cancelContentRef>
<div class="card-footer text-center border-0">
<a routerLink="/">
<button type="button" name="Action" value="Cancel" class="px-2 py-0 btn btn-link">
{{ 'AbpAccount::Cancel' | abpLocalization }}
</button>
</a>
{{ 'AbpAccount::RememberMe' | abpLocalization }}
</label>
</div>
</ng-template>
</abp-auth-wrapper>
<abp-button
[loading]="inProgress"
buttonType="submit"
name="Action"
buttonClass="btn-block btn-lg mt-3 btn btn-primary"
>
{{ 'AbpAccount::Login' | abpLocalization }}
</abp-button>
</form>
</ng-template>
<ng-template #cancelContentRef>
<div class="card-footer text-center border-0">
<a routerLink="/">
<button type="button" name="Action" value="Cancel" class="px-2 py-0 btn btn-link">
{{ 'AbpAccount::Cancel' | abpLocalization }}
</button>
</a>
</div>
</ng-template>

28
npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.html

@ -6,14 +6,22 @@
<div class="col-3">
<ul class="nav flex-column nav-pills" id="nav-tab" role="tablist">
<li class="nav-item" (click)="selectedTab = 0">
<a class="nav-link" [ngClass]="{ active: selectedTab === 0 }" role="tab" href="javascript:void(0)">{{
'AbpUi::ChangePassword' | abpLocalization
}}</a>
<a
class="nav-link"
[ngClass]="{ active: selectedTab === 0 }"
role="tab"
href="javascript:void(0)"
>{{ 'AbpUi::ChangePassword' | abpLocalization }}</a
>
</li>
<li class="nav-item" (click)="selectedTab = 1">
<a class="nav-link" [ngClass]="{ active: selectedTab === 1 }" role="tab" href="javascript:void(0)">{{
'AbpAccount::PersonalSettings' | abpLocalization
}}</a>
<a
class="nav-link"
[ngClass]="{ active: selectedTab === 1 }"
role="tab"
href="javascript:void(0)"
>{{ 'AbpAccount::PersonalSettings' | abpLocalization }}</a
>
</li>
</ul>
</div>
@ -24,7 +32,9 @@
{{ 'AbpIdentity::ChangePassword' | abpLocalization }}
<hr />
</h4>
<abp-change-password-form></abp-change-password-form>
<abp-change-password-form
*abpReplaceableTemplate="{ componentKey: 'Account.ChangePasswordComponent' }"
></abp-change-password-form>
</div>
</div>
<div class="tab-content" *ngIf="selectedTab === 1" [@fadeIn]>
@ -33,7 +43,9 @@
{{ 'AbpIdentity::PersonalSettings' | abpLocalization }}
<hr />
</h4>
<abp-personal-settings-form></abp-personal-settings-form>
<abp-personal-settings-form
*abpReplaceableTemplate="{ componentKey: 'Account.PersonalSettingsComponent' }"
></abp-personal-settings-form>
</div>
</div>
</div>

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

@ -5,14 +5,20 @@ import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { take, withLatestFrom, finalize } from 'rxjs/operators';
import { ToasterService } from '@abp/ng.theme.shared';
import { Account } from '../../models/account';
const { maxLength, required, email } = Validators;
@Component({
selector: 'abp-personal-settings-form',
templateUrl: './personal-settings.component.html',
exportAs: 'abpPersonalSettingsForm',
})
export class PersonalSettingsComponent implements OnInit {
export class PersonalSettingsComponent
implements
OnInit,
Account.PersonalSettingsComponentInputs,
Account.PersonalSettingsComponentOutputs {
@Select(ProfileState.getProfile)
profile$: Observable<Profile.Response>;
@ -20,7 +26,11 @@ export class PersonalSettingsComponent implements OnInit {
inProgress: boolean;
constructor(private fb: FormBuilder, private store: Store, private toasterService: ToasterService) {}
constructor(
private fb: FormBuilder,
private store: Store,
private toasterService: ToasterService,
) {}
ngOnInit() {
this.buildForm();
@ -29,10 +39,7 @@ export class PersonalSettingsComponent implements OnInit {
buildForm() {
this.store
.dispatch(new GetProfile())
.pipe(
withLatestFrom(this.profile$),
take(1),
)
.pipe(withLatestFrom(this.profile$), take(1))
.subscribe(([, profile]) => {
this.form = this.fb.group({
userName: [profile.userName, [required, maxLength(256)]],

87
npm/ng-packs/packages/account/src/lib/components/register/register.component.html

@ -1,32 +1,57 @@
<abp-auth-wrapper [mainContentRef]="mainContentRef">
<ng-template #mainContentRef>
<h4>{{ 'AbpAccount::Register' | abpLocalization }}</h4>
<strong>
{{ 'AbpAccount::AlreadyRegistered' | abpLocalization }}
<a class="text-decoration-none" routerLink="/account/login">{{ 'AbpAccount::Login' | abpLocalization }}</a>
</strong>
<form [formGroup]="form" (ngSubmit)="onSubmit()" validateOnSubmit class="mt-4">
<div class="form-group">
<label for="input-user-name">{{ 'AbpAccount::UserName' | abpLocalization }}</label
><span> * </span
><input autofocus type="text" id="input-user-name" class="form-control" formControlName="username" />
</div>
<div class="form-group">
<label for="input-email-address">{{ 'AbpAccount::EmailAddress' | abpLocalization }}</label
><span> * </span><input type="email" id="input-email-address" class="form-control" formControlName="email" />
</div>
<div class="form-group">
<label for="input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label
><span> * </span><input type="password" id="input-password" class="form-control" formControlName="password" />
</div>
<abp-button
[loading]="inProgress"
buttonType="submit"
name="Action"
buttonClass="btn-block btn-lg mt-3 btn btn-primary"
>
{{ 'AbpAccount::Register' | abpLocalization }}
</abp-button>
</form>
</ng-template>
<abp-auth-wrapper
*abpReplaceableTemplate="{
componentKey: 'Account.AuthWrapperComponent',
inputs: {
mainContentRef: { value: mainContentRef }
}
}"
[mainContentRef]="mainContentRef"
>
</abp-auth-wrapper>
<ng-template #mainContentRef>
<h4>{{ 'AbpAccount::Register' | abpLocalization }}</h4>
<strong>
{{ 'AbpAccount::AlreadyRegistered' | abpLocalization }}
<a class="text-decoration-none" routerLink="/account/login">{{
'AbpAccount::Login' | abpLocalization
}}</a>
</strong>
<form [formGroup]="form" (ngSubmit)="onSubmit()" validateOnSubmit class="mt-4">
<div class="form-group">
<label for="input-user-name">{{ 'AbpAccount::UserName' | abpLocalization }}</label
><span> * </span
><input
autofocus
type="text"
id="input-user-name"
class="form-control"
formControlName="username"
autocomplete="username"
/>
</div>
<div class="form-group">
<label for="input-email-address">{{ 'AbpAccount::EmailAddress' | abpLocalization }}</label
><span> * </span
><input type="email" id="input-email-address" class="form-control" formControlName="email" />
</div>
<div class="form-group">
<label for="input-password">{{ 'AbpAccount::Password' | abpLocalization }}</label
><span> * </span
><input
type="password"
id="input-password"
class="form-control"
formControlName="password"
autocomplete="current-password"
/>
</div>
<abp-button
[loading]="inProgress"
buttonType="submit"
name="Action"
buttonClass="btn-block btn-lg mt-3 btn btn-primary"
>
{{ 'AbpAccount::Register' | abpLocalization }}
</abp-button>
</form>
</ng-template>

20
npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts

@ -6,12 +6,14 @@ import { throwError } from 'rxjs';
import { catchError, take, finalize } from 'rxjs/operators';
import snq from 'snq';
import { AccountService } from '../../services/account.service';
import { Account } from '../../models/account';
@Component({
selector: 'abp-tenant-box',
templateUrl: './tenant-box.component.html',
})
export class TenantBoxComponent implements OnInit {
export class TenantBoxComponent
implements OnInit, Account.TenantBoxComponentInputs, Account.TenantBoxComponentOutputs {
tenant = {} as ABP.BasicItem;
tenantName: string;
@ -20,7 +22,11 @@ export class TenantBoxComponent implements OnInit {
inProgress: boolean;
constructor(private store: Store, private toasterService: ToasterService, private accountService: AccountService) {}
constructor(
private store: Store,
private toasterService: ToasterService,
private accountService: AccountService,
) {}
ngOnInit() {
this.tenant = this.store.selectSnapshot(SessionState.getTenant) || ({} as ABP.BasicItem);
@ -56,9 +62,13 @@ export class TenantBoxComponent implements OnInit {
this.tenantName = this.tenant.name;
this.isModalVisible = false;
} else {
this.toasterService.error('AbpUiMultiTenancy::GivenTenantIsNotAvailable', 'AbpUi::Error', {
messageLocalizationParams: [this.tenant.name],
});
this.toasterService.error(
'AbpUiMultiTenancy::GivenTenantIsNotAvailable',
'AbpUi::Error',
{
messageLocalizationParams: [this.tenant.name],
},
);
this.tenant = {} as ABP.BasicItem;
}
this.store.dispatch(new SetTenant(success ? this.tenant : null));

29
npm/ng-packs/packages/account/src/lib/models/account.ts

@ -0,0 +1,29 @@
import { TemplateRef } from '@angular/core';
export namespace Account {
export interface AuthWrapperComponentInputs {
readonly mainContentRef: TemplateRef<any>;
readonly cancelContentRef?: TemplateRef<any>;
}
// tslint:disable-next-line: no-empty-interface
export interface AuthWrapperComponentOutputs {}
// tslint:disable-next-line: no-empty-interface
export interface TenantBoxComponentInputs {}
// tslint:disable-next-line: no-empty-interface
export interface TenantBoxComponentOutputs {}
// tslint:disable-next-line: no-empty-interface
export interface PersonalSettingsComponentInputs {}
// tslint:disable-next-line: no-empty-interface
export interface PersonalSettingsComponentOutputs {}
// tslint:disable-next-line: no-empty-interface
export interface ChangePasswordComponentInputs {}
// tslint:disable-next-line: no-empty-interface
export interface ChangePasswordComponentOutputs {}
}

1
npm/ng-packs/packages/account/src/lib/models/index.ts

@ -1,3 +1,4 @@
export * from './account';
export * from './options';
export * from './user';
export * from './tenant';

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

@ -1,6 +1,11 @@
{
"name": "@abp/ng.core",
"version": "1.1.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@ngxs/router-plugin": "^3.5.1",
"@ngxs/storage-plugin": "^3.5.1",

1
npm/ng-packs/packages/core/src/lib/actions/index.ts

@ -1,5 +1,6 @@
export * from './config.actions';
export * from './loader.actions';
export * from './profile.actions';
export * from './replaceable-components.actions';
export * from './rest.actions';
export * from './session.actions';

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

@ -0,0 +1,6 @@
import { ReplaceableComponents } from '../models/replaceable-components';
export class AddReplaceableComponent {
static readonly type = '[ReplaceableComponents] Add';
constructor(public payload: ReplaceableComponents.ReplaceableComponent) {}
}

1
npm/ng-packs/packages/core/src/lib/components/index.ts

@ -1,2 +1,3 @@
export * from './dynamic-layout.component';
export * from './replaceable-route-container.component';
export * from './router-outlet.component';

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

@ -0,0 +1,39 @@
import { Component, OnDestroy, OnInit, Type } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngxs/store';
import { distinctUntilChanged } from 'rxjs/operators';
import { ABP } from '../models/common';
import { ReplaceableComponents } from '../models/replaceable-components';
import { ReplaceableComponentsState } from '../states/replaceable-components.state';
import { takeUntilDestroy } from '../utils/rxjs-utils';
@Component({
selector: 'abp-replaceable-route-container',
template: `
<ng-container *ngComponentOutlet="externalComponent || defaultComponent"></ng-container>
`,
})
export class ReplaceableRouteContainerComponent implements OnInit, OnDestroy {
defaultComponent: Type<any>;
componentKey: string;
externalComponent: Type<any>;
constructor(private route: ActivatedRoute, private store: Store) {}
ngOnInit() {
this.defaultComponent = this.route.snapshot.data.replaceableComponent.defaultComponent;
this.componentKey = (this.route.snapshot.data
.replaceableComponent as ReplaceableComponents.RouteData).key;
this.store
.select(ReplaceableComponentsState.getComponent(this.componentKey))
.pipe(takeUntilDestroy(this), distinctUntilChanged())
.subscribe((res = {} as ReplaceableComponents.ReplaceableComponent) => {
this.externalComponent = res.component;
});
}
ngOnDestroy() {}
}

26
npm/ng-packs/packages/core/src/lib/core.module.ts

@ -29,10 +29,14 @@ import { ProfileState } from './states/profile.state';
import { SessionState } from './states/session.state';
import { getInitialData, localeInitializer } from './utils/initial-utils';
import './utils/date-extensions';
import { ReplaceableRouteContainerComponent } from './components/replaceable-route-container.component';
import { ReplaceableComponentsState } from './states/replaceable-components.state';
import { InitDirective } from './directives/init.directive';
import { ReplaceableTemplateDirective } from './directives/replaceable-template.directive';
@NgModule({
imports: [
NgxsModule.forFeature([ProfileState, SessionState, ConfigState]),
NgxsModule.forFeature([ReplaceableComponentsState, ProfileState, SessionState, ConfigState]),
NgxsRouterPluginModule.forRoot(),
NgxsStoragePluginModule.forRoot({ key: ['SessionState'] }),
OAuthModule.forRoot(),
@ -43,6 +47,7 @@ import './utils/date-extensions';
RouterModule,
],
declarations: [
ReplaceableRouteContainerComponent,
RouterOutletComponent,
DynamicLayoutComponent,
AutofocusDirective,
@ -51,10 +56,12 @@ import './utils/date-extensions';
FormSubmitDirective,
LocalizationPipe,
SortPipe,
InitDirective,
PermissionDirective,
VisibilityDirective,
InputEventDebounceDirective,
StopPropagationDirective,
ReplaceableTemplateDirective,
AbstractNgModelComponent,
],
exports: [
@ -65,21 +72,28 @@ import './utils/date-extensions';
RouterModule,
RouterOutletComponent,
DynamicLayoutComponent,
AbstractNgModelComponent,
ReplaceableRouteContainerComponent,
AutofocusDirective,
EllipsisDirective,
ForDirective,
FormSubmitDirective,
LocalizationPipe,
SortPipe,
InitDirective,
PermissionDirective,
VisibilityDirective,
InputEventDebounceDirective,
LocalizationPipe,
ReplaceableTemplateDirective,
StopPropagationDirective,
AbstractNgModelComponent,
LocalizationPipe,
SortPipe,
LocalizationPipe,
],
providers: [LocalizationPipe],
entryComponents: [RouterOutletComponent, DynamicLayoutComponent],
entryComponents: [
RouterOutletComponent,
DynamicLayoutComponent,
ReplaceableRouteContainerComponent,
],
})
export class CoreModule {
static forRoot(options = {} as ABP.Root): ModuleWithProviders {

2
npm/ng-packs/packages/core/src/lib/directives/index.ts

@ -2,5 +2,7 @@ export * from './autofocus.directive';
export * from './ellipsis.directive';
export * from './for.directive';
export * from './form-submit.directive';
export * from './init.directive';
export * from './permission.directive';
export * from './replaceable-template.directive';
export * from './visibility.directive';

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

@ -0,0 +1,12 @@
import { Directive, Output, EventEmitter, ElementRef, AfterViewInit } from '@angular/core';
@Directive({ selector: '[abpInit]' })
export class InitDirective implements AfterViewInit {
@Output('abpInit') readonly init = new EventEmitter<ElementRef<any>>();
constructor(private elRef: ElementRef) {}
ngAfterViewInit() {
this.init.emit(this.elRef);
}
}

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

@ -0,0 +1,164 @@
import {
ComponentFactoryResolver,
Directive,
Injector,
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChanges,
TemplateRef,
Type,
ViewContainerRef,
} from '@angular/core';
import { Store } from '@ngxs/store';
import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
import { ABP } from '../models/common';
import { ReplaceableComponents } from '../models/replaceable-components';
import { ReplaceableComponentsState } from '../states/replaceable-components.state';
import { takeUntilDestroy } from '../utils/rxjs-utils';
import compare from 'just-compare';
import snq from 'snq';
@Directive({ selector: '[abpReplaceableTemplate]' })
export class ReplaceableTemplateDirective implements OnInit, OnDestroy, OnChanges {
@Input('abpReplaceableTemplate')
data: ReplaceableComponents.ReplaceableTemplateDirectiveInput<any, any>;
providedData = { inputs: {}, outputs: {} } as ReplaceableComponents.ReplaceableTemplateData<
any,
any
>;
context = {} as any;
externalComponent: Type<any>;
defaultComponentRef: any;
defaultComponentSubscriptions = {} as ABP.Dictionary<Subscription>;
initialized = false;
constructor(
private injector: Injector,
private templateRef: TemplateRef<any>,
private cfRes: ComponentFactoryResolver,
private vcRef: ViewContainerRef,
private store: Store,
) {
this.context = {
initTemplate: ref => {
this.resetDefaultComponent();
this.defaultComponentRef = ref;
this.setDefaultComponentInputs();
},
};
}
ngOnInit() {
this.store
.select(ReplaceableComponentsState.getComponent(this.data.componentKey))
.pipe(
filter(
(res = {} as ReplaceableComponents.ReplaceableComponent) =>
!this.initialized || !compare(res.component, this.externalComponent),
),
takeUntilDestroy(this),
)
.subscribe((res = {} as ReplaceableComponents.ReplaceableComponent) => {
this.vcRef.clear();
this.externalComponent = res.component;
if (this.defaultComponentRef) {
this.resetDefaultComponent();
}
if (res.component) {
this.setProvidedData();
const customInjector = Injector.create({
providers: [{ provide: 'REPLACEABLE_DATA', useValue: this.providedData }],
parent: this.injector,
});
this.vcRef.createComponent(
this.cfRes.resolveComponentFactory(res.component),
0,
customInjector,
);
} else {
this.vcRef.createEmbeddedView(this.templateRef, this.context);
}
this.initialized = true;
});
}
ngOnChanges(changes: SimpleChanges) {
if (snq(() => changes.data.currentValue.inputs) && this.defaultComponentRef) {
this.setDefaultComponentInputs();
}
}
ngOnDestroy() {}
setDefaultComponentInputs() {
if (!this.defaultComponentRef || (!this.data.inputs && !this.data.outputs)) return;
if (this.data.inputs) {
for (const key in this.data.inputs) {
if (this.data.inputs.hasOwnProperty(key)) {
if (!compare(this.defaultComponentRef[key], this.data.inputs[key].value)) {
this.defaultComponentRef[key] = this.data.inputs[key].value;
}
}
}
}
if (this.data.outputs) {
for (const key in this.data.outputs) {
if (this.data.outputs.hasOwnProperty(key)) {
if (!this.defaultComponentSubscriptions[key]) {
this.defaultComponentSubscriptions[key] = this.defaultComponentRef[key].subscribe(
value => {
this.data.outputs[key](value);
},
);
}
}
}
}
}
setProvidedData() {
this.providedData = { ...this.data, inputs: {} };
if (!this.data.inputs) return;
Object.defineProperties(this.providedData.inputs, {
...Object.keys(this.data.inputs).reduce(
(acc, key) => ({
...acc,
[key]: {
enumerable: true,
configurable: true,
get: () => this.data.inputs[key].value,
...(this.data.inputs[key].twoWay && {
set: newValue => {
this.data.inputs[key].value = newValue;
this.data.outputs[`${key}Change`](newValue);
},
}),
},
}),
{},
),
});
}
resetDefaultComponent() {
Object.keys(this.defaultComponentSubscriptions).forEach(key => {
this.defaultComponentSubscriptions[key].unsubscribe();
});
this.defaultComponentSubscriptions = {} as ABP.Dictionary<Subscription>;
this.defaultComponentRef = null;
}
}

13
npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import snq from 'snq';
import { RestOccurError } from '../actions';
@ -14,11 +14,18 @@ export class PermissionGuard implements CanActivate {
constructor(private store: Store) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
let resource = snq(() => route.data.routes.requiredPolicy) || snq(() => route.data.requiredPolicy as string);
let resource =
snq(() => route.data.routes.requiredPolicy) || snq(() => route.data.requiredPolicy as string);
if (!resource) {
resource = snq(
() => route.routeConfig.children.find(child => state.url.indexOf(child.path) > -1).data.requiredPolicy,
() =>
route.routeConfig.children.find(child => state.url.indexOf(child.path) > -1).data
.requiredPolicy,
);
if (!resource) {
return of(true);
}
}
return this.store.select(ConfigState.getGrantedPolicy(resource)).pipe(

8
npm/ng-packs/packages/core/src/lib/models/common.ts

@ -1,5 +1,7 @@
import { Config } from './config';
import { eLayoutType } from '../enums/common';
import { Config } from './config';
import { EventEmitter } from '@angular/core';
import { Subject } from 'rxjs';
export namespace ABP {
export interface Root {
@ -47,4 +49,8 @@ export namespace ABP {
export interface Dictionary<T = any> {
[key: string]: T;
}
export type ExtractFromOutput<
T extends EventEmitter<any> | Subject<any>
> = T extends EventEmitter<infer X> ? X : T extends Subject<infer Y> ? Y : never;
}

3
npm/ng-packs/packages/core/src/lib/models/index.ts

@ -1,6 +1,7 @@
export * from './application-configuration';
export * from './common';
export * from './config';
export * from './profile';
export * from './replaceable-components';
export * from './rest';
export * from './session';
export * from './profile';

47
npm/ng-packs/packages/core/src/lib/models/replaceable-components.ts

@ -0,0 +1,47 @@
import { Type, EventEmitter } from '@angular/core';
import { ABP } from './common';
import { Subject, BehaviorSubject } from 'rxjs';
export namespace ReplaceableComponents {
export interface State {
replaceableComponents: ReplaceableComponent[];
}
export interface ReplaceableComponent {
component: Type<any>;
key: string;
}
export interface ReplaceableTemplateDirectiveInput<
I,
O extends { [K in keyof O]: EventEmitter<any> | Subject<any> }
> {
inputs: { -readonly [K in keyof I]: { value: I[K]; twoWay?: boolean } };
outputs: { -readonly [K in keyof O]: (value: ABP.ExtractFromOutput<O[K]>) => void };
componentKey: string;
}
export interface ReplaceableTemplateData<
I,
O extends { [K in keyof O]: EventEmitter<any> | Subject<any> }
> {
inputs: ReplaceableTemplateInputs<I>;
outputs: ReplaceableTemplateOutputs<O>;
componentKey: string;
}
export type ReplaceableTemplateInputs<T> = {
[K in keyof T]: T[K];
};
export type ReplaceableTemplateOutputs<
T extends { [K in keyof T]: EventEmitter<any> | Subject<any> }
> = {
[K in keyof T]: (value: ABP.ExtractFromOutput<T[K]>) => void;
};
export interface RouteData<T = any> {
key: string;
defaultComponent: Type<T>;
}
}

3
npm/ng-packs/packages/core/src/lib/states/index.ts

@ -1,3 +1,4 @@
export * from './profile.state';
export * from './replaceable-components.state';
export * from './config.state';
export * from './profile.state';
export * from './session.state';

50
npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts

@ -0,0 +1,50 @@
import { State, Action, StateContext, Selector, createSelector } from '@ngxs/store';
import { AddReplaceableComponent } from '../actions/replaceable-components.actions';
import { ReplaceableComponents } from '../models/replaceable-components';
import snq from 'snq';
@State<ReplaceableComponents.State>({
name: 'ReplaceableComponentsState',
defaults: { replaceableComponents: [] } as ReplaceableComponents.State,
})
export class ReplaceableComponentsState {
@Selector()
static getAll({
replaceableComponents,
}: ReplaceableComponents.State): ReplaceableComponents.ReplaceableComponent[] {
return replaceableComponents || [];
}
static getComponent(key: string) {
const selector = createSelector(
[ReplaceableComponentsState],
(state: ReplaceableComponents.State): ReplaceableComponents.ReplaceableComponent => {
return snq(() => state.replaceableComponents.find(component => component.key === key));
},
);
return selector;
}
@Action(AddReplaceableComponent)
replaceableComponentsAction(
{ getState, patchState }: StateContext<ReplaceableComponents.State>,
{ payload }: AddReplaceableComponent,
) {
let { replaceableComponents } = getState();
const index = snq(
() => replaceableComponents.findIndex(component => component.key === payload.key),
-1,
);
if (index > -1) {
replaceableComponents[index] = payload;
} else {
replaceableComponents = [...replaceableComponents, payload];
}
patchState({
replaceableComponents,
});
}
}

41
npm/ng-packs/packages/core/src/lib/tests/replaceable-components.state.spec.ts

@ -0,0 +1,41 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { NgxsModule, Store } from '@ngxs/store';
import { ReplaceableComponentsState } from '../states/replaceable-components.state';
import { Component } from '@angular/core';
import { AddReplaceableComponent } from '../actions';
@Component({ selector: 'abp-dummy', template: 'dummy works' })
class DummyComponent {}
describe('ReplaceableComponentsState', () => {
let spectator: SpectatorHost<DummyComponent>;
const createHost = createHostFactory({
component: DummyComponent,
imports: [NgxsModule.forRoot([ReplaceableComponentsState])],
});
beforeEach(() => {
spectator = createHost('<abp-dummy></abp-dummy>');
});
it('should add a component to the state', () => {
const store = spectator.get(Store);
expect(store.selectSnapshot(ReplaceableComponentsState.getAll)).toEqual([]);
store.dispatch(new AddReplaceableComponent({ component: DummyComponent, key: 'Dummy' }));
expect(store.selectSnapshot(ReplaceableComponentsState.getComponent('Dummy'))).toEqual({
component: DummyComponent,
key: 'Dummy',
});
});
it('should replace a exist component', () => {
const store = spectator.get(Store);
store.dispatch(new AddReplaceableComponent({ component: DummyComponent, key: 'Dummy' }));
store.dispatch(new AddReplaceableComponent({ component: null, key: 'Dummy' }));
expect(store.selectSnapshot(ReplaceableComponentsState.getComponent('Dummy'))).toEqual({
component: null,
key: 'Dummy',
});
expect(store.selectSnapshot(ReplaceableComponentsState.getAll)).toHaveLength(1);
});
});

66
npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts

@ -0,0 +1,66 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngxs/store';
import { of, Subject, BehaviorSubject } from 'rxjs';
import { ReplaceableRouteContainerComponent } from '../components/replaceable-route-container.component';
import { ReplaceableComponentsState } from '../states';
@Component({
selector: 'abp-external-component',
template: '<p>external</p>',
})
export class ExternalComponent {}
@Component({
selector: 'abp-default-component',
template: '<p>default</p>',
})
export class DefaultComponent {}
const activatedRouteMock = {
snapshot: {
data: {
replaceableComponent: {
defaultComponent: DefaultComponent,
key: 'TestModule.TestComponent',
},
},
},
};
describe('ReplaceableRouteContainerComponent', () => {
const selectResponse = new BehaviorSubject(undefined);
const mockSelect = jest.fn(() => selectResponse);
let spectator: SpectatorHost<ReplaceableRouteContainerComponent>;
const createHost = createHostFactory({
component: ReplaceableRouteContainerComponent,
providers: [
{ provide: ActivatedRoute, useValue: activatedRouteMock },
{ provide: Store, useValue: { select: mockSelect } },
],
declarations: [ExternalComponent, DefaultComponent],
entryComponents: [DefaultComponent, ExternalComponent],
});
beforeEach(() => {
spectator = createHost('<abp-replaceable-route-container></abp-replaceable-route-container>', {
detectChanges: true,
});
});
it('should display the default component', () => {
expect(spectator.query('p')).toHaveText('default');
});
it("should display the external component if it's available in store.", () => {
selectResponse.next({ component: ExternalComponent });
spectator.detectChanges();
expect(spectator.query('p')).toHaveText('external');
selectResponse.next({ component: null });
spectator.detectChanges();
expect(spectator.query('p')).toHaveText('default');
});
});

175
npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts

@ -0,0 +1,175 @@
import { Component, EventEmitter, Inject, Input, OnInit, Optional, Output } from '@angular/core';
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest';
import { Store } from '@ngxs/store';
import { Subject } from 'rxjs';
import { ReplaceableTemplateDirective } from '../directives';
import { ReplaceableComponents } from '../models';
@Component({
selector: 'abp-default-component',
template: `
<p>default</p>
`,
exportAs: 'abpDefaultComponent',
})
class DefaultComponent implements OnInit {
@Input()
oneWay;
@Input()
twoWay: boolean;
@Output()
readonly twoWayChange = new EventEmitter<boolean>();
@Output()
readonly someOutput = new EventEmitter<string>();
ngOnInit() {}
setTwoWay(value) {
this.twoWay = value;
this.twoWayChange.emit(value);
}
}
@Component({
selector: 'abp-external-component',
template: `
<p>external</p>
`,
})
class ExternalComponent {
constructor(
@Optional()
@Inject('REPLACEABLE_DATA')
public data: ReplaceableComponents.ReplaceableTemplateData<any, any>,
) {}
}
describe('ReplaceableTemplateDirective', () => {
const selectResponse = new Subject();
const mockSelect = jest.fn(() => selectResponse);
let spectator: SpectatorDirective<ReplaceableTemplateDirective>;
const createDirective = createDirectiveFactory({
directive: ReplaceableTemplateDirective,
providers: [{ provide: Store, useValue: { select: mockSelect } }],
declarations: [DefaultComponent, ExternalComponent],
entryComponents: [ExternalComponent],
});
describe('without external component', () => {
const twoWayChange = jest.fn(a => a);
const someOutput = jest.fn(a => a);
beforeEach(() => {
spectator = createDirective(
`
<div *abpReplaceableTemplate="{inputs: {oneWay: {value: oneWay}, twoWay: {value: twoWay, twoWay: true}}, outputs: {twoWayChange: twoWayChange, someOutput: someOutput}, componentKey: 'TestModule.TestComponent'}; let initTemplate = initTemplate">
<abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component>
</div>
`,
{ hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } },
);
selectResponse.next(undefined);
const component = spectator.query(DefaultComponent);
spectator.directive.context.initTemplate(component);
spectator.detectChanges();
});
afterEach(() => twoWayChange.mockClear());
it('should display the default template when store response is undefined', () => {
expect(spectator.query('abp-default-component')).toBeTruthy();
});
it('should be setted inputs and outputs', () => {
const component = spectator.query(DefaultComponent);
expect(component.oneWay).toEqual({ label: 'Test' });
expect(component.twoWay).toEqual(false);
});
it('should change the component inputs', () => {
const component = spectator.query(DefaultComponent);
spectator.setHostInput({ oneWay: 'test' });
component.setTwoWay(true);
component.someOutput.emit('someOutput emitted');
expect(component.oneWay).toBe('test');
expect(twoWayChange).toHaveBeenCalledWith(true);
expect(someOutput).toHaveBeenCalledWith('someOutput emitted');
});
});
describe('with external component', () => {
const twoWayChange = jest.fn(a => a);
const someOutput = jest.fn(a => a);
beforeEach(() => {
spectator = createDirective(
`
<div *abpReplaceableTemplate="{inputs: {oneWay: {value: oneWay}, twoWay: {value: twoWay, twoWay: true}}, outputs: {twoWayChange: twoWayChange, someOutput: someOutput}, componentKey: 'TestModule.TestComponent'}; let initTemplate = initTemplate">
<abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component>
</div>
`,
{ hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } },
);
selectResponse.next({ component: ExternalComponent, key: 'TestModule.TestComponent' });
});
afterEach(() => twoWayChange.mockClear());
it('should display the external component', () => {
expect(spectator.query('p')).toHaveText('external');
});
it('should be injected the data object', () => {
const externalComponent = spectator.query(ExternalComponent);
expect(externalComponent.data).toEqual({
componentKey: 'TestModule.TestComponent',
inputs: { oneWay: { label: 'Test' }, twoWay: false },
outputs: { someOutput, twoWayChange },
});
});
it('should be worked all data properties', () => {
const externalComponent = spectator.query(ExternalComponent);
spectator.setHostInput({ oneWay: 'test' });
externalComponent.data.inputs.twoWay = true;
externalComponent.data.outputs.someOutput('someOutput emitted');
expect(externalComponent.data.inputs.oneWay).toBe('test');
expect(twoWayChange).toHaveBeenCalledWith(true);
expect(someOutput).toHaveBeenCalledWith('someOutput emitted');
spectator.setHostInput({ twoWay: 'twoWay test' });
expect(externalComponent.data.inputs.twoWay).toBe('twoWay test');
});
it('should be worked correctly the default component when the external component has been removed from store', () => {
expect(spectator.query('p')).toHaveText('external');
const externalComponent = spectator.query(ExternalComponent);
spectator.setHostInput({ oneWay: 'test' });
externalComponent.data.inputs.twoWay = true;
selectResponse.next({ component: null, key: 'TestModule.TestComponent' });
spectator.detectChanges();
const component = spectator.query(DefaultComponent);
spectator.directive.context.initTemplate(component);
expect(spectator.query('abp-default-component')).toBeTruthy();
expect(component.oneWay).toEqual('test');
expect(component.twoWay).toEqual(true);
});
it('should reset default component subscriptions', () => {
selectResponse.next({ component: null, key: 'TestModule.TestComponent' });
const component = spectator.query(DefaultComponent);
spectator.directive.context.initTemplate(component);
spectator.detectChanges();
const unsubscribe = jest.fn(() => {});
spectator.directive.defaultComponentSubscriptions.twoWayChange.unsubscribe = unsubscribe;
selectResponse.next({ component: ExternalComponent, key: 'TestModule.TestComponent' });
expect(unsubscribe).toHaveBeenCalled();
});
});
});

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

@ -1,6 +1,11 @@
{
"name": "@abp/ng.feature-management",
"version": "1.1.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.theme.shared": "^1.1.1"
},

6
npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts

@ -10,8 +10,12 @@ import { pluck, finalize } from 'rxjs/operators';
@Component({
selector: 'abp-feature-management',
templateUrl: './feature-management.component.html',
exportAs: 'abpFeatureManagement',
})
export class FeatureManagementComponent {
export class FeatureManagementComponent
implements
FeatureManagement.FeatureManagementComponentInputs,
FeatureManagement.FeatureManagementComponentOutputs {
@Input()
providerKey: string;

12
npm/ng-packs/packages/feature-management/src/lib/models/feature-management.ts

@ -1,3 +1,5 @@
import { EventEmitter } from '@angular/core';
export namespace FeatureManagement {
export interface State {
features: Feature[];
@ -26,4 +28,14 @@ export namespace FeatureManagement {
providerName: string;
providerKey: string;
}
export interface FeatureManagementComponentInputs {
visible: boolean;
readonly providerName: string;
readonly providerKey: string;
}
export interface FeatureManagementComponentOutputs {
readonly visibleChange: EventEmitter<boolean>;
}
}

5
npm/ng-packs/packages/identity-config/package.json

@ -1,6 +1,11 @@
{
"name": "@abp/ng.identity.config",
"version": "1.1.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"publishConfig": {
"access": "public"
}

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

@ -1,6 +1,11 @@
{
"name": "@abp/ng.identity",
"version": "1.1.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.identity.config": "^1.1.1",
"@abp/ng.permission-management": "^1.1.1",

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

@ -163,9 +163,39 @@
</abp-modal>
<abp-permission-management
#abpPermissionManagement="abpPermissionManagement"
*abpReplaceableTemplate="
{
inputs: {
providerName: { value: 'R' },
providerKey: { value: providerKey },
visible: { value: visiblePermissions, twoWay: true },
hideBadges: { value: true }
},
outputs: { visibleChange: onVisiblePermissionChange },
componentKey: 'PermissionManagement.PermissionManagementComponent'
};
let init = initTemplate
"
(abpInit)="init(abpPermissionManagement)"
>
</abp-permission-management>
<!-- <abp-permission-management
#abpPermissionManagement="abpPermissionManagement"
*abpReplaceableTemplate="{
inputs: {
providerName: { value: 'R' },
providerKey: { value: providerKey },
visible: { value: visiblePermissions, twoWay: true },
hideBadges: { value: true }
},
outputs: { visibleChange: onVisiblePermissionChange },
componentKey: 'PermissionManagement.PermissionManagementComponent'
}"
[(visible)]="visiblePermissions"
providerName="R"
[providerKey]="providerKey"
[hideBadges]="true"
providerName="R"
>
</abp-permission-management>
</abp-permission-management> -->

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

@ -49,6 +49,10 @@ export class RolesComponent implements OnInit {
@ViewChild('formRef', { static: false, read: ElementRef })
formRef: ElementRef<HTMLFormElement>;
onVisiblePermissionChange = event => {
this.visiblePermissions = event;
};
constructor(
private confirmationService: ConfirmationService,
private fb: FormBuilder,

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

@ -78,7 +78,11 @@
</th>
<th pResizableColumn (click)="sortOrderIcon.sort('phoneNumber')">
{{ 'AbpIdentity::PhoneNumber' | abpLocalization }}
<abp-sort-order-icon sortKey="phoneNumber" [(selectedSortKey)]="sortKey" [(order)]="sortOrder">
<abp-sort-order-icon
sortKey="phoneNumber"
[(selectedSortKey)]="sortKey"
[(order)]="sortOrder"
>
</abp-sort-order-icon>
</th>
</tr>
@ -259,8 +263,19 @@
</abp-modal>
<abp-permission-management
[(visible)]="visiblePermissions"
providerName="U"
[providerKey]="providerKey"
#abpPermissionManagement="abpPermissionManagement"
*abpReplaceableTemplate="
{
inputs: {
providerName: { value: 'U' },
providerKey: { value: providerKey },
visible: { value: visiblePermissions, twoWay: true }
},
outputs: { visibleChange: onVisiblePermissionChange },
componentKey: 'PermissionManagement.PermissionManagementComponent'
};
let init = initTemplate
"
(abpInit)="init(abpPermissionManagement)"
>
</abp-permission-management>

4
npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts

@ -69,6 +69,10 @@ export class UsersComponent implements OnInit {
trackByFn: TrackByFunction<AbstractControl> = (index, item) => Object.keys(item)[0] || index;
onVisiblePermissionChange = event => {
this.visiblePermissions = event;
};
get roleGroups(): FormGroup[] {
return snq(() => (this.form.get('roleNames') as FormArray).controls as FormGroup[], []);
}

35
npm/ng-packs/packages/identity/src/lib/identity-routing.module.ts

@ -1,6 +1,13 @@
import { AuthGuard, DynamicLayoutComponent, PermissionGuard } from '@abp/ng.core';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {
AuthGuard,
DynamicLayoutComponent,
PermissionGuard,
CoreModule,
ReplaceableRouteContainerComponent,
ReplaceableComponents,
} from '@abp/ng.core';
import { NgModule, Type } from '@angular/core';
import { RouterModule, Routes, Router, ActivatedRoute } from '@angular/router';
import { RolesComponent } from './components/roles/roles.component';
import { UsersComponent } from './components/users/users.component';
@ -13,20 +20,32 @@ const routes: Routes = [
children: [
{
path: 'roles',
component: RolesComponent,
data: { requiredPolicy: 'AbpIdentity.Roles' },
component: ReplaceableRouteContainerComponent,
data: {
requiredPolicy: 'AbpIdentity.Roles',
replaceableComponent: {
key: 'Identity.RolesComponent',
defaultComponent: RolesComponent,
} as ReplaceableComponents.RouteData<RolesComponent>,
},
},
{
path: 'users',
component: UsersComponent,
data: { requiredPolicy: 'AbpIdentity.Users' },
component: ReplaceableRouteContainerComponent,
data: {
requiredPolicy: 'AbpIdentity.Users',
replaceableComponent: {
key: 'Identity.UsersComponent',
defaultComponent: UsersComponent,
} as ReplaceableComponents.RouteData<UsersComponent>,
},
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
imports: [RouterModule.forChild(routes), CoreModule],
exports: [RouterModule],
})
export class IdentityRoutingModule {}

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

@ -1,6 +1,11 @@
{
"name": "@abp/ng.permission-management",
"version": "1.1.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.theme.shared": "^1.1.1"
},

72
npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts

@ -1,17 +1,7 @@
import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
Renderer2,
SimpleChanges,
TrackByFunction,
} from '@angular/core';
import { Component, EventEmitter, Input, Output, Renderer2, TrackByFunction } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { from, Observable } from 'rxjs';
import { map, pluck, take, finalize } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { finalize, map, pluck, take, tap } from 'rxjs/operators';
import { GetPermissions, UpdatePermissions } from '../actions/permission-management.actions';
import { PermissionManagement } from '../models/permission-management';
import { PermissionManagementState } from '../states/permission-management.state';
@ -23,18 +13,22 @@ type PermissionWithMargin = PermissionManagement.Permission & {
@Component({
selector: 'abp-permission-management',
templateUrl: './permission-management.component.html',
exportAs: 'abpPermissionManagement',
})
export class PermissionManagementComponent implements OnInit, OnChanges {
export class PermissionManagementComponent
implements
PermissionManagement.PermissionManagementComponentInputs,
PermissionManagement.PermissionManagementComponentOutputs {
@Input()
providerName: string;
readonly providerName: string;
@Input()
providerKey: string;
readonly providerKey: string;
@Input()
hideBadges = false;
readonly hideBadges = false;
protected _visible;
protected _visible = false;
@Input()
get visible(): boolean {
@ -42,13 +36,17 @@ export class PermissionManagementComponent implements OnInit, OnChanges {
}
set visible(value: boolean) {
if (!this.selectedGroup) return;
if (value === this._visible) return;
this._visible = value;
this.visibleChange.emit(value);
if (!value) {
if (value) {
this.openModal().subscribe(() => {
this._visible = true;
this.visibleChange.emit(true);
});
} else {
this.selectedGroup = null;
this._visible = false;
this.visibleChange.emit(false);
}
}
@ -94,8 +92,6 @@ export class PermissionManagementComponent implements OnInit, OnChanges {
constructor(private store: Store, private renderer: Renderer2) {}
ngOnInit(): void {}
getChecked(name: string) {
return (this.permissions.find(per => per.name === name) || { isGranted: false }).isGranted;
}
@ -237,36 +233,26 @@ export class PermissionManagementComponent implements OnInit, OnChanges {
throw new Error('Provider Key and Provider Name are required.');
}
this.store
return this.store
.dispatch(
new GetPermissions({
providerKey: this.providerKey,
providerName: this.providerName,
}),
)
.pipe(pluck('PermissionManagementState', 'permissionRes'))
.subscribe((permissionRes: PermissionManagement.Response) => {
this.selectedGroup = permissionRes.groups[0];
this.permissions = getPermissions(permissionRes.groups);
this.visible = true;
});
.pipe(
pluck('PermissionManagementState', 'permissionRes'),
tap((permissionRes: PermissionManagement.Response) => {
this.selectedGroup = permissionRes.groups[0];
this.permissions = getPermissions(permissionRes.groups);
}),
);
}
initModal() {
this.setTabCheckboxState();
this.setGrantCheckboxState();
}
ngOnChanges({ visible }: SimpleChanges): void {
if (!visible) return;
if (visible.currentValue) {
this.openModal();
} else if (visible.currentValue === false && this.visible) {
this.visible = false;
}
}
}
function findMargin(

13
npm/ng-packs/packages/permission-management/src/lib/models/permission-management.ts

@ -1,3 +1,5 @@
import { EventEmitter } from '@angular/core';
export namespace PermissionManagement {
export interface State {
permissionRes: Response;
@ -34,4 +36,15 @@ export namespace PermissionManagement {
export interface UpdateRequest {
permissions: MinimumPermission[];
}
export interface PermissionManagementComponentInputs {
visible: boolean;
readonly providerName: string;
readonly providerKey: string;
readonly hideBadges: boolean;
}
export interface PermissionManagementComponentOutputs {
readonly visibleChange: EventEmitter<boolean>;
}
}

5
npm/ng-packs/packages/setting-management-config/package.json

@ -1,6 +1,11 @@
{
"name": "@abp/ng.setting-management.config",
"version": "1.1.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"publishConfig": {
"access": "public"
}

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

@ -1,6 +1,11 @@
{
"name": "@abp/ng.setting-management",
"version": "1.1.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.setting-management.config": "^1.1.1",
"@abp/ng.theme.shared": "^1.1.1"

18
npm/ng-packs/packages/setting-management/src/lib/setting-management-routing.module.ts

@ -1,14 +1,28 @@
import {
DynamicLayoutComponent,
ReplaceableComponents,
ReplaceableRouteContainerComponent,
} from '@abp/ng.core';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SettingManagementComponent } from './components/setting-management.component';
import { DynamicLayoutComponent } from '@abp/ng.core';
const routes: Routes = [
{
path: '',
component: DynamicLayoutComponent,
children: [
{ path: '', component: SettingManagementComponent, data: { requiredPolicy: 'AbpAccount.SettingManagement' } },
{
path: '',
component: ReplaceableRouteContainerComponent,
data: {
requiredPolicy: 'AbpAccount.SettingManagement',
replaceableComponent: {
key: 'SettingManagement.SettingManagementComponent',
defaultComponent: SettingManagementComponent,
} as ReplaceableComponents.RouteData,
},
},
],
},
];

5
npm/ng-packs/packages/tenant-management-config/package.json

@ -1,6 +1,11 @@
{
"name": "@abp/ng.tenant-management.config",
"version": "1.1.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"publishConfig": {
"access": "public"
}

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

@ -1,6 +1,11 @@
{
"name": "@abp/ng.tenant-management",
"version": "1.1.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.feature-management": "^1.1.1",
"@abp/ng.tenant-management.config": "^1.1.1",

15
npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html

@ -182,5 +182,18 @@
</form>
</ng-template>
<abp-feature-management [(visible)]="visibleFeatures" providerName="T" [providerKey]="providerKey">
<abp-feature-management
*abpReplaceableTemplate="{
inputs: {
providerName: { value: 'T' },
providerKey: { value: providerKey },
visible: { value: visibleFeatures, twoWay: true }
},
outputs: { visibleChange: onVisibleFeaturesChange },
componentKey: 'FeatureManagement.FeatureManagementComponent'
}"
[(visible)]="visibleFeatures"
providerName="T"
[providerKey]="providerKey"
>
</abp-feature-management>

4
npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.ts

@ -94,6 +94,10 @@ export class TenantsComponent implements OnInit {
}
}
onVisibleFeaturesChange = (value: boolean) => {
this.visibleFeatures = value;
};
constructor(
private confirmationService: ConfirmationService,
private tenantService: TenantManagementService,

25
npm/ng-packs/packages/tenant-management/src/lib/tenant-management-routing.module.ts

@ -1,4 +1,10 @@
import { AuthGuard, DynamicLayoutComponent, PermissionGuard } from '@abp/ng.core';
import {
AuthGuard,
DynamicLayoutComponent,
PermissionGuard,
ReplaceableComponents,
ReplaceableRouteContainerComponent,
} from '@abp/ng.core';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { TenantsComponent } from './components/tenants/tenants.component';
@ -6,11 +12,22 @@ import { TenantsComponent } from './components/tenants/tenants.component';
const routes: Routes = [
{ path: '', redirectTo: 'tenants', pathMatch: 'full' },
{
path: 'tenants',
path: '',
component: DynamicLayoutComponent,
canActivate: [AuthGuard, PermissionGuard],
data: { requiredPolicy: 'AbpTenantManagement.Tenants' },
children: [{ path: '', component: TenantsComponent }],
children: [
{
path: 'tenants',
component: ReplaceableRouteContainerComponent,
data: {
requiredPolicy: 'AbpTenantManagement.Tenants',
replaceableComponent: {
key: 'TenantManagement.TenantsComponent',
defaultComponent: TenantsComponent,
} as ReplaceableComponents.RouteData<TenantsComponent>,
},
},
],
},
];

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

@ -1,6 +1,11 @@
{
"name": "@abp/ng.theme.basic",
"version": "1.1.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.theme.shared": "^1.1.1"
},

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

@ -1,6 +1,11 @@
{
"name": "@abp/ng.theme.shared",
"version": "1.1.1",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"dependencies": {
"@abp/ng.core": "^1.1.1",
"@angular/cdk": "^8.2.3",

679
npm/ng-packs/yarn.lock

File diff suppressed because it is too large

23
templates/module/angular/projects/my-project-name/src/lib/my-project-name-routing.module.ts

@ -1,4 +1,10 @@
import { AuthGuard, DynamicLayoutComponent, PermissionGuard } from '@abp/ng.core';
import {
AuthGuard,
DynamicLayoutComponent,
PermissionGuard,
ReplaceableComponents,
ReplaceableRouteContainerComponent,
} from '@abp/ng.core';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { MyProjectNameComponent } from './components/my-project-name.component';
@ -8,8 +14,19 @@ const routes: Routes = [
path: '',
component: DynamicLayoutComponent,
canActivate: [AuthGuard, PermissionGuard],
data: { requiredPolicy: '' },
children: [{ path: '', component: MyProjectNameComponent }],
children: [
{
path: '',
component: ReplaceableRouteContainerComponent,
data: {
requiredPolicy: '',
replaceableComponent: {
defaultComponent: MyProjectNameComponent,
key: 'MyProjectName.MyProjectNameComponent',
} as ReplaceableComponents.RouteData<MyProjectNameComponent>,
},
},
],
},
];

Loading…
Cancel
Save