1
0
Эх сурвалжийг харах

feat(admin-ui): Allow extensions to define CustomField controls

Relates to #55
Michael Bromley 6 жил өмнө
parent
commit
83d909047d

+ 1 - 0
packages/admin-ui/src/app/catalog/components/collection-detail/collection-detail.component.html

@@ -71,6 +71,7 @@
                 <ng-container *ngFor="let customField of customFields">
                     <vdr-custom-field-control
                         *ngIf="customFieldIsSet(customField.name)"
+                        entityName="Collection"
                         [customFieldsFormGroup]="detailForm.get(['customFields'])"
                         [customField]="customField"
                     ></vdr-custom-field-control>

+ 2 - 0
packages/admin-ui/src/app/catalog/components/facet-detail/facet-detail.component.html

@@ -78,6 +78,7 @@
             <ng-container *ngFor="let customField of customFields">
                 <vdr-custom-field-control
                     *ngIf="customFieldIsSet(customField.name)"
+                    entityName="Facet"
                     [customFieldsFormGroup]="detailForm.get(['facet', 'customFields'])"
                     [customField]="customField"
                 ></vdr-custom-field-control>
@@ -118,6 +119,7 @@
                         <td>
                             <vdr-custom-field-control
                                 *ngIf="customValueFieldIsSet(i, customField.name)"
+                                entityName="FacetValue"
                                 [showLabel]="false"
                                 [customFieldsFormGroup]="detailForm.get(['values', i, 'customFields'])"
                                 [customField]="customField"

+ 1 - 0
packages/admin-ui/src/app/catalog/components/product-detail/product-detail.component.html

@@ -85,6 +85,7 @@
                                 <ng-container *ngFor="let customField of customFields">
                                     <vdr-custom-field-control
                                         *ngIf="customFieldIsSet(customField.name)"
+                                        entityName="Product"
                                         [customFieldsFormGroup]="detailForm.get(['product', 'customFields'])"
                                         [customField]="customField"
                                         [readonly]="!('UpdateCatalog' | hasPermission)"

+ 1 - 0
packages/admin-ui/src/app/catalog/components/product-variants-list/product-variants-list.component.html

@@ -119,6 +119,7 @@
                                     <ng-container *ngFor="let customField of customFields">
                                         <vdr-custom-field-control
                                             *ngIf="customFieldIsSet(i, customField.name)"
+                                            entityName="ProductVariant"
                                             [compact]="true"
                                             [customFieldsFormGroup]="formArray.at(i).get(['customFields'])"
                                             [readonly]="!('UpdateCatalog' | hasPermission)"

+ 2 - 0
packages/admin-ui/src/app/core/core.module.ts

@@ -13,6 +13,7 @@ import { OverlayHostComponent } from './components/overlay-host/overlay-host.com
 import { UiLanguageSwitcherComponent } from './components/ui-language-switcher/ui-language-switcher.component';
 import { UserMenuComponent } from './components/user-menu/user-menu.component';
 import { AuthService } from './providers/auth/auth.service';
+import { CustomFieldComponentService } from './providers/custom-field-component/custom-field-component.service';
 import { AuthGuard } from './providers/guard/auth.guard';
 import { I18nService } from './providers/i18n/i18n.service';
 import { JobQueueService } from './providers/job-queue/job-queue.service';
@@ -33,6 +34,7 @@ import { OverlayHostService } from './providers/overlay-host/overlay-host.servic
         NotificationService,
         JobQueueService,
         NavBuilderService,
+        CustomFieldComponentService,
     ],
     declarations: [
         AppShellComponent,

+ 55 - 0
packages/admin-ui/src/app/core/providers/custom-field-component/custom-field-component.service.ts

@@ -0,0 +1,55 @@
+import {
+    ComponentFactory,
+    ComponentFactoryResolver,
+    ComponentRef,
+    Injectable,
+    Injector,
+} from '@angular/core';
+import { FormControl } from '@angular/forms';
+import { Type } from 'shared/shared-types';
+
+import { CustomFields, CustomFieldsFragment } from '../../../common/generated-types';
+export type CustomFieldConfig = CustomFieldsFragment;
+
+export interface CustomFieldControl {
+    formControl: FormControl;
+    customFieldConfig: CustomFieldConfig;
+}
+
+export type CustomFieldEntityName = Exclude<keyof CustomFields, '__typename'>;
+
+/**
+ * This service allows the registration of custom controls for customFields.
+ */
+@Injectable()
+export class CustomFieldComponentService {
+    private registry: { [K in CustomFieldEntityName]?: { [name: string]: Type<CustomFieldControl> } } = {};
+
+    constructor(private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector) {}
+
+    /**
+     * Register a CustomFieldControl component to be used with the specified customField and entity.
+     */
+    registerCustomFieldComponent(
+        entity: CustomFieldEntityName,
+        fieldName: string,
+        component: Type<CustomFieldControl>,
+    ) {
+        if (!this.registry[entity]) {
+            this.registry[entity] = {};
+        }
+        Object.assign(this.registry[entity], { [fieldName]: component });
+    }
+
+    getCustomFieldComponent(
+        entity: CustomFieldEntityName,
+        fieldName: string,
+    ): ComponentFactory<CustomFieldControl> | undefined {
+        const entityFields = this.registry[entity];
+        const componentType = entityFields && entityFields[fieldName];
+        if (componentType) {
+            const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentType);
+            return componentFactory;
+        }
+    }
+}

+ 1 - 0
packages/admin-ui/src/app/customer/components/customer-detail/customer-detail.component.html

@@ -59,6 +59,7 @@
         <ng-container *ngFor="let customField of customFields">
             <vdr-custom-field-control
                 *ngIf="customFieldIsSet(customField.name)"
+                entityName="Customer"
                 [customFieldsFormGroup]="detailForm.get('customFields')"
                 [customField]="customField"
             ></vdr-custom-field-control>

+ 1 - 0
packages/admin-ui/src/app/order/components/order-custom-fields-card/order-custom-fields-card.component.html

@@ -6,6 +6,7 @@
         <div class="card-text custom-field-form" >
             <ng-container *ngFor="let customField of customFieldsConfig">
                 <vdr-custom-field-control
+                    entityName="Order"
                     [customFieldsFormGroup]="customFieldForm"
                     [compact]="true"
                     [readonly]="true"

+ 1 - 0
packages/admin-ui/src/app/settings/components/global-settings/global-settings.component.html

@@ -48,6 +48,7 @@
         <ng-container *ngFor="let customField of customFields">
             <vdr-custom-field-control
                 *ngIf="customFieldIsSet(customField.name)"
+                entityName="GlobalSettings"
                 [customFieldsFormGroup]="detailForm.get('customFields')"
                 [customField]="customField"
             ></vdr-custom-field-control>

+ 12 - 2
packages/admin-ui/src/app/shared/components/custom-field-control/custom-field-control.component.html

@@ -2,12 +2,22 @@
     <label for="basic" class="clr-control-label">{{ label }}</label>
     <div class="clr-control-container">
         <div class="clr-input-wrapper">
-            <ng-container *ngTemplateOutlet="inputs"></ng-container>
+            <ng-container *ngIf="hasCustomControl; else standardControls">
+                <div #customComponentPlaceholder></div>
+            </ng-container>
+            <ng-template #standardControls>
+                <ng-container *ngTemplateOutlet="inputs"></ng-container>
+            </ng-template>
         </div>
     </div>
 </div>
 <vdr-form-field [label]="label" [for]="customField.name" *ngIf="!compact">
-    <ng-container *ngTemplateOutlet="inputs"></ng-container>
+    <ng-container *ngIf="hasCustomControl; else standardControls">
+        <div #customComponentPlaceholder></div>
+    </ng-container>
+    <ng-template #standardControls>
+        <ng-container *ngTemplateOutlet="inputs"></ng-container>
+    </ng-template>
 </vdr-form-field>
 <ng-template #inputs>
     <input

+ 42 - 9
packages/admin-ui/src/app/shared/components/custom-field-control/custom-field-control.component.ts

@@ -1,14 +1,22 @@
-import { Component, Input, OnDestroy, OnInit } from '@angular/core';
+import {
+    AfterViewInit,
+    Component,
+    ComponentFactory,
+    Input,
+    OnDestroy,
+    OnInit,
+    ViewChild,
+    ViewContainerRef,
+} from '@angular/core';
 import { FormControl, FormGroup } from '@angular/forms';
 import { Subscription } from 'rxjs';
 
+import { CustomFieldsFragment, LanguageCode, LocalizedString } from '../../../common/generated-types';
 import {
-    CustomFieldConfig,
-    CustomFieldsFragment,
-    GetServerConfig,
-    LanguageCode,
-    LocalizedString,
-} from '../../../common/generated-types';
+    CustomFieldComponentService,
+    CustomFieldControl,
+    CustomFieldEntityName,
+} from '../../../core/providers/custom-field-component/custom-field-component.service';
 import { DataService } from '../../../data/providers/data.service';
 
 /**
@@ -20,16 +28,24 @@ import { DataService } from '../../../data/providers/data.service';
     templateUrl: './custom-field-control.component.html',
     styleUrls: ['./custom-field-control.component.scss'],
 })
-export class CustomFieldControlComponent implements OnInit, OnDestroy {
+export class CustomFieldControlComponent implements OnInit, OnDestroy, AfterViewInit {
+    @Input() entityName: CustomFieldEntityName;
     @Input('customFieldsFormGroup') formGroup: FormGroup;
     @Input() customField: CustomFieldsFragment;
     @Input() compact = false;
     @Input() showLabel = true;
     @Input() readonly = false;
+    hasCustomControl = false;
+    @ViewChild('customComponentPlaceholder', { read: ViewContainerRef, static: false })
+    private customComponentPlaceholder: ViewContainerRef;
+    private customComponentFactory: ComponentFactory<CustomFieldControl> | undefined;
     private uiLanguageCode: LanguageCode;
     private sub: Subscription;
 
-    constructor(private dataService: DataService) {}
+    constructor(
+        private dataService: DataService,
+        private customFieldComponentService: CustomFieldComponentService,
+    ) {}
 
     ngOnInit(): void {
         this.sub = this.dataService.client
@@ -38,6 +54,23 @@ export class CustomFieldControlComponent implements OnInit, OnDestroy {
             .subscribe(language => {
                 this.uiLanguageCode = language;
             });
+        this.customComponentFactory = this.customFieldComponentService.getCustomFieldComponent(
+            this.entityName,
+            this.customField.name,
+        );
+        this.hasCustomControl = !!this.customComponentFactory;
+    }
+
+    ngAfterViewInit(): void {
+        if (this.customComponentFactory) {
+            const customComponentRef = this.customComponentPlaceholder.createComponent(
+                this.customComponentFactory,
+            );
+            customComponentRef.instance.customFieldConfig = this.customField;
+            customComponentRef.instance.formControl = this.formGroup.get(
+                this.customField.name,
+            ) as FormControl;
+        }
     }
 
     ngOnDestroy(): void {

+ 3 - 2
packages/admin-ui/src/app/shared/pipes/has-permission.pipe.ts

@@ -1,4 +1,4 @@
-import { OnDestroy, Pipe, PipeTransform } from '@angular/core';
+import { ChangeDetectorRef, OnDestroy, Pipe, PipeTransform } from '@angular/core';
 import { DataService } from '@vendure/admin-ui/src/app/data/providers/data.service';
 import { Observable, Subscription } from 'rxjs';
 
@@ -21,7 +21,7 @@ export class HasPermissionPipe implements PipeTransform, OnDestroy {
     private permission: string | null = null;
     private subscription: Subscription;
 
-    constructor(private dataService: DataService) {
+    constructor(private dataService: DataService, private changeDetectorRef: ChangeDetectorRef) {
         this.currentPermissions$ = this.dataService.client
             .userStatus()
             .mapStream(data => data.userStatus.permissions);
@@ -34,6 +34,7 @@ export class HasPermissionPipe implements PipeTransform, OnDestroy {
             this.dispose();
             this.subscription = this.currentPermissions$.subscribe(permissions => {
                 this.hasPermission = permissions.includes(permission);
+                this.changeDetectorRef.markForCheck();
             });
         }
 

+ 1 - 0
packages/admin-ui/src/index.ts

@@ -11,4 +11,5 @@ export { ModalService } from './app/shared/providers/modal/modal.service';
 export { SharedModule } from './app/shared/shared.module';
 export { NavBuilderService } from './app/core/providers/nav-builder/nav-builder.service';
 export * from './app/core/providers/nav-builder/nav-builder-types';
+export * from './app/core/providers/custom-field-component/custom-field-component.service';
 export * from './app/shared/shared-declarations';