Add basic csv view inside a table loaded through duckdb

This commit is contained in:
2025-04-24 18:47:53 +09:30
parent 5c42ba0dd4
commit 42e9470400
16 changed files with 999 additions and 517 deletions

View File

@@ -0,0 +1,125 @@
import {
ChangeDetectionStrategy,
Component,
computed,
effect,
inject,
input,
signal,
viewChild,
} from '@angular/core';
import { Skeleton } from 'primeng/skeleton';
import { Table, TableLazyLoadEvent, TableModule } from 'primeng/table';
import * as duckdb from '@duckdb/duckdb-wasm';
import { Column, DuckdbService } from '../duckdb.service';
@Component({
selector: 'app-file-viewer',
standalone: true,
imports: [TableModule, Skeleton],
template: `
@if (file() && columns().length > 0) {
<p-table
[columns]="columns()"
[value]="currentValue()"
[scrollable]="true"
scrollHeight="400px"
[rows]="100"
[virtualScroll]="true"
[virtualScrollItemSize]="46"
[lazy]="true"
(onLazyLoad)="onLazyLoad($event)"
>
<ng-template #header let-columns>
<tr>
@for (col of columns; track $index) {
<th style="width: 20%">
{{ 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>
{{ 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);
// 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) });
newValue.splice(0, 100, ...rows.rows);
this.currentValue.set(newValue);
}
// TODO: Refresh table? Or is this automatic when columns change?
// this.table().
//TODO: Get rows in file
});
}
// TODO: Getting an infinite loop
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(),
[],
[],
);
if (!this.currentValue()) {
this.currentValue.set(Array.from({ length: Number(rows.totalRows) }));
}
this.currentValue().splice(event.first!, event.rows!, ...rows.rows);
}
}
}