+
+
+
+
+
+
diff --git a/src/lib/listing/workflow/workflow.component.ts b/src/lib/listing/workflow/workflow.component.ts
index a364ad9..58ace36 100644
--- a/src/lib/listing/workflow/workflow.component.ts
+++ b/src/lib/listing/workflow/workflow.component.ts
@@ -14,15 +14,15 @@ import {
ViewChildren,
} from '@angular/core';
import { ListingComponent } from '../listing-component.directive';
-import { CdkDrag, CdkDragDrop, CdkDropList } from '@angular/cdk/drag-drop';
+import { CdkDrag, CdkDragDrop, CdkDragStart, CdkDropList } from '@angular/cdk/drag-drop';
import { AutoUnsubscribe, Debounce, Required } from '../../utils';
import { LoadingService } from '../../loading';
import { IListable } from '../models';
import { EntitiesService, ListingService } from '../services';
-import { BehaviorSubject } from 'rxjs';
+import { BehaviorSubject, Observable } from 'rxjs';
import { filter, tap } from 'rxjs/operators';
-export interface WorkflowColumn {
+export interface WorkflowColumn {
key: K;
label: string;
color: string;
@@ -31,12 +31,35 @@ export interface WorkflowColumn {
entities: BehaviorSubject;
}
-export interface WorkflowConfig {
+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;
+ }
+}
+
@Component({
selector: 'iqser-workflow',
templateUrl: './workflow.component.html',
@@ -47,7 +70,7 @@ export class WorkflowComponent extends Au
@Input() headerTemplate?: TemplateRef;
@Input() @Required() itemTemplate!: TemplateRef;
@Input() @Required() config!: WorkflowConfig;
- @Input() itemClasses?: { [key: string]: (e: T) => boolean };
+ @Input() itemClasses!: Record boolean>;
@Input() addElementIcon?: string;
@Input() addElementColumn?: K;
@Input() noDataText?: string;
@@ -64,8 +87,9 @@ export class WorkflowComponent extends Au
dragging = false;
sourceColumn?: WorkflowColumn;
selectionColumn?: WorkflowColumn;
+ readonly draggingEntities$ = new BehaviorSubject([]);
+ all: { [key: string]: EntityWrapper } = {};
@ViewChildren(CdkDropList) private readonly _dropLists!: QueryList;
- private _existingEntities: { [key: string]: T } = {};
private _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
this._updateItemWidth(entries[0]);
});
@@ -85,31 +109,13 @@ export class WorkflowComponent extends Au
return this.listingComponent.tableHeaderLabel;
}
- getItemClasses(entity: T, preview = false): { [key: string]: boolean } {
- 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;
- if (!preview) {
- classes['no-border'] = this.dragging && this.listingService.isSelected(entity);
- }
- return classes;
- }
-
- async move(event: CdkDragDrop<(T | number)[]>): Promise {
+ async move(event: CdkDragDrop): Promise {
if (event.previousContainer !== event.container) {
const column = this._getColumnByKey((event.container.id) as K);
- if (this.selectionColumn) {
- // TODO: Improve this
- await Promise.all(this.listingService.selected.map(file => column.enterFn(file)));
- this.listingService.setSelected([]);
- } else {
- await column.enterFn(event.item.data);
- }
+ // TODO: Improve this
+ await Promise.all(this.draggingEntities$.value.map(entity => column.enterFn(entity)));
+ this.listingService.setSelected([]); // TODO: Clear only when moving selected???
}
}
@@ -128,12 +134,19 @@ export class WorkflowComponent extends Au
return (item: CdkDrag) => column.enterPredicate(item.data);
}
- startDragging(column: WorkflowColumn): void {
+ startDragging(column: WorkflowColumn, $event: CdkDragStart): void {
+ const entity: T = $event.source.data as T;
+ if (this.listingService.selected.includes(entity)) {
+ this.draggingEntities$.next(this.listingService.selected);
+ } else {
+ this.draggingEntities$.next([entity]);
+ }
this.dragging = true;
this.sourceColumn = column;
}
stopDragging(): void {
+ this.draggingEntities$.next([]);
this.dragging = false;
this.sourceColumn = undefined;
}
@@ -159,9 +172,10 @@ export class WorkflowComponent extends Au
@Debounce(30)
private _updateItemWidth(entry: ResizeObserverEntry): void {
- if (entry.contentRect.width === 0) {
+ if (entry.contentRect.height === 0) {
this._observer.unobserve(entry.target);
this._setupResizeObserver();
+ return;
}
this.itemWidth = entry.contentRect.width;
this.itemHeight = entry.contentRect.height;
@@ -180,22 +194,20 @@ export class WorkflowComponent extends Au
// Remove deleted entities
const updatedIds = entities.map(entity => entity.id);
- for (const id of Object.keys(this._existingEntities)) {
+ for (const id of Object.keys(this.all)) {
if (!updatedIds.includes(id)) {
- this._removeEntity(this._existingEntities[id]);
+ this._removeEntity(this.all[id].entity);
}
}
// Add or move updated entities
entities.forEach(entity => {
const shouldAdd = this._shouldAdd(entity);
- const shouldMove = this._shouldMove(entity);
+ const shouldUpdate = this._shouldUpdate(entity);
- if (shouldMove) {
- this._removeEntity(entity);
- }
-
- if (shouldAdd || shouldMove) {
+ if (shouldUpdate) {
+ this._updateEntity(entity);
+ } else if (shouldAdd) {
this._addEntity(entity);
}
});
@@ -208,29 +220,46 @@ export class WorkflowComponent extends Au
if (!column) {
return;
}
- this._existingEntities[entity.id] = entity;
+ this.all[entity.id] = new EntityWrapper(entity, this.itemClasses, this.listingService.isSelected$(entity));
column.entities.next([...column.entities.value, entity]);
}
+ private _updateEntity(newEntity: T): void {
+ const existingEntity = this.all[newEntity.id];
+ const oldColumn = this._getColumnByKey(this.config.columnIdentifierFn(existingEntity.entity));
+ const newColumn = this._getColumnByKey(this.config.columnIdentifierFn(newEntity));
+
+ if (oldColumn === newColumn) {
+ this.all[newEntity.id] = new EntityWrapper(newEntity, this.itemClasses, this.listingService.isSelected$(newEntity));
+ const entitiesArr = [...oldColumn.entities.value];
+ const idx = entitiesArr.indexOf(existingEntity.entity);
+ entitiesArr[idx] = newEntity;
+ newColumn.entities.next(entitiesArr);
+ } else {
+ this._removeEntity(newEntity);
+ this._addEntity(newEntity);
+ }
+ }
+
private _removeEntity(entity: T): void {
- const existingEntity = this._existingEntities[entity.id];
- const column = this._getColumnByKey(this.config.columnIdentifierFn(existingEntity));
+ 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._existingEntities[entity.id];
+ delete this.all[entity.id];
}
- private _shouldMove(entity: T): boolean {
- const existingEntity = this._existingEntities[entity.id];
+ private _shouldUpdate(entity: T): boolean {
+ const existingEntity = this.all[entity.id]?.entity;
return existingEntity && this.config.itemVersionFn(entity) !== this.config.itemVersionFn(existingEntity);
}
private _shouldAdd(entity: T): boolean {
- return !this._existingEntities[entity.id];
+ return !this.all[entity.id];
}
private _getColumnByKey(key: K | undefined): WorkflowColumn {