Browse Source

Merge pull request #18432 from abpframework/auto-merge/rel-8-0/2336

Merge branch dev with rel-8.0
pull/18437/head
maliming 3 years ago
committed by GitHub
parent
commit
6f1ce29e6d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 60
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts
  2. 2
      npm/ng-packs/packages/components/extensible/src/lib/enums/components.ts
  3. 109
      npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts
  4. 7
      npm/ng-packs/packages/core/src/lib/constants/default-layouts.ts
  5. 1
      npm/ng-packs/packages/core/src/lib/constants/index.ts
  6. 6
      npm/ng-packs/packages/core/src/lib/core.module.ts
  7. 5
      npm/ng-packs/packages/core/src/lib/enums/common.ts
  8. 1
      npm/ng-packs/packages/core/src/lib/models/common.ts
  9. 3
      npm/ng-packs/packages/core/src/lib/tokens/dynamic-layout.token.ts
  10. 3
      templates/app/angular/src/app/app.module.ts

60
npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts

@ -1,5 +1,5 @@
import { EXTENSIONS_FORM_PROP, EXTENSIONS_FORM_PROP_DATA } from './../../tokens/extensions.token'; import {EXTENSIONS_FORM_PROP, EXTENSIONS_FORM_PROP_DATA} from './../../tokens/extensions.token';
import { ABP, LocalizationModule, PermissionDirective, ShowPasswordDirective, TrackByService } from '@abp/ng.core'; import {ABP, LocalizationModule, PermissionDirective, ShowPasswordDirective, TrackByService} from '@abp/ng.core';
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
@ -28,20 +28,20 @@ import {
NgbTimepickerModule, NgbTimepickerModule,
NgbTypeaheadModule, NgbTypeaheadModule,
} from '@ng-bootstrap/ng-bootstrap'; } from '@ng-bootstrap/ng-bootstrap';
import { Observable, of } from 'rxjs'; import {Observable, of} from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators'; import {debounceTime, distinctUntilChanged, switchMap} from 'rxjs/operators';
import { DateAdapter, DisabledDirective, TimeAdapter } from '@abp/ng.theme.shared'; import {DateAdapter, DisabledDirective, TimeAdapter} from '@abp/ng.theme.shared';
import { EXTRA_PROPERTIES_KEY } from '../../constants/extra-properties'; import {EXTRA_PROPERTIES_KEY} from '../../constants/extra-properties';
import { FormProp } from '../../models/form-props'; import {FormProp} from '../../models/form-props';
import { PropData } from '../../models/props'; import {PropData} from '../../models/props';
import { selfFactory } from '../../utils/factory.util'; import {selfFactory} from '../../utils/factory.util';
import { addTypeaheadTextSuffix } from '../../utils/typeahead.util'; import {addTypeaheadTextSuffix} from '../../utils/typeahead.util';
import { eThemeSharedComponents } from '../../enums/components'; import {eExtensibleComponents} from '../../enums/components';
import { ExtensibleDateTimePickerComponent } from '../date-time-picker/extensible-date-time-picker.component'; import {ExtensibleDateTimePickerComponent} from '../date-time-picker/extensible-date-time-picker.component';
import { NgxValidateCoreModule } from '@ngx-validate/core'; import {NgxValidateCoreModule} from '@ngx-validate/core';
import { ExtensibleFormPropService } from '../../services/extensible-form-prop.service'; import {ExtensibleFormPropService} from '../../services/extensible-form-prop.service';
import { CreateInjectorPipe } from '../../pipes/create-injector.pipe'; import {CreateInjectorPipe} from '../../pipes/create-injector.pipe';
import { AsyncPipe, NgClass, NgSwitch, NgSwitchCase, NgTemplateOutlet } from '@angular/common'; import {AsyncPipe, NgClass, NgSwitch, NgSwitchCase, NgTemplateOutlet} from '@angular/common';
@Component({ @Component({
selector: 'abp-extensible-form-prop', selector: 'abp-extensible-form-prop',
@ -73,8 +73,8 @@ import { AsyncPipe, NgClass, NgSwitch, NgSwitchCase, NgTemplateOutlet } from '@a
useFactory: selfFactory, useFactory: selfFactory,
deps: [[new Optional(), new SkipSelf(), ControlContainer]], deps: [[new Optional(), new SkipSelf(), ControlContainer]],
}, },
{ provide: NgbDateAdapter, useClass: DateAdapter }, {provide: NgbDateAdapter, useClass: DateAdapter},
{ provide: NgbTimeAdapter, useClass: TimeAdapter }, {provide: NgbTimeAdapter, useClass: TimeAdapter},
], ],
}) })
export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
@ -98,7 +98,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
validators: ValidatorFn[] = []; validators: ValidatorFn[] = [];
readonly!: boolean; readonly!: boolean;
typeaheadModel: any; typeaheadModel: any;
passwordKey = eThemeSharedComponents.PasswordComponent; passwordKey = eExtensibleComponents.PasswordComponent;
disabledFn = (data: PropData) => false; disabledFn = (data: PropData) => false;
@ -107,8 +107,8 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
} }
setTypeaheadValue(selectedOption: ABP.Option<string>) { setTypeaheadValue(selectedOption: ABP.Option<string>) {
this.typeaheadModel = selectedOption || { key: null, value: null }; this.typeaheadModel = selectedOption || {key: null, value: null};
const { key, value } = this.typeaheadModel; const {key, value} = this.typeaheadModel;
const [keyControl, valueControl] = this.getTypeaheadControls(); const [keyControl, valueControl] = this.getTypeaheadControls();
if (valueControl?.value && !value) valueControl.markAsDirty(); if (valueControl?.value && !value) valueControl.markAsDirty();
keyControl?.setValue(key); keyControl?.setValue(key);
@ -118,10 +118,10 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
search = (text$: Observable<string>) => search = (text$: Observable<string>) =>
text$ text$
? text$.pipe( ? text$.pipe(
debounceTime(300), debounceTime(300),
distinctUntilChanged(), distinctUntilChanged(),
switchMap(text => this.prop?.options?.(this.data, text) || of([])), switchMap(text => this.prop?.options?.(this.data, text) || of([])),
) )
: of([]); : of([]);
typeaheadFormatter = (option: ABP.Option<any>) => option.key; typeaheadFormatter = (option: ABP.Option<any>) => option.key;
@ -134,7 +134,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
} }
private getTypeaheadControls() { private getTypeaheadControls() {
const { name } = this.prop; const {name} = this.prop;
const extraPropName = `${EXTRA_PROPERTIES_KEY}.${name}`; const extraPropName = `${EXTRA_PROPERTIES_KEY}.${name}`;
const keyControl = const keyControl =
this.form.get(addTypeaheadTextSuffix(extraPropName)) || this.form.get(addTypeaheadTextSuffix(extraPropName)) ||
@ -162,9 +162,9 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
return this.service.getType(prop); return this.service.getType(prop);
} }
ngOnChanges({ prop, data }: SimpleChanges) { ngOnChanges({prop, data}: SimpleChanges) {
const currentProp = prop?.currentValue as FormProp; const currentProp = prop?.currentValue as FormProp;
const { options, readonly, disabled, validators, className, template } = currentProp || {}; const {options, readonly, disabled, validators, className, template} = currentProp || {};
if (template) { if (template) {
this.injectorForCustomComponent = Injector.create({ this.injectorForCustomComponent = Injector.create({
providers: [ providers: [
@ -176,7 +176,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
provide: EXTENSIONS_FORM_PROP_DATA, provide: EXTENSIONS_FORM_PROP_DATA,
useValue: (data?.currentValue as PropData)?.record, useValue: (data?.currentValue as PropData)?.record,
}, },
{ provide: ControlContainer, useExisting: FormGroupDirective }, {provide: ControlContainer, useExisting: FormGroupDirective},
], ],
parent: this.injector, parent: this.injector,
}); });
@ -198,6 +198,6 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit {
const [keyControl, valueControl] = this.getTypeaheadControls(); const [keyControl, valueControl] = this.getTypeaheadControls();
if (keyControl && valueControl) if (keyControl && valueControl)
this.typeaheadModel = { key: keyControl.value, value: valueControl.value }; this.typeaheadModel = {key: keyControl.value, value: valueControl.value};
} }
} }

2
npm/ng-packs/packages/components/extensible/src/lib/enums/components.ts

@ -1,3 +1,3 @@
export const enum eThemeSharedComponents { export const enum eExtensibleComponents {
PasswordComponent = 'ThemeShared.Extensions.PasswordComponent', PasswordComponent = 'ThemeShared.Extensions.PasswordComponent',
} }

109
npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts

@ -1,60 +1,59 @@
import { Component, Injector, isDevMode, OnInit, Optional, SkipSelf, Type } from '@angular/core'; import {
import { ActivatedRoute, Router } from '@angular/router'; Component,
import { eLayoutType } from '../enums/common'; inject,
import { ABP } from '../models'; isDevMode,
import { ReplaceableComponents } from '../models/replaceable-components'; OnInit,
import { LocalizationService } from '../services/localization.service'; Optional,
import { ReplaceableComponentsService } from '../services/replaceable-components.service'; SkipSelf,
import { RouterEvents } from '../services/router-events.service'; Type
import { RoutesService } from '../services/routes.service'; } from '@angular/core';
import { SubscriptionService } from '../services/subscription.service'; import {ActivatedRoute, Router} from '@angular/router';
import { findRoute, getRoutePath } from '../utils/route-utils'; import {eLayoutType} from '../enums/common';
import { TreeNode } from '../utils/tree-utils'; import {ABP} from '../models';
import {ReplaceableComponents} from '../models/replaceable-components';
import {LocalizationService} from '../services/localization.service';
import {ReplaceableComponentsService} from '../services/replaceable-components.service';
import {RouterEvents} from '../services/router-events.service';
import {RoutesService} from '../services/routes.service';
import {SubscriptionService} from '../services/subscription.service';
import {findRoute, getRoutePath} from '../utils/route-utils';
import {TreeNode} from '../utils/tree-utils';
import {DYNAMIC_LAYOUTS_TOKEN} from "../tokens/dynamic-layout.token";
@Component({ @Component({
selector: 'abp-dynamic-layout', selector: 'abp-dynamic-layout',
template: ` <ng-container *ngIf="isLayoutVisible" [ngComponentOutlet]="layout"></ng-container> `, template: `
<ng-container *ngIf="isLayoutVisible" [ngComponentOutlet]="layout"></ng-container> `,
providers: [SubscriptionService], providers: [SubscriptionService],
}) })
export class DynamicLayoutComponent implements OnInit { export class DynamicLayoutComponent implements OnInit {
layout?: Type<any>; layout?: Type<any>;
layoutKey?: eLayoutType; layoutKey?: eLayoutType;
readonly layouts = inject(DYNAMIC_LAYOUTS_TOKEN)
// TODO: Consider a shared enum (eThemeSharedComponents) for known layouts
readonly layouts = new Map([
['application', 'Theme.ApplicationLayoutComponent'],
['account', 'Theme.AccountLayoutComponent'],
['empty', 'Theme.EmptyLayoutComponent'],
]);
isLayoutVisible = true; isLayoutVisible = true;
private router!: Router; private readonly router = inject(Router);
private route!: ActivatedRoute; private readonly route = inject(ActivatedRoute);
private routes!: RoutesService; private readonly routes = inject(RoutesService);
private localizationService = inject(LocalizationService)
private replaceableComponents = inject(ReplaceableComponentsService)
private subscription = inject(SubscriptionService)
private routerEvents = inject(RouterEvents)
constructor( constructor(
injector: Injector,
private localizationService: LocalizationService,
private replaceableComponents: ReplaceableComponentsService,
private subscription: SubscriptionService,
private routerEvents: RouterEvents,
@Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent, @Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent,
) { ) {
if (dynamicLayoutComponent) { if (dynamicLayoutComponent) {
if (isDevMode()) console.warn('DynamicLayoutComponent must be used only in AppComponent.'); if (isDevMode()) console.warn('DynamicLayoutComponent must be used only in AppComponent.');
return; return;
} }
this.route = injector.get(ActivatedRoute);
this.router = injector.get(Router);
this.routes = injector.get(RoutesService);
this.checkLayoutOnNavigationEnd(); this.checkLayoutOnNavigationEnd();
this.listenToLanguageChange(); this.listenToLanguageChange();
} }
ngOnInit(): void { ngOnInit(): void {
if(this.layout){ if (this.layout) {
return; return;
} }
this.getLayout() this.getLayout()
@ -65,22 +64,10 @@ export class DynamicLayoutComponent implements OnInit {
this.subscription.addOne(navigationEnd$, () => this.getLayout()); this.subscription.addOne(navigationEnd$, () => this.getLayout());
} }
private getLayout() {
let expectedLayout = (this.route.snapshot.data || {}).layout;
if (!expectedLayout) { private getLayout() {
let node = findRoute(this.routes, getRoutePath(this.router)); let expectedLayout = this.getExtractedLayout();
node = { parent: node } as TreeNode<ABP.Route>;
while (node.parent) {
node = node.parent;
if (node.layout) {
expectedLayout = node.layout;
break;
}
}
}
if (!expectedLayout) expectedLayout = eLayoutType.empty; if (!expectedLayout) expectedLayout = eLayoutType.empty;
@ -91,14 +78,36 @@ export class DynamicLayoutComponent implements OnInit {
this.layout = this.getComponent(key)?.component; this.layout = this.getComponent(key)?.component;
this.layoutKey = expectedLayout; this.layoutKey = expectedLayout;
} }
if(!this.layout){ if (!this.layout) {
this.showLayoutNotFoundError(expectedLayout); this.showLayoutNotFoundError(expectedLayout);
} }
} }
private getExtractedLayout() {
const routeData = (this.route.snapshot.data || {});
let expectedLayout = routeData['layout'] as eLayoutType;
if (expectedLayout) {
return expectedLayout;
}
let node = findRoute(this.routes, getRoutePath(this.router));
node = {parent: node} as TreeNode<ABP.Route>;
while (node.parent) {
node = node.parent;
if (node.layout) {
expectedLayout = node.layout;
break;
}
}
return expectedLayout;
}
showLayoutNotFoundError(layoutName: string) { showLayoutNotFoundError(layoutName: string) {
let message = `Layout ${layoutName} not found.`; let message = `Layout ${layoutName} not found.`;
if(layoutName === 'account'){ if (layoutName === 'account') {
message = 'Account layout not found. Please check your configuration. If you are using LeptonX, please make sure you have added "AccountLayoutModule.forRoot()" to your app.module configuration.'; message = 'Account layout not found. Please check your configuration. If you are using LeptonX, please make sure you have added "AccountLayoutModule.forRoot()" to your app.module configuration.';
} }
console.warn(message); console.warn(message);

7
npm/ng-packs/packages/core/src/lib/constants/default-layouts.ts

@ -0,0 +1,7 @@
import {eLayoutType, eThemeSharedComponents} from "../enums";
export const DEFAULT_DYNAMIC_LAYOUTS = new Map<string, string>([
[eLayoutType.application, eThemeSharedComponents.ApplicationLayoutComponent],
[eLayoutType.account, eThemeSharedComponents.AccountLayoutComponent],
[eLayoutType.empty, eThemeSharedComponents.EmptyLayoutComponent],
]);

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

@ -1 +1,2 @@
export * from './different-locales'; export * from './different-locales';
export * from './default-layouts';

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

@ -39,6 +39,8 @@ import { QUEUE_MANAGER } from './tokens/queue.token';
import { DefaultQueueManager } from './utils/queue'; import { DefaultQueueManager } from './utils/queue';
import { IncludeLocalizationResourcesProvider } from './providers/include-localization-resources.provider'; import { IncludeLocalizationResourcesProvider } from './providers/include-localization-resources.provider';
import { SORT_COMPARE_FUNC, compareFuncFactory } from './tokens/compare-func.token'; import { SORT_COMPARE_FUNC, compareFuncFactory } from './tokens/compare-func.token';
import {DYNAMIC_LAYOUTS_TOKEN} from "./tokens/dynamic-layout.token";
import {DEFAULT_DYNAMIC_LAYOUTS} from "./constants";
const standaloneDirectives = [ const standaloneDirectives = [
AutofocusDirective, AutofocusDirective,
@ -186,6 +188,10 @@ export class CoreModule {
useValue: options.othersGroup || 'AbpUi::OthersGroup', useValue: options.othersGroup || 'AbpUi::OthersGroup',
}, },
IncludeLocalizationResourcesProvider, IncludeLocalizationResourcesProvider,
{
provide: DYNAMIC_LAYOUTS_TOKEN,
useValue: options.dynamicLayouts || DEFAULT_DYNAMIC_LAYOUTS
}
], ],
}; };
} }

5
npm/ng-packs/packages/core/src/lib/enums/common.ts

@ -3,3 +3,8 @@ export const enum eLayoutType {
application = 'application', application = 'application',
empty = 'empty', empty = 'empty',
} }
export const enum eThemeSharedComponents {
ApplicationLayoutComponent = 'Theme.ApplicationLayoutComponent',
AccountLayoutComponent = 'Theme.AccountLayoutComponent',
EmptyLayoutComponent = 'Theme.EmptyLayoutComponent',
}

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

@ -13,6 +13,7 @@ export namespace ABP {
tenantKey?: string; tenantKey?: string;
localizations?: Localization[]; localizations?: Localization[];
othersGroup?: string; othersGroup?: string;
dynamicLayouts?: Map<string, string>;
} }
export interface Child { export interface Child {

3
npm/ng-packs/packages/core/src/lib/tokens/dynamic-layout.token.ts

@ -0,0 +1,3 @@
import {InjectionToken} from "@angular/core";
export const DYNAMIC_LAYOUTS_TOKEN = new InjectionToken<Map<string, string>>('DYNAMIC_LAYOUTS')

3
templates/app/angular/src/app/app.module.ts

@ -16,7 +16,7 @@ import { AppComponent } from './app.component';
import { APP_ROUTE_PROVIDER } from './route.provider'; import { APP_ROUTE_PROVIDER } from './route.provider';
import { FeatureManagementModule } from '@abp/ng.feature-management'; import { FeatureManagementModule } from '@abp/ng.feature-management';
import { AbpOAuthModule } from '@abp/ng.oauth'; import { AbpOAuthModule } from '@abp/ng.oauth';
import { AccountLayoutModule } from '@abp/ng.theme.lepton-x/account';
@NgModule({ @NgModule({
imports: [ imports: [
BrowserModule, BrowserModule,
@ -28,6 +28,7 @@ import { AbpOAuthModule } from '@abp/ng.oauth';
}), }),
AbpOAuthModule.forRoot(), AbpOAuthModule.forRoot(),
ThemeSharedModule.forRoot(), ThemeSharedModule.forRoot(),
AccountLayoutModule.forRoot(),
AccountConfigModule.forRoot(), AccountConfigModule.forRoot(),
IdentityConfigModule.forRoot(), IdentityConfigModule.forRoot(),
TenantManagementConfigModule.forRoot(), TenantManagementConfigModule.forRoot(),

Loading…
Cancel
Save