129 lines
3.6 KiB
TypeScript
129 lines
3.6 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]="numRows"
|
|
[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[]>([]);
|
|
protected numRows = 200;
|
|
|
|
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,
|
|
this.numRows,
|
|
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
|
|
// Keep data in previous/next page so when user scrolls between 2 pages they don't see missing data
|
|
for (let i = 0; i < this.currentValue().length; i++) {
|
|
if (
|
|
i < event.first! - this.numRows ||
|
|
i > event.first! + event.rows! + this.numRows
|
|
) {
|
|
this.currentValue()[i] = 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();
|
|
}
|
|
}
|
|
}
|