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 './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 { |
export interface AccountConfigOptions { |
||||
redirectUrl?: string; |
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 './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 |
<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> |
||||
|
|||||
@ -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