From 274bc7da12768ae24ebfad88727505380a3c0e5b Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Fri, 8 Dec 2023 16:45:36 +0300 Subject: [PATCH 1/2] User can set dynamic layouts via the inject token --- .../extensible-form-prop.component.ts | 60 +++++----- .../extensible/src/lib/enums/components.ts | 2 +- .../components/dynamic-layout.component.ts | 109 ++++++++++-------- .../core/src/lib/constants/default-layouts.ts | 7 ++ .../packages/core/src/lib/constants/index.ts | 1 + .../packages/core/src/lib/core.module.ts | 8 +- .../packages/core/src/lib/enums/common.ts | 5 + .../packages/core/src/lib/models/common.ts | 1 + .../src/lib/tokens/dynamic-layout.token.ts | 3 + templates/app/angular/src/app/app.module.ts | 3 +- 10 files changed, 116 insertions(+), 83 deletions(-) create mode 100644 npm/ng-packs/packages/core/src/lib/constants/default-layouts.ts create mode 100644 npm/ng-packs/packages/core/src/lib/tokens/dynamic-layout.token.ts diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts index 024a8ce80e..29e33f9e55 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts +++ b/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 { ABP, LocalizationModule, PermissionDirective, ShowPasswordDirective, TrackByService } from '@abp/ng.core'; +import {EXTENSIONS_FORM_PROP, EXTENSIONS_FORM_PROP_DATA} from './../../tokens/extensions.token'; +import {ABP, LocalizationModule, PermissionDirective, ShowPasswordDirective, TrackByService} from '@abp/ng.core'; import { AfterViewInit, ChangeDetectionStrategy, @@ -28,20 +28,20 @@ import { NgbTimepickerModule, NgbTypeaheadModule, } from '@ng-bootstrap/ng-bootstrap'; -import { Observable, of } from 'rxjs'; -import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators'; -import { DateAdapter, DisabledDirective, TimeAdapter } from '@abp/ng.theme.shared'; -import { EXTRA_PROPERTIES_KEY } from '../../constants/extra-properties'; -import { FormProp } from '../../models/form-props'; -import { PropData } from '../../models/props'; -import { selfFactory } from '../../utils/factory.util'; -import { addTypeaheadTextSuffix } from '../../utils/typeahead.util'; -import { eThemeSharedComponents } from '../../enums/components'; -import { ExtensibleDateTimePickerComponent } from '../date-time-picker/extensible-date-time-picker.component'; -import { NgxValidateCoreModule } from '@ngx-validate/core'; -import { ExtensibleFormPropService } from '../../services/extensible-form-prop.service'; -import { CreateInjectorPipe } from '../../pipes/create-injector.pipe'; -import { AsyncPipe, NgClass, NgSwitch, NgSwitchCase, NgTemplateOutlet } from '@angular/common'; +import {Observable, of} from 'rxjs'; +import {debounceTime, distinctUntilChanged, switchMap} from 'rxjs/operators'; +import {DateAdapter, DisabledDirective, TimeAdapter} from '@abp/ng.theme.shared'; +import {EXTRA_PROPERTIES_KEY} from '../../constants/extra-properties'; +import {FormProp} from '../../models/form-props'; +import {PropData} from '../../models/props'; +import {selfFactory} from '../../utils/factory.util'; +import {addTypeaheadTextSuffix} from '../../utils/typeahead.util'; +import {eExtensibleComponents} from '../../enums/components'; +import {ExtensibleDateTimePickerComponent} from '../date-time-picker/extensible-date-time-picker.component'; +import {NgxValidateCoreModule} from '@ngx-validate/core'; +import {ExtensibleFormPropService} from '../../services/extensible-form-prop.service'; +import {CreateInjectorPipe} from '../../pipes/create-injector.pipe'; +import {AsyncPipe, NgClass, NgSwitch, NgSwitchCase, NgTemplateOutlet} from '@angular/common'; @Component({ selector: 'abp-extensible-form-prop', @@ -73,8 +73,8 @@ import { AsyncPipe, NgClass, NgSwitch, NgSwitchCase, NgTemplateOutlet } from '@a useFactory: selfFactory, deps: [[new Optional(), new SkipSelf(), ControlContainer]], }, - { provide: NgbDateAdapter, useClass: DateAdapter }, - { provide: NgbTimeAdapter, useClass: TimeAdapter }, + {provide: NgbDateAdapter, useClass: DateAdapter}, + {provide: NgbTimeAdapter, useClass: TimeAdapter}, ], }) export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { @@ -98,7 +98,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { validators: ValidatorFn[] = []; readonly!: boolean; typeaheadModel: any; - passwordKey = eThemeSharedComponents.PasswordComponent; + passwordKey = eExtensibleComponents.PasswordComponent; disabledFn = (data: PropData) => false; @@ -107,8 +107,8 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { } setTypeaheadValue(selectedOption: ABP.Option) { - this.typeaheadModel = selectedOption || { key: null, value: null }; - const { key, value } = this.typeaheadModel; + this.typeaheadModel = selectedOption || {key: null, value: null}; + const {key, value} = this.typeaheadModel; const [keyControl, valueControl] = this.getTypeaheadControls(); if (valueControl?.value && !value) valueControl.markAsDirty(); keyControl?.setValue(key); @@ -118,10 +118,10 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { search = (text$: Observable) => text$ ? text$.pipe( - debounceTime(300), - distinctUntilChanged(), - switchMap(text => this.prop?.options?.(this.data, text) || of([])), - ) + debounceTime(300), + distinctUntilChanged(), + switchMap(text => this.prop?.options?.(this.data, text) || of([])), + ) : of([]); typeaheadFormatter = (option: ABP.Option) => option.key; @@ -134,7 +134,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { } private getTypeaheadControls() { - const { name } = this.prop; + const {name} = this.prop; const extraPropName = `${EXTRA_PROPERTIES_KEY}.${name}`; const keyControl = this.form.get(addTypeaheadTextSuffix(extraPropName)) || @@ -162,9 +162,9 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { return this.service.getType(prop); } - ngOnChanges({ prop, data }: SimpleChanges) { + ngOnChanges({prop, data}: SimpleChanges) { const currentProp = prop?.currentValue as FormProp; - const { options, readonly, disabled, validators, className, template } = currentProp || {}; + const {options, readonly, disabled, validators, className, template} = currentProp || {}; if (template) { this.injectorForCustomComponent = Injector.create({ providers: [ @@ -176,7 +176,7 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { provide: EXTENSIONS_FORM_PROP_DATA, useValue: (data?.currentValue as PropData)?.record, }, - { provide: ControlContainer, useExisting: FormGroupDirective }, + {provide: ControlContainer, useExisting: FormGroupDirective}, ], parent: this.injector, }); @@ -198,6 +198,6 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { const [keyControl, valueControl] = this.getTypeaheadControls(); if (keyControl && valueControl) - this.typeaheadModel = { key: keyControl.value, value: valueControl.value }; + this.typeaheadModel = {key: keyControl.value, value: valueControl.value}; } } diff --git a/npm/ng-packs/packages/components/extensible/src/lib/enums/components.ts b/npm/ng-packs/packages/components/extensible/src/lib/enums/components.ts index 562041c807..19e039f690 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/enums/components.ts +++ b/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', } diff --git a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts index 5215c95398..5118f7ec42 100644 --- a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts +++ b/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 { ActivatedRoute, Router } from '@angular/router'; -import { eLayoutType } from '../enums/common'; -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 { + Component, + inject, + isDevMode, + OnInit, + Optional, + SkipSelf, + Type +} from '@angular/core'; +import {ActivatedRoute, Router} from '@angular/router'; +import {eLayoutType} from '../enums/common'; +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({ selector: 'abp-dynamic-layout', - template: ` `, + template: ` + `, providers: [SubscriptionService], }) export class DynamicLayoutComponent implements OnInit { layout?: Type; layoutKey?: eLayoutType; - - // TODO: Consider a shared enum (eThemeSharedComponents) for known layouts - readonly layouts = new Map([ - ['application', 'Theme.ApplicationLayoutComponent'], - ['account', 'Theme.AccountLayoutComponent'], - ['empty', 'Theme.EmptyLayoutComponent'], - ]); - + readonly layouts = inject(DYNAMIC_LAYOUTS_TOKEN) isLayoutVisible = true; - private router!: Router; - private route!: ActivatedRoute; - private routes!: RoutesService; + private readonly router = inject(Router); + private readonly route = inject(ActivatedRoute); + private readonly routes = inject(RoutesService); + private localizationService = inject(LocalizationService) + private replaceableComponents = inject(ReplaceableComponentsService) + private subscription = inject(SubscriptionService) + private routerEvents = inject(RouterEvents) + constructor( - injector: Injector, - private localizationService: LocalizationService, - private replaceableComponents: ReplaceableComponentsService, - private subscription: SubscriptionService, - private routerEvents: RouterEvents, @Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent, ) { if (dynamicLayoutComponent) { if (isDevMode()) console.warn('DynamicLayoutComponent must be used only in AppComponent.'); return; } - this.route = injector.get(ActivatedRoute); - this.router = injector.get(Router); - this.routes = injector.get(RoutesService); - this.checkLayoutOnNavigationEnd(); this.listenToLanguageChange(); } ngOnInit(): void { - if(this.layout){ + if (this.layout) { return; } this.getLayout() @@ -65,22 +64,10 @@ export class DynamicLayoutComponent implements OnInit { this.subscription.addOne(navigationEnd$, () => this.getLayout()); } - private getLayout() { - let expectedLayout = (this.route.snapshot.data || {}).layout; - if (!expectedLayout) { - let node = findRoute(this.routes, getRoutePath(this.router)); - node = { parent: node } as TreeNode; - - while (node.parent) { - node = node.parent; + private getLayout() { + let expectedLayout = this.getExtractedLayout(); - if (node.layout) { - expectedLayout = node.layout; - break; - } - } - } if (!expectedLayout) expectedLayout = eLayoutType.empty; @@ -91,14 +78,36 @@ export class DynamicLayoutComponent implements OnInit { this.layout = this.getComponent(key)?.component; this.layoutKey = expectedLayout; } - if(!this.layout){ + if (!this.layout) { 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; + + while (node.parent) { + node = node.parent; + + if (node.layout) { + expectedLayout = node.layout; + break; + } + } + return expectedLayout; + } + showLayoutNotFoundError(layoutName: string) { 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.'; } console.warn(message); diff --git a/npm/ng-packs/packages/core/src/lib/constants/default-layouts.ts b/npm/ng-packs/packages/core/src/lib/constants/default-layouts.ts new file mode 100644 index 0000000000..fc9e561e2b --- /dev/null +++ b/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([ + [eLayoutType.application, eThemeSharedComponents.ApplicationLayoutComponent], + [eLayoutType.account, eThemeSharedComponents.AccountLayoutComponent], + [eLayoutType.empty, eThemeSharedComponents.EmptyLayoutComponent], +]); diff --git a/npm/ng-packs/packages/core/src/lib/constants/index.ts b/npm/ng-packs/packages/core/src/lib/constants/index.ts index 54aaba730c..95ca979dc1 100644 --- a/npm/ng-packs/packages/core/src/lib/constants/index.ts +++ b/npm/ng-packs/packages/core/src/lib/constants/index.ts @@ -1 +1,2 @@ export * from './different-locales'; +export * from './default-layouts'; diff --git a/npm/ng-packs/packages/core/src/lib/core.module.ts b/npm/ng-packs/packages/core/src/lib/core.module.ts index 57133ab69e..edc2dcb514 100644 --- a/npm/ng-packs/packages/core/src/lib/core.module.ts +++ b/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 { IncludeLocalizationResourcesProvider } from './providers/include-localization-resources.provider'; 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 = [ AutofocusDirective, @@ -97,7 +99,7 @@ const standaloneDirectives = [ ShortTimePipe, ShortDatePipe, ], - providers: [LocalizationPipe], + providers: [LocalizationPipe,], }) export class BaseCoreModule {} @@ -186,6 +188,10 @@ export class CoreModule { useValue: options.othersGroup || 'AbpUi::OthersGroup', }, IncludeLocalizationResourcesProvider, + { + provide: DYNAMIC_LAYOUTS_TOKEN, + useValue: options.dynamicLayouts || DEFAULT_DYNAMIC_LAYOUTS + } ], }; } diff --git a/npm/ng-packs/packages/core/src/lib/enums/common.ts b/npm/ng-packs/packages/core/src/lib/enums/common.ts index 08ddf05b6d..3d28eb6986 100644 --- a/npm/ng-packs/packages/core/src/lib/enums/common.ts +++ b/npm/ng-packs/packages/core/src/lib/enums/common.ts @@ -3,3 +3,8 @@ export const enum eLayoutType { application = 'application', empty = 'empty', } +export const enum eThemeSharedComponents { + ApplicationLayoutComponent = 'Theme.ApplicationLayoutComponent', + AccountLayoutComponent = 'Theme.AccountLayoutComponent', + EmptyLayoutComponent = 'Theme.EmptyLayoutComponent', +} diff --git a/npm/ng-packs/packages/core/src/lib/models/common.ts b/npm/ng-packs/packages/core/src/lib/models/common.ts index 7a285b4461..a3aec61b88 100644 --- a/npm/ng-packs/packages/core/src/lib/models/common.ts +++ b/npm/ng-packs/packages/core/src/lib/models/common.ts @@ -13,6 +13,7 @@ export namespace ABP { tenantKey?: string; localizations?: Localization[]; othersGroup?: string; + dynamicLayouts?: Map; } export interface Child { diff --git a/npm/ng-packs/packages/core/src/lib/tokens/dynamic-layout.token.ts b/npm/ng-packs/packages/core/src/lib/tokens/dynamic-layout.token.ts new file mode 100644 index 0000000000..2c92cd7336 --- /dev/null +++ b/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>('DYNAMIC_LAYOUTS') diff --git a/templates/app/angular/src/app/app.module.ts b/templates/app/angular/src/app/app.module.ts index 13d8731b67..34d59b4ec4 100644 --- a/templates/app/angular/src/app/app.module.ts +++ b/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 { FeatureManagementModule } from '@abp/ng.feature-management'; import { AbpOAuthModule } from '@abp/ng.oauth'; - +import { AccountLayoutModule } from '@abp/ng.theme.lepton-x/account'; @NgModule({ imports: [ BrowserModule, @@ -28,6 +28,7 @@ import { AbpOAuthModule } from '@abp/ng.oauth'; }), AbpOAuthModule.forRoot(), ThemeSharedModule.forRoot(), + AccountLayoutModule.forRoot(), AccountConfigModule.forRoot(), IdentityConfigModule.forRoot(), TenantManagementConfigModule.forRoot(), From eeba6f255d6e0c437d925c9472857d4b1959a5dc Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Mon, 11 Dec 2023 13:11:30 +0300 Subject: [PATCH 2/2] Update core.module.ts --- npm/ng-packs/packages/core/src/lib/core.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/core/src/lib/core.module.ts b/npm/ng-packs/packages/core/src/lib/core.module.ts index edc2dcb514..d39912b496 100644 --- a/npm/ng-packs/packages/core/src/lib/core.module.ts +++ b/npm/ng-packs/packages/core/src/lib/core.module.ts @@ -99,7 +99,7 @@ const standaloneDirectives = [ ShortTimePipe, ShortDatePipe, ], - providers: [LocalizationPipe,], + providers: [LocalizationPipe], }) export class BaseCoreModule {}