Selaa lähdekoodia

feat(admin-ui): Manage tags interface

Relates to #316
Michael Bromley 5 vuotta sitten
vanhempi
sitoutus
205391dba9

+ 24 - 24
packages/admin-ui/i18n-coverage.json

@@ -1,50 +1,50 @@
 {
-  "generatedOn": "2021-01-19T09:29:39.498Z",
-  "lastCommit": "c63f9ace215e3a452785cf212f820b7d00933032",
+  "generatedOn": "2021-01-19T12:13:56.694Z",
+  "lastCommit": "43a930b617662716c4b02b322289c6c0b1024489",
   "translationStatus": {
     "cs": {
-      "tokenCount": 758,
-      "translatedCount": 755,
-      "percentage": 100
+      "tokenCount": 760,
+      "translatedCount": 754,
+      "percentage": 99
     },
     "de": {
-      "tokenCount": 758,
-      "translatedCount": 596,
-      "percentage": 79
+      "tokenCount": 760,
+      "translatedCount": 595,
+      "percentage": 78
     },
     "en": {
-      "tokenCount": 758,
-      "translatedCount": 757,
+      "tokenCount": 760,
+      "translatedCount": 758,
       "percentage": 100
     },
     "es": {
-      "tokenCount": 758,
-      "translatedCount": 458,
+      "tokenCount": 760,
+      "translatedCount": 457,
       "percentage": 60
     },
     "fr": {
-      "tokenCount": 758,
-      "translatedCount": 692,
+      "tokenCount": 760,
+      "translatedCount": 691,
       "percentage": 91
     },
     "pl": {
-      "tokenCount": 758,
-      "translatedCount": 551,
-      "percentage": 73
+      "tokenCount": 760,
+      "translatedCount": 550,
+      "percentage": 72
     },
     "pt_BR": {
-      "tokenCount": 758,
-      "translatedCount": 642,
-      "percentage": 85
+      "tokenCount": 760,
+      "translatedCount": 641,
+      "percentage": 84
     },
     "zh_Hans": {
-      "tokenCount": 758,
-      "translatedCount": 533,
+      "tokenCount": 760,
+      "translatedCount": 532,
       "percentage": 70
     },
     "zh_Hant": {
-      "tokenCount": 758,
-      "translatedCount": 533,
+      "tokenCount": 760,
+      "translatedCount": 532,
       "percentage": 70
     }
   }

+ 9 - 2
packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.html

@@ -84,9 +84,16 @@
             </button>
         </vdr-labeled-data>
         <vdr-labeled-data [label]="'common.tags' | translate">
-            <vdr-tag-selector *ngIf="editable" formControlName="tags"></vdr-tag-selector>
+            <ng-container *ngIf="editable">
+                <vdr-tag-selector formControlName="tags"></vdr-tag-selector>
+                <button class="btn btn-link btn-sm" (click)="manageTags()">
+                    <clr-icon shape="tags"></clr-icon> {{ 'common.manage-tags' | translate }}
+                </button>
+            </ng-container>
             <div *ngIf="!editable">
-                <vdr-chip *ngFor="let tag of asset.tags" [colorFrom]="tag.value"><clr-icon shape="tag" class="mr2"></clr-icon> {{ tag.value }}</vdr-chip>
+                <vdr-chip *ngFor="let tag of asset.tags" [colorFrom]="tag.value"
+                    ><clr-icon shape="tag" class="mr2"></clr-icon> {{ tag.value }}</vdr-chip
+                >
             </div>
         </vdr-labeled-data>
     </form>

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.scss

@@ -75,7 +75,7 @@
 
     .preview-select {
         display: flex;
-        align-items: flex-end;
+        align-items: center;
         margin-bottom: 12px;
         clr-select-container {
             margin-right: 12px;

+ 15 - 0
packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.ts

@@ -17,8 +17,10 @@ import { debounceTime } from 'rxjs/operators';
 
 import { GetAsset, GetAssetList, UpdateAssetInput } from '../../../common/generated-types';
 import { DataService } from '../../../data/providers/data.service';
+import { ModalService } from '../../../providers/modal/modal.service';
 import { NotificationService } from '../../../providers/notification/notification.service';
 import { Point } from '../focal-point-control/focal-point-control.component';
+import { ManageTagsDialogComponent } from '../manage-tags-dialog/manage-tags-dialog.component';
 
 export type PreviewPreset = 'tiny' | 'thumb' | 'small' | 'medium' | 'large' | '';
 type AssetLike = GetAssetList.Items | GetAsset.Asset;
@@ -53,6 +55,7 @@ export class AssetPreviewComponent implements OnInit, OnDestroy {
         private dataService: DataService,
         private notificationService: NotificationService,
         private changeDetector: ChangeDetectorRef,
+        private modalService: ModalService,
     ) {}
 
     get fpx(): number | null {
@@ -188,4 +191,16 @@ export class AssetPreviewComponent implements OnInit, OnDestroy {
                 );
         }
     }
+
+    manageTags() {
+        this.modalService
+            .fromComponent(ManageTagsDialogComponent, {
+                size: 'sm',
+            })
+            .subscribe(result => {
+                if (result) {
+                    this.notificationService.success(_('notify.updated-tags-success'));
+                }
+            });
+    }
 }

+ 26 - 0
packages/admin-ui/src/lib/core/src/shared/components/manage-tags-dialog/manage-tags-dialog.component.html

@@ -0,0 +1,26 @@
+<ng-template vdrDialogTitle>
+    <span>{{ 'common.manage-tags' | translate }}</span>
+</ng-template>
+<p class="mt0 mb4">{{ 'common.manage-tags-description' | translate }}</p>
+<ul class="tag-list" *ngFor="let tag of allTags$ | async">
+    <li class="mb2 p1" [class.to-delete]="markedAsDeleted(tag.id)">
+        <clr-icon shape="tag" class="is-solid mr2" [style.color]="tag.value | stringToColor"></clr-icon>
+        <input type="text" (input)="updateTagValue(tag.id, $event.target.value)" [value]="tag.value" />
+        <button class="icon-button" (click)="toggleDelete(tag.id)">
+            <clr-icon shape="trash" class="is-danger" [class.is-solid]="markedAsDeleted(tag.id)"></clr-icon>
+        </button>
+    </li>
+</ul>
+<ng-template vdrDialogButtons>
+    <button type="submit" (click)="resolveWith(false)" class="btn btn-secondary">
+        {{ 'common.cancel' | translate }}
+    </button>
+    <button
+        type="submit"
+        (click)="saveChanges()"
+        class="btn btn-primary"
+        [disabled]="!toUpdate.length && !toDelete.length"
+    >
+        {{ 'common.update' | translate }}
+    </button>
+</ng-template>

+ 19 - 0
packages/admin-ui/src/lib/core/src/shared/components/manage-tags-dialog/manage-tags-dialog.component.scss

@@ -0,0 +1,19 @@
+.tag-list {
+    list-style-type: none;
+
+    li {
+        display: flex;
+        align-items: center;
+        input {
+            max-width: 170px;
+        }
+    }
+
+    li.to-delete {
+        opacity: 0.7;
+        background-color: var(--color-component-bg-300);
+        input {
+            background-color: transparent !important;
+        }
+    }
+}

+ 60 - 0
packages/admin-ui/src/lib/core/src/shared/components/manage-tags-dialog/manage-tags-dialog.component.ts

@@ -0,0 +1,60 @@
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { forkJoin, Observable } from 'rxjs';
+
+import { GetTagList } from '../../../common/generated-types';
+import { DataService } from '../../../data/providers/data.service';
+import { Dialog } from '../../../providers/modal/modal.service';
+
+@Component({
+    selector: 'vdr-manage-tags-dialog',
+    templateUrl: './manage-tags-dialog.component.html',
+    styleUrls: ['./manage-tags-dialog.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ManageTagsDialogComponent implements Dialog<boolean>, OnInit {
+    resolveWith: (result: boolean | undefined) => void;
+    allTags$: Observable<GetTagList.Items[]>;
+    toDelete: string[] = [];
+    toUpdate: Array<{ id: string; value: string }> = [];
+
+    constructor(private dataService: DataService) {}
+
+    ngOnInit(): void {
+        this.allTags$ = this.dataService.product.getTagList().mapStream(data => data.tags.items);
+    }
+
+    toggleDelete(id: string) {
+        const marked = this.markedAsDeleted(id);
+        if (marked) {
+            this.toDelete = this.toDelete.filter(_id => _id !== id);
+        } else {
+            this.toDelete.push(id);
+        }
+    }
+
+    markedAsDeleted(id: string): boolean {
+        return this.toDelete.includes(id);
+    }
+
+    updateTagValue(id: string, value: string) {
+        const exists = this.toUpdate.find(i => i.id === id);
+        if (exists) {
+            exists.value = value;
+        } else {
+            this.toUpdate.push({ id, value });
+        }
+    }
+
+    saveChanges() {
+        const operations: Array<Observable<any>> = [];
+        for (const id of this.toDelete) {
+            operations.push(this.dataService.product.deleteTag(id));
+        }
+        for (const item of this.toUpdate) {
+            if (!this.toDelete.includes(item.id)) {
+                operations.push(this.dataService.product.updateTag(item));
+            }
+        }
+        return forkJoin(operations).subscribe(() => this.resolveWith(true));
+    }
+}

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

@@ -59,6 +59,7 @@ import { HistoryEntryDetailComponent } from './components/history-entry-detail/h
 import { ItemsPerPageControlsComponent } from './components/items-per-page-controls/items-per-page-controls.component';
 import { LabeledDataComponent } from './components/labeled-data/labeled-data.component';
 import { LanguageSelectorComponent } from './components/language-selector/language-selector.component';
+import { ManageTagsDialogComponent } from './components/manage-tags-dialog/manage-tags-dialog.component';
 import { DialogButtonsDirective } from './components/modal-dialog/dialog-buttons.directive';
 import { DialogComponentOutletComponent } from './components/modal-dialog/dialog-component-outlet.component';
 import { DialogTitleDirective } from './components/modal-dialog/dialog-title.directive';
@@ -218,7 +219,7 @@ const DYNAMIC_FORM_INPUTS = [
 @NgModule({
     imports: [IMPORTS],
     exports: [...IMPORTS, ...DECLARATIONS, ...DYNAMIC_FORM_INPUTS],
-    declarations: [...DECLARATIONS, ...DYNAMIC_FORM_INPUTS],
+    declarations: [...DECLARATIONS, ...DYNAMIC_FORM_INPUTS, ManageTagsDialogComponent],
     providers: [
         // This needs to be shared, since lazy-loaded
         // modules have their own entryComponents which

+ 5 - 1
packages/admin-ui/src/lib/static/i18n-messages/cs.json

@@ -12,7 +12,6 @@
     "original-asset-size": "Originální velikost",
     "preview": "Náhled",
     "remove-asset": "Smazat médium",
-    "search-asset-name": "Vyhledat médium dle jména",
     "select-assets": "Vybrat média",
     "set-as-featured-asset": "Zvýraznit médium",
     "set-focal-point": "Nastavit ohnisko",
@@ -197,6 +196,8 @@
     "live-update": "Živé aktualizace",
     "log-out": "Odhlásit",
     "login": "Přihlásit",
+    "manage-tags": "",
+    "manage-tags-description": "",
     "more": "Více...",
     "name": "jméno",
     "no-results": "Žádné výsledky",
@@ -559,6 +560,9 @@
     "tax-rates": "Daňové sazby",
     "zones": "Zóny"
   },
+  "notify": {
+    "updated-tags-success": ""
+  },
   "order": {
     "add-item-to-order": "Přidat k objednávce",
     "add-note": "Přidat poznámku",

+ 5 - 1
packages/admin-ui/src/lib/static/i18n-messages/de.json

@@ -12,7 +12,6 @@
     "original-asset-size": "Dateigröße",
     "preview": "Vorschau",
     "remove-asset": "Asset entfernen",
-    "search-asset-name": "Assets nach Namen durchsuchen",
     "select-assets": "Assets auswählen",
     "set-as-featured-asset": "Als \"Featured Asset\" festlegen",
     "set-focal-point": "Fokuspunkt setzen",
@@ -197,6 +196,8 @@
     "live-update": "Live-Aktualisierung",
     "log-out": "Abmelden",
     "login": "Anmelden",
+    "manage-tags": "",
+    "manage-tags-description": "",
     "more": "Mehr...",
     "name": "Name",
     "no-results": "Keine Ergebnisse",
@@ -559,6 +560,9 @@
     "tax-rates": "Steuersätze",
     "zones": "Zonen"
   },
+  "notify": {
+    "updated-tags-success": ""
+  },
   "order": {
     "add-item-to-order": "",
     "add-note": "Notiz hinzufügen",

+ 5 - 1
packages/admin-ui/src/lib/static/i18n-messages/en.json

@@ -12,7 +12,6 @@
     "original-asset-size": "Source size",
     "preview": "Preview",
     "remove-asset": "Remove asset",
-    "search-asset-name": "Search assets by name",
     "select-assets": "Select assets",
     "set-as-featured-asset": "Set as featured asset",
     "set-focal-point": "Set focal point",
@@ -197,6 +196,8 @@
     "live-update": "Live update",
     "log-out": "Log out",
     "login": "Log in",
+    "manage-tags": "Manage tags",
+    "manage-tags-description": "Update or delete tags globally.",
     "more": "More...",
     "name": "Name",
     "no-results": "No results",
@@ -559,6 +560,9 @@
     "tax-rates": "Tax Rates",
     "zones": "Zones"
   },
+  "notify": {
+    "updated-tags-success": ""
+  },
   "order": {
     "add-item-to-order": "Add item to order",
     "add-note": "Add note",

+ 5 - 1
packages/admin-ui/src/lib/static/i18n-messages/es.json

@@ -12,7 +12,6 @@
     "original-asset-size": "Tamaño original",
     "preview": "Vista previa",
     "remove-asset": "Eliminar archivo",
-    "search-asset-name": "Buscar archivos por nombre",
     "select-assets": "Seleccionar archivos",
     "set-as-featured-asset": "Seleccionar como archivo por defecto",
     "set-focal-point": "",
@@ -197,6 +196,8 @@
     "live-update": "Actualización en vivo",
     "log-out": "Salir",
     "login": "Entrar",
+    "manage-tags": "",
+    "manage-tags-description": "",
     "more": "Más...",
     "name": "Nombre",
     "no-results": "Sin resultados",
@@ -559,6 +560,9 @@
     "tax-rates": "Tasas de impuestos",
     "zones": "Zonas"
   },
+  "notify": {
+    "updated-tags-success": ""
+  },
   "order": {
     "add-item-to-order": "",
     "add-note": "",

+ 5 - 1
packages/admin-ui/src/lib/static/i18n-messages/fr.json

@@ -12,7 +12,6 @@
     "original-asset-size": "Taille originale",
     "preview": "Apercu",
     "remove-asset": "Retirer le fichier",
-    "search-asset-name": "Chercher fichier par nom",
     "select-assets": "Selectionner fichiers",
     "set-as-featured-asset": "Définir en tant que fichier en vedette",
     "set-focal-point": "Définir point de focale",
@@ -197,6 +196,8 @@
     "live-update": "Mise à jour automatique",
     "log-out": "Déconnexion",
     "login": "Connexion",
+    "manage-tags": "",
+    "manage-tags-description": "",
     "more": "Plus...",
     "name": "Nom",
     "no-results": "Aucun resultat",
@@ -559,6 +560,9 @@
     "tax-rates": "Taux de Taxe",
     "zones": "Zones"
   },
+  "notify": {
+    "updated-tags-success": ""
+  },
   "order": {
     "add-item-to-order": "",
     "add-note": "Ajouter note",

+ 5 - 1
packages/admin-ui/src/lib/static/i18n-messages/pl.json

@@ -12,7 +12,6 @@
     "original-asset-size": "Rozmiar bazowy",
     "preview": "Podgląd",
     "remove-asset": "Usuń zasób",
-    "search-asset-name": "Szukaj zasobu po nazwie",
     "select-assets": "Wybierz zasób",
     "set-as-featured-asset": "Ustaw jako polecany",
     "set-focal-point": "Ustaw punk centralny",
@@ -197,6 +196,8 @@
     "live-update": "Aktualizacja live",
     "log-out": "Wyloguj",
     "login": "Zaloguj",
+    "manage-tags": "",
+    "manage-tags-description": "",
     "more": "Więcej...",
     "name": "Nazwa",
     "no-results": "Brak wyników",
@@ -559,6 +560,9 @@
     "tax-rates": "Stawki podatkowe",
     "zones": ""
   },
+  "notify": {
+    "updated-tags-success": ""
+  },
   "order": {
     "add-item-to-order": "",
     "add-note": "Dodaj notatke",

+ 5 - 1
packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json

@@ -12,7 +12,6 @@
     "original-asset-size": "Tamanho do arquivo",
     "preview": "Pré-visualização",
     "remove-asset": "Excluir imagens",
-    "search-asset-name": "Procurar imagens por nome",
     "select-assets": "Selecione imagens",
     "set-as-featured-asset": "Definir como imagem em destaque",
     "set-focal-point": "Definir ponto central",
@@ -197,6 +196,8 @@
     "live-update": "Atualização ao vivo",
     "log-out": "Sair",
     "login": "Entrar",
+    "manage-tags": "",
+    "manage-tags-description": "",
     "more": "Mais...",
     "name": "Nome",
     "no-results": "Sem resultados",
@@ -559,6 +560,9 @@
     "tax-rates": "Taxas de impostos",
     "zones": "Zonas"
   },
+  "notify": {
+    "updated-tags-success": ""
+  },
   "order": {
     "add-item-to-order": "",
     "add-note": "Adicionar nota",

+ 5 - 1
packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json

@@ -12,7 +12,6 @@
     "original-asset-size": "资源大小",
     "preview": "预览",
     "remove-asset": "移除资源",
-    "search-asset-name": "输入要搜索的资源名称",
     "select-assets": "选择资源",
     "set-as-featured-asset": "设置为特征图片",
     "set-focal-point": "设置图片焦点",
@@ -197,6 +196,8 @@
     "live-update": "",
     "log-out": "退出",
     "login": "登陆",
+    "manage-tags": "",
+    "manage-tags-description": "",
     "more": "更多...",
     "name": "名称",
     "no-results": "没找到任何结果",
@@ -559,6 +560,9 @@
     "tax-rates": "税表管理",
     "zones": ""
   },
+  "notify": {
+    "updated-tags-success": ""
+  },
   "order": {
     "add-item-to-order": "",
     "add-note": "添加备注",

+ 5 - 1
packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json

@@ -12,7 +12,6 @@
     "original-asset-size": "檔案大小",
     "preview": "預覽",
     "remove-asset": "移除檔案",
-    "search-asset-name": "輸入檔案名稱",
     "select-assets": "選擇檔案",
     "set-as-featured-asset": "設置為精選圖片",
     "set-focal-point": "設置圖片焦點",
@@ -197,6 +196,8 @@
     "live-update": "",
     "log-out": "退出",
     "login": "登陆",
+    "manage-tags": "",
+    "manage-tags-description": "",
     "more": "更多...",
     "name": "名稱",
     "no-results": "没找到任何結果",
@@ -559,6 +560,9 @@
     "tax-rates": "税率管理",
     "zones": ""
   },
+  "notify": {
+    "updated-tags-success": ""
+  },
   "order": {
     "add-item-to-order": "",
     "add-note": "新增備注",