juxscript 1.1.184 → 1.1.185
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/dataframe.d.ts +9 -1
- package/lib/components/dataframe.d.ts.map +1 -1
- package/lib/components/dataframe.js +118 -5
- package/lib/components/dataframe.ts +134 -5
- package/lib/storage/TabularDriver.d.ts +1 -2
- package/lib/storage/TabularDriver.d.ts.map +1 -1
- package/lib/storage/TabularDriver.js +13 -48
- package/lib/storage/TabularDriver.ts +13 -54
- package/package.json +1 -1
|
@@ -98,9 +98,17 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
98
98
|
private _updateTable;
|
|
99
99
|
private _showFilterInput;
|
|
100
100
|
/**
|
|
101
|
-
* ✅
|
|
101
|
+
* ✅ UPDATED: Show settings modal for CSV OR Excel reshaping
|
|
102
102
|
*/
|
|
103
103
|
private _showReshapeModal;
|
|
104
|
+
/**
|
|
105
|
+
* ✅ NEW: Excel-specific reshape modal (header row picker)
|
|
106
|
+
*/
|
|
107
|
+
private _showExcelReshapeModal;
|
|
108
|
+
/**
|
|
109
|
+
* ✅ CSV-specific reshape modal (existing implementation)
|
|
110
|
+
*/
|
|
111
|
+
private _showCSVReshapeModal;
|
|
104
112
|
update(prop: string, value: any): void;
|
|
105
113
|
render(targetId?: string | HTMLElement | BaseComponent<any>): this;
|
|
106
114
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAOnC,MAAM,WAAW,gBAAgB;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,cAAc,GAAG,SAAS,GAAG;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,qBAAa,kBAAmB,SAAQ,aAAa,CAAC,cAAc,CAAC;IACjE,OAAO,CAAC,GAAG,CAA0B;IACrC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,aAAa,CAOnB;IACF,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,aAAa,CAAgE;IACrF,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,aAAa,CAAkB;IACvC,OAAO,CAAC,eAAe,CAAiB;IACxC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,mBAAmB,CAAiB;IAC5C,OAAO,CAAC,YAAY,
|
|
1
|
+
{"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
|
|
@@ -34,7 +34,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
34
34
|
this._sheetChunkSize = 10000; // ✅ Default 10k chunk
|
|
35
35
|
this._maxFileSize = 50; // ✅ Default 50MB
|
|
36
36
|
this._showReshapeWarning = true;
|
|
37
|
-
this._rawFileData = null; // ✅
|
|
37
|
+
this._rawFileData = null; // ✅ Add isExcel flag
|
|
38
38
|
this._driver = new TabularDriver(options.dbName ?? 'jux-dataframes', options.storeName ?? 'frames');
|
|
39
39
|
this._showStatus = options.showStatus ?? true;
|
|
40
40
|
this._icon = options.icon ?? '';
|
|
@@ -104,6 +104,8 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
104
104
|
const isExcel = file.name.toLowerCase().endsWith('.xlsx') ||
|
|
105
105
|
file.name.toLowerCase().endsWith('.xls');
|
|
106
106
|
if (isExcel) {
|
|
107
|
+
// ✅ Store raw file for reshape
|
|
108
|
+
this._rawFileData = { file, isExcel: true };
|
|
107
109
|
// ✅ Pass chunking options for large files
|
|
108
110
|
const sheets = await this._driver.streamFileMultiSheet(file, {
|
|
109
111
|
maxSheetSize: this._maxSheetSize,
|
|
@@ -125,7 +127,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
125
127
|
else {
|
|
126
128
|
// ✅ CSV/TSV: Store raw text for reshaping
|
|
127
129
|
const text = await file.text();
|
|
128
|
-
this._rawFileData = { file, text };
|
|
130
|
+
this._rawFileData = { file, text, isExcel: false };
|
|
129
131
|
const df = this._driver.parseCSV(text, {
|
|
130
132
|
autoDetectDelimiter: true,
|
|
131
133
|
hasHeader: true
|
|
@@ -393,8 +395,8 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
393
395
|
}
|
|
394
396
|
this._updateTable();
|
|
395
397
|
this._updateStatus(`${sourceName} — ${this._df.height} rows × ${this._df.width} cols`, 'success');
|
|
396
|
-
// ✅ Add reshape warning button if CSV and enabled
|
|
397
|
-
if (this._showReshapeWarning && this._rawFileData
|
|
398
|
+
// ✅ FIXED: Add reshape warning button if CSV OR Excel and enabled
|
|
399
|
+
if (this._showReshapeWarning && this._rawFileData) {
|
|
398
400
|
const statusEl = document.getElementById(`${this._id}-status`);
|
|
399
401
|
if (statusEl) {
|
|
400
402
|
const settingsBtn = document.createElement('button');
|
|
@@ -482,9 +484,118 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
482
484
|
});
|
|
483
485
|
}
|
|
484
486
|
/**
|
|
485
|
-
* ✅
|
|
487
|
+
* ✅ UPDATED: Show settings modal for CSV OR Excel reshaping
|
|
486
488
|
*/
|
|
487
489
|
_showReshapeModal() {
|
|
490
|
+
if (!this._rawFileData)
|
|
491
|
+
return;
|
|
492
|
+
const isExcel = this._rawFileData.isExcel;
|
|
493
|
+
if (isExcel) {
|
|
494
|
+
// ✅ Excel-specific modal: Let user pick header row from sheet
|
|
495
|
+
this._showExcelReshapeModal();
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
// ✅ CSV-specific modal: delimiter + header row
|
|
499
|
+
this._showCSVReshapeModal();
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* ✅ NEW: Excel-specific reshape modal (header row picker)
|
|
504
|
+
*/
|
|
505
|
+
async _showExcelReshapeModal() {
|
|
506
|
+
if (!this._rawFileData?.file)
|
|
507
|
+
return;
|
|
508
|
+
const modal = document.createElement('div');
|
|
509
|
+
modal.className = 'jux-modal-overlay';
|
|
510
|
+
modal.innerHTML = `
|
|
511
|
+
<div class="jux-modal" style="max-width: 800px;">
|
|
512
|
+
<div class="jux-modal-header">
|
|
513
|
+
<div class="jux-modal-header-title">Excel Import Settings</div>
|
|
514
|
+
<button class="jux-modal-close" id="${this._id}-modal-close">×</button>
|
|
515
|
+
</div>
|
|
516
|
+
<div class="jux-modal-content" style="padding: 1.5rem;">
|
|
517
|
+
<div style="margin-bottom: 1rem;">
|
|
518
|
+
<label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Header Row (0-based)</label>
|
|
519
|
+
<input type="number" id="${this._id}-header-row" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
|
|
520
|
+
<div style="font-size: 0.875rem; color: hsl(var(--muted-foreground)); margin-top: 0.5rem;">
|
|
521
|
+
⚠️ Detected: Row 0 has "Exported On:", row 2 has "340B ID". Try setting this to 2.
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
|
|
525
|
+
<div style="border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 1rem; background: hsl(var(--muted) / 0.3); max-height: 300px; overflow-y: auto;">
|
|
526
|
+
<div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview (first 10 rows)</div>
|
|
527
|
+
<div id="${this._id}-preview" style="font-family: monospace; font-size: 0.75rem; white-space: pre; color: hsl(var(--muted-foreground));"></div>
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
<div class="jux-modal-footer">
|
|
531
|
+
<button id="${this._id}-cancel-reshape" class="jux-button jux-button-ghost">Cancel</button>
|
|
532
|
+
<button id="${this._id}-apply-reshape" class="jux-button">Apply & Re-import</button>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
`;
|
|
536
|
+
document.body.appendChild(modal);
|
|
537
|
+
const headerRowInput = document.getElementById(`${this._id}-header-row`);
|
|
538
|
+
const previewDiv = document.getElementById(`${this._id}-preview`);
|
|
539
|
+
// ✅ Update preview on header row change
|
|
540
|
+
const updatePreview = async () => {
|
|
541
|
+
const headerRow = parseInt(headerRowInput.value) || 0;
|
|
542
|
+
try {
|
|
543
|
+
// Re-parse Excel with specified header row
|
|
544
|
+
const sheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
|
|
545
|
+
headerRow, // ✅ Pass header row to parser
|
|
546
|
+
maxSheetSize: 10 // Preview only
|
|
547
|
+
});
|
|
548
|
+
const firstSheet = Object.values(sheets)[0];
|
|
549
|
+
if (!firstSheet) {
|
|
550
|
+
previewDiv.textContent = '⚠️ No data found';
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const preview = firstSheet.toRows().slice(0, 10).map((row, i) => {
|
|
554
|
+
const cols = Object.values(row).map(v => String(v).padEnd(20)).join(' | ');
|
|
555
|
+
return `${i === 0 ? '📌 ' : ' '}${cols}`;
|
|
556
|
+
}).join('\n');
|
|
557
|
+
previewDiv.textContent = `Columns: ${firstSheet.columns.join(' | ')}\n\n${preview}`;
|
|
558
|
+
}
|
|
559
|
+
catch (err) {
|
|
560
|
+
previewDiv.textContent = `⚠️ Error: ${err.message}`;
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
headerRowInput.addEventListener('input', updatePreview);
|
|
564
|
+
updatePreview();
|
|
565
|
+
// ✅ Apply button
|
|
566
|
+
document.getElementById(`${this._id}-apply-reshape`).addEventListener('click', async () => {
|
|
567
|
+
const headerRow = parseInt(headerRowInput.value) || 0;
|
|
568
|
+
this.state.loading = true;
|
|
569
|
+
this._updateStatus('⏳ Re-parsing with new settings...', 'loading');
|
|
570
|
+
try {
|
|
571
|
+
const sheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
|
|
572
|
+
headerRow,
|
|
573
|
+
maxSheetSize: this._maxSheetSize,
|
|
574
|
+
sheetChunkSize: this._sheetChunkSize
|
|
575
|
+
});
|
|
576
|
+
const sheetNames = Object.keys(sheets);
|
|
577
|
+
await this._driver.store(this._rawFileData.file.name, sheets[sheetNames[0]], { source: this._rawFileData.file.name });
|
|
578
|
+
if (sheetNames.length > 1) {
|
|
579
|
+
this._renderMultiSheet(sheets, this._rawFileData.file.name);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
this._setDataFrame(sheets[sheetNames[0]], this._rawFileData.file.name);
|
|
583
|
+
}
|
|
584
|
+
modal.remove();
|
|
585
|
+
}
|
|
586
|
+
catch (err) {
|
|
587
|
+
this._updateStatus(`❌ ${err.message}`, 'error');
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
// ✅ Cancel/Close buttons
|
|
591
|
+
const closeModal = () => modal.remove();
|
|
592
|
+
document.getElementById(`${this._id}-cancel-reshape`).addEventListener('click', closeModal);
|
|
593
|
+
document.getElementById(`${this._id}-modal-close`).addEventListener('click', closeModal);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* ✅ CSV-specific reshape modal (existing implementation)
|
|
597
|
+
*/
|
|
598
|
+
_showCSVReshapeModal() {
|
|
488
599
|
if (!this._rawFileData)
|
|
489
600
|
return;
|
|
490
601
|
const modal = document.createElement('div');
|
|
@@ -633,6 +744,8 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
633
744
|
const isExcel = file.name.toLowerCase().endsWith('.xlsx') ||
|
|
634
745
|
file.name.toLowerCase().endsWith('.xls');
|
|
635
746
|
if (isExcel) {
|
|
747
|
+
// ✅ Store raw file for reshape
|
|
748
|
+
this._rawFileData = { file, isExcel: true };
|
|
636
749
|
const sheets = await this._driver.streamFileMultiSheet(file, {
|
|
637
750
|
maxSheetSize: this._maxSheetSize,
|
|
638
751
|
sheetChunkSize: this._sheetChunkSize,
|
|
@@ -59,7 +59,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
59
59
|
private _sheetChunkSize: number = 10000; // ✅ Default 10k chunk
|
|
60
60
|
private _maxFileSize: number = 50; // ✅ Default 50MB
|
|
61
61
|
private _showReshapeWarning: boolean = true;
|
|
62
|
-
private _rawFileData: { file: File; text?: string } | null = null; // ✅
|
|
62
|
+
private _rawFileData: { file: File; text?: string; isExcel?: boolean } | null = null; // ✅ Add isExcel flag
|
|
63
63
|
|
|
64
64
|
constructor(id: string, options: DataFrameOptions = {}) {
|
|
65
65
|
super(id, {
|
|
@@ -149,6 +149,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
149
149
|
file.name.toLowerCase().endsWith('.xls');
|
|
150
150
|
|
|
151
151
|
if (isExcel) {
|
|
152
|
+
// ✅ Store raw file for reshape
|
|
153
|
+
this._rawFileData = { file, isExcel: true };
|
|
154
|
+
|
|
152
155
|
// ✅ Pass chunking options for large files
|
|
153
156
|
const sheets = await this._driver.streamFileMultiSheet(file, {
|
|
154
157
|
maxSheetSize: this._maxSheetSize,
|
|
@@ -171,7 +174,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
171
174
|
} else {
|
|
172
175
|
// ✅ CSV/TSV: Store raw text for reshaping
|
|
173
176
|
const text = await file.text();
|
|
174
|
-
this._rawFileData = { file, text };
|
|
177
|
+
this._rawFileData = { file, text, isExcel: false };
|
|
175
178
|
|
|
176
179
|
const df = this._driver.parseCSV(text, {
|
|
177
180
|
autoDetectDelimiter: true,
|
|
@@ -493,8 +496,8 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
493
496
|
'success'
|
|
494
497
|
);
|
|
495
498
|
|
|
496
|
-
// ✅ Add reshape warning button if CSV and enabled
|
|
497
|
-
if (this._showReshapeWarning && this._rawFileData
|
|
499
|
+
// ✅ FIXED: Add reshape warning button if CSV OR Excel and enabled
|
|
500
|
+
if (this._showReshapeWarning && this._rawFileData) {
|
|
498
501
|
const statusEl = document.getElementById(`${this._id}-status`);
|
|
499
502
|
if (statusEl) {
|
|
500
503
|
const settingsBtn = document.createElement('button');
|
|
@@ -597,11 +600,134 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
597
600
|
}
|
|
598
601
|
|
|
599
602
|
/**
|
|
600
|
-
* ✅
|
|
603
|
+
* ✅ UPDATED: Show settings modal for CSV OR Excel reshaping
|
|
601
604
|
*/
|
|
602
605
|
private _showReshapeModal(): void {
|
|
603
606
|
if (!this._rawFileData) return;
|
|
604
607
|
|
|
608
|
+
const isExcel = this._rawFileData.isExcel;
|
|
609
|
+
|
|
610
|
+
if (isExcel) {
|
|
611
|
+
// ✅ Excel-specific modal: Let user pick header row from sheet
|
|
612
|
+
this._showExcelReshapeModal();
|
|
613
|
+
} else {
|
|
614
|
+
// ✅ CSV-specific modal: delimiter + header row
|
|
615
|
+
this._showCSVReshapeModal();
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* ✅ NEW: Excel-specific reshape modal (header row picker)
|
|
621
|
+
*/
|
|
622
|
+
private async _showExcelReshapeModal(): Promise<void> {
|
|
623
|
+
if (!this._rawFileData?.file) return;
|
|
624
|
+
|
|
625
|
+
const modal = document.createElement('div');
|
|
626
|
+
modal.className = 'jux-modal-overlay';
|
|
627
|
+
modal.innerHTML = `
|
|
628
|
+
<div class="jux-modal" style="max-width: 800px;">
|
|
629
|
+
<div class="jux-modal-header">
|
|
630
|
+
<div class="jux-modal-header-title">Excel Import Settings</div>
|
|
631
|
+
<button class="jux-modal-close" id="${this._id}-modal-close">×</button>
|
|
632
|
+
</div>
|
|
633
|
+
<div class="jux-modal-content" style="padding: 1.5rem;">
|
|
634
|
+
<div style="margin-bottom: 1rem;">
|
|
635
|
+
<label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Header Row (0-based)</label>
|
|
636
|
+
<input type="number" id="${this._id}-header-row" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
|
|
637
|
+
<div style="font-size: 0.875rem; color: hsl(var(--muted-foreground)); margin-top: 0.5rem;">
|
|
638
|
+
⚠️ Detected: Row 0 has "Exported On:", row 2 has "340B ID". Try setting this to 2.
|
|
639
|
+
</div>
|
|
640
|
+
</div>
|
|
641
|
+
|
|
642
|
+
<div style="border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 1rem; background: hsl(var(--muted) / 0.3); max-height: 300px; overflow-y: auto;">
|
|
643
|
+
<div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview (first 10 rows)</div>
|
|
644
|
+
<div id="${this._id}-preview" style="font-family: monospace; font-size: 0.75rem; white-space: pre; color: hsl(var(--muted-foreground));"></div>
|
|
645
|
+
</div>
|
|
646
|
+
</div>
|
|
647
|
+
<div class="jux-modal-footer">
|
|
648
|
+
<button id="${this._id}-cancel-reshape" class="jux-button jux-button-ghost">Cancel</button>
|
|
649
|
+
<button id="${this._id}-apply-reshape" class="jux-button">Apply & Re-import</button>
|
|
650
|
+
</div>
|
|
651
|
+
</div>
|
|
652
|
+
`;
|
|
653
|
+
|
|
654
|
+
document.body.appendChild(modal);
|
|
655
|
+
|
|
656
|
+
const headerRowInput = document.getElementById(`${this._id}-header-row`) as HTMLInputElement;
|
|
657
|
+
const previewDiv = document.getElementById(`${this._id}-preview`)!;
|
|
658
|
+
|
|
659
|
+
// ✅ Update preview on header row change
|
|
660
|
+
const updatePreview = async () => {
|
|
661
|
+
const headerRow = parseInt(headerRowInput.value) || 0;
|
|
662
|
+
|
|
663
|
+
try {
|
|
664
|
+
// Re-parse Excel with specified header row
|
|
665
|
+
const sheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
|
|
666
|
+
headerRow, // ✅ Pass header row to parser
|
|
667
|
+
maxSheetSize: 10 // Preview only
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
const firstSheet = Object.values(sheets)[0];
|
|
671
|
+
if (!firstSheet) {
|
|
672
|
+
previewDiv.textContent = '⚠️ No data found';
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const preview = firstSheet.toRows().slice(0, 10).map((row, i) => {
|
|
677
|
+
const cols = Object.values(row).map(v => String(v).padEnd(20)).join(' | ');
|
|
678
|
+
return `${i === 0 ? '📌 ' : ' '}${cols}`;
|
|
679
|
+
}).join('\n');
|
|
680
|
+
|
|
681
|
+
previewDiv.textContent = `Columns: ${firstSheet.columns.join(' | ')}\n\n${preview}`;
|
|
682
|
+
} catch (err: any) {
|
|
683
|
+
previewDiv.textContent = `⚠️ Error: ${err.message}`;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
headerRowInput.addEventListener('input', updatePreview);
|
|
688
|
+
updatePreview();
|
|
689
|
+
|
|
690
|
+
// ✅ Apply button
|
|
691
|
+
document.getElementById(`${this._id}-apply-reshape`)!.addEventListener('click', async () => {
|
|
692
|
+
const headerRow = parseInt(headerRowInput.value) || 0;
|
|
693
|
+
|
|
694
|
+
this.state.loading = true;
|
|
695
|
+
this._updateStatus('⏳ Re-parsing with new settings...', 'loading');
|
|
696
|
+
|
|
697
|
+
try {
|
|
698
|
+
const sheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
|
|
699
|
+
headerRow,
|
|
700
|
+
maxSheetSize: this._maxSheetSize,
|
|
701
|
+
sheetChunkSize: this._sheetChunkSize
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
const sheetNames = Object.keys(sheets);
|
|
705
|
+
await this._driver.store(this._rawFileData!.file.name, sheets[sheetNames[0]], { source: this._rawFileData!.file.name });
|
|
706
|
+
|
|
707
|
+
if (sheetNames.length > 1) {
|
|
708
|
+
this._renderMultiSheet(sheets, this._rawFileData!.file.name);
|
|
709
|
+
} else {
|
|
710
|
+
this._setDataFrame(sheets[sheetNames[0]], this._rawFileData!.file.name);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
modal.remove();
|
|
714
|
+
} catch (err: any) {
|
|
715
|
+
this._updateStatus(`❌ ${err.message}`, 'error');
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
// ✅ Cancel/Close buttons
|
|
720
|
+
const closeModal = () => modal.remove();
|
|
721
|
+
document.getElementById(`${this._id}-cancel-reshape`)!.addEventListener('click', closeModal);
|
|
722
|
+
document.getElementById(`${this._id}-modal-close`)!.addEventListener('click', closeModal);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* ✅ CSV-specific reshape modal (existing implementation)
|
|
727
|
+
*/
|
|
728
|
+
private _showCSVReshapeModal(): void {
|
|
729
|
+
if (!this._rawFileData) return;
|
|
730
|
+
|
|
605
731
|
const modal = document.createElement('div');
|
|
606
732
|
modal.className = 'jux-modal-overlay';
|
|
607
733
|
modal.innerHTML = `
|
|
@@ -768,6 +894,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
768
894
|
file.name.toLowerCase().endsWith('.xls');
|
|
769
895
|
|
|
770
896
|
if (isExcel) {
|
|
897
|
+
// ✅ Store raw file for reshape
|
|
898
|
+
this._rawFileData = { file, isExcel: true };
|
|
899
|
+
|
|
771
900
|
const sheets = await this._driver.streamFileMultiSheet(file, {
|
|
772
901
|
maxSheetSize: this._maxSheetSize,
|
|
773
902
|
sheetChunkSize: this._sheetChunkSize,
|
|
@@ -88,8 +88,7 @@ export declare class TabularDriver {
|
|
|
88
88
|
*/
|
|
89
89
|
fetch(url: string, options?: ParseOptions): Promise<DataFrame>;
|
|
90
90
|
/**
|
|
91
|
-
* ✅
|
|
92
|
-
* Handles large files by processing rows in batches
|
|
91
|
+
* ✅ UPDATED: Stream Excel file with optional headerRow override
|
|
93
92
|
*/
|
|
94
93
|
streamFileMultiSheet(file: File, options?: ParseOptions): Promise<Record<string, DataFrame>>;
|
|
95
94
|
private _splitLines;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,qBAAa,aAAa;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,GAAG,CAA4B;gBAE3B,MAAM,GAAE,MAAsB,EAAE,SAAS,GAAE,MAAiB;IAKlE,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IA4BlC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAgCxB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAwBxB;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,SAAS;IA6D7D;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IAoG5E;;OAEG;YACW,UAAU;IAuExB;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAiBlD;;OAEG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBzF;;OAEG;IACG,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAwBjD;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IA4BzD;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAqBlH;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB5B;;OAEG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IA0ExE
|
|
1
|
+
{"version":3,"file":"TabularDriver.d.ts","sourceRoot":"","sources":["TabularDriver.ts"],"names":[],"mappings":"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"}
|
|
@@ -487,11 +487,10 @@ export class TabularDriver {
|
|
|
487
487
|
return df;
|
|
488
488
|
}
|
|
489
489
|
/**
|
|
490
|
-
* ✅
|
|
491
|
-
* Handles large files by processing rows in batches
|
|
490
|
+
* ✅ UPDATED: Stream Excel file with optional headerRow override
|
|
492
491
|
*/
|
|
493
492
|
async streamFileMultiSheet(file, options = {}) {
|
|
494
|
-
const { maxSheetSize = 100000, sheetChunkSize = 10000, onProgress } = options;
|
|
493
|
+
const { maxSheetSize = 100000, sheetChunkSize = 10000, onProgress, headerRow } = options;
|
|
495
494
|
let XLSX;
|
|
496
495
|
try {
|
|
497
496
|
XLSX = await import('xlsx');
|
|
@@ -506,63 +505,29 @@ export class TabularDriver {
|
|
|
506
505
|
onProgress(file.size * 0.3, file.size);
|
|
507
506
|
const workbook = XLSX.read(buffer, {
|
|
508
507
|
type: 'array',
|
|
509
|
-
sheetRows: maxSheetSize,
|
|
510
|
-
dense: false
|
|
508
|
+
sheetRows: maxSheetSize,
|
|
509
|
+
dense: false
|
|
511
510
|
});
|
|
512
511
|
const sheets = {};
|
|
513
512
|
const totalSheets = workbook.SheetNames.length;
|
|
514
513
|
let processedSheets = 0;
|
|
515
514
|
for (const sheetName of workbook.SheetNames) {
|
|
516
515
|
const worksheet = workbook.Sheets[sheetName];
|
|
517
|
-
// ✅ Get sheet range to determine size
|
|
518
516
|
const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
|
|
519
517
|
const totalRows = range.e.r - range.s.r + 1;
|
|
520
518
|
if (totalRows === 0) {
|
|
521
519
|
processedSheets++;
|
|
522
520
|
continue;
|
|
523
521
|
}
|
|
524
|
-
// ✅
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
header: startRow === 0 ? undefined : headers,
|
|
534
|
-
defval: null,
|
|
535
|
-
raw: false, // ✅ Convert dates/numbers to strings to reduce memory
|
|
536
|
-
blankrows: false
|
|
537
|
-
});
|
|
538
|
-
if (startRow === 0 && chunkData.length > 0) {
|
|
539
|
-
headers = Object.keys(chunkData[0]);
|
|
540
|
-
}
|
|
541
|
-
rows.push(...chunkData);
|
|
542
|
-
if (onProgress) {
|
|
543
|
-
const progress = 0.3 + (0.6 * (processedSheets + (startRow / totalRows)) / totalSheets);
|
|
544
|
-
onProgress(file.size * progress, file.size);
|
|
545
|
-
}
|
|
546
|
-
// ✅ Stop if we hit max rows
|
|
547
|
-
if (rows.length >= maxSheetSize) {
|
|
548
|
-
console.warn(`⚠️ Sheet "${sheetName}" truncated to ${maxSheetSize} rows`);
|
|
549
|
-
break;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
if (rows.length > 0) {
|
|
553
|
-
sheets[sheetName] = new DataFrame(rows);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
// ✅ Small sheet: process normally
|
|
558
|
-
const jsonData = XLSX.utils.sheet_to_json(worksheet, {
|
|
559
|
-
defval: null,
|
|
560
|
-
raw: false,
|
|
561
|
-
blankrows: false
|
|
562
|
-
});
|
|
563
|
-
if (jsonData.length > 0) {
|
|
564
|
-
sheets[sheetName] = new DataFrame(jsonData);
|
|
565
|
-
}
|
|
522
|
+
// ✅ NEW: Support headerRow option
|
|
523
|
+
const jsonData = XLSX.utils.sheet_to_json(worksheet, {
|
|
524
|
+
range: headerRow !== undefined ? headerRow : undefined, // ✅ Start from specified row
|
|
525
|
+
defval: null,
|
|
526
|
+
raw: false,
|
|
527
|
+
blankrows: false
|
|
528
|
+
});
|
|
529
|
+
if (jsonData.length > 0) {
|
|
530
|
+
sheets[sheetName] = new DataFrame(jsonData);
|
|
566
531
|
}
|
|
567
532
|
processedSheets++;
|
|
568
533
|
if (onProgress) {
|
|
@@ -607,11 +607,10 @@ export class TabularDriver {
|
|
|
607
607
|
}
|
|
608
608
|
|
|
609
609
|
/**
|
|
610
|
-
* ✅
|
|
611
|
-
* Handles large files by processing rows in batches
|
|
610
|
+
* ✅ UPDATED: Stream Excel file with optional headerRow override
|
|
612
611
|
*/
|
|
613
612
|
async streamFileMultiSheet(file: File, options: ParseOptions = {}): Promise<Record<string, DataFrame>> {
|
|
614
|
-
const { maxSheetSize = 100000, sheetChunkSize = 10000, onProgress } = options;
|
|
613
|
+
const { maxSheetSize = 100000, sheetChunkSize = 10000, onProgress, headerRow } = options;
|
|
615
614
|
|
|
616
615
|
let XLSX: any;
|
|
617
616
|
try {
|
|
@@ -628,8 +627,8 @@ export class TabularDriver {
|
|
|
628
627
|
|
|
629
628
|
const workbook = XLSX.read(buffer, {
|
|
630
629
|
type: 'array',
|
|
631
|
-
sheetRows: maxSheetSize,
|
|
632
|
-
dense: false
|
|
630
|
+
sheetRows: maxSheetSize,
|
|
631
|
+
dense: false
|
|
633
632
|
});
|
|
634
633
|
|
|
635
634
|
const sheets: Record<string, DataFrame> = {};
|
|
@@ -639,7 +638,6 @@ export class TabularDriver {
|
|
|
639
638
|
for (const sheetName of workbook.SheetNames) {
|
|
640
639
|
const worksheet = workbook.Sheets[sheetName];
|
|
641
640
|
|
|
642
|
-
// ✅ Get sheet range to determine size
|
|
643
641
|
const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1');
|
|
644
642
|
const totalRows = range.e.r - range.s.r + 1;
|
|
645
643
|
|
|
@@ -648,55 +646,16 @@ export class TabularDriver {
|
|
|
648
646
|
continue;
|
|
649
647
|
}
|
|
650
648
|
|
|
651
|
-
// ✅
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
// ✅ Read chunk with limited row range
|
|
660
|
-
const chunkData: Record<string, any>[] = XLSX.utils.sheet_to_json(worksheet, {
|
|
661
|
-
range: startRow,
|
|
662
|
-
header: startRow === 0 ? undefined : headers,
|
|
663
|
-
defval: null,
|
|
664
|
-
raw: false, // ✅ Convert dates/numbers to strings to reduce memory
|
|
665
|
-
blankrows: false
|
|
666
|
-
});
|
|
667
|
-
|
|
668
|
-
if (startRow === 0 && chunkData.length > 0) {
|
|
669
|
-
headers = Object.keys(chunkData[0]);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
rows.push(...chunkData);
|
|
673
|
-
|
|
674
|
-
if (onProgress) {
|
|
675
|
-
const progress = 0.3 + (0.6 * (processedSheets + (startRow / totalRows)) / totalSheets);
|
|
676
|
-
onProgress(file.size * progress, file.size);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
// ✅ Stop if we hit max rows
|
|
680
|
-
if (rows.length >= maxSheetSize) {
|
|
681
|
-
console.warn(`⚠️ Sheet "${sheetName}" truncated to ${maxSheetSize} rows`);
|
|
682
|
-
break;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
if (rows.length > 0) {
|
|
687
|
-
sheets[sheetName] = new DataFrame(rows);
|
|
688
|
-
}
|
|
689
|
-
} else {
|
|
690
|
-
// ✅ Small sheet: process normally
|
|
691
|
-
const jsonData: Record<string, any>[] = XLSX.utils.sheet_to_json(worksheet, {
|
|
692
|
-
defval: null,
|
|
693
|
-
raw: false,
|
|
694
|
-
blankrows: false
|
|
695
|
-
});
|
|
649
|
+
// ✅ NEW: Support headerRow option
|
|
650
|
+
const jsonData: Record<string, any>[] = XLSX.utils.sheet_to_json(worksheet, {
|
|
651
|
+
range: headerRow !== undefined ? headerRow : undefined, // ✅ Start from specified row
|
|
652
|
+
defval: null,
|
|
653
|
+
raw: false,
|
|
654
|
+
blankrows: false
|
|
655
|
+
});
|
|
696
656
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
}
|
|
657
|
+
if (jsonData.length > 0) {
|
|
658
|
+
sheets[sheetName] = new DataFrame(jsonData);
|
|
700
659
|
}
|
|
701
660
|
|
|
702
661
|
processedSheets++;
|