Prechádzať zdrojové kódy

feat(admin-ui): Add support for shift-select to DataTableComponent

Relates to #853
Michael Bromley 3 rokov pred
rodič
commit
87f406257f

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

@@ -66,6 +66,24 @@ export class SelectionManager<T> {
         return !!this._selection.find(a => this.options.itemsAreEqual(a, item));
     }
 
+    areAllCurrentItemsSelected(): boolean {
+        return this.items.every(a => this._selection.find(b => this.options.itemsAreEqual(a, b)));
+    }
+
+    toggleSelectAll() {
+        if (this.areAllCurrentItemsSelected()) {
+            this._selection = this._selection.filter(
+                a => !this.items.find(b => this.options.itemsAreEqual(a, b)),
+            );
+        } else {
+            for (const item of this.items) {
+                if (!this._selection.find(a => this.options.itemsAreEqual(a, item))) {
+                    this._selection.push(item);
+                }
+            }
+        }
+    }
+
     lastSelected(): T {
         return this._selection[this._selection.length - 1];
     }

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

@@ -42,7 +42,7 @@ export class AssetGalleryComponent implements OnChanges {
     ngOnChanges(changes: SimpleChanges) {
         if (this.assets) {
             for (const asset of this.selectionManager.selection) {
-                // Update and selected assets with any changes
+                // Update any selected assets with any changes
                 const match = this.assets.find(a => a.id === asset.id);
                 if (match) {
                     Object.assign(asset, match);

+ 3 - 3
packages/admin-ui/src/lib/core/src/shared/components/data-table/data-table.component.html

@@ -1,5 +1,5 @@
 <ng-container *ngIf="!items || (items && items.length); else emptyPlaceholder">
-    <table class="table">
+    <table class="table" [class.no-select]="disableSelect">
         <thead>
             <tr>
                 <th *ngIf="isRowSelectedFn" class="align-middle">
@@ -29,12 +29,12 @@
                     trackBy: trackByFn
                 "
             >
-                <td *ngIf="isRowSelectedFn" class="align-middle">
+                <td *ngIf="isRowSelectedFn" class="align-middle selection-col">
                     <input
                         type="checkbox"
                         clrCheckbox
                         [checked]="isRowSelectedFn(item)"
-                        (change)="rowSelectChange.emit(item)"
+                        (click)="rowSelectChange.emit({ event: $event, item })"
                     />
                 </td>
                 <ng-container

+ 8 - 0
packages/admin-ui/src/lib/core/src/shared/components/data-table/data-table.component.scss

@@ -5,6 +5,10 @@
     overflow: auto;
 }
 
+table.no-select {
+    user-select: none;
+}
+
 thead th {
     position: sticky;
     top: 24px;
@@ -15,6 +19,10 @@ thead th {
     }
 }
 
+.selection-col {
+    width: 24px;
+}
+
 .table-footer {
     display: flex;
     align-items: baseline;

+ 42 - 4
packages/admin-ui/src/lib/core/src/shared/components/data-table/data-table.component.ts

@@ -1,12 +1,17 @@
 import {
     AfterContentInit,
     ChangeDetectionStrategy,
+    ChangeDetectorRef,
     Component,
     ContentChildren,
     EventEmitter,
-    Input, OnChanges,
+    Input,
+    OnChanges,
+    OnDestroy,
+    OnInit,
     Output,
-    QueryList, SimpleChanges,
+    QueryList,
+    SimpleChanges,
     TemplateRef,
 } from '@angular/core';
 import { PaginationService } from 'ngx-pagination';
@@ -79,7 +84,7 @@ import { DataTableColumnComponent } from './data-table-column.component';
     changeDetection: ChangeDetectionStrategy.OnPush,
     providers: [PaginationService],
 })
-export class DataTableComponent<T> implements AfterContentInit, OnChanges {
+export class DataTableComponent<T> implements AfterContentInit, OnChanges, OnInit, OnDestroy {
     @Input() items: T[];
     @Input() itemsPerPage: number;
     @Input() currentPage: number;
@@ -88,7 +93,7 @@ export class DataTableComponent<T> implements AfterContentInit, OnChanges {
     @Input() isRowSelectedFn: (item: T) => boolean;
     @Input() emptyStateLabel: string;
     @Output() allSelectChange = new EventEmitter<void>();
-    @Output() rowSelectChange = new EventEmitter<T>();
+    @Output() rowSelectChange = new EventEmitter<{ event: MouseEvent; item: T }>();
     @Output() pageChange = new EventEmitter<number>();
     @Output() itemsPerPageChange = new EventEmitter<number>();
     @ContentChildren(DataTableColumnComponent) columns: QueryList<DataTableColumnComponent>;
@@ -96,6 +101,39 @@ export class DataTableComponent<T> implements AfterContentInit, OnChanges {
     rowTemplate: TemplateRef<any>;
     currentStart: number;
     currentEnd: number;
+    // This is used to apply a `user-select: none` CSS rule to the table,
+    // which allows shift-click multi-row selection
+    disableSelect = false;
+
+    constructor(private changeDetectorRef: ChangeDetectorRef) {}
+
+    private shiftDownHandler = (event: KeyboardEvent) => {
+        if (event.shiftKey && !this.disableSelect) {
+            this.disableSelect = true;
+            this.changeDetectorRef.markForCheck();
+        }
+    };
+
+    private shiftUpHandler = (event: KeyboardEvent) => {
+        if (this.disableSelect) {
+            this.disableSelect = false;
+            this.changeDetectorRef.markForCheck();
+        }
+    };
+
+    ngOnInit() {
+        if (typeof this.isRowSelectedFn === 'function') {
+            document.addEventListener('keydown', this.shiftDownHandler, { passive: true });
+            document.addEventListener('keyup', this.shiftUpHandler, { passive: true });
+        }
+    }
+
+    ngOnDestroy() {
+        if (typeof this.isRowSelectedFn === 'function') {
+            document.removeEventListener('keydown', this.shiftDownHandler);
+            document.removeEventListener('keyup', this.shiftUpHandler);
+        }
+    }
 
     ngAfterContentInit(): void {
         this.rowTemplate = this.templateRefs.last;

+ 9 - 10
packages/admin-ui/src/lib/customer/src/components/customer-group-member-list/customer-group-member-list.component.ts

@@ -9,8 +9,7 @@ import {
 } from '@angular/core';
 import { FormControl } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
-import { Customer, DataService, GetCustomerGroupWithCustomers } from '@vendure/admin-ui/core';
-import { ZoneMember } from '@vendure/admin-ui/settings';
+import { Customer, DataService } from '@vendure/admin-ui/core';
 import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
 import { debounceTime, distinctUntilChanged, map, startWith, takeUntil, tap } from 'rxjs/operators';
 
@@ -46,15 +45,15 @@ export class CustomerGroupMemberListComponent implements OnInit, OnDestroy {
 
     ngOnInit() {
         this.membersCurrentPage$ = this.route.paramMap.pipe(
-            map((qpm) => qpm.get('membersPage')),
-            map((page) => (!page ? 1 : +page)),
+            map(qpm => qpm.get('membersPage')),
+            map(page => (!page ? 1 : +page)),
             startWith(1),
             distinctUntilChanged(),
         );
 
         this.membersItemsPerPage$ = this.route.paramMap.pipe(
-            map((qpm) => qpm.get('membersPerPage')),
-            map((perPage) => (!perPage ? 10 : +perPage)),
+            map(qpm => qpm.get('membersPerPage')),
+            map(perPage => (!perPage ? 10 : +perPage)),
             startWith(10),
             distinctUntilChanged(),
         );
@@ -114,19 +113,19 @@ export class CustomerGroupMemberListComponent implements OnInit, OnDestroy {
         if (this.areAllSelected()) {
             this.selectionChange.emit([]);
         } else {
-            this.selectionChange.emit(this.members.map((v) => v.id));
+            this.selectionChange.emit(this.members.map(v => v.id));
         }
     }
 
-    toggleSelectMember(member: ZoneMember) {
+    toggleSelectMember({ item: member }: { item: { id: string } }) {
         if (this.selectedMemberIds.includes(member.id)) {
-            this.selectionChange.emit(this.selectedMemberIds.filter((id) => id !== member.id));
+            this.selectionChange.emit(this.selectedMemberIds.filter(id => id !== member.id));
         } else {
             this.selectionChange.emit([...this.selectedMemberIds, member.id]);
         }
     }
 
-    isMemberSelected = (member: ZoneMember): boolean => {
+    isMemberSelected = (member: { id: string }): boolean => {
         return -1 < this.selectedMemberIds.indexOf(member.id);
     };
 }

+ 1 - 1
packages/admin-ui/src/lib/settings/src/components/zone-member-list/zone-member-list.component.ts

@@ -47,7 +47,7 @@ export class ZoneMemberListComponent {
         }
     }
 
-    toggleSelectMember(member: ZoneMember) {
+    toggleSelectMember({ item: member }: { item: ZoneMember }) {
         if (this.selectedMemberIds.includes(member.id)) {
             this.selectionChange.emit(this.selectedMemberIds.filter(id => id !== member.id));
         } else {