From 125951475a44136aec37d7636e8695651e94b865 Mon Sep 17 00:00:00 2001 From: Kjetil Date: Sat, 28 Mar 2026 06:18:25 +0100 Subject: [PATCH] fix(canvastable): show progress while selecting all rows Batch all-row selection so the UI can repaint and show progress while large folders are being selected. #1551 --- src/app/canvastable/canvastable.spec.ts | 52 ++++++++++++++++++- src/app/canvastable/canvastable.ts | 50 +++++++++++++++--- .../canvastablecontainer.component.html | 3 ++ .../canvastablecontainer.component.scss | 14 +++++ 4 files changed, 111 insertions(+), 8 deletions(-) diff --git a/src/app/canvastable/canvastable.spec.ts b/src/app/canvastable/canvastable.spec.ts index ef51405f1..6e05db9cb 100644 --- a/src/app/canvastable/canvastable.spec.ts +++ b/src/app/canvastable/canvastable.spec.ts @@ -17,7 +17,7 @@ // along with Runbox 7. If not, see . // ---------- END RUNBOX LICENSE ---------- -import { TestBed } from '@angular/core/testing'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { CanvasTableModule, CanvasTableContainerComponent } from './canvastable'; import { MessageList } from '../common/messagelist'; @@ -73,4 +73,54 @@ describe('canvastable', () => { expect(fixture.componentInstance.canvastable.floatingTooltip).toBeTruthy(); expect(fixture.componentInstance.canvastable.columnOverlay).toBeTruthy(); }); + + it('should show progress while selecting all rows in batches', fakeAsync(() => { + const fixture = TestBed.createComponent(CanvasTableContainerComponent); + const rows = new MessageList(Array.from({ length: 600 }, (_, index) => ({ + id: index + 1, + seenFlag: false, + from: [], + to: [], + subject: `Subject ${index + 1}`, + plaintext: '', + size: 0, + attachment: false, + answeredFlag: false, + flaggedFlag: false, + messageDate: new Date() + }))); + + fixture.componentInstance.canvastableselectlistener = { + rowSelected: (rowIndex: number, colIndex: number, multiSelect?: boolean): void => { + rows.rowSelected(rowIndex, colIndex, multiSelect); + }, + saveColumnWidthsPreference: (): void => undefined + }; + fixture.componentInstance.canvastable.rows = rows; + + fixture.componentInstance.canvastable.selectAllRows(); + fixture.detectChanges(); + + expect(fixture.componentInstance.canvastable.bulkSelectionInProgress).toBeTrue(); + expect(fixture.componentInstance.canvastable.bulkSelectionMessage).toContain('Selecting 0 of 600 messages'); + + tick(); + fixture.detectChanges(); + + expect(fixture.componentInstance.canvastable.bulkSelectionInProgress).toBeTrue(); + expect(fixture.componentInstance.canvastable.bulkSelectionMessage).toContain('Selecting 250 of 600 messages'); + + tick(); + fixture.detectChanges(); + + expect(fixture.componentInstance.canvastable.bulkSelectionInProgress).toBeTrue(); + expect(fixture.componentInstance.canvastable.bulkSelectionMessage).toContain('Selecting 500 of 600 messages'); + + tick(); + fixture.detectChanges(); + + expect(fixture.componentInstance.canvastable.bulkSelectionInProgress).toBeFalse(); + expect(fixture.componentInstance.canvastable.bulkSelectionMessage).toBe(''); + expect(rows.selectedMessageIds().length).toBe(600); + })); }); diff --git a/src/app/canvastable/canvastable.ts b/src/app/canvastable/canvastable.ts index 30dbe0895..95af21a3a 100644 --- a/src/app/canvastable/canvastable.ts +++ b/src/app/canvastable/canvastable.ts @@ -84,6 +84,7 @@ export namespace CanvasTable { }) export class CanvasTableComponent implements AfterViewInit, DoCheck, OnInit { static incrementalId = 1; + private static readonly selectAllBatchSize = 250; public elementId: string; private _topindex = 0.0; public get topindex(): number { return this._topindex; } @@ -212,6 +213,8 @@ export class CanvasTableComponent implements AfterViewInit, DoCheck, OnInit { public scrollLimitHit: BehaviorSubject = new BehaviorSubject(0); public floatingTooltip: FloatingTooltip; + public bulkSelectionInProgress = false; + public bulkSelectionMessage = ''; @Input() selectListener: CanvasTableSelectListener; @Output() touchscroll = new EventEmitter(); @@ -723,6 +726,10 @@ export class CanvasTableComponent implements AfterViewInit, DoCheck, OnInit { } public selectRows() { + if (this.bulkSelectionInProgress) { + return; + } + if (this.selectWhichRows === CanvasTable.RowSelect.Visible) { this.selectAllVisibleRows(); } else { @@ -732,14 +739,36 @@ export class CanvasTableComponent implements AfterViewInit, DoCheck, OnInit { public selectAllRows() { const allSelected = this.rows.allSelected(); + const totalRows = this.rows.rowCount(); + let processedRows = 0; - this.rows.rows.forEach((rowobj, rowIndex) => - this.selectListener.rowSelected( - rowIndex, - 0, - !allSelected - ) - ); + if (totalRows === 0) { + return; + } + + this.updateBulkSelectionMessage(allSelected, processedRows, totalRows); + + const selectNextBatch = () => { + const batchEnd = Math.min(processedRows + CanvasTableComponent.selectAllBatchSize, totalRows); + + for (let rowIndex = processedRows; rowIndex < batchEnd; rowIndex++) { + this.selectListener.rowSelected(rowIndex, 0, !allSelected); + } + + processedRows = batchEnd; + + if (processedRows < totalRows) { + this.updateBulkSelectionMessage(allSelected, processedRows, totalRows); + setTimeout(selectNextBatch); + return; + } + + this.bulkSelectionInProgress = false; + this.bulkSelectionMessage = ''; + this.hasChanges = true; + }; + + setTimeout(selectNextBatch); } public selectAllVisibleRows() { @@ -759,6 +788,13 @@ export class CanvasTableComponent implements AfterViewInit, DoCheck, OnInit { this.hasChanges = true; } + private updateBulkSelectionMessage(allSelected: boolean, processedRows: number, totalRows: number) { + this.bulkSelectionInProgress = true; + this.bulkSelectionMessage = + `${allSelected ? 'Clearing' : 'Selecting'} ${processedRows} of ${totalRows} messages...`; + this.hasChanges = true; + } + public selectRow(clientX: number, clientY: number, multiSelect?: boolean) { const selectedRowIndex = this.getRowIndexByClientY(clientY); this.selectRowByIndex(clientX, selectedRowIndex, multiSelect); diff --git a/src/app/canvastable/canvastablecontainer.component.html b/src/app/canvastable/canvastablecontainer.component.html index 0c43569ff..4235dac73 100644 --- a/src/app/canvastable/canvastablecontainer.component.html +++ b/src/app/canvastable/canvastablecontainer.component.html @@ -82,6 +82,9 @@ +
+ {{ canvastable.bulkSelectionMessage }} +