Browse Source

refactor(admin-ui): Extract multi selection logic into helper class

Michael Bromley 3 years ago
parent
commit
60c5e0ca17

+ 66 - 0
packages/admin-ui/src/lib/core/src/common/utilities/selection-manager.ts

@@ -0,0 +1,66 @@
+/**
+ * @description
+ * A helper class used to manage selection of list items. Supports multiple selection via
+ * cmd/ctrl/shift key.
+ */
+export class SelectionManager<T> {
+    constructor(private options: { multiSelect: boolean; itemsAreEqual: (a: T, b: T) => boolean }) {}
+
+    get selection(): T[] {
+        return this._selection;
+    }
+
+    private _selection: T[] = [];
+    private items: T[] = [];
+
+    setMultiSelect(isMultiSelect: boolean) {
+        this.options.multiSelect = isMultiSelect;
+    }
+
+    setCurrentItems(items: T[]) {
+        this.items = items;
+    }
+
+    toggleSelection(item: T, event?: MouseEvent) {
+        const { multiSelect, itemsAreEqual } = this.options;
+        const index = this._selection.findIndex(a => itemsAreEqual(a, item));
+        if (multiSelect && event?.shiftKey && 1 <= this._selection.length) {
+            const lastSelection = this._selection[this._selection.length - 1];
+            const lastSelectionIndex = this.items.findIndex(a => itemsAreEqual(a, lastSelection));
+            const currentIndex = this.items.findIndex(a => itemsAreEqual(a, item));
+            const start = currentIndex < lastSelectionIndex ? currentIndex : lastSelectionIndex;
+            const end = currentIndex > lastSelectionIndex ? currentIndex + 1 : lastSelectionIndex;
+            this._selection.push(
+                ...this.items.slice(start, end).filter(a => !this._selection.find(s => itemsAreEqual(a, s))),
+            );
+        } else if (index === -1) {
+            if (multiSelect && (event?.ctrlKey || event?.shiftKey)) {
+                this._selection.push(item);
+            } else {
+                this._selection = [item];
+            }
+        } else {
+            if (multiSelect && event?.ctrlKey) {
+                this._selection.splice(index, 1);
+            } else if (1 < this._selection.length) {
+                this._selection = [item];
+            } else {
+                this._selection.splice(index, 1);
+            }
+        }
+        // Make the selection mutable
+        this._selection = this._selection.map(x => ({ ...x }));
+    }
+
+    selectMultiple(items: T[]) {
+        this._selection = items;
+    }
+
+    isSelected(item: T): boolean {
+        return !!this._selection.find(a => this.options.itemsAreEqual(a, item));
+    }
+
+    lastSelected(): T {
+        return this._selection[this._selection.length - 1];
+    }
+}

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

@@ -22,21 +22,21 @@
 <div class="info-bar">
     <div class="card">
         <div class="card-img">
-            <div class="placeholder" *ngIf="selection.length === 0">
+            <div class="placeholder" *ngIf="selectionManager.selection.length === 0">
                 <clr-icon shape="image" size="128"></clr-icon>
                 <div>{{ 'catalog.no-selection' | translate }}</div>
             </div>
             <img
                 class="preview"
-                *ngIf="selection.length >= 1"
+                *ngIf="selectionManager.selection.length >= 1"
                 [src]="lastSelected().preview + '?preset=medium'"
             />
         </div>
-        <div class="card-block details" *ngIf="selection.length >= 1">
+        <div class="card-block details" *ngIf="selectionManager.selection.length >= 1">
             <div class="name">{{ lastSelected().name }}</div>
             <div>{{ 'asset.original-asset-size' | translate }}: {{ lastSelected().fileSize | filesize }}</div>
 
-            <ng-container *ngIf="selection.length === 1">
+            <ng-container *ngIf="selectionManager.selection.length === 1">
                 <vdr-chip *ngFor="let tag of lastSelected().tags" [colorFrom]="tag.value"
                     ><clr-icon shape="tag" class="mr2"></clr-icon> {{ tag.value }}</vdr-chip
                 >
@@ -55,17 +55,17 @@
                 </div>
             </ng-container>
             <div *ngIf="canDelete">
-                <button (click)="deleteAssets.emit(selection)" class="btn btn-link">
+                <button (click)="deleteAssets.emit(selectionManager.selection)" class="btn btn-link">
                     <clr-icon shape="trash" class="is-danger"></clr-icon> {{ 'common.delete' | translate }}
                 </button>
             </div>
         </div>
     </div>
-    <div class="card stack" [class.visible]="selection.length > 1"></div>
-    <div class="selection-count" [class.visible]="selection.length > 1">
-        {{ 'asset.assets-selected-count' | translate: { count: selection.length } }}
+    <div class="card stack" [class.visible]="selectionManager.selection.length > 1"></div>
+    <div class="selection-count" [class.visible]="selectionManager.selection.length > 1">
+        {{ 'asset.assets-selected-count' | translate: { count: selectionManager.selection.length } }}
         <ul>
-            <li *ngFor="let asset of selection">{{ asset.name }}</li>
+            <li *ngFor="let asset of selectionManager.selection">{{ asset.name }}</li>
         </ul>
     </div>
 </div>

+ 29 - 37
packages/admin-ui/src/lib/core/src/shared/components/asset-gallery/asset-gallery.component.ts

@@ -1,6 +1,15 @@
-import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
+import {
+    ChangeDetectionStrategy,
+    Component,
+    EventEmitter,
+    Input,
+    OnChanges,
+    Output,
+    SimpleChanges,
+} from '@angular/core';
 
-import { Asset, GetAssetList } from '../../../common/generated-types';
+import { GetAssetList } from '../../../common/generated-types';
+import { SelectionManager } from '../../../common/utilities/selection-manager';
 import { ModalService } from '../../../providers/modal/modal.service';
 import { AssetPreviewDialogComponent } from '../asset-preview-dialog/asset-preview-dialog.component';
 
@@ -22,13 +31,16 @@ export class AssetGalleryComponent implements OnChanges {
     @Output() selectionChange = new EventEmitter<AssetLike[]>();
     @Output() deleteAssets = new EventEmitter<AssetLike[]>();
 
-    selection: AssetLike[] = [];
+    selectionManager = new SelectionManager<AssetLike>({
+        multiSelect: this.multiSelect,
+        itemsAreEqual: (a, b) => a.id === b.id,
+    });
 
     constructor(private modalService: ModalService) {}
 
-    ngOnChanges() {
+    ngOnChanges(changes: SimpleChanges) {
         if (this.assets) {
-            for (const asset of this.selection) {
+            for (const asset of this.selectionManager.selection) {
                 // Update and selected assets with any changes
                 const match = this.assets.find(a => a.id === asset.id);
                 if (match) {
@@ -36,50 +48,30 @@ export class AssetGalleryComponent implements OnChanges {
                 }
             }
         }
+        if (changes['assets']) {
+            this.selectionManager.setCurrentItems(this.assets);
+        }
+        if (changes['multiSelect']) {
+            this.selectionManager.setMultiSelect(this.multiSelect);
+        }
     }
 
     toggleSelection(asset: AssetLike, event?: MouseEvent) {
-        const index = this.selection.findIndex(a => a.id === asset.id);
-        if (this.multiSelect && event?.shiftKey && 1 <= this.selection.length) {
-            const lastSelection = this.selection[this.selection.length - 1];
-            const lastSelectionIndex = this.assets.findIndex(a => a.id === lastSelection.id);
-            const currentIndex = this.assets.findIndex(a => a.id === asset.id);
-            const start = currentIndex < lastSelectionIndex ? currentIndex : lastSelectionIndex;
-            const end = currentIndex > lastSelectionIndex ? currentIndex + 1 : lastSelectionIndex;
-            this.selection.push(
-                ...this.assets.slice(start, end).filter(a => !this.selection.find(s => s.id === a.id)),
-            );
-        } else if (index === -1) {
-            if (this.multiSelect && (event?.ctrlKey || event?.shiftKey)) {
-                this.selection.push(asset);
-            } else {
-                this.selection = [asset];
-            }
-        } else {
-            if (this.multiSelect && event?.ctrlKey) {
-                this.selection.splice(index, 1);
-            } else if (1 < this.selection.length) {
-                this.selection = [asset];
-            } else {
-                this.selection.splice(index, 1);
-            }
-        }
-        // Make the selection mutable
-        this.selection = this.selection.map(x => ({ ...x }));
-        this.selectionChange.emit(this.selection);
+        this.selectionManager.toggleSelection(asset, event);
+        this.selectionChange.emit(this.selectionManager.selection);
     }
 
     selectMultiple(assets: AssetLike[]) {
-        this.selection = assets;
-        this.selectionChange.emit(this.selection);
+        this.selectionManager.selectMultiple(assets);
+        this.selectionChange.emit(this.selectionManager.selection);
     }
 
     isSelected(asset: AssetLike): boolean {
-        return !!this.selection.find(a => a.id === asset.id);
+        return this.selectionManager.isSelected(asset);
     }
 
     lastSelected(): AssetLike {
-        return this.selection[this.selection.length - 1];
+        return this.selectionManager.lastSelected();
     }
 
     previewAsset(asset: AssetLike) {