瀏覽代碼

fix(admin-ui): Styling improvements to custom field relation controls

Michael Bromley 2 年之前
父節點
當前提交
fb8aca618a
共有 21 個文件被更改,包括 80 次插入40 次删除
  1. 2 2
      packages/admin-ui/src/lib/core/src/common/component-registry-types.ts
  2. 2 0
      packages/admin-ui/src/lib/core/src/shared/components/tabbed-custom-fields/tabbed-custom-fields.component.ts
  3. 1 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/customer-group-form-input/customer-group-form-input.component.html
  4. 10 3
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/customer-group-form-input/customer-group-form-input.component.ts
  5. 5 3
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.html
  6. 4 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.scss
  7. 7 4
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.ts
  8. 2 2
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/product-multi-selector-form-input/product-multi-selector-form-input.component.ts
  9. 1 1
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/product-selector-form-input/product-selector-form-input.component.html
  10. 3 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/product-selector-form-input/product-selector-form-input.component.scss
  11. 14 8
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/product-selector-form-input/product-selector-form-input.component.ts
  12. 1 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/relation-form-input/asset/relation-asset-input.component.scss
  13. 1 5
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/relation-form-input/asset/relation-asset-input.component.ts
  14. 0 2
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/relation-form-input/customer/relation-customer-input.component.ts
  15. 11 6
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/relation-form-input/product-variant/relation-product-variant-input.component.html
  16. 5 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/relation-form-input/product-variant/relation-product-variant-input.component.scss
  17. 3 1
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/relation-form-input/product/relation-product-input.component.html
  18. 6 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/relation-form-input/product/relation-product-input.component.scss
  19. 1 1
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/relation-form-input/relation-card/relation-card.component.html
  20. 1 0
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/relation-form-input/relation-form-input.component.scss
  21. 0 2
      packages/common/src/shared-types.ts

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

@@ -1,4 +1,4 @@
-import { UntypedFormControl } from '@angular/forms';
+import { FormControl } from '@angular/forms';
 
 /**
  * @description
@@ -30,7 +30,7 @@ export interface FormInputComponent<C = InputComponentConfig> {
      *
      * Full documentation can be found in the [Angular docs](https://angular.io/api/forms/FormControl).
      */
-    formControl: UntypedFormControl;
+    formControl: FormControl;
     /**
      * @description
      * The `config` property contains the full configuration object of the custom field or configurable argument.

+ 2 - 0
packages/admin-ui/src/lib/core/src/shared/components/tabbed-custom-fields/tabbed-custom-fields.component.ts

@@ -40,10 +40,12 @@ export class TabbedCustomFieldsComponent implements OnInit {
             'password-form-input',
             'select-form-input',
             'text-form-input',
+            'relation-form-input',
         ];
         return (
             customField.type === 'text' ||
             customField.type === 'localeText' ||
+            customField.type === 'relation' ||
             (customField.ui?.component && !smallComponents.includes(customField.ui?.component))
         );
     }

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

@@ -4,6 +4,7 @@
     [addTag]="false"
     [multiple]="false"
     bindValue="id"
+    [compareWith]="compareWith"
     [clearable]="true"
     [searchable]="false"
     [ngModel]="formControl.value"

+ 10 - 3
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/customer-group-form-input/customer-group-form-input.component.ts

@@ -1,5 +1,5 @@
 import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
-import { UntypedFormControl } from '@angular/forms';
+import { FormControl, UntypedFormControl } from '@angular/forms';
 import { DefaultFormComponentConfig, DefaultFormComponentId } from '@vendure/common/lib/shared-types';
 import { Observable } from 'rxjs';
 import { startWith } from 'rxjs/operators';
@@ -26,7 +26,7 @@ import { DataService } from '../../../data/providers/data.service';
 export class CustomerGroupFormInputComponent implements FormInputComponent, OnInit {
     static readonly id: DefaultFormComponentId = 'customer-group-form-input';
     @Input() readonly: boolean;
-    formControl: UntypedFormControl;
+    formControl: FormControl<string | { id: string }>;
     customerGroups$: Observable<GetCustomerGroupsQuery['customerGroups']['items']>;
     config: DefaultFormComponentConfig<'customer-group-form-input'>;
 
@@ -42,6 +42,13 @@ export class CustomerGroupFormInputComponent implements FormInputComponent, OnIn
     }
 
     selectGroup(group: ItemOf<GetCustomerGroupsQuery, 'customerGroups'>) {
-        this.formControl.setValue(group.id);
+        this.formControl.setValue(group ?? undefined);
+    }
+
+    compareWith(
+        o1: ItemOf<GetCustomerGroupsQuery, 'customerGroups'>,
+        o2: ItemOf<GetCustomerGroupsQuery, 'customerGroups'>,
+    ) {
+        return o1.id === o2.id;
     }
 }

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

@@ -8,17 +8,19 @@
             *ngFor="let item of listItems; trackBy: trackById"
             cdkDrag
             [cdkDragData]="item"
+            [cdkDragLockAxis]="'y'"
         >
-            <ng-container #listItem></ng-container>
+            <div class="flex-spacer pr-2">
+                <ng-container #listItem></ng-container>
+            </div>
             <button
-                class="btn btn-link btn-sm btn-warning"
+                class="button-small"
                 *ngIf="!readonly"
                 (click)="removeListItem(item)"
                 [title]="'common.remove-item-from-list' | translate"
             >
                 <clr-icon shape="times"></clr-icon>
             </button>
-            <div class="flex-spacer"></div>
             <div class="drag-handle" cdkDragHandle [class.hidden]="readonly">
                 <clr-icon shape="drag-handle" size="24"></clr-icon>
             </div>

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

@@ -30,6 +30,10 @@
         0 3px 14px 2px rgba(0, 0, 0, 0.12);
 }
 
+.drag-handle {
+    cursor: move;
+}
+
 .drag-handle.hidden {
     display: none;
 }

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

@@ -21,13 +21,15 @@ import {
 } from '@angular/core';
 import {
     ControlValueAccessor,
+    FormArray,
+    FormControl,
     NG_VALUE_ACCESSOR,
     UntypedFormArray,
     UntypedFormControl,
 } from '@angular/forms';
 import { StringCustomFieldConfig } from '@vendure/common/lib/generated-types';
 import { ConfigArgType, CustomFieldType, DefaultFormComponentId } from '@vendure/common/lib/shared-types';
-import { assertNever } from '@vendure/common/lib/shared-utils';
+import { assertNever, notNullOrUndefined } from '@vendure/common/lib/shared-utils';
 import { simpleDeepClone } from '@vendure/common/lib/simple-deep-clone';
 import { Subject, Subscription } from 'rxjs';
 import { switchMap, take, takeUntil } from 'rxjs/operators';
@@ -71,7 +73,7 @@ export class DynamicFormInputComponent
     listItems: InputListItem[] = [];
     private singleComponentRef: ComponentRef<FormInputComponent>;
     private listId = 1;
-    private listFormArray = new UntypedFormArray([]);
+    private listFormArray = new FormArray([] as Array<FormControl<any>>);
     private componentType: Type<FormInputComponent>;
     private onChange: (val: any) => void;
     private onTouch: () => void;
@@ -151,8 +153,9 @@ export class DynamicFormInputComponent
                             .subscribe(val => {
                                 this.control.markAsTouched();
                                 this.control.markAsDirty();
-                                this.onChange(val);
-                                this.control.patchValue(val, { emitEvent: false });
+                                const truthyValues = val.filter(notNullOrUndefined);
+                                this.onChange(truthyValues);
+                                this.control.patchValue(truthyValues, { emitEvent: false });
                             });
                         setTimeout(() => this.changeDetectorRef.markForCheck());
                     }

+ 2 - 2
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/product-multi-selector-form-input/product-multi-selector-form-input.component.ts

@@ -1,5 +1,5 @@
 import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
-import { UntypedFormControl } from '@angular/forms';
+import { FormControl, UntypedFormControl } from '@angular/forms';
 import { DefaultFormComponentConfig, DefaultFormComponentId } from '@vendure/common/lib/shared-types';
 
 import { FormInputComponent } from '../../../common/component-registry-types';
@@ -15,7 +15,7 @@ import { ProductMultiSelectorDialogComponent } from '../../components/product-mu
 })
 export class ProductMultiSelectorFormInputComponent implements OnInit, FormInputComponent {
     @Input() config: DefaultFormComponentConfig<'product-multi-form-input'>;
-    @Input() formControl: UntypedFormControl;
+    @Input() formControl: FormControl<string[] | Array<{ id: string }>>;
     @Input() readonly: boolean;
     mode: 'product' | 'variant' = 'product';
     readonly isListInput = true;

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

@@ -9,7 +9,7 @@
         </div>
         <div class="flex-spacer"></div>
         <button
-            class="btn btn-link btn-sm btn-warning"
+            class="button-small"
             (click)="removeProductVariant(variant.id)"
             [title]="'common.remove-item-from-list' | translate"
         >

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

@@ -10,6 +10,9 @@
 }
 .thumb {
     margin-right: 6px;
+    img {
+        border-radius: var(--border-radius);
+    }
 }
 .sku {
     color: var(--color-grey-400);

+ 14 - 8
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/product-selector-form-input/product-selector-form-input.component.ts

@@ -1,11 +1,11 @@
 import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
-import { UntypedFormControl } from '@angular/forms';
-import { DefaultFormComponentId } from '@vendure/common/lib/shared-types';
+import { FormControl } from '@angular/forms';
+import { DefaultFormComponentId, DefaultFormComponentUiConfig } from '@vendure/common/lib/shared-types';
 import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
 import { forkJoin, Observable, of } from 'rxjs';
 import { map, startWith, switchMap } from 'rxjs/operators';
 
-import { FormInputComponent, InputComponentConfig } from '../../../common/component-registry-types';
+import { FormInputComponent } from '../../../common/component-registry-types';
 import { GetProductVariantQuery, ProductSelectorSearchQuery } from '../../../common/generated-types';
 import { DataService } from '../../../data/providers/data.service';
 
@@ -27,8 +27,8 @@ export class ProductSelectorFormInputComponent implements FormInputComponent, On
     static readonly id: DefaultFormComponentId = 'product-selector-form-input';
     readonly isListInput = true;
     readonly: boolean;
-    formControl: UntypedFormControl;
-    config: InputComponentConfig;
+    formControl: FormControl<Array<string | { id: string }>>;
+    config: DefaultFormComponentUiConfig<'product-selector-form-input'>;
     selection$: Observable<Array<GetProductVariantQuery['productVariant']>>;
 
     constructor(private dataService: DataService) {}
@@ -52,7 +52,7 @@ export class ProductSelectorFormInputComponent implements FormInputComponent, On
                     return forkJoin(
                         value.map(id =>
                             this.dataService.product
-                                .getProductVariant(id)
+                                .getProductVariant(this.getId(id))
                                 .mapSingle(data => data.productVariant),
                         ),
                     );
@@ -66,10 +66,16 @@ export class ProductSelectorFormInputComponent implements FormInputComponent, On
     addProductVariant(product: ProductSelectorSearchQuery['search']['items'][number]) {
         const value = this.formControl.value as string[];
         this.formControl.setValue([...new Set([...value, product.productVariantId])]);
+        this.formControl.markAsDirty();
     }
 
     removeProductVariant(id: string) {
-        const value = this.formControl.value as string[];
-        this.formControl.setValue(value.filter(_id => _id !== id));
+        const value = this.formControl.value;
+        this.formControl.setValue(value.map(this.getId).filter(_id => _id !== id));
+        this.formControl.markAsDirty();
+    }
+
+    private getId(value: (typeof this.formControl.value)[number]) {
+        return typeof value === 'string' ? value : value.id;
     }
 }

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

@@ -1,5 +1,6 @@
 .preview {
     cursor: pointer;
+    border-radius: var(--border-radius);
 }
 
 .detail {

+ 1 - 5
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/relation-form-input/asset/relation-asset-input.component.ts

@@ -1,11 +1,8 @@
 import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
 import { UntypedFormControl } from '@angular/forms';
-import { DefaultFormComponentId } from '@vendure/common/lib/shared-types';
 import { gql } from 'apollo-angular';
 import { Observable, of } from 'rxjs';
 import { distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators';
-
-import { FormInputComponent } from '../../../../common/component-registry-types';
 import { GetAssetQuery, RelationCustomFieldConfig } from '../../../../common/generated-types';
 import { ASSET_FRAGMENT, TAG_FRAGMENT } from '../../../../data/definitions/product-definitions';
 import { DataService } from '../../../../data/providers/data.service';
@@ -32,8 +29,7 @@ export const RELATION_ASSET_INPUT_QUERY = gql`
     styleUrls: ['./relation-asset-input.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class RelationAssetInputComponent implements FormInputComponent, OnInit {
-    static readonly id: DefaultFormComponentId = 'asset-form-input';
+export class RelationAssetInputComponent implements OnInit {
     @Input() readonly: boolean;
     @Input('parentFormControl') formControl: UntypedFormControl;
     @Input() config: RelationCustomFieldConfig;

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

@@ -22,8 +22,6 @@ export class RelationCustomerInputComponent implements OnInit {
     @Input() config: RelationCustomFieldConfig;
 
     @ViewChild('selector') template: TemplateRef<any>;
-
-    searchControl = new UntypedFormControl('');
     searchTerm$ = new Subject<string>();
     results$: Observable<Codegen.GetCustomerListQuery['customers']['items']>;
 

+ 11 - 6
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/relation-form-input/product-variant/relation-product-variant-input.component.html

@@ -8,10 +8,13 @@
     [readonly]="readonly"
 >
     <ng-template vdrRelationCardPreview let-variant="entity">
-        <img
-            *ngIf="variant.featuredAsset || variant.product.featuredAsset as asset; else placeholder"
-            [src]="asset | assetPreview: 'tiny'"
-        />
+        <div>
+            <img
+                class="thumb"
+                *ngIf="variant.featuredAsset || variant.product.featuredAsset as asset; else placeholder"
+                [src]="asset | assetPreview : 'tiny'"
+            />
+        </div>
         <ng-template #placeholder>
             <div class="placeholder" *ngIf="!variant.featuredAsset">
                 <clr-icon shape="image" size="50"></clr-icon>
@@ -19,7 +22,9 @@
         </ng-template>
     </ng-template>
     <ng-template vdrRelationCardDetail let-variant="entity">
-        <a [routerLink]="['/catalog/inventory', variant.product.id, { tab: 'variants' }]">{{ variant.name }}</a>
+        <a [routerLink]="['/catalog/inventory', variant.product.id, { tab: 'variants' }]">{{
+            variant.name
+        }}</a>
         <div class="">{{ variant.sku }}</div>
     </ng-template>
 </vdr-relation-card>
@@ -29,7 +34,7 @@
         <ng-template ng-option-tmp let-item="item">
             <img
                 *ngIf="item.featuredAsset || item.product.featuredAsset as asset"
-                [src]="asset | assetPreview: 32"
+                [src]="asset | assetPreview : 32"
             />
             {{ item.name }}
         </ng-template>

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

@@ -1,3 +1,8 @@
 .placeholder {
     color: var(--color-grey-300);
 }
+.thumb {
+    min-width: 50px;
+    object-fit: contain;
+    border-radius: var(--border-radius);
+}

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

@@ -8,7 +8,9 @@
     [readonly]="readonly"
 >
     <ng-template vdrRelationCardPreview let-product="entity">
-        <img *ngIf="product.featuredAsset" [src]="product.featuredAsset | assetPreview: 'tiny'" />
+        <div>
+        <img class="thumb" *ngIf="product.featuredAsset" [src]="product.featuredAsset | assetPreview: 'tiny'" />
+        </div>
         <div class="placeholder" *ngIf="!product.featuredAsset">
             <clr-icon shape="image" size="50"></clr-icon>
         </div>

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

@@ -1,3 +1,9 @@
 .placeholder {
     color: var(--color-grey-300);
 }
+
+.thumb {
+    min-width: 50px;
+    object-fit: contain;
+    border-radius: var(--border-radius);
+}

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

@@ -4,7 +4,7 @@
             <ng-container *ngTemplateOutlet="previewTemplate; context: { entity: entity }"></ng-container>
         </div>
         <div class="detail">
-            <div class="pl3">
+            <div class="pl-1">
                 <ng-container *ngTemplateOutlet="detailTemplate; context: { entity: entity }"></ng-container>
             </div>
             <button *ngIf="!readonly" class="button-small m-1" (click)="select.emit()">

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

@@ -2,4 +2,5 @@
     display: block;
     background-color: var(--color-component-bg-200);
     padding: 3px;
+    border-radius: var(--border-radius);
 }

+ 0 - 2
packages/common/src/shared-types.ts

@@ -147,7 +147,6 @@ export type DefaultFormComponentId =
     | 'select-form-input'
     | 'text-form-input'
     | 'textarea-form-input'
-    | 'asset-form-input'
     | 'product-multi-form-input'
     | 'combination-mode-form-input';
 
@@ -177,7 +176,6 @@ type DefaultFormConfigHash = {
     'textarea-form-input': {
         spellcheck?: boolean;
     };
-    'asset-form-input': Record<string, never>;
     'product-multi-form-input': {
         selectionMode?: 'product' | 'variant';
     };