Browse Source

Merge pull request #13856 from abpframework/auto-merge/rel-6-0/1318

Merge branch dev with rel-6.0
pull/13858/head
Enis Necipoglu 3 years ago
committed by GitHub
parent
commit
0c0643cc9d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      docs/en/Modules/OpenIddict.md
  2. 4
      npm/ng-packs/packages/account/src/lib/account-routing.module.ts
  3. 11
      npm/ng-packs/packages/account/src/lib/account.module.ts
  4. 1
      npm/ng-packs/packages/account/src/lib/components/index.ts
  5. 37
      npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts
  6. 50
      npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.html
  7. 43
      npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts
  8. 47
      npm/ng-packs/packages/account/src/lib/defaults/default-personal-settings-form-props.ts
  9. 46
      npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts
  10. 1
      npm/ng-packs/packages/account/src/lib/guards/index.ts
  11. 8
      npm/ng-packs/packages/account/src/lib/models/config-options.ts
  12. 17
      npm/ng-packs/packages/account/src/lib/tokens/extensions.token.ts
  13. 1
      npm/ng-packs/packages/account/src/lib/tokens/index.ts
  14. 4
      npm/ng-packs/packages/core/src/lib/utils/common-utils.ts
  15. 2
      npm/ng-packs/packages/core/src/lib/validators/index.ts
  16. 4
      npm/ng-packs/packages/core/src/lib/validators/required.validator.ts
  17. 5
      npm/ng-packs/packages/core/src/lib/validators/url.validator.ts
  18. 29
      npm/ng-packs/packages/core/src/lib/validators/username.validator.ts
  19. 2
      npm/ng-packs/packages/identity/src/lib/components/users/users.component.html
  20. 3
      npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts
  21. 6
      npm/ng-packs/packages/identity/src/lib/defaults/default-users-form-props.ts
  22. 242
      npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html
  23. 50
      npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.ts
  24. 4
      npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form.component.html
  25. 9
      npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.ts
  26. 4
      npm/ng-packs/packages/theme-shared/extensions/src/lib/models/form-props.ts
  27. 2
      npm/ng-packs/packages/theme-shared/extensions/src/lib/models/props.ts
  28. 3
      npm/ng-packs/packages/theme-shared/extensions/src/lib/tokens/extensible-form-view-provider.token.ts
  29. 4
      npm/ng-packs/packages/theme-shared/extensions/src/lib/tokens/extensions.token.ts
  30. 1
      npm/ng-packs/packages/theme-shared/extensions/src/public-api.ts
  31. 1
      npm/ng-packs/packages/theme-shared/src/lib/constants/validation.ts

14
docs/en/Modules/OpenIddict.md

@ -330,7 +330,19 @@ public class MyClaimDestinationsProvider : IAbpOpenIddictClaimDestinationsProvid
{ {
public virtual Task SetDestinationsAsync(AbpOpenIddictClaimDestinationsProviderContext context) public virtual Task SetDestinationsAsync(AbpOpenIddictClaimDestinationsProviderContext context)
{ {
// ... foreach (var claim in context.Claims)
{
if (claim.Type == MyClaims.MyClaimsType)
{
claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken);
}
if (claim.Type == MyClaims.MyClaimsType2)
{
claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken);
}
}
return Task.CompletedTask; return Task.CompletedTask;
} }
} }

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

@ -13,6 +13,8 @@ import { RegisterComponent } from './components/register/register.component';
import { ResetPasswordComponent } from './components/reset-password/reset-password.component'; import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
import { eAccountComponents } from './enums/components'; import { eAccountComponents } from './enums/components';
import { AuthenticationFlowGuard } from './guards/authentication-flow.guard'; import { AuthenticationFlowGuard } from './guards/authentication-flow.guard';
import { AccountExtensionsGuard } from './guards';
const routes: Routes = [ const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'login' }, { path: '', pathMatch: 'full', redirectTo: 'login' },
@ -68,7 +70,7 @@ const routes: Routes = [
{ {
path: 'manage', path: 'manage',
component: ReplaceableRouteContainerComponent, component: ReplaceableRouteContainerComponent,
canActivate: [AuthGuard], canActivate: [AuthGuard, AccountExtensionsGuard],
data: { data: {
replaceableComponent: { replaceableComponent: {
key: eAccountComponents.ManageProfile, key: eAccountComponents.ManageProfile,

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

@ -15,6 +15,10 @@ import { accountConfigOptionsFactory } from './utils/factory-utils';
import { AuthenticationFlowGuard } from './guards/authentication-flow.guard'; import { AuthenticationFlowGuard } from './guards/authentication-flow.guard';
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component'; import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
import { ResetPasswordComponent } from './components/reset-password/reset-password.component'; import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
import { UiExtensionsModule } from '@abp/ng.theme.shared/extensions';
import { ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS } from './tokens/extensions.token';
import { AccountExtensionsGuard } from './guards/extensions.guard';
import { PersonalSettingsHalfRowComponent } from './components/personal-settings/personal-settings-half-row.component';
const declarations = [ const declarations = [
LoginComponent, LoginComponent,
@ -24,6 +28,7 @@ const declarations = [
PersonalSettingsComponent, PersonalSettingsComponent,
ForgotPasswordComponent, ForgotPasswordComponent,
ResetPasswordComponent, ResetPasswordComponent,
PersonalSettingsHalfRowComponent,
]; ];
@NgModule({ @NgModule({
@ -34,6 +39,7 @@ const declarations = [
ThemeSharedModule, ThemeSharedModule,
NgbDropdownModule, NgbDropdownModule,
NgxValidateCoreModule, NgxValidateCoreModule,
UiExtensionsModule,
], ],
exports: [...declarations], exports: [...declarations],
}) })
@ -49,6 +55,11 @@ export class AccountModule {
useFactory: accountConfigOptionsFactory, useFactory: accountConfigOptionsFactory,
deps: [ACCOUNT_CONFIG_OPTIONS], deps: [ACCOUNT_CONFIG_OPTIONS],
}, },
{
provide: ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS,
useValue: options.editFormPropContributors,
},
AccountExtensionsGuard,
], ],
}; };
} }

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

@ -4,4 +4,5 @@ export * from './login/login.component';
export * from './manage-profile/manage-profile.component'; export * from './manage-profile/manage-profile.component';
export * from './register/register.component'; export * from './register/register.component';
export * from './personal-settings/personal-settings.component'; export * from './personal-settings/personal-settings.component';
export * from './personal-settings/personal-settings-half-row.component';
export * from './reset-password/reset-password.component'; export * from './reset-password/reset-password.component';

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

@ -0,0 +1,37 @@
import { Component, Inject } from '@angular/core';
import {
EXTENSIONS_FORM_PROP,
FormProp,
EXTENSIBLE_FORM_VIEW_PROVIDER,
} from '@abp/ng.theme.shared/extensions';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'abp-personal-settings-half-row',
template: ` <div class="w-50 d-inline">
<div class="mb-3 form-group">
<label [attr.for]="name" class="form-label">{{ displayName | abpLocalization }} </label>
<input
type="text"
[attr.id]="id"
class="form-control"
[attr.name]="name"
[formControlName]="name"
/>
</div>
</div>`,
styles: [],
viewProviders: [EXTENSIBLE_FORM_VIEW_PROVIDER],
})
export class PersonalSettingsHalfRowComponent {
public displayName: string;
public name: string;
public id: string;
public formGroup: FormGroup;
constructor(@Inject(EXTENSIONS_FORM_PROP) private propData: FormProp) {
this.displayName = propData.displayName;
this.name = propData.name;
this.id = propData.id;
}
}

50
npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.html

@ -1,55 +1,11 @@
<form validateOnSubmit *ngIf="form" [formGroup]="form" (ngSubmit)="submit()"> <form [formGroup]="form" *ngIf="form" (ngSubmit)="submit()" validateOnSubmit>
<div class="mb-3 form-group"> <abp-extensible-form [selectedRecord]="selected"></abp-extensible-form>
<label for="username" class="form-label">{{
'AbpIdentity::DisplayName:UserName' | abpLocalization
}}</label
><span> * </span
><input
type="text"
id="username"
class="form-control"
formControlName="userName"
autofocus
(keydown.space)="$event.preventDefault()"
/>
</div>
<div class="row">
<div class="col col-md-6">
<div class="mb-3 form-group">
<label for="name" class="form-label">{{
'AbpIdentity::DisplayName:Name' | abpLocalization
}}</label
><input type="text" id="name" class="form-control" formControlName="name" />
</div>
</div>
<div class="col col-md-6">
<div class="mb-3 form-group">
<label for="surname" class="form-label">{{
'AbpIdentity::DisplayName:Surname' | abpLocalization
}}</label
><input type="text" id="surname" class="form-control" formControlName="surname" />
</div>
</div>
</div>
<div class="mb-3 form-group">
<label for="email-address" class="form-label">{{
'AbpIdentity::DisplayName:Email' | abpLocalization
}}</label
><span> * </span
><input type="text" id="email-address" class="form-control" formControlName="email" />
</div>
<div class="mb-3 form-group">
<label for="phone-number" class="form-label">{{
'AbpIdentity::DisplayName:PhoneNumber' | abpLocalization
}}</label
><input type="text" id="phone-number" class="form-control" formControlName="phoneNumber" />
</div>
<abp-button <abp-button
buttonType="submit" buttonType="submit"
iconClass="fa fa-check" iconClass="fa fa-check"
buttonClass="btn btn-primary color-white" buttonClass="btn btn-primary color-white"
[loading]="inProgress" [loading]="inProgress"
[disabled]="form?.invalid"
> >
{{ 'AbpIdentity::Save' | abpLocalization }}</abp-button {{ 'AbpIdentity::Save' | abpLocalization }}</abp-button
> >

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

@ -1,17 +1,27 @@
import { ProfileService } from '@abp/ng.account.core/proxy'; import { ProfileDto, ProfileService } from '@abp/ng.account.core/proxy';
import { ToasterService } from '@abp/ng.theme.shared'; import { ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit } from '@angular/core'; import { Component, Injector, OnInit, ViewEncapsulation } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { finalize } from 'rxjs/operators'; import { finalize } from 'rxjs/operators';
import { Account } from '../../models/account'; import { Account } from '../../models/account';
import { ManageProfileStateService } from '../../services/manage-profile.state.service'; import { ManageProfileStateService } from '../../services/manage-profile.state.service';
import {
const { maxLength, required, email } = Validators; EXTENSIONS_IDENTIFIER,
FormPropData,
generateFormFromProps,
} from '@abp/ng.theme.shared/extensions';
import { eAccountComponents } from '../../enums';
@Component({ @Component({
selector: 'abp-personal-settings-form', selector: 'abp-personal-settings-form',
templateUrl: './personal-settings.component.html', templateUrl: './personal-settings.component.html',
exportAs: 'abpPersonalSettingsForm', exportAs: 'abpPersonalSettingsForm',
providers: [
{
provide: EXTENSIONS_IDENTIFIER,
useValue: eAccountComponents.PersonalSettings,
},
],
}) })
export class PersonalSettingsComponent export class PersonalSettingsComponent
implements implements
@ -19,6 +29,8 @@ export class PersonalSettingsComponent
Account.PersonalSettingsComponentInputs, Account.PersonalSettingsComponentInputs,
Account.PersonalSettingsComponentOutputs Account.PersonalSettingsComponentOutputs
{ {
selected: ProfileDto;
form: FormGroup; form: FormGroup;
inProgress: boolean; inProgress: boolean;
@ -28,21 +40,20 @@ export class PersonalSettingsComponent
private toasterService: ToasterService, private toasterService: ToasterService,
private profileService: ProfileService, private profileService: ProfileService,
private manageProfileState: ManageProfileStateService, private manageProfileState: ManageProfileStateService,
protected injector: Injector,
) {} ) {}
ngOnInit() { buildForm() {
this.buildForm(); this.selected = this.manageProfileState.getProfile();
if (!this.selected) {
return;
}
const data = new FormPropData(this.injector, this.selected);
this.form = generateFormFromProps(data);
} }
buildForm() { ngOnInit(): void {
const profile = this.manageProfileState.getProfile(); this.buildForm();
this.form = this.fb.group({
userName: [profile.userName, [required, maxLength(256)]],
email: [profile.email, [required, email, maxLength(256)]],
name: [profile.name || '', [maxLength(64)]],
surname: [profile.surname || '', [maxLength(64)]],
phoneNumber: [profile.phoneNumber || '', [maxLength(16)]],
});
} }
submit() { submit() {

47
npm/ng-packs/packages/account/src/lib/defaults/default-personal-settings-form-props.ts

@ -0,0 +1,47 @@
import { ePropType, FormProp } from '@abp/ng.theme.shared/extensions';
import { UpdateProfileDto } from '@abp/ng.account.core/proxy';
import { Validators } from '@angular/forms';
import { PersonalSettingsHalfRowComponent } from '../components/personal-settings/personal-settings-half-row.component';
const { maxLength, required, email } = Validators;
export const DEFAULT_PERSONAL_SETTINGS_UPDATE_FORM_PROPS = FormProp.createMany<UpdateProfileDto>([
{
type: ePropType.String,
name: 'userName',
displayName: 'AbpIdentity::DisplayName:UserName',
id: 'username',
validators: () => [required, maxLength(256)],
},
{
type: ePropType.String,
name: 'name',
displayName: 'AbpIdentity::DisplayName:Name',
id: 'name',
validators: () => [maxLength(64)],
template: PersonalSettingsHalfRowComponent,
className: 'd-inline-block w-50',
},
{
type: ePropType.String,
name: 'surname',
displayName: 'AbpIdentity::DisplayName:Surname',
id: 'surname',
validators: () => [maxLength(64)],
className: 'd-inline-block w-50 ps-4',
template: PersonalSettingsHalfRowComponent,
},
{
type: ePropType.String,
name: 'email',
displayName: 'AbpIdentity::DisplayName:Email',
id: 'email-address',
validators: () => [required, email, maxLength(256)],
},
{
type: ePropType.String,
name: 'phoneNumber',
displayName: 'AbpIdentity::DisplayName:PhoneNumber',
id: 'phone-number',
validators: () => [maxLength(16)],
},
]);

46
npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts

@ -0,0 +1,46 @@
import { Injectable, Injector } from '@angular/core';
import { CanActivate } from '@angular/router';
import { Observable } from 'rxjs';
import {
ExtensionsService,
getObjectExtensionEntitiesFromStore,
mapEntitiesToContributors,
mergeWithDefaultProps,
} from '@abp/ng.theme.shared/extensions';
import { ConfigStateService } from '@abp/ng.core';
import { tap, map, mapTo } from 'rxjs/operators';
import {
ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS,
DEFAULT_ACCOUNT_FORM_PROPS,
} from '../tokens/extensions.token';
import { AccountEditFormPropContributors } from '../models/config-options';
import { eAccountComponents } from '../enums/components';
@Injectable()
export class AccountExtensionsGuard implements CanActivate {
constructor(private injector: Injector) {}
canActivate(): Observable<boolean> {
const extensions: ExtensionsService = this.injector.get(ExtensionsService);
const editFormContributors: AccountEditFormPropContributors =
this.injector.get(ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS, null) || {};
const configState = this.injector.get(ConfigStateService);
return getObjectExtensionEntitiesFromStore(configState, 'Identity').pipe(
map(entities => ({
[eAccountComponents.PersonalSettings]: entities.User,
})),
mapEntitiesToContributors(configState, 'AbpIdentity'),
tap(objectExtensionContributors => {
mergeWithDefaultProps(
extensions.editFormProps,
DEFAULT_ACCOUNT_FORM_PROPS,
objectExtensionContributors.editForm,
editFormContributors,
);
}),
mapTo(true),
);
}
}

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

@ -1 +1,2 @@
export * from './authentication-flow.guard'; export * from './authentication-flow.guard';
export * from './extensions.guard';

8
npm/ng-packs/packages/account/src/lib/models/config-options.ts

@ -1,3 +1,11 @@
import { eAccountComponents } from '../enums';
import { EditFormPropContributorCallback } from '@abp/ng.theme.shared/extensions';
import { UpdateProfileDto } from '@abp/ng.account.core/proxy';
export interface AccountConfigOptions { export interface AccountConfigOptions {
redirectUrl?: string; redirectUrl?: string;
editFormPropContributors?: AccountEditFormPropContributors;
} }
export type AccountEditFormPropContributors = Partial<{
[eAccountComponents.PersonalSettings]: EditFormPropContributorCallback<UpdateProfileDto>[];
}>;

17
npm/ng-packs/packages/account/src/lib/tokens/extensions.token.ts

@ -0,0 +1,17 @@
import { eAccountComponents } from '../enums';
import { DEFAULT_PERSONAL_SETTINGS_UPDATE_FORM_PROPS } from '../defaults/default-personal-settings-form-props';
import { InjectionToken } from '@angular/core';
import { EditFormPropContributorCallback } from '@abp/ng.theme.shared/extensions';
import { UpdateProfileDto } from '@abp/ng.account.core/proxy';
export const DEFAULT_ACCOUNT_FORM_PROPS = {
[eAccountComponents.PersonalSettings]: DEFAULT_PERSONAL_SETTINGS_UPDATE_FORM_PROPS,
};
export const ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS = new InjectionToken<EditFormPropContributors>(
'ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS',
);
type EditFormPropContributors = Partial<{
[eAccountComponents.PersonalSettings]: EditFormPropContributorCallback<UpdateProfileDto>[];
}>;

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

@ -1 +1,2 @@
export * from './config-options.token'; export * from './config-options.token';
export * from './extensions.token';

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

@ -11,6 +11,10 @@ export function isNullOrUndefined(obj) {
return obj === null || obj === undefined; return obj === null || obj === undefined;
} }
export function isNullOrEmpty(obj){
return obj === null || obj === undefined || obj === '';
}
export function exists(obj) { export function exists(obj) {
return !isNullOrUndefined(obj); return !isNullOrUndefined(obj);
} }

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

@ -5,6 +5,7 @@ import { validateRange } from './range.validator';
import { validateRequired } from './required.validator'; import { validateRequired } from './required.validator';
import { validateStringLength } from './string-length.validator'; import { validateStringLength } from './string-length.validator';
import { validateUrl } from './url.validator'; import { validateUrl } from './url.validator';
import { validateUsername } from './username.validator';
export * from './age.validator'; export * from './age.validator';
export * from './credit-card.validator'; export * from './credit-card.validator';
export * from './range.validator'; export * from './range.validator';
@ -20,4 +21,5 @@ export const AbpValidators = {
required: validateRequired, required: validateRequired,
stringLength: validateStringLength, stringLength: validateStringLength,
url: validateUrl, url: validateUrl,
username: validateUsername,
}; };

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

@ -9,9 +9,11 @@ export interface RequiredOptions {
} }
export function validateRequired({ allowEmptyStrings }: RequiredOptions = {}): ValidatorFn { export function validateRequired({ allowEmptyStrings }: RequiredOptions = {}): ValidatorFn {
return (control: AbstractControl): RequiredError | null => { // note: please do not remove name of the function, it is used in function compare with type 'RequiredError'
const required = (control: AbstractControl): RequiredError | null => {
return isValidRequired(control.value, allowEmptyStrings) ? null : { required: true }; return isValidRequired(control.value, allowEmptyStrings) ? null : { required: true };
}; };
return required;
} }
function isValidRequired(value: any, allowEmptyStrings: boolean): boolean { function isValidRequired(value: any, allowEmptyStrings: boolean): boolean {

5
npm/ng-packs/packages/core/src/lib/validators/url.validator.ts

@ -1,4 +1,5 @@
import { AbstractControl, ValidatorFn } from '@angular/forms'; import { AbstractControl, ValidatorFn } from '@angular/forms';
import { isNullOrUndefined } from '../utils';
export interface UrlError { export interface UrlError {
url: true; url: true;
@ -6,11 +7,11 @@ export interface UrlError {
export function validateUrl(): ValidatorFn { export function validateUrl(): ValidatorFn {
return (control: AbstractControl): UrlError | null => { return (control: AbstractControl): UrlError | null => {
if (['', null, undefined].indexOf(control.value) > -1) return null; if (isNullOrUndefined(control.value)) return null;
return isValidUrl(control.value) ? null : { url: true }; return isValidUrl(control.value) ? null : { url: true };
}; };
} }
function isValidUrl(value: string): boolean { function isValidUrl(value: string): boolean {
if (/^http(s)?:\/\/[^/]/.test(value) || /^ftp:\/\/[^/]/.test(value)) { if (/^http(s)?:\/\/[^/]/.test(value) || /^ftp:\/\/[^/]/.test(value)) {

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

@ -0,0 +1,29 @@
import { AbstractControl, ValidatorFn } from '@angular/forms';
import { isNullOrEmpty } from '../utils';
export interface UsernamePatternError {
usernamePattern: {
actualValue: string;
};
}
export interface UsernameOptions {
pattern?: RegExp;
}
const onlyLetterAndNumberRegex = /^[a-zA-Z0-9]+$/;
export function validateUsername(
{ pattern }: UsernameOptions = { pattern: onlyLetterAndNumberRegex },
): ValidatorFn {
return (control: AbstractControl): UsernamePatternError | null => {
const isValid = isValidUserName(control.value, pattern);
return isValid ? null : { usernamePattern: { actualValue: control.value } };
};
}
function isValidUserName(value: any, pattern: RegExp): boolean {
if (isNullOrEmpty(value)) return true;
return pattern.test(value);
}

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

@ -39,7 +39,7 @@
><div class="text-center"><i class="fa fa-pulse fa-spinner"></i></div ><div class="text-center"><i class="fa fa-pulse fa-spinner"></i></div
></ng-template> ></ng-template>
<form *ngIf="form; else loaderRef" [blueprints]="blueprints" [formGroup]="form" (ngSubmit)="save()"> <form *ngIf="form; else loaderRef" [formGroup]="form" (ngSubmit)="save()">
<ul ngbNav #nav="ngbNav" class="nav-tabs"> <ul ngbNav #nav="ngbNav" class="nav-tabs">
<li ngbNavItem> <li ngbNavItem>
<a ngbNavLink>{{ 'AbpIdentity::UserInformations' | abpLocalization }}</a> <a ngbNavLink>{{ 'AbpIdentity::UserInformations' | abpLocalization }}</a>

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

@ -61,9 +61,6 @@ export class UsersComponent implements OnInit {
entityDisplayName: string; entityDisplayName: string;
blueprints = { pattern: 'AbpIdentity::Volo.Abp.Identity:InvalidUserName[{{ actualValue }}]' };
trackByFn: TrackByFunction<AbstractControl> = (index, item) => Object.keys(item)[0] || index; trackByFn: TrackByFunction<AbstractControl> = (index, item) => Object.keys(item)[0] || index;
onVisiblePermissionChange = event => { onVisiblePermissionChange = event => {

6
npm/ng-packs/packages/identity/src/lib/defaults/default-users-form-props.ts

@ -1,9 +1,9 @@
import { IdentityUserDto } from '@abp/ng.identity/proxy'; import { IdentityUserDto } from '@abp/ng.identity/proxy';
import { getPasswordValidators } from '@abp/ng.theme.shared'; import { getPasswordValidators } from '@abp/ng.theme.shared';
import { ePropType, FormProp } from '@abp/ng.theme.shared/extensions'; import { ePropType, FormProp } from '@abp/ng.theme.shared/extensions';
import { Validators } from '@angular/forms'; import { Validators } from '@angular/forms'
import { AbpValidators } from '@abp/ng.core';
const onlyLetterAndNumberRegex = /^[a-zA-Z0-9]+$/;
export const DEFAULT_USERS_CREATE_FORM_PROPS = FormProp.createMany<IdentityUserDto>([ export const DEFAULT_USERS_CREATE_FORM_PROPS = FormProp.createMany<IdentityUserDto>([
{ {
@ -11,7 +11,7 @@ export const DEFAULT_USERS_CREATE_FORM_PROPS = FormProp.createMany<IdentityUserD
name: 'userName', name: 'userName',
displayName: 'AbpIdentity::UserName', displayName: 'AbpIdentity::UserName',
id: 'user-name', id: 'user-name',
validators: () => [Validators.required, Validators.maxLength(256), Validators.pattern(onlyLetterAndNumberRegex)], validators: () => [Validators.required, Validators.maxLength(256), AbpValidators.username()],
}, },
{ {
type: ePropType.Password, type: ePropType.Password,

242
npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.html

@ -1,142 +1,152 @@
<div <ng-container [ngSwitch]="getComponent(prop)"
class="mb-3 form-group" *abpPermission="prop.permission; runChangeDetection: false">
*abpPermission="prop.permission; runChangeDetection: false"
[ngSwitch]="getComponent(prop)"
>
<ng-template ngSwitchCase="input">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<input
#field
[id]="prop.id"
[formControlName]="prop.name"
[autocomplete]="prop.autocomplete"
[type]="getType(prop)"
[abpDisabled]="disabled"
[readonly]="readonly"
class="form-control"
/>
</ng-template>
<ng-template ngSwitchCase="hidden"> <ng-template ngSwitchCase="template">
<input [formControlName]="prop.name" type="hidden" /> <ng-container
*ngComponentOutlet="prop.template;injector:injectorForCustomComponent">
</ng-container>
</ng-template> </ng-template>
<ng-template ngSwitchCase="checkbox"> <div
<div class="form-check" validationTarget> class="mb-3 form-group"
>
<ng-template ngSwitchCase="input">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<input <input
#field #field
[id]="prop.id" [id]="prop.id"
[formControlName]="prop.name" [formControlName]="prop.name"
[autocomplete]="prop.autocomplete"
[type]="getType(prop)"
[abpDisabled]="disabled" [abpDisabled]="disabled"
type="checkbox" [readonly]="readonly"
class="form-check-input" class="form-control"
/> />
<ng-template </ng-template>
[ngTemplateOutlet]="label"
[ngTemplateOutletContext]="{ $implicit: 'form-check-label' }"
></ng-template>
</div>
</ng-template>
<ng-template ngSwitchCase="select"> <ng-template ngSwitchCase="hidden">
<ng-template [ngTemplateOutlet]="label"></ng-template> <input [formControlName]="prop.name" type="hidden"/>
<select </ng-template>
#field
[id]="prop.id" <ng-template ngSwitchCase="checkbox">
[formControlName]="prop.name" <div class="form-check" validationTarget>
[abpDisabled]="disabled" <input
class="form-select form-control" #field
> [id]="prop.id"
<option [formControlName]="prop.name"
*ngFor="let option of options$ | async; trackBy: track.by('value')" [abpDisabled]="disabled"
[ngValue]="option.value" type="checkbox"
> class="form-check-input"
{{ option.key }} />
</option> <ng-template
</select> [ngTemplateOutlet]="label"
</ng-template> [ngTemplateOutletContext]="{ $implicit: 'form-check-label' }"
></ng-template>
</div>
</ng-template>
<ng-template ngSwitchCase="multiselect"> <ng-template ngSwitchCase="select">
<ng-template [ngTemplateOutlet]="label"></ng-template> <ng-template [ngTemplateOutlet]="label"></ng-template>
<select <select
#field #field
[id]="prop.id" [id]="prop.id"
[formControlName]="prop.name" [formControlName]="prop.name"
[abpDisabled]="disabled" [abpDisabled]="disabled"
multiple="multiple" class="form-select form-control"
class="form-select form-control"
>
<option
*ngFor="let option of options$ | async; trackBy: track.by('value')"
[ngValue]="option.value"
> >
{{ option.key }} <option
</option> *ngFor="let option of options$ | async; trackBy: track.by('value')"
</select> [ngValue]="option.value"
</ng-template> >
{{ option.key }}
</option>
</select>
</ng-template>
<ng-template ngSwitchCase="typeahead"> <ng-template ngSwitchCase="multiselect">
<ng-template [ngTemplateOutlet]="label"></ng-template> <ng-template [ngTemplateOutlet]="label"></ng-template>
<div #typeahead class="position-relative" validationStyle validationTarget> <select
<input
#field #field
[id]="prop.id" [id]="prop.id"
[autocomplete]="prop.autocomplete" [formControlName]="prop.name"
[abpDisabled]="disabled" [abpDisabled]="disabled"
[ngbTypeahead]="search" multiple="multiple"
[editable]="false" class="form-select form-control"
[inputFormatter]="typeaheadFormatter" >
[resultFormatter]="typeaheadFormatter" <option
[ngModelOptions]="{ standalone: true }" *ngFor="let option of options$ | async; trackBy: track.by('value')"
[(ngModel)]="typeaheadModel" [ngValue]="option.value"
(selectItem)="setTypeaheadValue($event.item)" >
(blur)="setTypeaheadValue(typeaheadModel)" {{ option.key }}
[class.is-invalid]="typeahead.classList.contains('is-invalid')" </option>
</select>
</ng-template>
<ng-template ngSwitchCase="typeahead">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<div #typeahead class="position-relative" validationStyle validationTarget>
<input
#field
[id]="prop.id"
[autocomplete]="prop.autocomplete"
[abpDisabled]="disabled"
[ngbTypeahead]="search"
[editable]="false"
[inputFormatter]="typeaheadFormatter"
[resultFormatter]="typeaheadFormatter"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="typeaheadModel"
(selectItem)="setTypeaheadValue($event.item)"
(blur)="setTypeaheadValue(typeaheadModel)"
[class.is-invalid]="typeahead.classList.contains('is-invalid')"
class="form-control"
/>
<input [formControlName]="prop.name" type="hidden"/>
</div>
</ng-template>
<ng-template ngSwitchCase="date">
<ng-template [ngTemplateOutlet]="label"></ng-template>
<input
[id]="prop.id"
[formControlName]="prop.name"
(click)="datepicker.open()"
(keyup.space)="datepicker.open()"
ngbDatepicker
#datepicker="ngbDatepicker"
type="text"
class="form-control" class="form-control"
/> />
<input [formControlName]="prop.name" type="hidden" /> </ng-template>
</div>
</ng-template>
<ng-template ngSwitchCase="date"> <ng-template ngSwitchCase="time">
<ng-template [ngTemplateOutlet]="label"></ng-template> <ng-template [ngTemplateOutlet]="label"></ng-template>
<input <ngb-timepicker [formControlName]="prop.name"></ngb-timepicker>
[id]="prop.id" </ng-template>
[formControlName]="prop.name"
(click)="datepicker.open()"
(keyup.space)="datepicker.open()"
ngbDatepicker
#datepicker="ngbDatepicker"
type="text"
class="form-control"
/>
</ng-template>
<ng-template ngSwitchCase="time"> <ng-template ngSwitchCase="dateTime">
<ng-template [ngTemplateOutlet]="label"></ng-template> <ng-template [ngTemplateOutlet]="label"></ng-template>
<ngb-timepicker [formControlName]="prop.name"></ngb-timepicker> <abp-date-time-picker [prop]="prop" [meridian]="meridian"></abp-date-time-picker>
</ng-template> </ng-template>
<ng-template ngSwitchCase="dateTime"> <ng-template ngSwitchCase="textarea">
<ng-template [ngTemplateOutlet]="label"></ng-template> <ng-template [ngTemplateOutlet]="label"></ng-template>
<abp-date-time-picker [prop]="prop" [meridian]="meridian"></abp-date-time-picker> <textarea
</ng-template> #field
[id]="prop.id"
[formControlName]="prop.name"
[abpDisabled]="disabled"
[readonly]="readonly"
class="form-control"
></textarea>
</ng-template>
</div>
<ng-template ngSwitchCase="textarea"> </ng-container>
<ng-template [ngTemplateOutlet]="label"></ng-template>
<textarea
#field
[id]="prop.id"
[formControlName]="prop.name"
[abpDisabled]="disabled"
[readonly]="readonly"
class="form-control"
></textarea>
</ng-template>
</div>
<ng-template #label let-classes> <ng-template #label let-classes>
<label [htmlFor]="prop.id" [ngClass]="classes || 'form-label'" <label [htmlFor]="prop.id" [ngClass]="classes || 'form-label'" x
>{{ prop.displayName | abpLocalization }} {{ asterisk }}</label >{{ prop.displayName | abpLocalization }} {{ asterisk }}</label
> >
</ng-template> </ng-template>

50
npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form-prop.component.ts

@ -1,3 +1,4 @@
import { EXTENSIONS_FORM_PROP_DATA, EXTENSIONS_FORM_PROP } from './../../tokens/extensions.token';
import { ABP, AbpValidators, ConfigStateService, TrackByService } from '@abp/ng.core'; import { ABP, AbpValidators, ConfigStateService, TrackByService } from '@abp/ng.core';
import { import {
AfterViewInit, AfterViewInit,
@ -5,6 +6,7 @@ import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
ElementRef, ElementRef,
Injector,
Input, Input,
OnChanges, OnChanges,
Optional, Optional,
@ -54,6 +56,8 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
@ViewChild('field') private fieldRef!: ElementRef<HTMLElement>; @ViewChild('field') private fieldRef!: ElementRef<HTMLElement>;
public injectorForCustomComponent: Injector;
asterisk = ''; asterisk = '';
options$: Observable<ABP.Option<any>[]> = of([]); options$: Observable<ABP.Option<any>[]> = of([]);
@ -62,15 +66,15 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
readonly!: boolean; readonly!: boolean;
disabledFn = (data:PropData) => false; typeaheadModel: any;
get disabled() {
return this.disabledFn(this.data)
}
private readonly form: FormGroup; private readonly form: FormGroup;
typeaheadModel: any; disabledFn = (data: PropData) => false;
get disabled() {
return this.disabledFn(this.data);
}
setTypeaheadValue(selectedOption: ABP.Option<string>) { setTypeaheadValue(selectedOption: ABP.Option<string>) {
this.typeaheadModel = selectedOption || { key: null, value: null }; this.typeaheadModel = selectedOption || { key: null, value: null };
@ -108,6 +112,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
public readonly track: TrackByService, public readonly track: TrackByService,
protected configState: ConfigStateService, protected configState: ConfigStateService,
groupDirective: FormGroupDirective, groupDirective: FormGroupDirective,
private injector: Injector,
) { ) {
this.form = groupDirective.form; this.form = groupDirective.form;
} }
@ -133,6 +138,9 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
} }
getComponent(prop: FormProp): string { getComponent(prop: FormProp): string {
if (prop.template) {
return 'template';
}
switch (prop.type) { switch (prop.type) {
case ePropType.Boolean: case ePropType.Boolean:
return 'checkbox'; return 'checkbox';
@ -173,13 +181,29 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
} }
} }
ngOnChanges({ prop }: SimpleChanges) { ngOnChanges({ prop, data }: SimpleChanges) {
const currentProp = prop?.currentValue; const currentProp = prop?.currentValue as FormProp;
const { options, readonly, disabled, validators } = currentProp || {}; const { options, readonly, disabled, validators, template } = currentProp || {};
if (template) {
this.injectorForCustomComponent = Injector.create({
providers: [
{
provide: EXTENSIONS_FORM_PROP,
useValue: currentProp,
},
{
provide: EXTENSIONS_FORM_PROP_DATA,
useValue: (data?.currentValue as PropData)?.record,
},
{ provide: ControlContainer, useExisting: FormGroupDirective },
],
parent: this.injector,
});
}
if (options) this.options$ = options(this.data); if (options) this.options$ = options(this.data);
if (readonly) this.readonly = readonly(this.data); if (readonly) this.readonly = readonly(this.data);
if (disabled) { if (disabled) {
this.disabledFn = disabled; this.disabledFn = disabled;
} }
@ -195,5 +219,9 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
} }
function isRequired(validator: ValidatorFn) { function isRequired(validator: ValidatorFn) {
return validator === Validators.required || validator === AbpValidators.required; return (
validator === Validators.required ||
validator === AbpValidators.required ||
validator.name === 'required'
);
} }

4
npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-form/extensible-form.component.html

@ -6,11 +6,11 @@
[formGroupName]="extraPropertiesKey" [formGroupName]="extraPropertiesKey"
*ngIf="extraProperties.controls[prop.name]; else tempDefault" *ngIf="extraProperties.controls[prop.name]; else tempDefault"
> >
<abp-extensible-form-prop [prop]="prop" [data]="data"></abp-extensible-form-prop> <abp-extensible-form-prop [class]="prop.className" [prop]="prop" [data]="data"></abp-extensible-form-prop>
</ng-container> </ng-container>
<ng-template #tempDefault> <ng-template #tempDefault>
<abp-extensible-form-prop <abp-extensible-form-prop [class]="prop.className"
*ngIf="form.get(prop.name)" *ngIf="form.get(prop.name)"
[prop]="prop" [prop]="prop"
[data]="data" [data]="data"

9
npm/ng-packs/packages/theme-shared/extensions/src/lib/components/extensible-table/extensible-table.component.ts

@ -158,15 +158,16 @@ export class ExtensibleTableComponent<R = any> implements OnChanges {
value, value,
}; };
if (prop.value.component) { if (prop.value.component) {
const injector = Injector.create(
[ const injector = Injector.create({
providers: [
{ {
provide: PROP_DATA_STREAM, provide: PROP_DATA_STREAM,
useValue: value, useValue: value,
}, },
], ],
this.injector, parent: this.injector,
); });
record[propKey].injector = injector; record[propKey].injector = injector;
record[propKey].component = prop.value.component; record[propKey].component = prop.value.component;
} }

4
npm/ng-packs/packages/theme-shared/extensions/src/lib/models/form-props.ts

@ -38,6 +38,8 @@ export class FormProp<R = any> extends Prop<R> {
readonly defaultValue: boolean | number | string | Date; readonly defaultValue: boolean | number | string | Date;
readonly options: PropCallback<R, Observable<ABP.Option<any>[]>> | undefined; readonly options: PropCallback<R, Observable<ABP.Option<any>[]>> | undefined;
readonly id: string | undefined; readonly id: string | undefined;
readonly template?: Type<any>;
readonly className?: string;
constructor(options: FormPropOptions<R>) { constructor(options: FormPropOptions<R>) {
super( super(
@ -47,6 +49,8 @@ export class FormProp<R = any> extends Prop<R> {
options.permission, options.permission,
options.visible, options.visible,
options.isExtra, options.isExtra,
options.template,
options.className,
); );
this.asyncValidators = options.asyncValidators || (_ => []); this.asyncValidators = options.asyncValidators || (_ => []);

2
npm/ng-packs/packages/theme-shared/extensions/src/lib/models/props.ts

@ -33,6 +33,8 @@ export abstract class Prop<R = any> {
public readonly permission: string, public readonly permission: string,
public readonly visible: PropPredicate<R> = _ => true, public readonly visible: PropPredicate<R> = _ => true,
public readonly isExtra = false, public readonly isExtra = false,
public readonly template?: Type<any>,
public readonly className?: string,
) { ) {
this.displayName = this.displayName || this.name; this.displayName = this.displayName || this.name;
} }

3
npm/ng-packs/packages/theme-shared/extensions/src/lib/tokens/extensible-form-view-provider.token.ts

@ -0,0 +1,3 @@
import { ControlContainer, FormGroupDirective } from "@angular/forms";
export const EXTENSIBLE_FORM_VIEW_PROVIDER = { provide: ControlContainer, useExisting: FormGroupDirective }

4
npm/ng-packs/packages/theme-shared/extensions/src/lib/tokens/extensions.token.ts

@ -3,6 +3,7 @@ import { ActionCallback, ReadonlyActionData as ActionData } from '../models/acti
import { ExtensionsService } from '../services/extensions.service'; import { ExtensionsService } from '../services/extensions.service';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ePropType } from '../enums/props.enum'; import { ePropType } from '../enums/props.enum';
import { FormProp } from '../models/form-props';
export const EXTENSIONS_IDENTIFIER = new InjectionToken<string>('EXTENSIONS_IDENTIFIER'); export const EXTENSIONS_IDENTIFIER = new InjectionToken<string>('EXTENSIONS_IDENTIFIER');
export type ActionKeys = Extract<'entityActions' | 'toolbarActions', keyof ExtensionsService>; export type ActionKeys = Extract<'entityActions' | 'toolbarActions', keyof ExtensionsService>;
@ -24,3 +25,6 @@ export const ENTITY_PROP_TYPE_CLASSES = new InjectionToken<EntityPropTypeClass>(
factory: () => ({} as EntityPropTypeClass), factory: () => ({} as EntityPropTypeClass),
}, },
); );
export const EXTENSIONS_FORM_PROP = new InjectionToken<FormProp>('EXTENSIONS_FORM_PROP');
export const EXTENSIONS_FORM_PROP_DATA = new InjectionToken<unknown>('EXTENSIONS_FORM_PROP_DATA');

1
npm/ng-packs/packages/theme-shared/extensions/src/public-api.ts

@ -64,6 +64,7 @@ export {
export * from './lib/pipes/create-injector.pipe'; export * from './lib/pipes/create-injector.pipe';
export * from './lib/services/extensions.service'; export * from './lib/services/extensions.service';
export * from './lib/tokens/extensions.token'; export * from './lib/tokens/extensions.token';
export * from './lib/tokens/extensible-form-view-provider.token';
export * from './lib/ui-extensions.module'; export * from './lib/ui-extensions.module';
export * from './lib/utils/actions.util'; export * from './lib/utils/actions.util';
export * from './lib/utils/form-props.util'; export * from './lib/utils/form-props.util';

1
npm/ng-packs/packages/theme-shared/src/lib/constants/validation.ts

@ -17,4 +17,5 @@ export const DEFAULT_VALIDATION_BLUEPRINTS = {
passwordRequiresUpper: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresUpper', passwordRequiresUpper: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresUpper',
passwordRequiresDigit: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresDigit', passwordRequiresDigit: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresDigit',
passwordRequiresNonAlphanumeric: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresNonAlphanumeric', passwordRequiresNonAlphanumeric: 'AbpIdentity::Volo.Abp.Identity:PasswordRequiresNonAlphanumeric',
usernamePattern: 'AbpIdentity::Volo.Abp.Identity:InvalidUserName[{{ actualValue }}]',
}; };

Loading…
Cancel
Save