mirror of https://github.com/abpframework/abp.git
committed by
GitHub
31 changed files with 447 additions and 208 deletions
@ -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; |
|||
} |
|||
} |
|||
@ -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)], |
|||
}, |
|||
]); |
|||
@ -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 +1,2 @@ |
|||
export * from './authentication-flow.guard'; |
|||
export * from './extensions.guard'; |
|||
|
|||
@ -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 { |
|||
redirectUrl?: string; |
|||
editFormPropContributors?: AccountEditFormPropContributors; |
|||
} |
|||
export type AccountEditFormPropContributors = Partial<{ |
|||
[eAccountComponents.PersonalSettings]: EditFormPropContributorCallback<UpdateProfileDto>[]; |
|||
}>; |
|||
@ -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 +1,2 @@ |
|||
export * from './config-options.token'; |
|||
export * from './extensions.token'; |
|||
|
|||
@ -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); |
|||
} |
|||
@ -1,142 +1,152 @@ |
|||
<div |
|||
class="mb-3 form-group" |
|||
*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-container [ngSwitch]="getComponent(prop)" |
|||
*abpPermission="prop.permission; runChangeDetection: false"> |
|||
|
|||
<ng-template ngSwitchCase="hidden"> |
|||
<input [formControlName]="prop.name" type="hidden" /> |
|||
<ng-template ngSwitchCase="template"> |
|||
<ng-container |
|||
*ngComponentOutlet="prop.template;injector:injectorForCustomComponent"> |
|||
</ng-container> |
|||
</ng-template> |
|||
|
|||
<ng-template ngSwitchCase="checkbox"> |
|||
<div class="form-check" validationTarget> |
|||
<div |
|||
class="mb-3 form-group" |
|||
> |
|||
|
|||
<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" |
|||
type="checkbox" |
|||
class="form-check-input" |
|||
[readonly]="readonly" |
|||
class="form-control" |
|||
/> |
|||
<ng-template |
|||
[ngTemplateOutlet]="label" |
|||
[ngTemplateOutletContext]="{ $implicit: 'form-check-label' }" |
|||
></ng-template> |
|||
</div> |
|||
</ng-template> |
|||
</ng-template> |
|||
|
|||
<ng-template ngSwitchCase="select"> |
|||
<ng-template [ngTemplateOutlet]="label"></ng-template> |
|||
<select |
|||
#field |
|||
[id]="prop.id" |
|||
[formControlName]="prop.name" |
|||
[abpDisabled]="disabled" |
|||
class="form-select form-control" |
|||
> |
|||
<option |
|||
*ngFor="let option of options$ | async; trackBy: track.by('value')" |
|||
[ngValue]="option.value" |
|||
> |
|||
{{ option.key }} |
|||
</option> |
|||
</select> |
|||
</ng-template> |
|||
<ng-template ngSwitchCase="hidden"> |
|||
<input [formControlName]="prop.name" type="hidden"/> |
|||
</ng-template> |
|||
|
|||
<ng-template ngSwitchCase="checkbox"> |
|||
<div class="form-check" validationTarget> |
|||
<input |
|||
#field |
|||
[id]="prop.id" |
|||
[formControlName]="prop.name" |
|||
[abpDisabled]="disabled" |
|||
type="checkbox" |
|||
class="form-check-input" |
|||
/> |
|||
<ng-template |
|||
[ngTemplateOutlet]="label" |
|||
[ngTemplateOutletContext]="{ $implicit: 'form-check-label' }" |
|||
></ng-template> |
|||
</div> |
|||
</ng-template> |
|||
|
|||
<ng-template ngSwitchCase="multiselect"> |
|||
<ng-template [ngTemplateOutlet]="label"></ng-template> |
|||
<select |
|||
#field |
|||
[id]="prop.id" |
|||
[formControlName]="prop.name" |
|||
[abpDisabled]="disabled" |
|||
multiple="multiple" |
|||
class="form-select form-control" |
|||
> |
|||
<option |
|||
*ngFor="let option of options$ | async; trackBy: track.by('value')" |
|||
[ngValue]="option.value" |
|||
<ng-template ngSwitchCase="select"> |
|||
<ng-template [ngTemplateOutlet]="label"></ng-template> |
|||
<select |
|||
#field |
|||
[id]="prop.id" |
|||
[formControlName]="prop.name" |
|||
[abpDisabled]="disabled" |
|||
class="form-select form-control" |
|||
> |
|||
{{ option.key }} |
|||
</option> |
|||
</select> |
|||
</ng-template> |
|||
<option |
|||
*ngFor="let option of options$ | async; trackBy: track.by('value')" |
|||
[ngValue]="option.value" |
|||
> |
|||
{{ option.key }} |
|||
</option> |
|||
</select> |
|||
</ng-template> |
|||
|
|||
<ng-template ngSwitchCase="typeahead"> |
|||
<ng-template [ngTemplateOutlet]="label"></ng-template> |
|||
<div #typeahead class="position-relative" validationStyle validationTarget> |
|||
<input |
|||
<ng-template ngSwitchCase="multiselect"> |
|||
<ng-template [ngTemplateOutlet]="label"></ng-template> |
|||
<select |
|||
#field |
|||
[id]="prop.id" |
|||
[autocomplete]="prop.autocomplete" |
|||
[formControlName]="prop.name" |
|||
[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')" |
|||
multiple="multiple" |
|||
class="form-select form-control" |
|||
> |
|||
<option |
|||
*ngFor="let option of options$ | async; trackBy: track.by('value')" |
|||
[ngValue]="option.value" |
|||
> |
|||
{{ option.key }} |
|||
</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" |
|||
/> |
|||
<input [formControlName]="prop.name" type="hidden" /> |
|||
</div> |
|||
</ng-template> |
|||
</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" |
|||
/> |
|||
</ng-template> |
|||
<ng-template ngSwitchCase="time"> |
|||
<ng-template [ngTemplateOutlet]="label"></ng-template> |
|||
<ngb-timepicker [formControlName]="prop.name"></ngb-timepicker> |
|||
</ng-template> |
|||
|
|||
<ng-template ngSwitchCase="time"> |
|||
<ng-template [ngTemplateOutlet]="label"></ng-template> |
|||
<ngb-timepicker [formControlName]="prop.name"></ngb-timepicker> |
|||
</ng-template> |
|||
<ng-template ngSwitchCase="dateTime"> |
|||
<ng-template [ngTemplateOutlet]="label"></ng-template> |
|||
<abp-date-time-picker [prop]="prop" [meridian]="meridian"></abp-date-time-picker> |
|||
</ng-template> |
|||
|
|||
<ng-template ngSwitchCase="dateTime"> |
|||
<ng-template [ngTemplateOutlet]="label"></ng-template> |
|||
<abp-date-time-picker [prop]="prop" [meridian]="meridian"></abp-date-time-picker> |
|||
</ng-template> |
|||
<ng-template ngSwitchCase="textarea"> |
|||
<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 ngSwitchCase="textarea"> |
|||
<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-container> |
|||
|
|||
<ng-template #label let-classes> |
|||
<label [htmlFor]="prop.id" [ngClass]="classes || 'form-label'" |
|||
>{{ prop.displayName | abpLocalization }} {{ asterisk }}</label |
|||
<label [htmlFor]="prop.id" [ngClass]="classes || 'form-label'" x |
|||
>{{ prop.displayName | abpLocalization }} {{ asterisk }}</label |
|||
> |
|||
</ng-template> |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
import { ControlContainer, FormGroupDirective } from "@angular/forms"; |
|||
|
|||
export const EXTENSIBLE_FORM_VIEW_PROVIDER = { provide: ControlContainer, useExisting: FormGroupDirective } |
|||
Loading…
Reference in new issue