From 8c661f25334ee67174a78ea836648f29dc49d9ee Mon Sep 17 00:00:00 2001 From: vato007 Date: Sun, 1 Jun 2025 16:21:42 +0930 Subject: [PATCH] Add support for multiple tabs --- src/app/app.component.html | 44 +++++++----- src/app/app.component.ts | 69 ++++++++++++++++-- src/app/app.config.ts | 7 ++ src/app/duckdb.service.ts | 3 - src/app/file-viewer/file-viewer.component.css | 1 - src/app/file-viewer/file-viewer.component.ts | 71 +++++++++++-------- src/index.html | 2 +- 7 files changed, 140 insertions(+), 57 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index d096794..ba5d355 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -11,12 +11,7 @@ - +
@@ -25,18 +20,31 @@ - - Tab 1 - Tab 2 - - - - - - - - +
+ @if (tabs().length > 0) { + + + @for (tab of tabs(); track $index) { + + {{ tab.name }} + + close + + + } + + + } + @if (selectedFile()) { + + } +
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 @@