Skip to content

Commit

Permalink
Fix the damn scroll issue finally with trackBy
Browse files Browse the repository at this point in the history
  • Loading branch information
shadowbas authored and bas080 committed Jan 22, 2025
1 parent b5aa06e commit 6887fd9
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 95 deletions.
5 changes: 3 additions & 2 deletions src/app/accessible-table/accessible-table.component.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<cdk-virtual-scroll-viewport [itemSize]="firstRowHeight | async" tabindex="0" class="email-viewport">
<cdk-virtual-scroll-viewport [itemSize]="firstRowHeight" class="email-viewport">
<table
tabindex="0" role="group" aria-label="Messages"
class="mat-mdc-table mdc-data-table__table cdk-table mat-sort mat-elevation-z8"
>
<ng-container *ngTemplateOutlet="theadTemplate"></ng-container>

<ng-container *cdkVirtualFor="let item of items; let $index = index;">
<ng-container *cdkVirtualFor="let item of items; trackBy: trackBy; let $index = index;">
<ng-container *ngTemplateOutlet="tbodyTemplate; context: {$implicit: item, item: item, index: $index}"></ng-container>
</ng-container>

Expand Down
39 changes: 24 additions & 15 deletions src/app/accessible-table/accessible-table.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
import { CommonModule } from '@angular/common';
import { ScrollingModule, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { ListRange } from '@angular/cdk/collections';
import { BehaviorSubject, Subscription } from 'rxjs';
import { Subscription } from 'rxjs';
import { debounceTime } from 'rxjs/operators';

@Component({
Expand All @@ -45,7 +45,7 @@ import { debounceTime } from 'rxjs/operators';
imports: [ScrollingModule, CommonModule, MatCheckboxModule],
templateUrl: './accessible-table.component.html',
styleUrls: ['./accessible-table.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AccessibleTableComponent implements OnDestroy, AfterViewInit, OnChanges {
@ContentChild('tbody', { read: TemplateRef }) tbodyTemplate!: TemplateRef<any> | null;
Expand All @@ -58,7 +58,7 @@ export class AccessibleTableComponent implements OnDestroy, AfterViewInit, OnCha

@Input() scrollToIndex: null | number = null

firstRowHeight = new BehaviorSubject<number>(100);
firstRowHeight: number = 100;

private renderedRangeSub!: Subscription;

Expand All @@ -68,34 +68,43 @@ export class AccessibleTableComponent implements OnDestroy, AfterViewInit, OnCha

ngAfterViewInit() {
this.renderedRangeSub = this.viewport.renderedRangeStream
.pipe(debounceTime(100))
.pipe(debounceTime(500))
.subscribe(range => {
this.renderedRangeChange.emit(range)
});

setTimeout(() => {
this.updateFirstRowHeight();
}, 1000)
}

ngOnDestroy(): void {
this.renderedRangeSub?.unsubscribe();
}

ngOnChanges(changes: SimpleChanges): void {
this.updateFirstRowHeight();
this.updateFirstRowHeight()

if (changes.scrollToIndex && this.items.length > this.scrollToIndex) {
this.viewport?.scrollToIndex(this.scrollToIndex, 'smooth');
this.doScrollToIndex(this.scrollToIndex)
}
}

private updateFirstRowHeight(): void {
const value = this.elementRef
.nativeElement
.parentElement
?.querySelector('tbody')
?.offsetHeight
|| this.firstRowHeight.getValue();
trackBy(index: number) {
return index;
}

console.log('firstRowHeight', value)
doScrollToIndex(index: number) {
return this.viewport?.scrollToIndex(index, 'smooth');
}

this.firstRowHeight.next(value)
private updateFirstRowHeight(): void {
this.firstRowHeight = this
.elementRef
.nativeElement
.parentElement
?.querySelector('tbody')
?.offsetHeight
|| this.firstRowHeight;
}
}
6 changes: 1 addition & 5 deletions src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ canvastablecontainer {
--selected-color: #ccc;
--border-color: #ddd;

--cell-y-spacing: 0.7lh;
--cell-y-spacing: 0.3lh;

& .checkbox-cell {
width: 2rem;
Expand All @@ -112,10 +112,6 @@ canvastablecontainer {
padding-top: var(--cell-y-spacing);
}

& tbody {
border-bottom: 1px solid var(--border-color);
}

& tbody.checked {
background-color: var(--checked-color);
}
Expand Down
84 changes: 11 additions & 73 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import { WebSocketSearchService } from './websocketsearch/websocketsearch.servic
import { WebSocketSearchMailList } from './websocketsearch/websocketsearchmaillist';

import { BUILD_TIMESTAMP } from './buildtimestamp';
import { from, Observable } from 'rxjs';
import { from, Observable, BehaviorSubject } from 'rxjs';
import { xapianLoadedSubject } from './xapian/xapianwebloader';
import { SwPush } from '@angular/service-worker';
import { exportKeysFromJWK } from './webpush/vapid.tools';
Expand Down Expand Up @@ -91,6 +91,10 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis


rows = [];

private rowsSubject= new BehaviorSubject(this.rows);
debouncedRows$ = this.rowsSubject.asObservable().pipe(debounceTime(300));

lastCheckedIndex: number = null;
scrollToIndex: number = 0;
rowSelectionModel = new FilterSelectionModel(
Expand Down Expand Up @@ -945,6 +949,10 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
const matchingRowIndex = this.canvastable.rows.findRowByMessageId(messageId);
if (matchingRowIndex > -1) {
this.rowSelected(matchingRowIndex, 1, false);
// For some reason this needs a timeout.
setTimeout(() => {
this.scrollToIndex = matchingRowIndex
}, 1000)
} else {
this.singlemailviewer.close();
}
Expand All @@ -953,7 +961,6 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
public rowSelected(rowIndex: number, columnIndex: number, multiSelect?: boolean) {
const isSelect = (columnIndex === 0) || multiSelect

this.scrollToIndex = rowIndex
this.rowSelectionModel.select(this.rows[rowIndex])
this.lastCheckedIndex = rowIndex

Expand Down Expand Up @@ -1483,7 +1490,7 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
}

async enrichRows() {
const { start, end } = this.renderedRange
const { start, end } = this.renderedRange;

for (let index = start; index < end; index++) {
const item = {
Expand All @@ -1505,34 +1512,7 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
}

this.rows = Object.create(this.rows)

// Use an emit to trigger redraw in table
return

this.rows = await Promise.all(mapOverIndexes(async (value, index) => {
// const [ index ] = value

// if (value.id) {
// value.plaintext = this.canvastable.columns[3].getContentPreviewText(index)
// return value
// }

const [ rowId ] = value

const doc = this.searchService.getDocData(rowId)

return Object.assign({}, doc, {
size: this.searchService.api.getNumericValue(rowId, 3),
messageDate: this.parseSillyDate(this.searchService.api.getStringValue(rowId, 2)),
from: [{name: doc.from}],
to: doc.recipients, // TODO: Turn into MailAddressInfo
id: this.searchService.getMessageIdFromDocId(rowId),
plaintext: doc.textcontent?.trim() || '',
})

}, start, end, this.rows))

this.lastCheckedIndex = this.lastCheckedIndex?? this.rows[0]
this.rowsSubject.next(this.rows)
}

rangeSelectFrom(from: number, to: number, check: boolean) {
Expand Down Expand Up @@ -1632,48 +1612,6 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis
this.enrichRows()
}

private parseSillyDate(dateString: string) {
return new Date(`${dateString.slice(0, 4)}-${dateString.slice(4, 6)}-${dateString.slice(6, 8)}T${dateString.slice(8, 10)}:${dateString.slice(10, 12)}:00`);
}

}


/**
* Applies a transformation function to a range of indexes in an array.
*
* @param array - The array to process.
* @param leftIndex - The start index (inclusive) of the range to transform.
* @param rightIndex - The end index (inclusive) of the range to transform.
* @param transformFn - The function to apply to elements in the specified range.
* @returns A new array with transformed elements within the range.
* @throws An error if `leftIndex` or `rightIndex` is out of bounds.
*/
function mapOverIndexes<T>(
transformFn: (item: T, index?: number) => T,
leftIndex: number,
rightIndex: number,
array: T[],
): T[] {
rightIndex = Math.max(leftIndex, Math.min(rightIndex, array.length))
leftIndex = Math.min(leftIndex, rightIndex)

if (leftIndex < 0 || leftIndex > rightIndex) {
throw new Error(
`Invalid indexes: leftIndex (${leftIndex}) and rightIndex (${rightIndex}) must be within array bounds [0, ${
array.length
}] and leftIndex <= rightIndex.`
);
}

const result = Object.create(array); // Create a copy of the array to avoid mutation


for (let i = leftIndex; i < rightIndex; i++) {
result[i] = transformFn(result[i], i);
}

return result;
}

const idValue = (x: any) => x.id
Expand Down

0 comments on commit 6887fd9

Please sign in to comment.