diff --git a/src/assets/icons/more-actions.svg b/src/assets/icons/more-actions.svg new file mode 100644 index 0000000..0d75f43 --- /dev/null +++ b/src/assets/icons/more-actions.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/src/lib/icons/icons.module.ts b/src/lib/icons/icons.module.ts index dfafb07..6869045 100644 --- a/src/lib/icons/icons.module.ts +++ b/src/lib/icons/icons.module.ts @@ -28,6 +28,7 @@ export class IqserIconsModule { 'list', 'logout', 'menu', + 'more-actions', 'ocr', 'offline', 'pages', @@ -44,11 +45,7 @@ export class IqserIconsModule { 'upload', ]); icons.forEach(icon => { - _iconRegistry.addSvgIconInNamespace( - 'iqser', - icon, - _sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/${icon}.svg`), - ); + _iconRegistry.addSvgIconInNamespace('iqser', icon, _sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/${icon}.svg`)); }); } } diff --git a/src/lib/listing/workflow/column-header/column-header.component.css b/src/lib/listing/workflow/column-header/column-header.component.scss similarity index 100% rename from src/lib/listing/workflow/column-header/column-header.component.css rename to src/lib/listing/workflow/column-header/column-header.component.scss 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 929b570..2e45723 100644 --- a/src/lib/listing/workflow/column-header/column-header.component.ts +++ b/src/lib/listing/workflow/column-header/column-header.component.ts @@ -14,23 +14,23 @@ import { combineLatest, Observable } from 'rxjs'; import { filter, map, tap } from 'rxjs/operators'; import { CircleButtonTypes } from '../../../buttons'; import { IListable } from '../../models'; -import { AutoUnsubscribe, Debounce, Required } from '../../../utils'; +import { AutoUnsubscribe, Debounce } from '../../../utils'; import { WorkflowColumn } from '../workflow.component'; import { ListingService } from '../../services'; @Component({ - selector: 'iqser-column-header', + selector: 'iqser-column-header [column] [selectionColumn]', templateUrl: './column-header.component.html', - styleUrls: ['./column-header.component.css'], + styleUrls: ['./column-header.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class ColumnHeaderComponent extends AutoUnsubscribe implements OnInit { readonly circleButtonTypes = CircleButtonTypes; - @Input() @Required() column!: WorkflowColumn; - @Input() @Required() selectionColumn?: WorkflowColumn; + @Input() column!: WorkflowColumn; + @Input() selectionColumn?: WorkflowColumn; @Input() bulkActions?: TemplateRef; - @Output() @Required() readonly selectionColumnChange = new EventEmitter | undefined>(); + @Output() readonly selectionColumnChange = new EventEmitter | undefined>(); allSelected$!: Observable; indeterminate$!: Observable; diff --git a/src/lib/listing/workflow/workflow.component.html b/src/lib/listing/workflow/workflow.component.html index b85076c..f87f517 100644 --- a/src/lib/listing/workflow/workflow.component.html +++ b/src/lib/listing/workflow/workflow.component.html @@ -15,7 +15,7 @@ [text]="noDataText" > -
-
+ + + + +
- -
+
- -
+ +
- - - +
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 {