Browse Source

feat(admin-ui): Asset names can be updated

Michael Bromley 6 years ago
parent
commit
fcb4f3d522

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

@@ -70,7 +70,7 @@
                 </div>
                 <vdr-dropdown-menu vdrPosition="bottom-right">
                     <button type="button" vdrDropdownItem (click)="previewAsset(asset)">
-                        {{ 'catalog.preview' | translate }}
+                        {{ 'asset.preview' | translate }}
                     </button>
                     <button
                         type="button"

+ 17 - 1
packages/admin-ui/src/app/common/generated-types.ts

@@ -1709,6 +1709,7 @@ export type Mutation = {
   assignRoleToAdministrator: Administrator,
   /** Create a new Asset */
   createAssets: Array<Asset>,
+  /** Update an existing Asset */
   updateAsset: Asset,
   login: LoginResult,
   logout: Scalars['Boolean'],
@@ -2791,7 +2792,9 @@ export type Query = {
   __typename?: 'Query',
   administrators: AdministratorList,
   administrator?: Maybe<Administrator>,
+  /** Get a list of Assets */
   assets: AssetList,
+  /** Get a single Asset by id */
   asset?: Maybe<Asset>,
   me?: Maybe<CurrentUser>,
   channels: Array<Channel>,
@@ -4027,7 +4030,7 @@ export type AddNoteToOrderMutationVariables = {
 
 export type AddNoteToOrderMutation = ({ __typename?: 'Mutation' } & { addNoteToOrder: ({ __typename?: 'Order' } & Pick<Order, 'id'>) });
 
-export type AssetFragment = ({ __typename?: 'Asset' } & Pick<Asset, 'id' | 'createdAt' | 'updatedAt' | 'name' | 'fileSize' | 'mimeType' | 'type' | 'preview' | 'source'>);
+export type AssetFragment = ({ __typename?: 'Asset' } & Pick<Asset, 'id' | 'createdAt' | 'updatedAt' | 'name' | 'fileSize' | 'mimeType' | 'type' | 'preview' | 'source' | 'width' | 'height'>);
 
 export type ProductVariantFragment = ({ __typename?: 'ProductVariant' } & Pick<ProductVariant, 'id' | 'createdAt' | 'updatedAt' | 'enabled' | 'languageCode' | 'name' | 'price' | 'currencyCode' | 'priceIncludesTax' | 'priceWithTax' | 'stockOnHand' | 'trackInventory' | 'sku'> & { taxRateApplied: ({ __typename?: 'TaxRate' } & Pick<TaxRate, 'id' | 'name' | 'value'>), taxCategory: ({ __typename?: 'TaxCategory' } & Pick<TaxCategory, 'id' | 'name'>), options: Array<({ __typename?: 'ProductOption' } & Pick<ProductOption, 'id' | 'code' | 'languageCode' | 'name' | 'groupId'> & { translations: Array<({ __typename?: 'ProductOptionTranslation' } & Pick<ProductOptionTranslation, 'id' | 'languageCode' | 'name'>)> })>, facetValues: Array<({ __typename?: 'FacetValue' } & Pick<FacetValue, 'id' | 'code' | 'name'> & { facet: ({ __typename?: 'Facet' } & Pick<Facet, 'id' | 'name'>) })>, featuredAsset: Maybe<({ __typename?: 'Asset' } & AssetFragment)>, assets: Array<({ __typename?: 'Asset' } & AssetFragment)>, translations: Array<({ __typename?: 'ProductVariantTranslation' } & Pick<ProductVariantTranslation, 'id' | 'languageCode' | 'name'>)> });
 
@@ -4142,6 +4145,13 @@ export type CreateAssetsMutationVariables = {
 
 export type CreateAssetsMutation = ({ __typename?: 'Mutation' } & { createAssets: Array<({ __typename?: 'Asset' } & AssetFragment)> });
 
+export type UpdateAssetMutationVariables = {
+  input: UpdateAssetInput
+};
+
+
+export type UpdateAssetMutation = ({ __typename?: 'Mutation' } & { updateAsset: ({ __typename?: 'Asset' } & AssetFragment) });
+
 export type SearchProductsQueryVariables = {
   input: SearchInput
 };
@@ -5143,6 +5153,12 @@ export namespace CreateAssets {
   export type CreateAssets = AssetFragment;
 }
 
+export namespace UpdateAsset {
+  export type Variables = UpdateAssetMutationVariables;
+  export type Mutation = UpdateAssetMutation;
+  export type UpdateAsset = AssetFragment;
+}
+
 export namespace SearchProducts {
   export type Variables = SearchProductsQueryVariables;
   export type Query = SearchProductsQuery;

+ 11 - 0
packages/admin-ui/src/app/data/definitions/product-definitions.ts

@@ -11,6 +11,8 @@ export const ASSET_FRAGMENT = gql`
         type
         preview
         source
+        width
+        height
     }
 `;
 
@@ -342,6 +344,15 @@ export const CREATE_ASSETS = gql`
     ${ASSET_FRAGMENT}
 `;
 
+export const UPDATE_ASSET = gql`
+    mutation UpdateAsset($input: UpdateAssetInput!) {
+        updateAsset(input: $input) {
+            ...Asset
+        }
+    }
+    ${ASSET_FRAGMENT}
+`;
+
 export const SEARCH_PRODUCTS = gql`
     query SearchProducts($input: SearchInput!) {
         search(input: $input) {

+ 9 - 0
packages/admin-ui/src/app/data/providers/product-data.service.ts

@@ -27,6 +27,8 @@ import {
     RemoveProductsFromChannelInput,
     SearchProducts,
     SortOrder,
+    UpdateAsset,
+    UpdateAssetInput,
     UpdateProduct,
     UpdateProductInput,
     UpdateProductOption,
@@ -53,6 +55,7 @@ import {
     REMOVE_OPTION_GROUP_FROM_PRODUCT,
     REMOVE_PRODUCTS_FROM_CHANNEL,
     SEARCH_PRODUCTS,
+    UPDATE_ASSET,
     UPDATE_PRODUCT,
     UPDATE_PRODUCT_OPTION,
     UPDATE_PRODUCT_VARIANTS,
@@ -265,6 +268,12 @@ export class ProductDataService {
         });
     }
 
+    updateAsset(input: UpdateAssetInput) {
+        return this.baseDataService.mutate<UpdateAsset.Mutation, UpdateAsset.Variables>(UPDATE_ASSET, {
+            input,
+        });
+    }
+
     assignProductsToChannel(input: AssignProductsToChannelInput) {
         return this.baseDataService.mutate<
             AssignProductsToChannel.Mutation,

+ 1 - 1
packages/admin-ui/src/app/shared/components/asset-gallery/asset-gallery.component.html

@@ -39,7 +39,7 @@
             </div>
             <div>
                 <button (click)="previewAsset(lastSelected())" class="btn btn-link">
-                    {{ 'catalog.preview' | translate }}
+                    {{ 'asset.preview' | translate }}
                 </button>
             </div>
             <div>

+ 14 - 2
packages/admin-ui/src/app/shared/components/asset-gallery/asset-gallery.component.ts

@@ -1,4 +1,4 @@
-import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
+import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
 
 import { Asset } from '../../../common/generated-types';
 import { ModalService } from '../../providers/modal/modal.service';
@@ -10,7 +10,7 @@ import { AssetPreviewDialogComponent } from '../asset-preview-dialog/asset-previ
     styleUrls: ['./asset-gallery.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class AssetGalleryComponent {
+export class AssetGalleryComponent implements OnChanges {
     @Input() assets: Asset[];
     /**
      * If true, allows multiple assets to be selected by ctrl+clicking.
@@ -22,6 +22,18 @@ export class AssetGalleryComponent {
 
     constructor(private modalService: ModalService) {}
 
+    ngOnChanges() {
+        if (this.assets) {
+            for (const asset of this.selection) {
+                // Update and selected assets with any changes
+                const match = this.assets.find(a => a.id === asset.id);
+                if (match) {
+                    Object.assign(asset, match);
+                }
+            }
+        }
+    }
+
     toggleSelection(event: MouseEvent, asset: Asset) {
         const index = this.selection.findIndex(a => a.id === asset.id);
         if (index === -1) {

+ 5 - 1
packages/admin-ui/src/app/shared/components/asset-preview-dialog/asset-preview-dialog.component.html

@@ -4,4 +4,8 @@
     </div>
 </ng-template>
 
-<vdr-asset-preview [asset]="asset"></vdr-asset-preview>
+<vdr-asset-preview [asset]="asset" (assetChange)="assetChanges = $event"></vdr-asset-preview>
+
+<ng-template vdrDialogButtons>
+    <button class="btn btn-primary" [disabled]="!assetChanges" (click)="updateAsset()">{{ 'common.update' | translate }}</button>
+</ng-template>

+ 23 - 1
packages/admin-ui/src/app/shared/components/asset-preview-dialog/asset-preview-dialog.component.ts

@@ -1,6 +1,9 @@
 import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { _ } from '@vendure/admin-ui/src/app/core/providers/i18n/mark-for-extraction';
 
-import { Asset } from '../../../common/generated-types';
+import { Asset, UpdateAssetInput } from '../../../common/generated-types';
+import { NotificationService } from '../../../core/providers/notification/notification.service';
+import { DataService } from '../../../data/providers/data.service';
 import { Dialog } from '../../providers/modal/modal.service';
 
 @Component({
@@ -11,5 +14,24 @@ import { Dialog } from '../../providers/modal/modal.service';
 })
 export class AssetPreviewDialogComponent implements Dialog<void> {
     asset: Asset;
+    assetChanges?: UpdateAssetInput;
     resolveWith: (result?: void) => void;
+
+    constructor(private dataService: DataService, private notificationService: NotificationService) {}
+
+    updateAsset() {
+        if (this.assetChanges) {
+            this.dataService.product.updateAsset(this.assetChanges).subscribe(
+                () => {
+                    this.assetChanges = undefined;
+                    this.notificationService.success(_('common.notify-update-success'), { entity: 'Asset' });
+                },
+                err => {
+                    this.notificationService.error(_('common.notify-update-error'), {
+                        entity: 'Asset',
+                    });
+                },
+            );
+        }
+    }
 }

+ 30 - 16
packages/admin-ui/src/app/shared/components/asset-preview/asset-preview.component.html

@@ -3,9 +3,33 @@
 </div>
 
 <div class="controls">
-    <div>
-        <label>{{ 'catalog.preview-size' | translate }}:</label>
-        <vdr-form-field>
+    <form [formGroup]="form">
+        <clr-input-container>
+            <label>{{ 'common.name' | translate }}</label>
+            <input
+                clrInput
+                type="text"
+                formControlName="name"
+                [readonly]="!('UpdateCatalog' | hasPermission)"
+            />
+        </clr-input-container>
+        <div class="asset-detail">
+            {{ 'asset.source-file' | translate }}:
+            <a [href]="asset.source" [title]="asset.source" target="_blank" class="source-link">{{
+                getSourceFileName()
+            }}</a>
+        </div>
+        <div class="asset-detail">
+            {{ 'asset.original-asset-size' | translate }}: {{ asset.fileSize | filesize }}
+        </div>
+        <div class="asset-detail">
+            {{ 'asset.dimensions' | translate }}: {{ asset.width }} x {{ asset.height }}
+        </div>
+    </form>
+    <div class="flex-spacer"></div>
+    <div class="preview-select">
+        <clr-select-container>
+            <label>{{ 'asset.preview' | translate }}</label>
             <select clrSelect name="options" [(ngModel)]="size">
                 <option value="tiny">tiny</option>
                 <option value="thumb">thumb</option>
@@ -14,18 +38,8 @@
                 <option value="large">large</option>
                 <option value="">full size</option>
             </select>
-        </vdr-form-field>
-        <div class="dimension">
-            <label>{{ 'catalog.width' | translate }}:</label>
-            {{ width }}px
-        </div>
-        <div class="dimension">
-            <label>{{ 'catalog.height' | translate }}:</label>
-            {{ height }}px
-        </div>
+        </clr-select-container>
+        <div class="asset-detail">{{ width }} x {{ height }}</div>
     </div>
-    <div class="flex-spacer"></div>
-    <a [href]="asset.source" target="_blank" class="btn btn-link">
-        {{ 'asset.open-asset-source' | translate }}
-    </a>
+
 </div>

+ 27 - 10
packages/admin-ui/src/app/shared/components/asset-preview/asset-preview.component.scss

@@ -30,19 +30,36 @@
 .controls {
     display: flex;
     flex-direction: column;
-    flex-wrap: wrap;
     margin-left: 12px;
+    min-width: 15vw;
 
-    label {
-        color: $color-grey-500;
+    ::ng-deep .clr-control-container {
+        width: 100%;
+        .clr-input {
+            width: 100%;
+        }
     }
-    vdr-form-field {
-        height: 40px;
-        margin-left: 6px;
-        margin-right: 24px;
-        max-width: 112px;
+
+    .asset-detail {
+        font-size: 12px;
+        display: flex;
+    }
+
+    .source-link {
+        max-width: 191px;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        flex: none;
+        direction: rtl;
     }
-    .dimension {
-        margin: 0 6px;
+
+    .preview-select {
+        display: flex;
+        align-items: flex-end;
+        margin-bottom: 30px;
+        clr-select-container {
+            margin-right: 6px;
+        }
     }
 }

+ 55 - 3
packages/admin-ui/src/app/shared/components/asset-preview/asset-preview.component.ts

@@ -1,6 +1,18 @@
-import { ChangeDetectionStrategy, Component, ElementRef, Input, ViewChild } from '@angular/core';
+import {
+    ChangeDetectionStrategy,
+    Component,
+    ElementRef,
+    EventEmitter,
+    Input,
+    OnDestroy,
+    OnInit,
+    Output,
+    ViewChild,
+} from '@angular/core';
+import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
+import { Subscription } from 'rxjs';
 
-import { Asset } from '../../../common/generated-types';
+import { Asset, UpdateAssetInput } from '../../../common/generated-types';
 import { Dialog } from '../../providers/modal/modal.service';
 
 @Component({
@@ -9,8 +21,11 @@ import { Dialog } from '../../providers/modal/modal.service';
     styleUrls: ['./asset-preview.component.scss'],
     changeDetection: ChangeDetectionStrategy.Default,
 })
-export class AssetPreviewComponent {
+export class AssetPreviewComponent implements OnInit, OnDestroy {
     @Input() asset: Asset;
+    @Output() assetChange = new EventEmitter<UpdateAssetInput>();
+
+    form: FormGroup;
 
     size = 'medium';
     resolveWith: (result?: void) => void;
@@ -19,6 +34,43 @@ export class AssetPreviewComponent {
     centered = true;
     @ViewChild('imageElement', { static: true }) private imageElementRef: ElementRef<HTMLImageElement>;
     @ViewChild('previewDiv', { static: true }) private previewDivRef: ElementRef<HTMLDivElement>;
+    private subscription: Subscription;
+
+    constructor(private formBuilder: FormBuilder) {}
+
+    ngOnInit() {
+        const { focalPoint } = this.asset;
+        this.form = this.formBuilder.group({
+            name: [this.asset.name],
+            focalPointX: [focalPoint ? focalPoint.x : null],
+            focalPointY: [focalPoint ? focalPoint.y : null],
+        });
+        this.subscription = this.form.valueChanges.subscribe(value => {
+            const focalPointValue =
+                value.focalPointX != null && value.focalPointY != null
+                    ? {
+                          x: value.focalPointX,
+                          y: value.focalPointY,
+                      }
+                    : null;
+            this.assetChange.emit({
+                id: this.asset.id,
+                name: value.name,
+                focalPoint: focalPointValue,
+            });
+        });
+    }
+
+    ngOnDestroy(): void {
+        if (this.subscription) {
+            this.subscription.unsubscribe();
+        }
+    }
+
+    getSourceFileName(): string {
+        const parts = this.asset.source.split('/');
+        return parts[parts.length - 1];
+    }
 
     getDimensions() {
         const img = this.imageElementRef.nativeElement;

+ 5 - 4
packages/admin-ui/src/i18n-messages/en.json

@@ -6,6 +6,7 @@
     "add-asset": "Add asset",
     "add-asset-with-count": "Add {count, plural, 0 {assets} one {1 asset} other {{count} assets}}",
     "assets-selected-count": "{ count } assets selected",
+    "dimensions": "Dimensions",
     "notify-create-assets-success": "Created {count, plural, one {new Asset} other {{count} new Assets}}",
     "open-asset-source": "Open asset source",
     "original-asset-size": "Source size",
@@ -13,7 +14,9 @@
     "search-asset-name": "Search assets by name",
     "select-assets": "Select assets",
     "set-as-featured-asset": "Set as featured asset",
-    "upload-assets": "Upload assets"
+    "source-file": "Source file",
+    "upload-assets": "Upload assets",
+    "preview": "Preview"
   },
   "breadcrumb": {
     "administrators": "Administrators",
@@ -84,8 +87,6 @@
     "option": "Option",
     "option-name": "Option name",
     "option-values": "Option values",
-    "preview": "Preview",
-    "preview-size": "Preview size",
     "price": "Price",
     "price-conversion-factor": "Price conversion factor",
     "price-in-channel": "Price in { channel }",
@@ -613,4 +614,4 @@
     "update": "Update",
     "zone": "Zone"
   }
-}
+}