diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 6e6a976..64de173 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,4 +1,4 @@
-import { Component, signal, viewChild } from '@angular/core';
+import { Component, effect, signal, viewChild } from '@angular/core';
import { ButtonModule } from 'primeng/button';
import { SplitterModule } from 'primeng/splitter';
import { TabsModule } from 'primeng/tabs';
@@ -28,7 +28,68 @@ import { FileViewerComponent } from './file-viewer/file-viewer.component';
styleUrl: './app.component.scss',
})
export class AppComponent {
- // TODO: When a file is selected, we add a new sheet with that file as the current file?
- // For now we'll just store the current file only
- selectedFile = signal
(undefined);
+ protected selectedFile = signal(undefined);
+ protected tabs = signal([]);
+ protected selectedTab = signal(0);
+
+ constructor() {
+ effect(() => {
+ const selectedFile = this.selectedFile();
+ if (selectedFile) {
+ if (
+ this.tabs().find(
+ (tab) => tab.webkitRelativePath === selectedFile.webkitRelativePath,
+ )
+ ) {
+ this.selectedTab.set(
+ this.tabs().findIndex(
+ (tab) =>
+ tab.webkitRelativePath === selectedFile.webkitRelativePath,
+ ),
+ );
+ } else {
+ this.tabs.update((tabs) => [...tabs, selectedFile]);
+ this.selectedTab.set(this.tabs().length - 1);
+ }
+ }
+ });
+
+ effect(() => {
+ if (this.selectedFile() !== this.tabs()[this.selectedTab()]) {
+ if (this.tabs().length > 0) {
+ this.selectedFile.set(this.tabs()[Number(this.selectedTab())]);
+ } else {
+ this.selectedFile.set(undefined);
+ }
+ }
+ });
+ }
+
+ protected removeTab(index: number) {
+ this.tabs.update((tabs) => {
+ const copy = tabs.slice();
+ copy.splice(index);
+ return copy;
+ });
+ if (this.selectedTab() === index) {
+ if (this.selectedTab() > 0) {
+ this.selectedTab.update((tab) => tab - 1);
+ this.selectedFile.set(this.tabs()[this.selectedTab()]);
+ } else if (this.tabs().length > 1) {
+ this.selectedTab.update((tab) => tab + 1);
+ this.selectedFile.set(this.tabs()[this.selectedTab()]);
+ } else {
+ this.selectedFile.set(undefined);
+ }
+ }
+ }
+
+ // TODO: Drop files over viewport
+ protected fileDropped(event: DragEvent) {
+ if (event.dataTransfer?.items.length ?? 0 > 1) {
+ // Open in the tabs
+ } else if (event.dataTransfer?.items.length ?? 0 > 0) {
+ // Open in current tab
+ }
+ }
}
diff --git a/src/app/app.config.ts b/src/app/app.config.ts
index 0e1e02c..4b8bff1 100644
--- a/src/app/app.config.ts
+++ b/src/app/app.config.ts
@@ -1,5 +1,7 @@
import {
ApplicationConfig,
+ inject,
+ provideAppInitializer,
provideZonelessChangeDetection,
} from '@angular/core';
import { provideRouter } from '@angular/router';
@@ -8,6 +10,7 @@ import { providePrimeNG } from 'primeng/config';
import Aura from '@primeng/themes/aura';
import { routes } from './app.routes';
+import { DuckdbService } from './duckdb.service';
export const appConfig: ApplicationConfig = {
providers: [
@@ -19,5 +22,9 @@ export const appConfig: ApplicationConfig = {
preset: Aura,
},
}),
+ provideAppInitializer(async () => {
+ const duckDbService = inject(DuckdbService);
+ await duckDbService.init();
+ }),
],
};
diff --git a/src/app/duckdb.service.ts b/src/app/duckdb.service.ts
index e9f8715..f521525 100644
--- a/src/app/duckdb.service.ts
+++ b/src/app/duckdb.service.ts
@@ -102,9 +102,6 @@ const suffix = (operator: FilterOperator) => {
export class DuckdbService {
private db!: duckdb.AsyncDuckDB;
- constructor() {
- this.init();
- }
async init() {
const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
diff --git a/src/app/file-viewer/file-viewer.component.css b/src/app/file-viewer/file-viewer.component.css
index dd67487..4cef8a0 100644
--- a/src/app/file-viewer/file-viewer.component.css
+++ b/src/app/file-viewer/file-viewer.component.css
@@ -1,5 +1,4 @@
:host {
display: flex;
flex-direction: column;
- height: 100%;
}
diff --git a/src/app/file-viewer/file-viewer.component.ts b/src/app/file-viewer/file-viewer.component.ts
index 749639d..361177a 100644
--- a/src/app/file-viewer/file-viewer.component.ts
+++ b/src/app/file-viewer/file-viewer.component.ts
@@ -2,6 +2,7 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
+ computed,
effect,
inject,
input,
@@ -123,34 +124,36 @@ import { PaginatorModule, PaginatorState } from 'primeng/paginator';
}
-
-
- @for (col of columns(); track $index) {
-
- @if (col.type === 'DOUBLE' || col.type === 'BIGINT') {
-
-
-
-
- mediation
-
-
-
-
- {{ aggregateValues()[$index] | number }}
-
-
- }
- |
- }
-
-
+ @if (hasAggregates()) {
+
+
+ @for (col of columns(); track $index) {
+
+ @if (col.type === 'DOUBLE' || col.type === 'BIGINT') {
+
+
+
+
+ mediation
+
+
+
+
+ {{ aggregateValues()[$index] | number }}
+
+
+ }
+ |
+ }
+
+
+ }
@if (currentRowCount() > PAGE_SIZE) {
@@ -191,6 +194,10 @@ export class FileViewerComponent {
private table = viewChild(Table);
+ protected hasAggregates = computed(
+ () => !!this.columns().find((col) => this.isAggregateColumn(col)),
+ );
+
protected aggregateItems: MenuItem[] = aggregateTypes.map((type) => ({
label: type,
command: () => this.updateAggregateValue(type),
@@ -198,7 +205,7 @@ export class FileViewerComponent {
constructor() {
effect(async () => {
- this.loadEmpty();
+ await this.loadEmpty();
});
}
@@ -268,7 +275,7 @@ export class FileViewerComponent {
}
// Can't update the current value, otherwise we get an infinite loop due to primeng change detection rerunning (plus it's faster to mutate)
- for (let i = 0; i < event.rows!; i++) {
+ for (let i = 0; i < rows.rows!.length; i++) {
this.currentValue()[event.first! + i] = rows.rows![i];
}
event.forceUpdate!();
@@ -340,4 +347,8 @@ export class FileViewerComponent {
}),
);
}
+
+ protected isAggregateColumn(col: Column) {
+ return col.type === 'DOUBLE' || col.type === 'BIGINT';
+ }
}
diff --git a/src/index.html b/src/index.html
index dda3cf2..dd9b143 100644
--- a/src/index.html
+++ b/src/index.html
@@ -8,7 +8,7 @@