Преглед изворни кода

refactor(admin-ui): Implement central input component registry

Relates to #415
Michael Bromley пре 5 година
родитељ
комит
b769f3513a
46 измењених фајлова са 510 додато и 231 уклоњено
  1. 0 2
      packages/admin-ui/src/lib/catalog/src/components/collection-detail/collection-detail.component.html
  2. 9 16
      packages/admin-ui/src/lib/catalog/src/components/collection-detail/collection-detail.component.ts
  3. 12 0
      packages/admin-ui/src/lib/core/src/common/component-registry-types.ts
  4. 2 2
      packages/admin-ui/src/lib/core/src/common/generated-types.ts
  5. 42 0
      packages/admin-ui/src/lib/core/src/common/utilities/configurable-operation-utils.ts
  6. 0 20
      packages/admin-ui/src/lib/core/src/common/utilities/get-default-config-arg-value.ts
  7. 2 2
      packages/admin-ui/src/lib/core/src/common/utilities/interpolate-description.ts
  8. 26 2
      packages/admin-ui/src/lib/core/src/core.module.ts
  9. 1 1
      packages/admin-ui/src/lib/core/src/data/definitions/shared-definitions.ts
  10. 16 0
      packages/admin-ui/src/lib/core/src/providers/component-registry/component-registry.service.spec.ts
  11. 23 0
      packages/admin-ui/src/lib/core/src/providers/component-registry/component-registry.service.ts
  12. 1 1
      packages/admin-ui/src/lib/core/src/public_api.ts
  13. 4 56
      packages/admin-ui/src/lib/core/src/shared/components/configurable-input/configurable-input.component.html
  14. 8 66
      packages/admin-ui/src/lib/core/src/shared/components/configurable-input/configurable-input.component.ts
  15. 11 5
      packages/admin-ui/src/lib/core/src/shared/components/facet-value-selector/facet-value-selector.component.ts
  16. 8 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/boolean-form-input/boolean-form-input.component.html
  17. 0 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/boolean-form-input/boolean-form-input.component.scss
  18. 16 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/boolean-form-input/boolean-form-input.component.ts
  19. 5 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/currency-form-input/currency-form-input.component.html
  20. 0 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/currency-form-input/currency-form-input.component.scss
  21. 26 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/currency-form-input/currency-form-input.component.ts
  22. 7 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/date-form-input/date-form-input.component.html
  23. 0 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/date-form-input/date-form-input.component.scss
  24. 20 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/date-form-input/date-form-input.component.ts
  25. 1 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.html
  26. 0 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.scss
  27. 113 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.ts
  28. 6 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/facet-value-form-input/facet-value-form-input.component.html
  29. 0 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/facet-value-form-input/facet-value-form-input.component.scss
  30. 28 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/facet-value-form-input/facet-value-form-input.component.ts
  31. 3 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/number-form-input/number-form-input.component.html
  32. 0 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/number-form-input/number-form-input.component.scss
  33. 20 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/number-form-input/number-form-input.component.ts
  34. 5 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/select-form-input/select-form-input.component.html
  35. 0 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/select-form-input/select-form-input.component.scss
  36. 19 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/select-form-input/select-form-input.component.ts
  37. 5 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/text-form-input/text-form-input.component.html
  38. 0 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/text-form-input/text-form-input.component.scss
  39. 16 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/text-form-input/text-form-input.component.ts
  40. 22 3
      packages/admin-ui/src/lib/core/src/shared/shared.module.ts
  41. 0 4
      packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.html
  42. 3 19
      packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.ts
  43. 13 2
      packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.html
  44. 14 25
      packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.ts
  45. 2 4
      packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.html
  46. 1 1
      packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.ts

+ 0 - 2
packages/admin-ui/src/lib/catalog/src/components/collection-detail/collection-detail.component.html

@@ -104,12 +104,10 @@
             <ng-container *ngFor="let filter of filters; index as i">
                 <vdr-configurable-input
                     (remove)="removeFilter($event)"
-                    [facets]="facets$ | async"
                     [operation]="filter"
                     [operationDefinition]="getFilterDefinition(filter)"
                     [formControlName]="i"
                     [readonly]="!('UpdateCatalog' | hasPermission)"
-                    [activeChannel]="activeChannel$ | async"
                 ></vdr-configurable-input>
             </ng-container>
 

+ 9 - 16
packages/admin-ui/src/lib/catalog/src/components/collection-detail/collection-detail.component.ts

@@ -13,14 +13,14 @@ import {
     BaseDetailComponent,
     Collection,
     ConfigurableOperation,
+    ConfigurableOperationDef,
     ConfigurableOperationDefinition,
     ConfigurableOperationInput,
     CreateCollectionInput,
     createUpdatedTranslatable,
     CustomFieldConfig,
     DataService,
-    FacetWithValues,
-    GetActiveChannel,
+    getConfigArgValue,
     LanguageCode,
     ModalService,
     NotificationService,
@@ -28,8 +28,8 @@ import {
     UpdateCollectionInput,
 } from '@vendure/admin-ui/core';
 import { normalizeString } from '@vendure/common/lib/normalize-string';
-import { combineLatest, Observable } from 'rxjs';
-import { mergeMap, shareReplay, take } from 'rxjs/operators';
+import { combineLatest } from 'rxjs';
+import { mergeMap, take } from 'rxjs/operators';
 
 import { CollectionContentsComponent } from '../collection-contents/collection-contents.component';
 
@@ -46,8 +46,6 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
     assetChanges: { assetIds?: string[]; featuredAssetId?: string } = {};
     filters: ConfigurableOperation[] = [];
     allFilters: ConfigurableOperationDefinition[] = [];
-    facets$: Observable<FacetWithValues.Fragment[]>;
-    activeChannel$: Observable<GetActiveChannel.ActiveChannel>;
     @ViewChild('collectionContents') contentsComponent: CollectionContentsComponent;
 
     constructor(
@@ -76,17 +74,9 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
 
     ngOnInit() {
         this.init();
-        this.facets$ = this.dataService.facet
-            .getAllFacets()
-            .mapSingle(data => data.facets.items)
-            .pipe(shareReplay(1));
-
         this.dataService.collection.getCollectionFilters().single$.subscribe(res => {
             this.allFilters = res.collectionFilters;
         });
-        this.activeChannel$ = this.dataService.settings
-            .getActiveChannel()
-            .mapStream(data => data.activeChannel);
     }
 
     ngOnDestroy() {
@@ -126,7 +116,7 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
             const argsHash = collectionFilter.args.reduce(
                 (output, arg) => ({
                     ...output,
-                    [arg.name]: arg.value,
+                    [arg.name]: getConfigArgValue(arg.value),
                 }),
                 {},
             );
@@ -136,7 +126,10 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
                     args: argsHash,
                 }),
             );
-            this.filters.push(collectionFilter);
+            this.filters.push({
+                code: collectionFilter.code,
+                args: collectionFilter.args.map(a => ({ name: a.name, value: getConfigArgValue(a.value) })),
+            });
         }
     }
 

+ 12 - 0
packages/admin-ui/src/lib/core/src/common/component-registry-types.ts

@@ -0,0 +1,12 @@
+import { FormControl } from '@angular/forms';
+
+export interface FormInputComponent {
+    readonly: boolean;
+    formControl: FormControl;
+    config: InputComponentConfig;
+}
+
+export type InputComponentConfig = {
+    component: string;
+    [prop: string]: any;
+};

+ 2 - 2
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -322,7 +322,7 @@ export type ConfigArgDefinition = {
   list: Scalars['Boolean'];
   label?: Maybe<Scalars['String']>;
   description?: Maybe<Scalars['String']>;
-  config?: Maybe<Scalars['JSON']>;
+  ui?: Maybe<Scalars['JSON']>;
 };
 
 export type ConfigArgInput = {
@@ -6714,7 +6714,7 @@ export type ConfigurableOperationDefFragment = (
   & Pick<ConfigurableOperationDefinition, 'code' | 'description'>
   & { args: Array<(
     { __typename?: 'ConfigArgDefinition' }
-    & Pick<ConfigArgDefinition, 'name' | 'type' | 'list' | 'config'>
+    & Pick<ConfigArgDefinition, 'name' | 'type' | 'list' | 'ui'>
   )> }
 );
 

+ 42 - 0
packages/admin-ui/src/lib/core/src/common/utilities/configurable-operation-utils.ts

@@ -0,0 +1,42 @@
+import { ConfigArgType } from '@vendure/common/lib/shared-types';
+import { assertNever } from '@vendure/common/lib/shared-utils';
+
+import { ConfigArgDefinition } from '../generated-types';
+
+/**
+ * ConfigArg values are always stored as strings. If they are not primitives, then
+ * they are JSON-encoded. This function unwraps them back into their original
+ * data type.
+ */
+export function getConfigArgValue(value: any) {
+    try {
+        return value ? JSON.parse(value) : undefined;
+    } catch (e) {
+        return value;
+    }
+}
+
+/**
+ * Returns a default value based on the type of the config arg.
+ */
+export function getDefaultConfigArgValue(arg: ConfigArgDefinition): any {
+    return arg.list ? [] : getSingleValue(arg.type as ConfigArgType);
+}
+
+function getSingleValue(type: ConfigArgType): any {
+    switch (type) {
+        case 'boolean':
+            return 'false';
+        case 'int':
+        case 'float':
+            return '0';
+        case 'ID':
+            return '';
+        case 'string':
+            return '';
+        case 'datetime':
+            return new Date();
+        default:
+            assertNever(type);
+    }
+}

+ 0 - 20
packages/admin-ui/src/lib/core/src/common/utilities/get-default-config-arg-value.ts

@@ -1,20 +0,0 @@
-import { ConfigArgType } from '@vendure/common/lib/shared-types';
-import { assertNever } from '@vendure/common/lib/shared-utils';
-
-export function getDefaultConfigArgValue(type: ConfigArgType): any {
-    switch (type) {
-        case 'boolean':
-            return false;
-        case 'int':
-        case 'float':
-            return '0';
-        case 'ID':
-            return '';
-        case 'string':
-            return '';
-        case 'datetime':
-            return new Date();
-        default:
-            assertNever(type);
-    }
-}

+ 2 - 2
packages/admin-ui/src/lib/core/src/common/utilities/interpolate-description.ts

@@ -19,9 +19,9 @@ export function interpolateDescription(
         }
         let formatted = value;
         const argDef = operation.args.find(arg => arg.name === normalizedArgName);
-        if (argDef && argDef.type === 'int' && argDef.config && argDef.config.inputType === 'money') {
+        /*if (argDef && argDef.type === 'int' && argDef.config && argDef.config.inputType === 'money') {
             formatted = value / 100;
-        }
+        }*/
         if (argDef && argDef.type === 'datetime' && value instanceof Date) {
             formatted = value.toLocaleDateString();
         }

+ 26 - 2
packages/admin-ui/src/lib/core/src/core.module.ts

@@ -1,6 +1,6 @@
 import { PlatformLocation } from '@angular/common';
 import { HttpClient } from '@angular/common/http';
-import { NgModule } from '@angular/core';
+import { APP_INITIALIZER, NgModule } from '@angular/core';
 import { BrowserModule } from '@angular/platform-browser';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { TranslateCompiler, TranslateLoader, TranslateModule } from '@ngx-translate/core';
@@ -17,10 +17,18 @@ import { OverlayHostComponent } from './components/overlay-host/overlay-host.com
 import { UiLanguageSwitcherDialogComponent } from './components/ui-language-switcher-dialog/ui-language-switcher-dialog.component';
 import { UserMenuComponent } from './components/user-menu/user-menu.component';
 import { DataModule } from './data/data.module';
+import { ComponentRegistryService } from './providers/component-registry/component-registry.service';
 import { CustomHttpTranslationLoader } from './providers/i18n/custom-http-loader';
 import { InjectableTranslateMessageFormatCompiler } from './providers/i18n/custom-message-format-compiler';
 import { I18nService } from './providers/i18n/i18n.service';
 import { LocalStorageService } from './providers/local-storage/local-storage.service';
+import { BooleanFormInputComponent } from './shared/dynamic-form-inputs/boolean-form-input/boolean-form-input.component';
+import { CurrencyFormInputComponent } from './shared/dynamic-form-inputs/currency-form-input/currency-form-input.component';
+import { DateFormInputComponent } from './shared/dynamic-form-inputs/date-form-input/date-form-input.component';
+import { FacetValueFormInputComponent } from './shared/dynamic-form-inputs/facet-value-form-input/facet-value-form-input.component';
+import { NumberFormInputComponent } from './shared/dynamic-form-inputs/number-form-input/number-form-input.component';
+import { SelectFormInputComponent } from './shared/dynamic-form-inputs/select-form-input/select-form-input.component';
+import { TextFormInputComponent } from './shared/dynamic-form-inputs/text-form-input/text-form-input.component';
 import { SharedModule } from './shared/shared.module';
 
 @NgModule({
@@ -38,7 +46,23 @@ import { SharedModule } from './shared/shared.module';
             compiler: { provide: TranslateCompiler, useClass: InjectableTranslateMessageFormatCompiler },
         }),
     ],
-    providers: [{ provide: MESSAGE_FORMAT_CONFIG, useFactory: getLocales }],
+    providers: [
+        { provide: MESSAGE_FORMAT_CONFIG, useFactory: getLocales },
+        {
+            provide: APP_INITIALIZER,
+            multi: true,
+            useFactory: (registry: ComponentRegistryService) => () => {
+                registry.registerInputComponent('boolean-form-input', BooleanFormInputComponent);
+                registry.registerInputComponent('currency-form-input', CurrencyFormInputComponent);
+                registry.registerInputComponent('date-form-input', DateFormInputComponent);
+                registry.registerInputComponent('facet-value-form-input', FacetValueFormInputComponent);
+                registry.registerInputComponent('number-form-input', NumberFormInputComponent);
+                registry.registerInputComponent('select-form-input', SelectFormInputComponent);
+                registry.registerInputComponent('text-form-input', TextFormInputComponent);
+            },
+            deps: [ComponentRegistryService],
+        },
+    ],
     exports: [SharedModule, OverlayHostComponent],
     declarations: [
         AppShellComponent,

+ 1 - 1
packages/admin-ui/src/lib/core/src/data/definitions/shared-definitions.ts

@@ -16,7 +16,7 @@ export const CONFIGURABLE_OPERATION_DEF_FRAGMENT = gql`
             name
             type
             list
-            config
+            ui
         }
         code
         description

+ 16 - 0
packages/admin-ui/src/lib/core/src/providers/component-registry/component-registry.service.spec.ts

@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { ComponentRegistryService } from './component-registry.service';
+
+describe('ComponentRegistryService', () => {
+    let service: ComponentRegistryService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({});
+        service = TestBed.inject(ComponentRegistryService);
+    });
+
+    it('should be created', () => {
+        expect(service).toBeTruthy();
+    });
+});

+ 23 - 0
packages/admin-ui/src/lib/core/src/providers/component-registry/component-registry.service.ts

@@ -0,0 +1,23 @@
+import { Injectable, Type } from '@angular/core';
+
+import { FormInputComponent, InputComponentConfig } from '../../common/component-registry-types';
+
+@Injectable({
+    providedIn: 'root',
+})
+export class ComponentRegistryService {
+    private inputComponentMap = new Map<string, Type<FormInputComponent>>();
+
+    registerInputComponent(id: string, component: Type<FormInputComponent>) {
+        if (this.inputComponentMap.has(id)) {
+            throw new Error(
+                `Cannot register an InputComponent with the id "${id}", as one with that id already exists`,
+            );
+        }
+        this.inputComponentMap.set(id, component);
+    }
+
+    getInputComponent(id: string): Type<FormInputComponent> | undefined {
+        return this.inputComponentMap.get(id);
+    }
+}

+ 1 - 1
packages/admin-ui/src/lib/core/src/public_api.ts

@@ -13,7 +13,7 @@ export * from './common/introspection-result';
 export * from './common/language-translation-strings';
 export * from './common/utilities/create-updated-translatable';
 export * from './common/utilities/flatten-facet-values';
-export * from './common/utilities/get-default-config-arg-value';
+export * from './common/utilities/configurable-operation-utils';
 export * from './common/utilities/get-default-ui-language';
 export * from './common/utilities/interpolate-description';
 export * from './common/utilities/string-to-color';

+ 4 - 56
packages/admin-ui/src/lib/core/src/shared/components/configurable-input/configurable-input.component.html

@@ -5,64 +5,12 @@
             <div *ngFor="let arg of operation.args" class="arg-row">
                 <ng-container *ngIf="form.get(arg.name)">
                     <label>{{ arg.name | sentenceCase }}</label>
-                    <clr-checkbox-wrapper *ngIf="getArgType(arg) === 'boolean'">
-                        <input
-                            type="checkbox"
-                            clrCheckbox
-                            [formControlName]="arg.name"
-                            [id]="arg.name"
-                            [vdrDisabled]="readonly"
-                        />
-                    </clr-checkbox-wrapper>
-                    <input
-                        *ngIf="isIntInput(arg)"
-                        [name]="arg.name"
-                        type="number"
-                        step="1"
+                    <vdr-dynamic-form-input
+                        [def]="getArgDef(arg)"
                         [readonly]="readonly"
+                        [control]="form.get(arg.name)"
                         [formControlName]="arg.name"
-                    />
-                    <input
-                        *ngIf="isStringWithoutOptions(arg)"
-                        [name]="arg.name"
-                        type="text"
-                        [readonly]="readonly"
-                        [formControlName]="arg.name"
-                    />
-                    <input
-                        *ngIf="getArgType(arg) === 'datetime'"
-                        [name]="arg.name"
-                        type="date"
-                        [readonly]="readonly"
-                        [formControlName]="arg.name"
-                    />
-                    <vdr-currency-input
-                        *ngIf="isMoneyInput(arg)"
-                        [formControlName]="arg.name"
-                        [readonly]="readonly"
-                        [currencyCode]="activeChannel?.currencyCode"
-                    ></vdr-currency-input>
-                    <vdr-percentage-suffix-input
-                        *ngIf="isPercentageInput(arg)"
-                        [readonly]="readonly"
-                        [formControlName]="arg.name"
-                    ></vdr-percentage-suffix-input>
-                    <vdr-facet-value-selector
-                        [readonly]="readonly"
-                        [facets]="facets"
-                        [formControlName]="arg.name"
-                        *ngIf="getArgType(arg) === 'facetValueIds' && facets"
-                    ></vdr-facet-value-selector>
-                    <select
-                        clrSelect
-                        [formControlName]="arg.name"
-                        *ngIf="isStringWithOptions(arg)"
-                        [vdrDisabled]="readonly"
-                    >
-                        <option *ngFor="let option of getStringOptions(arg)" [value]="option.value">
-                            {{ option.label || option.value }}
-                        </option>
-                    </select>
+                    ></vdr-dynamic-form-input>
                 </ng-container>
             </div>
         </form>

+ 8 - 66
packages/admin-ui/src/lib/core/src/shared/components/configurable-input/configurable-input.component.ts

@@ -21,17 +21,17 @@ import {
     Validators,
 } from '@angular/forms';
 import { ConfigArgType } from '@vendure/common/lib/shared-types';
+import { assertNever } from '@vendure/common/lib/shared-utils';
 import { Subscription } from 'rxjs';
 
+import { InputComponentConfig } from '../../../common/component-registry-types';
 import {
     ConfigArg,
+    ConfigArgDefinition,
     ConfigurableOperation,
     ConfigurableOperationDefinition,
-    FacetWithValues,
-    GetActiveChannel,
-    StringFieldOption,
 } from '../../../common/generated-types';
-import { getDefaultConfigArgValue } from '../../../common/utilities/get-default-config-arg-value';
+import { getDefaultConfigArgValue } from '../../../common/utilities/configurable-operation-utils';
 import { interpolateDescription } from '../../../common/utilities/interpolate-description';
 
 /**
@@ -58,8 +58,6 @@ import { interpolateDescription } from '../../../common/utilities/interpolate-de
 export class ConfigurableInputComponent implements OnChanges, OnDestroy, ControlValueAccessor, Validator {
     @Input() operation?: ConfigurableOperation;
     @Input() operationDefinition?: ConfigurableOperationDefinition;
-    @Input() facets: FacetWithValues.Fragment[] = [];
-    @Input() activeChannel: GetActiveChannel.ActiveChannel;
     @Input() readonly = false;
     @Output() remove = new EventEmitter<ConfigurableOperation>();
     argValues: { [name: string]: any } = {};
@@ -110,61 +108,9 @@ export class ConfigurableInputComponent implements OnChanges, OnDestroy, Control
         }
     }
 
-    isIntInput(arg: ConfigArg): boolean {
-        if (this.getArgType(arg) === 'int') {
-            const config = this.getArgConfig(arg);
-            return !!(!config || config.inputType === 'default');
-        }
-        return false;
-    }
-
-    isMoneyInput(arg: ConfigArg): boolean {
-        if (this.getArgType(arg) === 'int') {
-            const config = this.getArgConfig(arg);
-            return !!(config && config.inputType === 'money');
-        }
-        return false;
-    }
-
-    isPercentageInput(arg: ConfigArg): boolean {
-        if (this.getArgType(arg) === 'int') {
-            const config = this.getArgConfig(arg);
-            return !!(config && config.inputType === 'percentage');
-        }
-        return false;
-    }
-
-    isStringWithOptions(arg: ConfigArg): boolean {
-        if (this.getArgType(arg) === 'string') {
-            return 0 < this.getStringOptions(arg).length;
-        }
-        return false;
-    }
-
-    isStringWithoutOptions(arg: ConfigArg): boolean {
-        if (this.getArgType(arg) === 'string') {
-            return this.getStringOptions(arg).length === 0;
-        }
-        return false;
-    }
-
-    getStringOptions(arg: ConfigArg): StringFieldOption[] {
-        if (this.getArgType(arg) === 'string') {
-            const config = this.getArgConfig(arg);
-            return (config && config.options) || [];
-        }
-        return [];
-    }
-
-    getArgType(arg: ConfigArg): ConfigArgType | undefined {
-        return this.operationDefinition?.args.find(argDef => argDef.name === arg.name)?.type as ConfigArgType;
-    }
-
-    private getArgConfig(arg: ConfigArg): Record<string, any> | undefined {
-        if (this.operationDefinition) {
-            const match = this.operationDefinition.args.find(argDef => argDef.name === arg.name);
-            return match && match.config;
-        }
+    getArgDef(arg: ConfigArg): ConfigArgDefinition | undefined {
+        const argDef = this.operationDefinition?.args.find(a => a.name === arg.name);
+        return argDef;
     }
 
     private createForm() {
@@ -180,11 +126,7 @@ export class ConfigurableInputComponent implements OnChanges, OnDestroy, Control
             for (const arg of this.operationDefinition?.args || []) {
                 let value: any = this.operation.args.find(a => a.name === arg.name)?.value;
                 if (value === undefined) {
-                    value = getDefaultConfigArgValue(arg.type as ConfigArgType);
-                } else {
-                    if (arg.type === 'boolean') {
-                        value = value === 'true';
-                    }
+                    value = getDefaultConfigArgValue(arg);
                 }
                 this.form.addControl(arg.name, new FormControl(value, Validators.required));
             }

+ 11 - 5
packages/admin-ui/src/lib/core/src/shared/components/facet-value-selector/facet-value-selector.component.ts

@@ -56,9 +56,9 @@ export class FacetValueSelectorComponent implements OnInit, ControlValueAccessor
         if (this.readonly) {
             return;
         }
-        this.selectedValuesChange.emit(selected.map((s) => s.value));
+        this.selectedValuesChange.emit(selected.map(s => s.value));
         if (this.onChangeFn) {
-            this.onChangeFn(JSON.stringify(selected.map((s) => s.id)));
+            this.onChangeFn(JSON.stringify(selected.map(s => s.id)));
         }
     }
 
@@ -78,7 +78,7 @@ export class FacetValueSelectorComponent implements OnInit, ControlValueAccessor
         this.ngSelect.focus();
     }
 
-    writeValue(obj: string | FacetValue.Fragment[] | null): void {
+    writeValue(obj: string | FacetValue.Fragment[] | Array<string | number> | null): void {
         if (typeof obj === 'string') {
             try {
                 const facetIds = JSON.parse(obj) as string[];
@@ -87,8 +87,14 @@ export class FacetValueSelectorComponent implements OnInit, ControlValueAccessor
                 // TODO: log error
                 throw err;
             }
-        } else if (obj) {
-            this.value = obj.map((fv) => fv.id);
+        } else if (Array.isArray(obj)) {
+            const isIdArray = (input: unknown[]): input is Array<string | number> =>
+                input.every(i => typeof i === 'number' || typeof i === 'string');
+            if (isIdArray(obj)) {
+                this.value = obj.map(fv => fv.toString());
+            } else {
+                this.value = obj.map(fv => fv.id);
+            }
         }
     }
 

+ 8 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/boolean-form-input/boolean-form-input.component.html

@@ -0,0 +1,8 @@
+<clr-checkbox-wrapper>
+    <input
+        type="checkbox"
+        clrCheckbox
+        [formControl]="formControl"
+        [vdrDisabled]="readonly"
+    />
+</clr-checkbox-wrapper>

+ 0 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/boolean-form-input/boolean-form-input.component.scss


+ 16 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/boolean-form-input/boolean-form-input.component.ts

@@ -0,0 +1,16 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { FormControl } from '@angular/forms';
+
+import { FormInputComponent, InputComponentConfig } from '../../../common/component-registry-types';
+
+@Component({
+    selector: 'vdr-boolean-form-input',
+    templateUrl: './boolean-form-input.component.html',
+    styleUrls: ['./boolean-form-input.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class BooleanFormInputComponent implements FormInputComponent {
+    readonly: boolean;
+    formControl: FormControl;
+    config: InputComponentConfig;
+}

+ 5 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/currency-form-input/currency-form-input.component.html

@@ -0,0 +1,5 @@
+<vdr-currency-input
+    [formControl]="formControl"
+    [readonly]="readonly"
+    [currencyCode]="currencyCode$ | async"
+></vdr-currency-input>

+ 0 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/currency-form-input/currency-form-input.component.scss


+ 26 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/currency-form-input/currency-form-input.component.ts

@@ -0,0 +1,26 @@
+import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
+import { FormControl } from '@angular/forms';
+import { Observable } from 'rxjs';
+
+import { FormInputComponent, InputComponentConfig } from '../../../common/component-registry-types';
+import { CurrencyCode } from '../../../common/generated-types';
+import { DataService } from '../../../data/providers/data.service';
+
+@Component({
+    selector: 'vdr-currency-form-input',
+    templateUrl: './currency-form-input.component.html',
+    styleUrls: ['./currency-form-input.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class CurrencyFormInputComponent implements FormInputComponent {
+    @Input() readonly: boolean;
+    formControl: FormControl;
+    currencyCode$: Observable<CurrencyCode>;
+    config: InputComponentConfig;
+
+    constructor(private dataService: DataService) {
+        this.currencyCode$ = this.dataService.settings
+            .getActiveChannel()
+            .mapStream(data => data.activeChannel.currencyCode);
+    }
+}

+ 7 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/date-form-input/date-form-input.component.html

@@ -0,0 +1,7 @@
+<vdr-datetime-picker
+    [formControl]="formControl"
+    [min]="config.min"
+    [max]="config.max"
+    [readonly]="readonly"
+>
+</vdr-datetime-picker>

+ 0 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/date-form-input/date-form-input.component.scss


+ 20 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/date-form-input/date-form-input.component.ts

@@ -0,0 +1,20 @@
+import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
+import { FormControl } from '@angular/forms';
+
+import { FormInputComponent } from '../../../common/component-registry-types';
+
+@Component({
+    selector: 'vdr-date-form-input',
+    templateUrl: './date-form-input.component.html',
+    styleUrls: ['./date-form-input.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class DateFormInputComponent implements FormInputComponent {
+    @Input() readonly: boolean;
+    formControl: FormControl;
+    config: {
+        component: string;
+        min: string;
+        max: string;
+    };
+}

+ 1 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.html

@@ -0,0 +1 @@
+<ng-container #outlet></ng-container>

+ 0 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.scss


+ 113 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.ts

@@ -0,0 +1,113 @@
+import {
+    AfterViewInit,
+    ChangeDetectionStrategy,
+    ChangeDetectorRef,
+    Component,
+    ComponentFactoryResolver,
+    ComponentRef,
+    forwardRef,
+    Input,
+    OnChanges,
+    SimpleChanges,
+    ViewChild,
+    ViewContainerRef,
+} from '@angular/core';
+import { ControlValueAccessor, FormControl, FormControlName, NG_VALUE_ACCESSOR } from '@angular/forms';
+import { ConfigArgType } from '@vendure/common/lib/shared-types';
+import { assertNever } from '@vendure/common/lib/shared-utils';
+import { simpleDeepClone } from '@vendure/common/lib/simple-deep-clone';
+
+import { FormInputComponent, InputComponentConfig } from '../../../common/component-registry-types';
+import { ConfigArg, ConfigArgDefinition } from '../../../common/generated-types';
+import { ComponentRegistryService } from '../../../providers/component-registry/component-registry.service';
+
+@Component({
+    selector: 'vdr-dynamic-form-input',
+    templateUrl: './dynamic-form-input.component.html',
+    styleUrls: ['./dynamic-form-input.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+    providers: [
+        {
+            provide: NG_VALUE_ACCESSOR,
+            useExisting: DynamicFormInputComponent,
+            multi: true,
+        },
+    ],
+})
+export class DynamicFormInputComponent implements OnChanges, AfterViewInit, ControlValueAccessor {
+    @Input() def: ConfigArgDefinition;
+    @Input() readonly: boolean;
+    @Input() control: FormControl;
+    @ViewChild('outlet', { read: ViewContainerRef }) viewContainer: ViewContainerRef;
+    private onChange: (val: any) => void;
+    private onTouch: () => void;
+    private componentRef: ComponentRef<FormInputComponent>;
+
+    constructor(
+        private componentRegistryService: ComponentRegistryService,
+        private componentFactoryResolver: ComponentFactoryResolver,
+        private changeDetectorRef: ChangeDetectorRef,
+    ) {}
+
+    ngAfterViewInit() {
+        const componentType = this.componentRegistryService.getInputComponent(
+            this.getInputComponentConfig(this.def).component,
+        );
+        if (componentType) {
+            const factory = this.componentFactoryResolver.resolveComponentFactory(componentType);
+            const componentRef = this.viewContainer.createComponent(factory);
+            const { instance } = componentRef;
+            this.componentRef = componentRef;
+            instance.config = simpleDeepClone(this.def.ui);
+            instance.formControl = this.control;
+            instance.readonly = this.readonly;
+        }
+        setTimeout(() => this.changeDetectorRef.markForCheck());
+    }
+
+    ngOnChanges(changes: SimpleChanges) {
+        if (this.componentRef) {
+            if ('config' in changes) {
+                this.componentRef.instance.config = this.def.ui;
+            }
+            if ('readonly' in changes) {
+                this.componentRef.instance.readonly = this.readonly;
+            }
+            this.componentRef.injector.get(ChangeDetectorRef).markForCheck();
+        }
+    }
+
+    registerOnChange(fn: any): void {
+        this.onChange = fn;
+    }
+
+    registerOnTouched(fn: any): void {
+        this.onTouch = fn;
+    }
+
+    writeValue(obj: any): void {
+        /* empty */
+    }
+
+    private getInputComponentConfig(argDef: ConfigArgDefinition): InputComponentConfig {
+        if (argDef?.ui?.component) {
+            return argDef.ui;
+        }
+        const type = argDef?.type as ConfigArgType;
+        switch (type) {
+            case 'string':
+                return { component: 'text-form-input' };
+            case 'int':
+            case 'float':
+                return { component: 'number-form-input' };
+            case 'boolean':
+                return { component: 'boolean-form-input' };
+            case 'datetime':
+                return { component: 'date-form-input' };
+            case 'ID':
+                return { component: 'string-form-input' };
+            default:
+                assertNever(type);
+        }
+    }
+}

+ 6 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/facet-value-form-input/facet-value-form-input.component.html

@@ -0,0 +1,6 @@
+<vdr-facet-value-selector
+    *ngIf="facets$ | async as facets"
+    [readonly]="readonly"
+    [facets]="facets"
+    [formControl]="formControl"
+></vdr-facet-value-selector>

+ 0 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/facet-value-form-input/facet-value-form-input.component.scss


+ 28 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/facet-value-form-input/facet-value-form-input.component.ts

@@ -0,0 +1,28 @@
+import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
+import { FormControl } from '@angular/forms';
+import { DataService, FacetWithValues } from '@vendure/admin-ui/core';
+import { Observable } from 'rxjs';
+import { shareReplay } from 'rxjs/operators';
+
+import { FormInputComponent, InputComponentConfig } from '../../../common/component-registry-types';
+
+@Component({
+    selector: 'vdr-facet-value-form-input',
+    templateUrl: './facet-value-form-input.component.html',
+    styleUrls: ['./facet-value-form-input.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class FacetValueFormInputComponent implements FormInputComponent, OnInit {
+    readonly: boolean;
+    formControl: FormControl;
+    facets$: Observable<FacetWithValues.Fragment[]>;
+    config: InputComponentConfig;
+    constructor(private dataService: DataService) {}
+
+    ngOnInit() {
+        this.facets$ = this.dataService.facet
+            .getAllFacets()
+            .mapSingle(data => data.facets.items)
+            .pipe(shareReplay(1));
+    }
+}

+ 3 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/number-form-input/number-form-input.component.html

@@ -0,0 +1,3 @@
+<vdr-affixed-input [suffix]="config?.suffix" [prefix]="config?.prefix">
+    <input type="number" [readonly]="readonly" [formControl]="formControl" />
+</vdr-affixed-input>

+ 0 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/number-form-input/number-form-input.component.scss


+ 20 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/number-form-input/number-form-input.component.ts

@@ -0,0 +1,20 @@
+import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
+import { FormControl } from '@angular/forms';
+
+import { FormInputComponent } from '../../../common/component-registry-types';
+
+@Component({
+    selector: 'vdr-number-form-input',
+    templateUrl: './number-form-input.component.html',
+    styleUrls: ['./number-form-input.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class NumberFormInputComponent implements FormInputComponent {
+    @Input() readonly: boolean;
+    formControl: FormControl;
+    config: {
+        component: string;
+        prefix?: string;
+        suffix?: string;
+    };
+}

+ 5 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/select-form-input/select-form-input.component.html

@@ -0,0 +1,5 @@
+<select clrSelect [formControl]="formControl" [vdrDisabled]="readonly">
+    <option *ngFor="let option of config.options" [value]="option.value">
+        {{ option.label || option.value }}
+    </option>
+</select>

+ 0 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/select-form-input/select-form-input.component.scss


+ 19 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/select-form-input/select-form-input.component.ts

@@ -0,0 +1,19 @@
+import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
+import { FormControl } from '@angular/forms';
+
+import { FormInputComponent } from '../../../common/component-registry-types';
+
+@Component({
+    selector: 'vdr-select-form-input',
+    templateUrl: './select-form-input.component.html',
+    styleUrls: ['./select-form-input.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class SelectFormInputComponent implements FormInputComponent {
+    @Input() readonly: boolean;
+    formControl: FormControl;
+    config: {
+        component: string;
+        options: Array<{ value: string; label?: string }>;
+    };
+}

+ 5 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/text-form-input/text-form-input.component.html

@@ -0,0 +1,5 @@
+<input
+    type="text"
+    [readonly]="readonly"
+    [formControl]="formControl"
+/>

+ 0 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/text-form-input/text-form-input.component.scss


+ 16 - 0
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/text-form-input/text-form-input.component.ts

@@ -0,0 +1,16 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { FormControl } from '@angular/forms';
+
+import { FormInputComponent, InputComponentConfig } from '../../../common/component-registry-types';
+
+@Component({
+    selector: 'vdr-text-form-input',
+    templateUrl: './text-form-input.component.html',
+    styleUrls: ['./text-form-input.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class TextFormInputComponent implements FormInputComponent {
+    readonly: boolean;
+    formControl: FormControl;
+    config: InputComponentConfig;
+}

+ 22 - 3
packages/admin-ui/src/lib/core/src/shared/shared.module.ts

@@ -75,6 +75,14 @@ import { DisabledDirective } from './directives/disabled.directive';
 import { IfDefaultChannelActiveDirective } from './directives/if-default-channel-active.directive';
 import { IfMultichannelDirective } from './directives/if-multichannel.directive';
 import { IfPermissionsDirective } from './directives/if-permissions.directive';
+import { BooleanFormInputComponent } from './dynamic-form-inputs/boolean-form-input/boolean-form-input.component';
+import { CurrencyFormInputComponent } from './dynamic-form-inputs/currency-form-input/currency-form-input.component';
+import { DateFormInputComponent } from './dynamic-form-inputs/date-form-input/date-form-input.component';
+import { DynamicFormInputComponent } from './dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component';
+import { FacetValueFormInputComponent } from './dynamic-form-inputs/facet-value-form-input/facet-value-form-input.component';
+import { NumberFormInputComponent } from './dynamic-form-inputs/number-form-input/number-form-input.component';
+import { SelectFormInputComponent } from './dynamic-form-inputs/select-form-input/select-form-input.component';
+import { TextFormInputComponent } from './dynamic-form-inputs/text-form-input/text-form-input.component';
 import { AssetPreviewPipe } from './pipes/asset-preview.pipe';
 import { ChannelLabelPipe } from './pipes/channel-label.pipe';
 import { CurrencyNamePipe } from './pipes/currency-name.pipe';
@@ -176,10 +184,21 @@ const DECLARATIONS = [
     OrderStateI18nTokenPipe,
 ];
 
+const DYNAMIC_FORM_INPUTS = [
+    TextFormInputComponent,
+    NumberFormInputComponent,
+    DateFormInputComponent,
+    CurrencyFormInputComponent,
+    BooleanFormInputComponent,
+    SelectFormInputComponent,
+    FacetValueFormInputComponent,
+    DynamicFormInputComponent,
+];
+
 @NgModule({
-    imports: IMPORTS,
-    exports: [...IMPORTS, ...DECLARATIONS],
-    declarations: DECLARATIONS,
+    imports: [IMPORTS],
+    exports: [...IMPORTS, ...DECLARATIONS, ...DYNAMIC_FORM_INPUTS],
+    declarations: [...DECLARATIONS, ...DYNAMIC_FORM_INPUTS],
     providers: [
         // This needs to be shared, since lazy-loaded
         // modules have their own entryComponents which

+ 0 - 4
packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.html

@@ -72,12 +72,10 @@
             <ng-container *ngFor="let condition of conditions; index as i">
                 <vdr-configurable-input
                     (remove)="removeCondition($event)"
-                    [facets]="facets$ | async"
                     [readonly]="!('UpdatePromotion' | hasPermission)"
                     [operation]="condition"
                     [operationDefinition]="getConditionDefinition(condition)"
                     [formControlName]="i"
-                    [activeChannel]="activeChannel$ | async"
                 ></vdr-configurable-input>
             </ng-container>
 
@@ -105,12 +103,10 @@
             <vdr-configurable-input
                 *ngFor="let action of actions; index as i"
                 (remove)="removeAction($event)"
-                [facets]="facets$ | async"
                 [operation]="action"
                 [readonly]="!('UpdatePromotion' | hasPermission)"
                 [operationDefinition]="getActionDefinition(action)"
                 [formControlName]="i"
-                [activeChannel]="activeChannel$ | async"
             ></vdr-configurable-input>
             <div>
                 <vdr-dropdown *vdrIfPermissions="'UpdatePromotion'">

+ 3 - 19
packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.ts

@@ -9,8 +9,6 @@ import {
     ConfigurableOperationInput,
     CreatePromotionInput,
     DataService,
-    FacetWithValues,
-    GetActiveChannel,
     getDefaultConfigArgValue,
     LanguageCode,
     NotificationService,
@@ -18,9 +16,8 @@ import {
     ServerConfigService,
     UpdatePromotionInput,
 } from '@vendure/admin-ui/core';
-import { ConfigArgType } from '@vendure/common/lib/shared-types';
 import { Observable } from 'rxjs';
-import { mergeMap, shareReplay, take } from 'rxjs/operators';
+import { mergeMap, take } from 'rxjs/operators';
 
 @Component({
     selector: 'vdr-promotion-detail',
@@ -34,8 +31,6 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
     detailForm: FormGroup;
     conditions: ConfigurableOperation[] = [];
     actions: ConfigurableOperation[] = [];
-    facets$: Observable<FacetWithValues.Fragment[]>;
-    activeChannel$: Observable<GetActiveChannel.ActiveChannel>;
 
     private allConditions: ConfigurableOperationDefinition[] = [];
     private allActions: ConfigurableOperationDefinition[] = [];
@@ -64,23 +59,12 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
 
     ngOnInit() {
         this.init();
-        this.facets$ = this.dataService.facet
-            .getAllFacets()
-            .mapSingle(data => data.facets.items)
-            .pipe(shareReplay(1));
-
         this.promotion$ = this.entity$;
         this.dataService.promotion.getPromotionActionsAndConditions().single$.subscribe(data => {
             this.allActions = data.promotionActions;
             this.allConditions = data.promotionConditions;
+            this.changeDetector.markForCheck();
         });
-        this.activeChannel$ = this.dataService.settings
-            .getActiveChannel()
-            .mapStream(data => data.activeChannel);
-
-        // When creating a new Promotion, the initial bindings do not work
-        // unless explicitly re-running the change detector. Don't know why.
-        setTimeout(() => this.changeDetector.markForCheck(), 0);
     }
 
     ngOnDestroy() {
@@ -279,7 +263,7 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
         if (def) {
             const argDef = def.args.find(a => a.name === argName);
             if (argDef) {
-                return getDefaultConfigArgValue(argDef.type as ConfigArgType);
+                return getDefaultConfigArgValue(argDef);
             }
         }
         throw new Error(`Could not determine default value for "argName"`);

+ 13 - 2
packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.html

@@ -54,8 +54,8 @@
         >
             <div class="clr-col">
                 <label>{{ 'settings.payment-method-config-options' | translate }}</label>
-                <section class="form-block" *ngFor="let arg of paymentMethod.configArgs">
-                    <vdr-form-field [label]="arg.name" [for]="arg.name" *ngIf="getType(paymentMethod, arg.name) === 'string'">
+                <section class="form-block" *ngFor="let arg of paymentMethod.configArgs; index as i">
+                    <!--<vdr-form-field [label]="arg.name" [for]="arg.name" *ngIf="getType(paymentMethod, arg.name) === 'string'">
                         <input
                             [id]="arg.name"
                             type="text"
@@ -79,6 +79,17 @@
                             [formControlName]="arg.name"
                             clrCheckbox
                         />
+                    </vdr-form-field>-->
+                    <vdr-form-field
+                        [label]="arg.name"
+                        [for]="arg.name"
+                    >
+                        <vdr-dynamic-form-input
+                            [def]="paymentMethod.definition.args[i]"
+                            [formControlName]="arg.name"
+                            [control]="detailForm.get(['configArgs', arg.name])"
+                            [readonly]="!('UpdateSettings' | hasPermission)"
+                        ></vdr-dynamic-form-input>
                     </vdr-form-field>
                 </section>
             </div>

+ 14 - 25
packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.ts

@@ -2,12 +2,16 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnIni
 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
-import { BaseDetailComponent } from '@vendure/admin-ui/core';
-import { ConfigArg, PaymentMethod, UpdatePaymentMethodInput } from '@vendure/admin-ui/core';
-import { NotificationService } from '@vendure/admin-ui/core';
-import { DataService } from '@vendure/admin-ui/core';
-import { ServerConfigService } from '@vendure/admin-ui/core';
-import { ConfigArgSubset, ConfigArgType } from '@vendure/common/lib/shared-types';
+import {
+    BaseDetailComponent,
+    DataService,
+    getConfigArgValue,
+    NotificationService,
+    PaymentMethod,
+    ServerConfigService,
+    UpdatePaymentMethodInput,
+} from '@vendure/admin-ui/core';
+import { ConfigArgType } from '@vendure/common/lib/shared-types';
 import { mergeMap, take } from 'rxjs/operators';
 
 @Component({
@@ -45,11 +49,8 @@ export class PaymentMethodDetailComponent extends BaseDetailComponent<PaymentMet
         this.destroy();
     }
 
-    getType(
-        paymentMethod: PaymentMethod.Fragment,
-        argName: string,
-    ): ConfigArgSubset<'int' | 'string' | 'boolean'> | undefined {
-        return paymentMethod.definition.args.find(a => a.name === argName)?.type as ConfigArgSubset<any>;
+    getType(paymentMethod: PaymentMethod.Fragment, argName: string): ConfigArgType | undefined {
+        return paymentMethod.definition.args.find(a => a.name === argName)?.type as ConfigArgType;
     }
 
     configArgsIsPopulated(): boolean {
@@ -104,27 +105,15 @@ export class PaymentMethodDetailComponent extends BaseDetailComponent<PaymentMet
             for (const arg of paymentMethod.configArgs) {
                 const control = configArgsGroup.get(arg.name);
                 if (control) {
-                    control.patchValue(this.parseArgValue(paymentMethod, arg));
+                    control.patchValue(getConfigArgValue(arg.value));
                 } else {
                     configArgsGroup.addControl(
                         arg.name,
-                        this.formBuilder.control(this.parseArgValue(paymentMethod, arg)),
+                        this.formBuilder.control(getConfigArgValue(arg.value)),
                     );
                 }
             }
         }
         this.changeDetector.markForCheck();
     }
-
-    private parseArgValue(paymentMethod: PaymentMethod.Fragment, arg: ConfigArg): string | number | boolean {
-        const type = paymentMethod.definition.args.find(a => (a.name = arg.name))?.type;
-        switch (type) {
-            case 'int':
-                return Number.parseInt(arg.value || '0', 10);
-            case 'boolean':
-                return arg.value === 'false' ? false : true;
-            default:
-                return arg.value || '';
-        }
-    }
 }

+ 2 - 4
packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.html

@@ -44,11 +44,10 @@
         <div class="clr-col">
             <label class="clr-control-label">{{ 'settings.shipping-eligibility-checker' | translate }}</label>
             <vdr-configurable-input
-                *ngIf="selectedChecker"
+                *ngIf="selectedChecker && selectedCheckerDefinition"
                 [operation]="selectedChecker"
                 [operationDefinition]="selectedCheckerDefinition"
                 [readonly]="!('UpdateSettings' | hasPermission)"
-                [activeChannel]="activeChannel$ | async"
                 (remove)="selectedChecker = null"
                 formControlName="checker"
             ></vdr-configurable-input>
@@ -74,11 +73,10 @@
         <div class="clr-col">
             <label class="clr-control-label">{{ 'settings.shipping-calculator' | translate }}</label>
             <vdr-configurable-input
-                *ngIf="selectedCalculator"
+                *ngIf="selectedCalculator && selectedCalculatorDefinition"
                 [operation]="selectedCalculator"
                 [operationDefinition]="selectedCalculatorDefinition"
                 [readonly]="!('UpdateSettings' | hasPermission)"
-                [activeChannel]="activeChannel$ | async"
                 (remove)="selectedCalculator = null"
                 formControlName="calculator"
             ></vdr-configurable-input>

+ 1 - 1
packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.ts

@@ -157,7 +157,7 @@ export class ShippingMethodDetailComponent extends BaseDetailComponent<ShippingM
             args: def.args.map(arg => {
                 return {
                     ...arg,
-                    value: getDefaultConfigArgValue(arg.type as ConfigArgType),
+                    value: getDefaultConfigArgValue(arg),
                 };
             }),
         } as ConfigurableOperation;