import { Component, ElementRef, HostListener, effect, signal, viewChild, } from '@angular/core'; import { ButtonModule } from 'primeng/button'; import { SplitterModule } from 'primeng/splitter'; import { TabsModule } from 'primeng/tabs'; import { ToolbarModule } from 'primeng/toolbar'; import { IconField } from 'primeng/iconfield'; import { InputIcon } from 'primeng/inputicon'; import { SplitButtonModule } from 'primeng/splitbutton'; import { InputTextModule } from 'primeng/inputtext'; import { FileTreeComponent } from './file-tree/file-tree.component'; import { FileViewerComponent } from './file-viewer/file-viewer.component'; import { ColumnEditorComponent } from './column-editor/column-editor.component'; import { Column, Sheet } from './duckdb.service'; @Component({ selector: 'app-root', imports: [ ButtonModule, SplitterModule, TabsModule, ToolbarModule, IconField, InputIcon, InputTextModule, SplitButtonModule, FileTreeComponent, FileViewerComponent, ColumnEditorComponent, ], templateUrl: './app.component.html', styleUrl: './app.component.scss', }) export class AppComponent { protected selectedSheet = signal(undefined); protected selectedFileColumns = signal([]); protected sheets = signal([]); protected selectedTab = signal(0); protected dragging = signal(false); private fileInput = viewChild>('fileSelector'); constructor() { effect(() => { const selectedFile = this.selectedSheet(); if (selectedFile) { this.addSheet(selectedFile); } }); effect(() => { if (this.selectedSheet() !== this.sheets()[this.selectedTab()]) { if (this.sheets().length > 0) { this.selectedSheet.set(this.sheets()[Number(this.selectedTab())]); } else { this.selectedSheet.set(undefined); } } }); } protected removeTab(index: number) { this.sheets.update((tabs) => { const copy = tabs.slice(); copy.splice(index, 1); return copy; }); if (this.selectedTab() === index) { if (this.selectedTab() > 0) { this.selectedTab.update((tab) => tab - 1); this.selectedSheet.set(this.sheets()[this.selectedTab()]); } else if (this.sheets().length > 1) { this.selectedTab.update((tab) => tab + 1); this.selectedSheet.set(this.sheets()[this.selectedTab()]); } else { this.selectedSheet.set(undefined); } } } protected fileDropped(event: DragEvent) { event.preventDefault(); const files = event.dataTransfer?.files; if (files) { for (const file of files) { if (file.type === 'text/csv') { this.addSheet({ file, history: [] }); } } } this.dragging.set(false); } protected dragEnter(event: DragEvent) { event.preventDefault(); const numItems = event.dataTransfer?.items.length; if (numItems) { for (let i = 0; i < numItems; i++) { if (event.dataTransfer?.items[i].type === 'text/csv') { this.dragging.set(true); return; } } } } protected dragLeave(event: DragEvent) { event.preventDefault(); this.dragging.set(false); } protected fileChange(event: Event) { const files = this.fileInput()?.nativeElement.files; if (files) { for (const file of files) { if (file.type === 'text/csv') { this.addSheet({ file, history: [] }); } } this.selectedTab.set(this.sheets().length - 1); } } private addSheet(newSheet: Sheet) { if ( this.sheets().find( (sheet) => sheet.file.webkitRelativePath === newSheet.file.webkitRelativePath && sheet.file.name === newSheet.file.name, ) ) { this.selectedTab.set( this.sheets().findIndex( (sheet) => sheet.file.webkitRelativePath === newSheet.file.webkitRelativePath && sheet.file.name === newSheet.file.name, ), ); } else { this.sheets.update((tabs) => [...tabs, newSheet]); this.selectedTab.set(this.sheets().length - 1); } } @HostListener('window:keydown', [ '$event.key', '$event.ctrlKey', '$event.metaKey', ]) keydown(key: string, hasCtrl: boolean, hasMeta: boolean) { const sheets = this.sheets(); if (sheets.length > 1 && key === 'z' && (hasCtrl || hasMeta)) { this.sheets()[0].history.pop(); } } }