From 9ecdef1205375529aa0cfff54e9de30d3a95c415 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Tue, 18 Jan 2022 15:51:20 +0200 Subject: [PATCH 1/4] add virtual scroll in workflow --- src/lib/listing/index.ts | 4 ++ .../table-content/table-content.component.ts | 4 +- .../column-header/column-header.component.ts | 4 +- .../workflow/models/entity-wrapper.model.ts | 25 +++++++ .../workflow/models/workflow-column.model.ts | 11 +++ .../workflow/models/workflow-config.model.ts | 8 +++ .../listing/workflow/workflow.component.html | 42 +++++------ .../listing/workflow/workflow.component.ts | 70 +++++-------------- src/lib/utils/functions.ts | 2 +- 9 files changed, 91 insertions(+), 79 deletions(-) create mode 100644 src/lib/listing/workflow/models/entity-wrapper.model.ts create mode 100644 src/lib/listing/workflow/models/workflow-column.model.ts create mode 100644 src/lib/listing/workflow/models/workflow-config.model.ts diff --git a/src/lib/listing/index.ts b/src/lib/listing/index.ts index 2c3e281..208abd9 100644 --- a/src/lib/listing/index.ts +++ b/src/lib/listing/index.ts @@ -15,3 +15,7 @@ export * from './listing-component.directive'; export * from './page-header/page-header.component'; export * from './page-header/models'; + +export * from './workflow/models/entity-wrapper.model'; +export * from './workflow/models/workflow-config.model'; +export * from './workflow/models/workflow-column.model'; diff --git a/src/lib/listing/table-content/table-content.component.ts b/src/lib/listing/table-content/table-content.component.ts index 57f48b3..dd7635b 100644 --- a/src/lib/listing/table-content/table-content.component.ts +++ b/src/lib/listing/table-content/table-content.component.ts @@ -1,7 +1,7 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, forwardRef, Inject, Input, OnDestroy, ViewChild } from '@angular/core'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { delay, tap } from 'rxjs/operators'; -import { AutoUnsubscribe, trackBy } from '../../utils'; +import { AutoUnsubscribe, trackByFactory } from '../../utils'; import { IListable } from '../models'; import { ListingComponent, ListingService } from '../index'; import { HasScrollbarDirective } from '../../scrollbar'; @@ -20,7 +20,7 @@ export class TableContentComponent extends AutoUnsubscribe @Input() tableItemClasses?: Record boolean>; @Input() selectionEnabled!: boolean; @Input() helpModeKey?: 'dossier-list' | 'document-list'; - readonly trackBy = trackBy(); + readonly trackBy = trackByFactory(); @ViewChild(CdkVirtualScrollViewport, { static: true }) readonly scrollViewport!: CdkVirtualScrollViewport; @ViewChild(HasScrollbarDirective, { static: true }) readonly hasScrollbarDirective!: HasScrollbarDirective; diff --git a/src/lib/listing/workflow/column-header/column-header.component.ts b/src/lib/listing/workflow/column-header/column-header.component.ts index 5403981..aaae18a 100644 --- a/src/lib/listing/workflow/column-header/column-header.component.ts +++ b/src/lib/listing/workflow/column-header/column-header.component.ts @@ -15,8 +15,8 @@ import { filter, map, tap } from 'rxjs/operators'; import { CircleButtonTypes } from '../../../buttons'; import { IListable } from '../../models'; import { AutoUnsubscribe, Debounce } from '../../../utils'; -import { WorkflowColumn } from '../workflow.component'; import { ListingService } from '../../services'; +import { WorkflowColumn } from '../models/workflow-column.model'; @Component({ selector: 'iqser-column-header [column] [selectionColumn]', @@ -105,6 +105,6 @@ export class ColumnHeaderComponent extend this._updateBulkActionsContainerWidth(entries[0]); }); - observer.observe(this.bulkActionsContainer?.nativeElement); + observer.observe(this.bulkActionsContainer?.nativeElement as Element); } } diff --git a/src/lib/listing/workflow/models/entity-wrapper.model.ts b/src/lib/listing/workflow/models/entity-wrapper.model.ts new file mode 100644 index 0000000..8155e7e --- /dev/null +++ b/src/lib/listing/workflow/models/entity-wrapper.model.ts @@ -0,0 +1,25 @@ +import { IListable } from '@iqser/common-ui'; +import { BehaviorSubject, Observable } from 'rxjs'; + +export class EntityWrapper { + readonly classes$: BehaviorSubject>; + + constructor( + readonly entity: T, + private readonly _itemClasses: Record boolean>, + readonly isSelected$: Observable, + ) { + this.classes$ = new BehaviorSubject>(this._getItemClasses(entity)); + } + + private _getItemClasses(entity: T): Record { + const classes: { [key: string]: boolean } = {}; + for (const key in this._itemClasses) { + if (Object.prototype.hasOwnProperty.call(this._itemClasses, key)) { + classes[key] = this._itemClasses[key](entity); + } + } + classes.item = true; + return classes; + } +} diff --git a/src/lib/listing/workflow/models/workflow-column.model.ts b/src/lib/listing/workflow/models/workflow-column.model.ts new file mode 100644 index 0000000..537e0bf --- /dev/null +++ b/src/lib/listing/workflow/models/workflow-column.model.ts @@ -0,0 +1,11 @@ +import { IListable } from '@iqser/common-ui'; +import { BehaviorSubject } from 'rxjs'; + +export interface WorkflowColumn { + key: K; + label: string; + color: string; + enterFn: (entities: T[]) => Promise | void; + enterPredicate: (entities: T[]) => boolean; + entities: BehaviorSubject; +} diff --git a/src/lib/listing/workflow/models/workflow-config.model.ts b/src/lib/listing/workflow/models/workflow-config.model.ts new file mode 100644 index 0000000..ed9e360 --- /dev/null +++ b/src/lib/listing/workflow/models/workflow-config.model.ts @@ -0,0 +1,8 @@ +import { IListable } from '@iqser/common-ui'; +import { WorkflowColumn } from './workflow-column.model'; + +export interface WorkflowConfig { + columnIdentifierFn: (entity: T) => K; + itemVersionFn: (entity: T) => string; + columns: WorkflowColumn[]; +} diff --git a/src/lib/listing/workflow/workflow.component.html b/src/lib/listing/workflow/workflow.component.html index 58c087e..a0b4f12 100644 --- a/src/lib/listing/workflow/workflow.component.html +++ b/src/lib/listing/workflow/workflow.component.html @@ -1,4 +1,4 @@ - + @@ -27,12 +27,13 @@ class="column" > -
@@ -40,7 +41,7 @@ (cdkDragEnded)="stopDragging()" (cdkDragStarted)="startDragging(column, $event)" (click)="selectionColumn === column && listingService.select(entity)" - *ngFor="let entity of entities" + *cdkVirtualFor="let entity of column.entities; trackBy: trackBy" [cdkDragData]="entity" [class.no-border]="dragging && draggingEntities.includes(entity)" [class.selected]="all[entity.id].isSelected$ | async" @@ -50,26 +51,27 @@ - -
-
-
- -
- -
- -
-
-
+ +
+
+
+ +
+ +
+ +
+
+
+
- + diff --git a/src/lib/listing/workflow/workflow.component.ts b/src/lib/listing/workflow/workflow.component.ts index 7932f7f..e9b9910 100644 --- a/src/lib/listing/workflow/workflow.component.ts +++ b/src/lib/listing/workflow/workflow.component.ts @@ -15,61 +15,25 @@ import { } from '@angular/core'; import { ListingComponent } from '../listing-component.directive'; import { CdkDragDrop, CdkDragStart, CdkDropList } from '@angular/cdk/drag-drop'; -import { AutoUnsubscribe, Debounce, Required } from '../../utils'; -import { LoadingService } from '../../loading'; +import { AutoUnsubscribe, Debounce, trackByFactory } from '../../utils'; import { IListable } from '../models'; import { EntitiesService, ListingService } from '../services'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { filter, tap } from 'rxjs/operators'; - -export interface WorkflowColumn { - key: K; - label: string; - color: string; - enterFn: (entities: T[]) => Promise | void; - enterPredicate: (entities: T[]) => boolean; - entities: BehaviorSubject; -} - -export interface WorkflowConfig { - columnIdentifierFn: (entity: T) => K; - itemVersionFn: (entity: T) => string; - columns: WorkflowColumn[]; -} - -class EntityWrapper { - readonly classes$: BehaviorSubject>; - - constructor( - readonly entity: T, - private readonly _itemClasses: Record boolean>, - readonly isSelected$: Observable, - ) { - this.classes$ = new BehaviorSubject>(this._getItemClasses(entity)); - } - - private _getItemClasses(entity: T): Record { - const classes: { [key: string]: boolean } = {}; - for (const key in this._itemClasses) { - if (Object.prototype.hasOwnProperty.call(this._itemClasses, key)) { - classes[key] = this._itemClasses[key](entity); - } - } - classes.item = true; - return classes; - } -} +import { WorkflowConfig } from './models/workflow-config.model'; +import { WorkflowColumn } from './models/workflow-column.model'; +import { EntityWrapper } from './models/entity-wrapper.model'; @Component({ - selector: 'iqser-workflow', + selector: 'iqser-workflow [itemTemplate] [config]', templateUrl: './workflow.component.html', styleUrls: ['./workflow.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class WorkflowComponent extends AutoUnsubscribe implements OnInit { @Input() headerTemplate?: TemplateRef; - @Input() @Required() itemTemplate!: TemplateRef; - @Input() @Required() config!: WorkflowConfig; + @Input() itemTemplate!: TemplateRef; + @Input() config!: WorkflowConfig; @Input() itemClasses!: Record boolean>; @Input() addElementIcon?: string; @Input() addElementColumn?: K; @@ -82,6 +46,7 @@ export class WorkflowComponent extends Au @Output() readonly noDataAction = new EventEmitter(); @Output() readonly addElement = new EventEmitter(); + readonly trackBy = trackByFactory(); itemHeight?: number; itemWidth?: number; dragging = false; @@ -90,13 +55,12 @@ export class WorkflowComponent extends Au readonly draggingEntities$ = new BehaviorSubject([]); all: { [key: string]: EntityWrapper } = {}; @ViewChildren(CdkDropList) private readonly _dropLists!: QueryList; - private _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => { + private readonly _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => { this._updateItemSize(entries[0]); }); constructor( @Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent, - private readonly _loadingService: LoadingService, private readonly _changeRef: ChangeDetectorRef, private readonly _elementRef: ElementRef, readonly listingService: ListingService, @@ -105,10 +69,6 @@ export class WorkflowComponent extends Au super(); } - get tableHeaderLabel(): string | undefined { - return this.listingComponent.tableHeaderLabel; - } - async move(event: CdkDragDrop): Promise { if (event.previousContainer !== event.container) { const column = this._getColumnByKey((event.container.id) as K); @@ -135,9 +95,9 @@ export class WorkflowComponent extends Au return () => column.enterPredicate(this.draggingEntities$.value); } - startDragging(column: WorkflowColumn, $event: CdkDragStart): void { - const entity: T = $event.source.data as T; - if (this.listingService.selected.includes(entity)) { + startDragging(column: WorkflowColumn, $event: CdkDragStart): void { + const entity = $event.source.data; + if (this.listingService.selectedIds.includes(entity.id)) { this.draggingEntities$.next(this.listingService.selected); } else { this.draggingEntities$.next([entity]); @@ -214,7 +174,7 @@ export class WorkflowComponent extends Au } }); - this._changeRef.detectChanges(); + this._changeRef.markForCheck(); } private _addEntity(entity: T): void { @@ -246,12 +206,14 @@ export class WorkflowComponent extends Au private _removeEntity(entity: T): void { const existingEntity = this.all[entity.id]; const column = this._getColumnByKey(this.config.columnIdentifierFn(existingEntity.entity)); + if (column) { const entities = column.entities.value; const idx = entities.findIndex(item => item.id === entity.id); entities.splice(idx, 1); column.entities.next(entities); } + delete this.all[entity.id]; } diff --git a/src/lib/utils/functions.ts b/src/lib/utils/functions.ts index 302c4d7..912c7f9 100644 --- a/src/lib/utils/functions.ts +++ b/src/lib/utils/functions.ts @@ -31,6 +31,6 @@ export function toNumber(str: string): number { } } -export function trackBy() { +export function trackByFactory() { return (index: number, item: T): string => item.id; } From 215e418e4021d58cd067435410355931298a422c Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Wed, 19 Jan 2022 10:36:50 +0200 Subject: [PATCH 2/4] fix virtual scroll drag&drop preview and placeholder --- .../listing/workflow/workflow.component.html | 30 +++++++++---------- .../listing/workflow/workflow.component.scss | 1 - .../listing/workflow/workflow.component.ts | 3 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/lib/listing/workflow/workflow.component.html b/src/lib/listing/workflow/workflow.component.html index a0b4f12..03fcb5d 100644 --- a/src/lib/listing/workflow/workflow.component.html +++ b/src/lib/listing/workflow/workflow.component.html @@ -33,7 +33,7 @@ [cdkDropListData]="entities" [cdkDropListEnterPredicate]="canMoveTo(column)" [id]="column.key" - [itemSize]="80" + [itemSize]="itemHeight" cdkDropList cdkDropListSortingDisabled > @@ -51,22 +51,22 @@ - -
-
-
+
+
+
-
- -
- -
-
+
+ +
+ +
+
+
diff --git a/src/lib/listing/workflow/workflow.component.scss b/src/lib/listing/workflow/workflow.component.scss index 2e58e78..7943c0e 100644 --- a/src/lib/listing/workflow/workflow.component.scss +++ b/src/lib/listing/workflow/workflow.component.scss @@ -58,7 +58,6 @@ > .heading, .add-btn, ::ng-deep.item > * > * { opacity: 0.3; } - } } } diff --git a/src/lib/listing/workflow/workflow.component.ts b/src/lib/listing/workflow/workflow.component.ts index e9b9910..eff1ee0 100644 --- a/src/lib/listing/workflow/workflow.component.ts +++ b/src/lib/listing/workflow/workflow.component.ts @@ -125,8 +125,9 @@ export class WorkflowComponent extends Au return !!this._getListById(column.key)?._dropListRef.isReceiving(); } + @Debounce(30) private _setupResizeObserver(): void { - const cdkDragElement: Element = this._elementRef.nativeElement.querySelector('.cdk-drag') as Element; + const cdkDragElement = this._elementRef.nativeElement.querySelector('.cdk-drag') as Element; if (cdkDragElement) { this._observer.observe(cdkDragElement); } From 212576cb77e56ed925d40366e4914fb0753d0fc9 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Thu, 20 Jan 2022 12:34:56 +0200 Subject: [PATCH 3/4] fix show add button when multi select active --- src/lib/listing/workflow/workflow.component.html | 1 + src/lib/listing/workflow/workflow.component.scss | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/lib/listing/workflow/workflow.component.html b/src/lib/listing/workflow/workflow.component.html index 03fcb5d..7306332 100644 --- a/src/lib/listing/workflow/workflow.component.html +++ b/src/lib/listing/workflow/workflow.component.html @@ -32,6 +32,7 @@ *ngIf="column.entities | async as entities" [cdkDropListData]="entities" [cdkDropListEnterPredicate]="canMoveTo(column)" + [class.multi-select-active]="selectionColumn === column" [id]="column.key" [itemSize]="itemHeight" cdkDropList diff --git a/src/lib/listing/workflow/workflow.component.scss b/src/lib/listing/workflow/workflow.component.scss index 7943c0e..99aa44c 100644 --- a/src/lib/listing/workflow/workflow.component.scss +++ b/src/lib/listing/workflow/workflow.component.scss @@ -67,6 +67,10 @@ overflow-y: auto; @include no-scroll-bar; min-height: calc(100% - 36px); + + &.multi-select-active { + min-height: calc(100% - 86px); + } } .item { From 3616ccaf42f0e50a440d040805cc1c2200f9d8fe Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Thu, 20 Jan 2022 14:46:30 +0200 Subject: [PATCH 4/4] use ng-template for drop preview and placeholder --- .../listing/workflow/workflow.component.html | 83 ++++++++++--------- .../listing/workflow/workflow.component.scss | 5 ++ 2 files changed, 47 insertions(+), 41 deletions(-) diff --git a/src/lib/listing/workflow/workflow.component.html b/src/lib/listing/workflow/workflow.component.html index 7306332..336d191 100644 --- a/src/lib/listing/workflow/workflow.component.html +++ b/src/lib/listing/workflow/workflow.component.html @@ -27,51 +27,52 @@ class="column" > - +
- - - - -
-
-
- -
- -
- -
+
+ + -
-
-
- + +
+
+ + + +
+ +
+
+
+
+ +
+ +
diff --git a/src/lib/listing/workflow/workflow.component.scss b/src/lib/listing/workflow/workflow.component.scss index 99aa44c..a98fcc7 100644 --- a/src/lib/listing/workflow/workflow.component.scss +++ b/src/lib/listing/workflow/workflow.component.scss @@ -127,3 +127,8 @@ border-radius: 8px; margin: 0 8px 4px 8px; } + +cdk-virtual-scroll-viewport { + height: 100%; + @include no-scroll-bar; +}