Explorar o código

feat(admin-ui): Add Collection slug to detail form

Relates to #335
Michael Bromley %!s(int64=5) %!d(string=hai) anos
pai
achega
700f4d6dfb

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

@@ -56,6 +56,20 @@
                     type="text"
                     formControlName="name"
                     [readonly]="!('UpdateCatalog' | hasPermission)"
+                    (input)="updateSlug($event.target.value)"
+                />
+            </vdr-form-field>
+            <vdr-form-field
+                [label]="'catalog.slug' | translate"
+                for="slug"
+                [errors]="{ pattern: ('catalog.slug-pattern-error' | translate) }"
+            >
+                <input
+                    id="slug"
+                    type="text"
+                    formControlName="slug"
+                    [readonly]="!('UpdateCatalog' | hasPermission)"
+                    pattern="[a-z0-9_-]+"
                 />
             </vdr-form-field>
             <vdr-rich-text-editor

+ 29 - 11
packages/admin-ui/src/lib/catalog/src/components/collection-detail/collection-detail.component.ts

@@ -27,6 +27,7 @@ import {
     ServerConfigService,
     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';
 
@@ -63,6 +64,7 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
         this.customFields = this.getCustomFieldConfig('Collection');
         this.detailForm = this.formBuilder.group({
             name: ['', Validators.required],
+            slug: '',
             description: '',
             visible: false,
             filters: this.formBuilder.array([]),
@@ -76,15 +78,15 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
         this.init();
         this.facets$ = this.dataService.facet
             .getAllFacets()
-            .mapSingle((data) => data.facets.items)
+            .mapSingle(data => data.facets.items)
             .pipe(shareReplay(1));
 
-        this.dataService.collection.getCollectionFilters().single$.subscribe((res) => {
+        this.dataService.collection.getCollectionFilters().single$.subscribe(res => {
             this.allFilters = res.collectionFilters;
         });
         this.activeChannel$ = this.dataService.settings
             .getActiveChannel()
-            .mapStream((data) => data.activeChannel);
+            .mapStream(data => data.activeChannel);
     }
 
     ngOnDestroy() {
@@ -92,7 +94,7 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
     }
 
     getFilterDefinition(filter: ConfigurableOperation): ConfigurableOperationDefinition | undefined {
-        return this.allFilters.find((f) => f.code === filter.code);
+        return this.allFilters.find(f => f.code === filter.code);
     }
 
     customFieldIsSet(name: string): boolean {
@@ -103,9 +105,23 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
         return !!Object.values(this.assetChanges).length;
     }
 
+    /**
+     * If creating a new product, automatically generate the slug based on the collection name.
+     */
+    updateSlug(nameValue: string) {
+        this.isNew$.pipe(take(1)).subscribe(isNew => {
+            if (isNew) {
+                const slugControl = this.detailForm.get(['slug']);
+                if (slugControl && slugControl.pristine) {
+                    slugControl.setValue(normalizeString(`${nameValue}`, '-'));
+                }
+            }
+        });
+    }
+
     addFilter(collectionFilter: ConfigurableOperation) {
         const filtersArray = this.detailForm.get('filters') as FormArray;
-        const index = filtersArray.value.findIndex((o) => o.code === collectionFilter.code);
+        const index = filtersArray.value.findIndex(o => o.code === collectionFilter.code);
         if (index === -1) {
             const argsHash = collectionFilter.args.reduce(
                 (output, arg) => ({
@@ -126,7 +142,7 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
 
     removeFilter(collectionFilter: ConfigurableOperation) {
         const filtersArray = this.detailForm.get('filters') as FormArray;
-        const index = filtersArray.value.findIndex((o) => o.code === collectionFilter.code);
+        const index = filtersArray.value.findIndex(o => o.code === collectionFilter.code);
         if (index !== -1) {
             filtersArray.removeAt(index);
             this.filters.splice(index, 1);
@@ -154,7 +170,7 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
                 }),
             )
             .subscribe(
-                (data) => {
+                data => {
                     this.notificationService.success(_('common.notify-create-success'), {
                         entity: 'Collection',
                     });
@@ -163,7 +179,7 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
                     this.changeDetector.markForCheck();
                     this.router.navigate(['../', data.createCollection.id], { relativeTo: this.route });
                 },
-                (err) => {
+                err => {
                     this.notificationService.error(_('common.notify-create-error'), {
                         entity: 'Collection',
                     });
@@ -194,7 +210,7 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
                     });
                     this.contentsComponent.refresh();
                 },
-                (err) => {
+                err => {
                     this.notificationService.error(_('common.notify-update-error'), {
                         entity: 'Collection',
                     });
@@ -210,15 +226,16 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
      * Sets the values of the form on changes to the category or current language.
      */
     protected setFormValues(entity: Collection.Fragment, languageCode: LanguageCode) {
-        const currentTranslation = entity.translations.find((t) => t.languageCode === languageCode);
+        const currentTranslation = entity.translations.find(t => t.languageCode === languageCode);
 
         this.detailForm.patchValue({
             name: currentTranslation ? currentTranslation.name : '',
+            slug: currentTranslation ? currentTranslation.slug : '',
             description: currentTranslation ? currentTranslation.description : '',
             visible: !entity.isPrivate,
         });
 
-        entity.filters.forEach((f) => this.addFilter(f));
+        entity.filters.forEach(f => this.addFilter(f));
 
         if (this.customFields.length) {
             const customFieldsGroup = this.detailForm.get(['customFields']) as FormGroup;
@@ -254,6 +271,7 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
             defaultTranslation: {
                 languageCode,
                 name: category.name || '',
+                slug: category.slug || '',
                 description: category.description || '',
             },
         });

+ 2 - 1
packages/admin-ui/src/lib/catalog/src/providers/routing/collection-resolver.ts

@@ -19,6 +19,7 @@ export class CollectionResolver extends BaseEntityResolver<Collection.Fragment>
                 updatedAt: '',
                 languageCode: getDefaultUiLanguage(),
                 name: '',
+                slug: '',
                 isPrivate: false,
                 description: '',
                 featuredAsset: null,
@@ -28,7 +29,7 @@ export class CollectionResolver extends BaseEntityResolver<Collection.Fragment>
                 parent: {} as any,
                 children: null,
             },
-            (id) => dataService.collection.getCollection(id).mapStream((data) => data.collection),
+            id => dataService.collection.getCollection(id).mapStream(data => data.collection),
         );
     }
 }

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

@@ -456,6 +456,7 @@ export type CreateCollectionTranslationInput = {
   name: Scalars['String'];
   slug: Scalars['String'];
   description: Scalars['String'];
+  customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type CreateCountryInput = {
@@ -592,7 +593,7 @@ export type CreateZoneInput = {
 /**
  * @description
  * ISO 4217 currency code
- * 
+ *
  * @docsCategory common
  */
 export enum CurrencyCode {
@@ -1387,7 +1388,7 @@ export type JobSortParameter = {
 /**
  * @description
  * The state of a Job in the JobQueue
- * 
+ *
  * @docsCategory common
  */
 export enum JobState {
@@ -1405,7 +1406,7 @@ export enum JobState {
  * region or script modifier (e.g. de_AT). The selection available is based
  * on the [Unicode CLDR summary list](https://unicode-org.github.io/cldr-staging/charts/37/summary/root.html)
  * and includes the major spoken languages of the world and any widely-used variants.
- * 
+ *
  * @docsCategory common
  */
 export enum LanguageCode {
@@ -2580,7 +2581,7 @@ export type PaymentMethodSortParameter = {
  * @description
  * Permissions for administrators and customers. Used to control access to
  * GraphQL resolvers via the {@link Allow} decorator.
- * 
+ *
  * @docsCategory common
  */
 export enum Permission {
@@ -3589,6 +3590,7 @@ export type UpdateCollectionTranslationInput = {
   name?: Maybe<Scalars['String']>;
   slug?: Maybe<Scalars['String']>;
   description?: Maybe<Scalars['String']>;
+  customFields?: Maybe<Scalars['JSON']>;
 };
 
 export type UpdateCountryInput = {
@@ -4116,7 +4118,7 @@ export type GetCollectionFiltersQuery = (
 
 export type CollectionFragment = (
   { __typename?: 'Collection' }
-  & Pick<Collection, 'id' | 'createdAt' | 'updatedAt' | 'name' | 'description' | 'isPrivate' | 'languageCode'>
+  & Pick<Collection, 'id' | 'createdAt' | 'updatedAt' | 'name' | 'slug' | 'description' | 'isPrivate' | 'languageCode'>
   & { featuredAsset?: Maybe<(
     { __typename?: 'Asset' }
     & AssetFragment
@@ -4128,7 +4130,7 @@ export type CollectionFragment = (
     & ConfigurableOperationFragment
   )>, translations: Array<(
     { __typename?: 'CollectionTranslation' }
-    & Pick<CollectionTranslation, 'id' | 'languageCode' | 'name' | 'description'>
+    & Pick<CollectionTranslation, 'id' | 'languageCode' | 'name' | 'slug' | 'description'>
   )>, parent?: Maybe<(
     { __typename?: 'Collection' }
     & Pick<Collection, 'id' | 'name'>
@@ -4150,7 +4152,7 @@ export type GetCollectionListQuery = (
     & Pick<CollectionList, 'totalItems'>
     & { items: Array<(
       { __typename?: 'Collection' }
-      & Pick<Collection, 'id' | 'name' | 'description' | 'isPrivate'>
+      & Pick<Collection, 'id' | 'name' | 'slug' | 'description' | 'isPrivate'>
       & { featuredAsset?: Maybe<(
         { __typename?: 'Asset' }
         & AssetFragment

+ 3 - 0
packages/admin-ui/src/lib/core/src/data/definitions/collection-definitions.ts

@@ -18,6 +18,7 @@ export const COLLECTION_FRAGMENT = gql`
         createdAt
         updatedAt
         name
+        slug
         description
         isPrivate
         languageCode
@@ -34,6 +35,7 @@ export const COLLECTION_FRAGMENT = gql`
             id
             languageCode
             name
+            slug
             description
         }
         parent {
@@ -55,6 +57,7 @@ export const GET_COLLECTION_LIST = gql`
             items {
                 id
                 name
+                slug
                 description
                 isPrivate
                 featuredAsset {