Compare commits
1 Commits
main
...
dockview-a
| Author | SHA1 | Date | |
|---|---|---|---|
| c8d0ac80f3 |
29
angular.json
29
angular.json
@@ -2,8 +2,7 @@
|
|||||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"cli": {
|
"cli": {
|
||||||
"packageManager": "bun",
|
"packageManager": "bun"
|
||||||
"analytics": false
|
|
||||||
},
|
},
|
||||||
"newProjectRoot": "projects",
|
"newProjectRoot": "projects",
|
||||||
"projects": {
|
"projects": {
|
||||||
@@ -92,6 +91,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"dockview-angular": {
|
||||||
|
"projectType": "library",
|
||||||
|
"root": "projects/dockview-angular",
|
||||||
|
"sourceRoot": "projects/dockview-angular/src",
|
||||||
|
"prefix": "lib",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular/build:ng-packagr",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"tsConfig": "projects/dockview-angular/tsconfig.lib.prod.json"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"tsConfig": "projects/dockview-angular/tsconfig.lib.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular/build:karma",
|
||||||
|
"options": {
|
||||||
|
"tsConfig": "projects/dockview-angular/tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"schematics": {
|
"schematics": {
|
||||||
|
|||||||
10520
package-lock.json
generated
10520
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@@ -10,41 +10,41 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^21.2.4",
|
"@angular/animations": "^20.3.6",
|
||||||
"@angular/common": "^21.2.4",
|
"@angular/common": "^20.3.6",
|
||||||
"@angular/compiler": "^21.2.4",
|
"@angular/compiler": "^20.3.6",
|
||||||
"@angular/core": "^21.2.4",
|
"@angular/core": "^20.3.6",
|
||||||
"@angular/forms": "^21.2.4",
|
"@angular/forms": "^20.3.6",
|
||||||
"@angular/platform-browser": "^21.2.4",
|
"@angular/platform-browser": "^20.3.6",
|
||||||
"@angular/platform-browser-dynamic": "^21.2.4",
|
"@angular/platform-browser-dynamic": "^20.3.6",
|
||||||
"@angular/router": "^21.2.4",
|
"@angular/router": "^20.3.6",
|
||||||
"@duckdb/duckdb-wasm": "^1.32.0",
|
"@duckdb/duckdb-wasm": "^1.30.0",
|
||||||
"@primeng/themes": "^21.0.4",
|
"@primeng/themes": "20.2.0",
|
||||||
"@tailwindcss/postcss": "^4.2.1",
|
"@tailwindcss/postcss": "^4.1.15",
|
||||||
|
"dockview-core": "^4.9.0",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.6.2",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primeng": "^21.1.3",
|
"primeng": "20.2.0",
|
||||||
"rxjs": "~7.8.2",
|
"rxjs": "~7.8.2",
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.1.15",
|
||||||
"tailwindcss-primeui": "^0.6.1",
|
"tailwindcss-primeui": "0.6.1",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"zod": "^4.3.6"
|
"zod": "4.1.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/build": "^21.2.2",
|
"@angular/build": "^20.3.6",
|
||||||
"@angular/cli": "^21.2.2",
|
"@angular/cli": "^20.3.6",
|
||||||
"@angular/compiler-cli": "^21.2.4",
|
"@angular/compiler-cli": "^20.3.6",
|
||||||
"@tauri-apps/cli": "^2.10.1",
|
"@tauri-apps/cli": "^2.9.0",
|
||||||
"@types/jasmine": "~5.1.15",
|
"@types/jasmine": "~5.1.12",
|
||||||
"baseline-browser-mapping": "^2.10.24",
|
"jasmine-core": "~5.12.0",
|
||||||
"jasmine-core": "~5.12.1",
|
|
||||||
"karma": "~6.4.4",
|
"karma": "~6.4.4",
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
"karma-coverage": "~2.2.1",
|
"karma-coverage": "~2.2.1",
|
||||||
"karma-jasmine": "~5.1.0",
|
"karma-jasmine": "~5.1.0",
|
||||||
"karma-jasmine-html-reporter": "~2.1.0",
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
"postcss": "^8.5.8",
|
"ng-packagr": "^20.3.0",
|
||||||
"typescript": "~5.9.3"
|
"typescript": "~5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
63
projects/dockview-angular/README.md
Normal file
63
projects/dockview-angular/README.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# DockviewAngular
|
||||||
|
|
||||||
|
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.3.0.
|
||||||
|
|
||||||
|
## Code scaffolding
|
||||||
|
|
||||||
|
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng generate component component-name
|
||||||
|
```
|
||||||
|
|
||||||
|
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng generate --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To build the library, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng build dockview-angular
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
|
||||||
|
|
||||||
|
### Publishing the Library
|
||||||
|
|
||||||
|
Once the project is built, you can publish your library by following these steps:
|
||||||
|
|
||||||
|
1. Navigate to the `dist` directory:
|
||||||
|
```bash
|
||||||
|
cd dist/dockview-angular
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run the `npm publish` command to publish your library to the npm registry:
|
||||||
|
```bash
|
||||||
|
npm publish
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running end-to-end tests
|
||||||
|
|
||||||
|
For end-to-end (e2e) testing, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
||||||
7
projects/dockview-angular/ng-package.json
Normal file
7
projects/dockview-angular/ng-package.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
||||||
|
"dest": "../../dist/dockview-angular",
|
||||||
|
"lib": {
|
||||||
|
"entryFile": "src/public-api.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
projects/dockview-angular/package.json
Normal file
12
projects/dockview-angular/package.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "dockview-angular",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": "^20.3.0",
|
||||||
|
"@angular/core": "^20.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"sideEffects": false
|
||||||
|
}
|
||||||
23
projects/dockview-angular/src/lib/dockview-angular.spec.ts
Normal file
23
projects/dockview-angular/src/lib/dockview-angular.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DockviewAngular } from './dockview-angular';
|
||||||
|
|
||||||
|
describe('DockviewAngular', () => {
|
||||||
|
let component: DockviewAngular;
|
||||||
|
let fixture: ComponentFixture<DockviewAngular>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [DockviewAngular]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(DockviewAngular);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
26
projects/dockview-angular/src/lib/dockview-angular.ts
Normal file
26
projects/dockview-angular/src/lib/dockview-angular.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
contentChildren,
|
||||||
|
viewChild,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { DockviewApi, createDockview } from 'dockview-core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'lib-dockview-angular',
|
||||||
|
imports: [],
|
||||||
|
template: ` <div #dockingElement></div> `,
|
||||||
|
styles: ``,
|
||||||
|
})
|
||||||
|
export class DockviewAngular {
|
||||||
|
// Child html elements that will be shown in separate dockview panels
|
||||||
|
// TODO: Maybe better as a directive so that we can provide the group api to each panel
|
||||||
|
// It would also allow us to set properties for each panel, like its starting position
|
||||||
|
private readonly childElements = contentChildren<ElementRef>('panel');
|
||||||
|
|
||||||
|
private readonly dockingElement = viewChild<ElementRef>('dockingElement');
|
||||||
|
private readonly api: DockviewApi;
|
||||||
|
constructor() {
|
||||||
|
this.api = createDockview(this.dockingElement()?.nativeElement, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
5
projects/dockview-angular/src/public-api.ts
Normal file
5
projects/dockview-angular/src/public-api.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/*
|
||||||
|
* Public API Surface of dockview-angular
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './lib/dockview-angular';
|
||||||
18
projects/dockview-angular/tsconfig.lib.json
Normal file
18
projects/dockview-angular/tsconfig.lib.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||||
|
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../out-tsc/lib",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/*.spec.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
11
projects/dockview-angular/tsconfig.lib.prod.json
Normal file
11
projects/dockview-angular/tsconfig.lib.prod.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||||
|
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.lib.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"declarationMap": false
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"compilationMode": "partial"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
projects/dockview-angular/tsconfig.spec.json
Normal file
14
projects/dockview-angular/tsconfig.spec.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||||
|
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../out-tsc/spec",
|
||||||
|
"types": [
|
||||||
|
"jasmine"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -16,9 +16,7 @@
|
|||||||
>
|
>
|
||||||
<ng-template #panel>
|
<ng-template #panel>
|
||||||
<div class="h-full w-full overflow-auto">
|
<div class="h-full w-full overflow-auto">
|
||||||
<app-file-tree
|
<app-file-tree (selectFile)="selectedFile.set($event)"></app-file-tree>
|
||||||
(selectFile)="selectedSheet.set({ file: $event, history: [] })"
|
|
||||||
></app-file-tree>
|
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #panel>
|
<ng-template #panel>
|
||||||
@@ -33,12 +31,12 @@
|
|||||||
[class.border-dashed]="dragging()"
|
[class.border-dashed]="dragging()"
|
||||||
[class.border-2]="dragging()"
|
[class.border-2]="dragging()"
|
||||||
>
|
>
|
||||||
@if (sheets().length > 0) {
|
@if (tabs().length > 0) {
|
||||||
<p-tabs [(value)]="selectedTab" class="w-full" scrollable>
|
<p-tabs [(value)]="selectedTab" class="w-full" scrollable>
|
||||||
<p-tablist>
|
<p-tablist>
|
||||||
@for (tab of sheets(); track $index) {
|
@for (tab of tabs(); track $index) {
|
||||||
<p-tab [value]="$index" (auxclick)="removeTab($index)">
|
<p-tab [value]="$index" (auxclick)="removeTab($index)">
|
||||||
<span>{{ tab.file.name }}</span>
|
<span>{{ tab.name }}</span>
|
||||||
<span
|
<span
|
||||||
(click)="removeTab($index)"
|
(click)="removeTab($index)"
|
||||||
class="material-symbols-outlined"
|
class="material-symbols-outlined"
|
||||||
@@ -50,10 +48,10 @@
|
|||||||
</p-tablist>
|
</p-tablist>
|
||||||
</p-tabs>
|
</p-tabs>
|
||||||
}
|
}
|
||||||
@if (selectedSheet()) {
|
@if (selectedFile()) {
|
||||||
<app-file-viewer
|
<app-file-viewer
|
||||||
class="flex w-full flex-1"
|
class="flex w-full flex-1"
|
||||||
[sheet]="selectedSheet()"
|
[file]="selectedFile()"
|
||||||
[(columns)]="selectedFileColumns"
|
[(columns)]="selectedFileColumns"
|
||||||
></app-file-viewer>
|
></app-file-viewer>
|
||||||
} @else {
|
} @else {
|
||||||
@@ -69,21 +67,13 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #panel>
|
<ng-template #panel>
|
||||||
<div class="col flex items-center justify-center">
|
<div class="col flex items-center justify-center">
|
||||||
@let selectedSheet = this.selectedSheet();
|
Panel 3
|
||||||
@if (selectedSheet) {
|
</div></ng-template
|
||||||
@for (state of selectedSheet.history; track $index) {
|
>
|
||||||
<!-- TODO: Make a diff compared to the initial state. -->
|
|
||||||
<!-- Alternative is to make state a list of patches, but probably more complicated -->
|
|
||||||
<div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}</div
|
|
||||||
></ng-template>
|
|
||||||
</p-splitter>
|
</p-splitter>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #panel>
|
<ng-template #panel>
|
||||||
@if (selectedSheet()) {
|
@if (selectedFile()) {
|
||||||
<app-column-editor
|
<app-column-editor
|
||||||
class="w-full overflow-auto"
|
class="w-full overflow-auto"
|
||||||
[(columns)]="selectedFileColumns"
|
[(columns)]="selectedFileColumns"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
HostListener,
|
|
||||||
effect,
|
effect,
|
||||||
signal,
|
signal,
|
||||||
viewChild,
|
viewChild,
|
||||||
@@ -17,7 +16,7 @@ import { InputTextModule } from 'primeng/inputtext';
|
|||||||
import { FileTreeComponent } from './file-tree/file-tree.component';
|
import { FileTreeComponent } from './file-tree/file-tree.component';
|
||||||
import { FileViewerComponent } from './file-viewer/file-viewer.component';
|
import { FileViewerComponent } from './file-viewer/file-viewer.component';
|
||||||
import { ColumnEditorComponent } from './column-editor/column-editor.component';
|
import { ColumnEditorComponent } from './column-editor/column-editor.component';
|
||||||
import { Column, Sheet } from './duckdb.service';
|
import { Column } from './duckdb.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@@ -38,9 +37,9 @@ import { Column, Sheet } from './duckdb.service';
|
|||||||
styleUrl: './app.component.scss',
|
styleUrl: './app.component.scss',
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
protected selectedSheet = signal<Sheet | undefined>(undefined);
|
protected selectedFile = signal<File | undefined>(undefined);
|
||||||
protected selectedFileColumns = signal<Column[]>([]);
|
protected selectedFileColumns = signal<Column[]>([]);
|
||||||
protected sheets = signal<Sheet[]>([]);
|
protected tabs = signal<File[]>([]);
|
||||||
protected selectedTab = signal(0);
|
protected selectedTab = signal(0);
|
||||||
protected dragging = signal(false);
|
protected dragging = signal(false);
|
||||||
|
|
||||||
@@ -48,25 +47,25 @@ export class AppComponent {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
effect(() => {
|
effect(() => {
|
||||||
const selectedFile = this.selectedSheet();
|
const selectedFile = this.selectedFile();
|
||||||
if (selectedFile) {
|
if (selectedFile) {
|
||||||
this.addSheet(selectedFile);
|
this.addFile(selectedFile);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
effect(() => {
|
effect(() => {
|
||||||
if (this.selectedSheet() !== this.sheets()[this.selectedTab()]) {
|
if (this.selectedFile() !== this.tabs()[this.selectedTab()]) {
|
||||||
if (this.sheets().length > 0) {
|
if (this.tabs().length > 0) {
|
||||||
this.selectedSheet.set(this.sheets()[Number(this.selectedTab())]);
|
this.selectedFile.set(this.tabs()[Number(this.selectedTab())]);
|
||||||
} else {
|
} else {
|
||||||
this.selectedSheet.set(undefined);
|
this.selectedFile.set(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected removeTab(index: number) {
|
protected removeTab(index: number) {
|
||||||
this.sheets.update((tabs) => {
|
this.tabs.update((tabs) => {
|
||||||
const copy = tabs.slice();
|
const copy = tabs.slice();
|
||||||
copy.splice(index, 1);
|
copy.splice(index, 1);
|
||||||
return copy;
|
return copy;
|
||||||
@@ -74,12 +73,12 @@ export class AppComponent {
|
|||||||
if (this.selectedTab() === index) {
|
if (this.selectedTab() === index) {
|
||||||
if (this.selectedTab() > 0) {
|
if (this.selectedTab() > 0) {
|
||||||
this.selectedTab.update((tab) => tab - 1);
|
this.selectedTab.update((tab) => tab - 1);
|
||||||
this.selectedSheet.set(this.sheets()[this.selectedTab()]);
|
this.selectedFile.set(this.tabs()[this.selectedTab()]);
|
||||||
} else if (this.sheets().length > 1) {
|
} else if (this.tabs().length > 1) {
|
||||||
this.selectedTab.update((tab) => tab + 1);
|
this.selectedTab.update((tab) => tab + 1);
|
||||||
this.selectedSheet.set(this.sheets()[this.selectedTab()]);
|
this.selectedFile.set(this.tabs()[this.selectedTab()]);
|
||||||
} else {
|
} else {
|
||||||
this.selectedSheet.set(undefined);
|
this.selectedFile.set(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,7 +89,7 @@ export class AppComponent {
|
|||||||
if (files) {
|
if (files) {
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.type === 'text/csv') {
|
if (file.type === 'text/csv') {
|
||||||
this.addSheet({ file, history: [] });
|
this.addFile(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,43 +119,31 @@ export class AppComponent {
|
|||||||
if (files) {
|
if (files) {
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.type === 'text/csv') {
|
if (file.type === 'text/csv') {
|
||||||
this.addSheet({ file, history: [] });
|
this.addFile(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.selectedTab.set(this.sheets().length - 1);
|
this.selectedTab.set(this.tabs().length - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private addSheet(newSheet: Sheet) {
|
private addFile(file: File) {
|
||||||
if (
|
if (
|
||||||
this.sheets().find(
|
this.tabs().find(
|
||||||
(sheet) =>
|
(tab) =>
|
||||||
sheet.file.webkitRelativePath === newSheet.file.webkitRelativePath &&
|
tab.webkitRelativePath === file.webkitRelativePath &&
|
||||||
sheet.file.name === newSheet.file.name,
|
tab.name === file.name,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
this.selectedTab.set(
|
this.selectedTab.set(
|
||||||
this.sheets().findIndex(
|
this.tabs().findIndex(
|
||||||
(sheet) =>
|
(tab) =>
|
||||||
sheet.file.webkitRelativePath ===
|
tab.webkitRelativePath === file.webkitRelativePath &&
|
||||||
newSheet.file.webkitRelativePath &&
|
tab.name === file.name,
|
||||||
sheet.file.name === newSheet.file.name,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.sheets.update((tabs) => [...tabs, newSheet]);
|
this.tabs.update((tabs) => [...tabs, file]);
|
||||||
this.selectedTab.set(this.sheets().length - 1);
|
this.selectedTab.set(this.tabs().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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,15 @@
|
|||||||
import {
|
import { ChangeDetectionStrategy, Component, model } from '@angular/core';
|
||||||
ChangeDetectionStrategy,
|
|
||||||
Component,
|
|
||||||
model,
|
|
||||||
signal,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { AccordionModule } from 'primeng/accordion';
|
import { AccordionModule } from 'primeng/accordion';
|
||||||
import { Column } from '../duckdb.service';
|
import { Column } from '../duckdb.service';
|
||||||
import { Checkbox } from 'primeng/checkbox';
|
import { Checkbox, CheckboxChangeEvent } from 'primeng/checkbox';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { Button } from 'primeng/button';
|
import { Button } from 'primeng/button';
|
||||||
import { Tooltip } from 'primeng/tooltip';
|
import { Tooltip } from 'primeng/tooltip';
|
||||||
import { Dialog } from 'primeng/dialog';
|
|
||||||
import { FloatLabel } from 'primeng/floatlabel';
|
|
||||||
import { InputText } from 'primeng/inputtext';
|
|
||||||
import { Select } from 'primeng/select';
|
|
||||||
import { Field, form, FormField } from '@angular/forms/signals';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-column-editor',
|
selector: 'app-column-editor',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [AccordionModule, Checkbox, FormsModule, Button, Tooltip],
|
||||||
AccordionModule,
|
|
||||||
Checkbox,
|
|
||||||
FormsModule,
|
|
||||||
Button,
|
|
||||||
Tooltip,
|
|
||||||
Dialog,
|
|
||||||
FloatLabel,
|
|
||||||
InputText,
|
|
||||||
Select,
|
|
||||||
FormField,
|
|
||||||
],
|
|
||||||
template: `
|
template: `
|
||||||
<p-accordion [value]="0">
|
<p-accordion [value]="0">
|
||||||
<p-accordion-panel [value]="0">
|
<p-accordion-panel [value]="0">
|
||||||
@@ -66,65 +45,12 @@ import { Field, form, FormField } from '@angular/forms/signals';
|
|||||||
</p-accordion-content>
|
</p-accordion-content>
|
||||||
</p-accordion-panel>
|
</p-accordion-panel>
|
||||||
</p-accordion>
|
</p-accordion>
|
||||||
<p-dialog
|
|
||||||
[(visible)]="editDialogVisible"
|
|
||||||
header="Edit Column"
|
|
||||||
[draggable]="false"
|
|
||||||
[resizable]="false"
|
|
||||||
[modal]="true"
|
|
||||||
[dismissableMask]="true"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<p-float-label variant="in">
|
|
||||||
<input
|
|
||||||
pInputText
|
|
||||||
id="column-edit-name"
|
|
||||||
[formField]="editingColumn.name"
|
|
||||||
/>
|
|
||||||
<label for="column-edit-name">Name</label>
|
|
||||||
</p-float-label>
|
|
||||||
<p-float-label variant="in">
|
|
||||||
<p-select
|
|
||||||
fluid
|
|
||||||
inputId="column-edit-type"
|
|
||||||
[formField]="$any(editingColumn.type)"
|
|
||||||
[options]="columnTypes"
|
|
||||||
appendTo="body"
|
|
||||||
></p-select>
|
|
||||||
<label for="column-edit-type">Type</label>
|
|
||||||
</p-float-label>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<p-check-box
|
|
||||||
[formField]="editingColumn.enabled"
|
|
||||||
[binary]="true"
|
|
||||||
inputId="column-edit-enabled"
|
|
||||||
></p-check-box>
|
|
||||||
<label for="column-edit-enabled">Enabled</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ng-template #footer>
|
|
||||||
<p-button (click)="saveColumn()">Save</p-button>
|
|
||||||
</ng-template>
|
|
||||||
</p-dialog>
|
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class ColumnEditorComponent {
|
export class ColumnEditorComponent {
|
||||||
columns = model<Column[]>();
|
columns = model<Column[]>();
|
||||||
|
|
||||||
protected readonly editDialogVisible = signal(false);
|
|
||||||
|
|
||||||
protected readonly columnTypes = ['string', 'number', 'boolean'];
|
|
||||||
|
|
||||||
protected readonly currentColumn = signal<Column>({
|
|
||||||
name: '',
|
|
||||||
enabled: true,
|
|
||||||
type: 'string',
|
|
||||||
});
|
|
||||||
protected readonly editIndex = signal<number | undefined>(undefined);
|
|
||||||
|
|
||||||
protected readonly editingColumn = form(this.currentColumn);
|
|
||||||
|
|
||||||
protected checkboxChanged(index: number) {
|
protected checkboxChanged(index: number) {
|
||||||
this.columns.update((columns) => {
|
this.columns.update((columns) => {
|
||||||
if (columns) {
|
if (columns) {
|
||||||
@@ -139,27 +65,5 @@ export class ColumnEditorComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected editColumn(index: number) {
|
protected editColumn(index: number) {}
|
||||||
const columns = this.columns();
|
|
||||||
if (columns) {
|
|
||||||
this.editIndex.set(index);
|
|
||||||
this.currentColumn.set(columns[index]);
|
|
||||||
this.editDialogVisible.set(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected saveColumn() {
|
|
||||||
const editIndex = this.editIndex();
|
|
||||||
if (editIndex == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.columns.update((columns) => {
|
|
||||||
const oldColumns = columns?.slice();
|
|
||||||
if (oldColumns) {
|
|
||||||
oldColumns[editIndex] = this.editingColumn().value();
|
|
||||||
}
|
|
||||||
return oldColumns;
|
|
||||||
});
|
|
||||||
this.editDialogVisible.set(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,18 +48,6 @@ export const RowsResponse = z.object({
|
|||||||
aggregateValues: AggregateValue.array(),
|
aggregateValues: AggregateValue.array(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const TableState = z.object({
|
|
||||||
columns: z.array(Column),
|
|
||||||
sorts: z.array(SortColumn),
|
|
||||||
filters: z.array(Filter),
|
|
||||||
aggregates: z.array(Aggregate)
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Sheet = z.object({
|
|
||||||
file: z.file(),
|
|
||||||
history: z.array(TableState),
|
|
||||||
})
|
|
||||||
|
|
||||||
export type Column = z.infer<typeof Column>;
|
export type Column = z.infer<typeof Column>;
|
||||||
export type SortColumn = z.infer<typeof SortColumn>;
|
export type SortColumn = z.infer<typeof SortColumn>;
|
||||||
export type FilterValue = z.infer<typeof FilterValue>;
|
export type FilterValue = z.infer<typeof FilterValue>;
|
||||||
@@ -69,8 +57,6 @@ export type AggregateType = z.infer<typeof AggregateType>;
|
|||||||
export type Aggregate = z.infer<typeof Aggregate>;
|
export type Aggregate = z.infer<typeof Aggregate>;
|
||||||
export type AggregateValue = z.infer<typeof AggregateValue>;
|
export type AggregateValue = z.infer<typeof AggregateValue>;
|
||||||
export type RowsResponse = z.infer<typeof RowsResponse>;
|
export type RowsResponse = z.infer<typeof RowsResponse>;
|
||||||
export type TableState = z.infer<typeof TableState>;
|
|
||||||
export type Sheet = z.infer<typeof Sheet>;
|
|
||||||
|
|
||||||
const sanitisedFileName = (file: File) =>
|
const sanitisedFileName = (file: File) =>
|
||||||
file.name.toLowerCase().replaceAll("'", '').replaceAll(/\s*/g, '');
|
file.name.toLowerCase().replaceAll("'", '').replaceAll(/\s*/g, '');
|
||||||
@@ -190,12 +176,15 @@ export class DuckdbService {
|
|||||||
file: File,
|
file: File,
|
||||||
start: number,
|
start: number,
|
||||||
numRows: number,
|
numRows: number,
|
||||||
state: TableState,
|
columns: Column[],
|
||||||
|
sorts: SortColumn[],
|
||||||
|
filters: Filter[],
|
||||||
|
aggregates: Aggregate[],
|
||||||
): Promise<RowsResponse> {
|
): Promise<RowsResponse> {
|
||||||
const conn = await this.db.connect();
|
const conn = await this.db.connect();
|
||||||
try {
|
try {
|
||||||
const whereClause = this.getWhereClause(state.filters);
|
const whereClause = this.getWhereClause(filters);
|
||||||
const mappedFilterValues = state.filters.flatMap((filter) =>
|
const mappedFilterValues = filters.flatMap((filter) =>
|
||||||
filter.value
|
filter.value
|
||||||
.filter((value) => value?.value)
|
.filter((value) => value?.value)
|
||||||
.map(
|
.map(
|
||||||
@@ -204,8 +193,8 @@ export class DuckdbService {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
let aggregatesQuery = 'SELECT COUNT(1) totalRows';
|
let aggregatesQuery = 'SELECT COUNT(1) totalRows';
|
||||||
if (state.aggregates.length > 0) {
|
if (aggregates.length > 0) {
|
||||||
for (const aggregate of state.aggregates) {
|
for (const aggregate of aggregates) {
|
||||||
aggregatesQuery += `, ${aggregate.type}("${aggregate.column}") "${aggregate.column}"`;
|
aggregatesQuery += `, ${aggregate.type}("${aggregate.column}") "${aggregate.column}"`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,9 +206,9 @@ export class DuckdbService {
|
|||||||
const aggregateValues: AggregateValue[] = Object.entries(aggregatesJson)
|
const aggregateValues: AggregateValue[] = Object.entries(aggregatesJson)
|
||||||
.filter(([key]) => key !== 'totalRows')
|
.filter(([key]) => key !== 'totalRows')
|
||||||
.map(([key, value]) => AggregateValue.parse({ column: key, value }));
|
.map(([key, value]) => AggregateValue.parse({ column: key, value }));
|
||||||
let query = `SELECT ${state.columns.map((column) => `"${column.name}"`).join(', ')} FROM ${sanitisedFileName(file)} ${whereClause}`;
|
let query = `SELECT ${columns.map((column) => `"${column.name}"`).join(', ')} FROM ${sanitisedFileName(file)} ${whereClause}`;
|
||||||
if (state.sorts.length > 0) {
|
if (sorts.length > 0) {
|
||||||
query += ` ORDER BY ${state.sorts.map((sort) => `"${sort.name}" ${sort.sortType}`).join(', ')}`;
|
query += ` ORDER BY ${sorts.map((sort) => `"${sort.name}" ${sort.sortType}`).join(', ')}`;
|
||||||
}
|
}
|
||||||
query += ` LIMIT ${numRows} OFFSET ${start}`;
|
query += ` LIMIT ${numRows} OFFSET ${start}`;
|
||||||
const stmt = await conn.prepare(query);
|
const stmt = await conn.prepare(query);
|
||||||
|
|||||||
@@ -2,7 +2,3 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
p-table tr > th {
|
|
||||||
background: var(--p-datatable-header-cell-background);
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,6 @@ import {
|
|||||||
DuckdbService,
|
DuckdbService,
|
||||||
Filter,
|
Filter,
|
||||||
FilterValue,
|
FilterValue,
|
||||||
Sheet,
|
|
||||||
aggregateTypes,
|
aggregateTypes,
|
||||||
} from '../duckdb.service';
|
} from '../duckdb.service';
|
||||||
import { ButtonModule } from 'primeng/button';
|
import { ButtonModule } from 'primeng/button';
|
||||||
@@ -45,7 +44,7 @@ import { PaginatorModule, PaginatorState } from 'primeng/paginator';
|
|||||||
PaginatorModule,
|
PaginatorModule,
|
||||||
],
|
],
|
||||||
template: `
|
template: `
|
||||||
@if (sheet() && enabledColumns().length > 0) {
|
@if (file() && enabledColumns().length > 0) {
|
||||||
<div class="h-full">
|
<div class="h-full">
|
||||||
<p-table
|
<p-table
|
||||||
#table
|
#table
|
||||||
@@ -171,37 +170,37 @@ import { PaginatorModule, PaginatorState } from 'primeng/paginator';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class FileViewerComponent {
|
export class FileViewerComponent {
|
||||||
sheet = input<Sheet | undefined>();
|
file = input<File | undefined>();
|
||||||
|
|
||||||
private duckdbService = inject(DuckdbService);
|
private duckdbService = inject(DuckdbService);
|
||||||
|
|
||||||
// Can't be computed since effect has to run first so file exists in duckdb
|
// Can't be computed since effect has to run first so file exists in duckdb
|
||||||
columns = model<Column[]>([]);
|
columns = model<Column[]>([]);
|
||||||
protected readonly aggregates = signal<(Aggregate | undefined)[]>([]);
|
protected aggregates = signal<(Aggregate | undefined)[]>([]);
|
||||||
protected readonly currentAggregateColumn = signal<number | undefined>(undefined);
|
protected currentAggregateColumn = signal<number | undefined>(undefined);
|
||||||
protected readonly aggregateValues = signal<(number | undefined)[]>([]);
|
protected aggregateValues = signal<(number | undefined)[]>([]);
|
||||||
protected readonly NUM_ROWS = 200;
|
protected readonly NUM_ROWS = 200;
|
||||||
protected readonly currentPage = signal(0);
|
protected currentPage = signal(0);
|
||||||
|
|
||||||
// This is required since once the number of rows exceeds ~300000, the footer gets blocked
|
// This is required since once the number of rows exceeds ~300000, the footer gets blocked
|
||||||
// and the user can't even scroll to those bottom rows.
|
// and the user can't even scroll to those bottom rows.
|
||||||
protected readonly PAGE_SIZE = 300_000;
|
protected readonly PAGE_SIZE = 300_000;
|
||||||
|
|
||||||
protected readonly currentValue = signal<any[]>([]);
|
protected currentValue = signal<any[]>([]);
|
||||||
protected readonly appliedFilters = signal<Filter[]>([]);
|
protected appliedFilters = signal<Filter[]>([]);
|
||||||
protected readonly currentRowCount = signal(0n);
|
protected currentRowCount = signal(0n);
|
||||||
|
|
||||||
private readonly table = viewChild(Table);
|
private table = viewChild(Table);
|
||||||
|
|
||||||
protected readonly hasAggregates = computed(
|
protected hasAggregates = computed(
|
||||||
() => !!this.columns().find((col) => this.isAggregateColumn(col)),
|
() => !!this.columns().find((col) => this.isAggregateColumn(col)),
|
||||||
);
|
);
|
||||||
|
|
||||||
protected readonly enabledColumns = computed(() =>
|
protected enabledColumns = computed(() =>
|
||||||
this.columns().filter((col) => col.enabled),
|
this.columns().filter((col) => col.enabled),
|
||||||
);
|
);
|
||||||
|
|
||||||
protected readonly aggregateItems: MenuItem[] = aggregateTypes.map((type) => ({
|
protected aggregateItems: MenuItem[] = aggregateTypes.map((type) => ({
|
||||||
label: type,
|
label: type,
|
||||||
command: () => this.updateAggregateValue(type),
|
command: () => this.updateAggregateValue(type),
|
||||||
}));
|
}));
|
||||||
@@ -219,8 +218,8 @@ export class FileViewerComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async onLazyLoad(event: TableLazyLoadEvent) {
|
protected async onLazyLoad(event: TableLazyLoadEvent) {
|
||||||
const sheet = this.sheet();
|
const file = this.file();
|
||||||
if (sheet) {
|
if (file) {
|
||||||
this.appliedFilters.set(
|
this.appliedFilters.set(
|
||||||
event.filters
|
event.filters
|
||||||
? Object.entries(event.filters).flatMap(([column, filter]) => {
|
? Object.entries(event.filters).flatMap(([column, filter]) => {
|
||||||
@@ -255,20 +254,16 @@ export class FileViewerComponent {
|
|||||||
: [],
|
: [],
|
||||||
);
|
);
|
||||||
const rows = await this.duckdbService.getRows(
|
const rows = await this.duckdbService.getRows(
|
||||||
sheet.file,
|
file,
|
||||||
(event.first ?? 0) + this.currentPage() * this.PAGE_SIZE,
|
(event.first ?? 0) + this.currentPage() * this.PAGE_SIZE,
|
||||||
event.rows ?? 0,
|
event.rows ?? 0,
|
||||||
{
|
this.columns(),
|
||||||
columns: this.columns(),
|
event.multiSortMeta?.map((meta) => ({
|
||||||
// TODO: We should be maintaining sorts separately so we can store them
|
name: meta.field,
|
||||||
sorts:
|
sortType: meta.order < 0 ? 'desc' : 'asc',
|
||||||
event.multiSortMeta?.map((meta) => ({
|
})) ?? [],
|
||||||
name: meta.field,
|
this.appliedFilters(),
|
||||||
sortType: meta.order < 0 ? 'desc' : 'asc',
|
this.aggregates().filter((agg) => !!agg) as Aggregate[],
|
||||||
})) ?? [],
|
|
||||||
filters: this.appliedFilters(),
|
|
||||||
aggregates: this.aggregates().filter((agg) => !!agg) as Aggregate[],
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
// First clear out existing data, don't want to risk loading entire file into memory
|
// 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
|
// Keep data in previous/next page so when user scrolls between 2 pages they don't see missing data
|
||||||
@@ -290,18 +285,20 @@ export class FileViewerComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async loadEmpty() {
|
private async loadEmpty() {
|
||||||
// TODO: Add to history, if the history is empty. If not empty, then load from that history
|
const file = this.file();
|
||||||
const sheet = this.sheet();
|
if (file) {
|
||||||
if (sheet) {
|
await this.duckdbService.addFile(file);
|
||||||
await this.duckdbService.addFile(sheet.file);
|
this.columns.set(await this.duckdbService.getColumns(file));
|
||||||
this.columns.set(await this.duckdbService.getColumns(sheet.file));
|
|
||||||
this.aggregates.set(new Array(this.columns().length));
|
this.aggregates.set(new Array(this.columns().length));
|
||||||
const rows = await this.duckdbService.getRows(sheet.file, 0, this.NUM_ROWS, {
|
const rows = await this.duckdbService.getRows(
|
||||||
columns: this.columns(),
|
file,
|
||||||
sorts: [],
|
0,
|
||||||
filters: [],
|
this.NUM_ROWS,
|
||||||
aggregates: [],
|
this.columns(),
|
||||||
});
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
);
|
||||||
this.currentRowCount.set(rows.totalRows);
|
this.currentRowCount.set(rows.totalRows);
|
||||||
this.currentPage.set(0);
|
this.currentPage.set(0);
|
||||||
const newValue = Array.from({
|
const newValue = Array.from({
|
||||||
@@ -321,7 +318,6 @@ export class FileViewerComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async updateAggregateValue(type: AggregateType) {
|
private async updateAggregateValue(type: AggregateType) {
|
||||||
// Update history
|
|
||||||
this.aggregates.update((aggregates) => {
|
this.aggregates.update((aggregates) => {
|
||||||
const copy = aggregates.slice();
|
const copy = aggregates.slice();
|
||||||
copy[this.currentAggregateColumn()!] = {
|
copy[this.currentAggregateColumn()!] = {
|
||||||
@@ -331,9 +327,8 @@ export class FileViewerComponent {
|
|||||||
return copy;
|
return copy;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const aggregateValue = await this.duckdbService.getAggregateValue(
|
const aggregateValue = await this.duckdbService.getAggregateValue(
|
||||||
this.sheet()!.file,
|
this.file()!,
|
||||||
this.appliedFilters(),
|
this.appliedFilters(),
|
||||||
this.aggregates()[this.currentAggregateColumn()!]!,
|
this.aggregates()[this.currentAggregateColumn()!]!,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
{
|
{
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"dockview-angular": [
|
||||||
|
"./dist/dockview-angular"
|
||||||
|
]
|
||||||
|
},
|
||||||
"outDir": "./dist/out-tsc",
|
"outDir": "./dist/out-tsc",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
@@ -23,5 +28,13 @@
|
|||||||
"strictInjectionParameters": true,
|
"strictInjectionParameters": true,
|
||||||
"strictInputAccessModifiers": true,
|
"strictInputAccessModifiers": true,
|
||||||
"strictTemplates": true
|
"strictTemplates": true
|
||||||
}
|
},
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./projects/dockview-angular/tsconfig.lib.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./projects/dockview-angular/tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user