Files
ingey-eager/src/app/file-viewer/file-viewer.component.ts
vato007 c2db4100a9
All checks were successful
build / build (push) Successful in 1m31s
Use flexible scroll height on table to fill view
2025-04-24 22:19:52 +09:30

127 lines
3.7 KiB
TypeScript

import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
effect,
inject,
input,
signal,
viewChild,
} from '@angular/core';
import { Skeleton } from 'primeng/skeleton';
import { Table, TableLazyLoadEvent, TableModule } from 'primeng/table';
import { Column, DuckdbService } from '../duckdb.service';
@Component({
selector: 'app-file-viewer',
standalone: true,
imports: [TableModule, Skeleton],
template: `
@if (file() && columns().length > 0) {
<p-table
[reorderableColumns]="true"
[resizableColumns]="true"
columnResizeMode="expand"
[columns]="columns()"
[value]="currentValue()"
showGridlines
[scrollable]="true"
scrollHeight="flex"
[rows]="100"
[virtualScroll]="true"
[virtualScrollItemSize]="46"
[lazy]="true"
(onLazyLoad)="onLazyLoad($event)"
>
<ng-template #header let-columns>
<tr>
@for (col of columns; track $index) {
<th pReorderableColumn pResizableColumn>
{{ col.name }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowData let-columns="columns">
<tr style="height: 46px">
@for (col of columns; track $index) {
<td pReorderableRowHandle>
{{ rowData[col.name] }}
</td>
}
</tr>
</ng-template>
<ng-template #loadingbody let-columns="columns">
<tr style="height: 46px">
@for (col of columns; track $index) {
<td>
<p-skeleton />
</td>
}
</tr>
</ng-template>
</p-table>
}
`,
styleUrl: './file-viewer.component.css',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FileViewerComponent {
file = input<File | undefined>();
private duckdbService = inject(DuckdbService);
private cdr = inject(ChangeDetectorRef);
// Can't be computed since effect has to run first so file exists in duckdb
protected columns = signal<Column[]>([]);
private table = viewChild(Table);
// TODO: Basically make this an array of the same length as the number of rows, but an empty array.
// We'll then store the actual current values in a separate array, then use the offset + row index
// to get the actual value. This is so we don't store a crazy amount of data.
// Alternative is to store current page data in the array (but keep the array length), and clear out the array each
// time the page changes.
protected currentValue = signal<any[]>([]);
constructor() {
effect(async () => {
const file = this.file();
if (file) {
await this.duckdbService.addFile(file);
this.columns.set(await this.duckdbService.getColumns(file));
const rows = await this.duckdbService.getRows(
file,
0,
100,
this.columns(),
[],
[],
);
const newValue = Array.from({ length: Number(rows.totalRows) });
this.currentValue.set(newValue);
}
});
}
protected async onLazyLoad(event: TableLazyLoadEvent) {
const file = this.file();
if (file) {
const rows = await this.duckdbService.getRows(
file,
event.first ?? 0,
event.rows ?? 0,
this.columns(),
[],
[],
);
// First clear out existing data, don't want to risk loading entire file into memory
this.currentValue().fill(undefined);
// Can't update the current value, otherwise we get an infinite loop
this.currentValue().splice(event.first!, event.rows!, ...rows.rows);
this.cdr.markForCheck();
}
}
}