juxscript 1.1.203 → 1.1.205
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.map +1 -1
- package/lib/components/dataframe.js +67 -107
- package/lib/components/dataframe.ts +73 -131
- package/lib/storage/TabularDriver.d.ts +3 -0
- package/lib/storage/TabularDriver.d.ts.map +1 -1
- package/lib/storage/TabularDriver.js +52 -53
- package/lib/storage/TabularDriver.ts +50 -52
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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
|
|
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;AASnC,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,CAAiE;IACrF,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,qBAAqB,CAAkB;gBAEnC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB;IAmCtD,SAAS,CAAC,gBAAgB,IAAI,SAAS,MAAM,EAAE;IAC/C,SAAS,CAAC,iBAAiB,IAAI,SAAS,MAAM,EAAE;IAMhD,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAwB9B,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAWpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI;IAiBnE,UAAU,CAAC,KAAK,GAAE,MAAsB,EAAE,MAAM,GAAE,MAAoC,EAAE,IAAI,GAAE,MAAiB,GAAG,IAAI;IAStH,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC5B,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAM3B,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,SAAS,GAAG,IAAI;IAQ7C,MAAM,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI;IAI7E,MAAM,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IAI/B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI;IAI7C,IAAI,CAAC,CAAC,GAAE,MAAU,GAAG,IAAI;IAIzB,IAAI,CAAC,CAAC,GAAE,MAAU,GAAG,IAAI;IAIzB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,GAAG,GAAG,IAAI;IAIpF,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,UAAU,GAAG,YAAY,GAAG,UAAU,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAQxH,IAAI,EAAE,IAAI,SAAS,GAAG,IAAI,CAAqB;IAC/C,IAAI,MAAM,IAAI,aAAa,CAAyB;IACpD,IAAI,KAAK,IAAI,KAAK,GAAG,IAAI,CAAwB;IACjD,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IACtC,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IACjC,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE;IAC/B,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAsC;IACnE,IAAI,OAAO,IAAI,MAAM,EAAE,CAAoC;IAErD,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAUhD,OAAO,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IACzB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC3B,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC1B,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC5B,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAC3B,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAC5B,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAC7B,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAC/B,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;YAMf,WAAW;IAgEzB,OAAO,CAAC,iBAAiB;IA+EzB,OAAO,CAAC,aAAa;IAuBrB,OAAO,CAAC,aAAa;IA4ErB,OAAO,CAAC,oBAAoB;IA6B5B,OAAO,CAAC,sBAAsB;IAmC9B,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,oBAAoB;YASd,sBAAsB;IAoNpC,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,oBAAoB;IA0L5B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,IAAI;IAExC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI;CAoErE;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,kBAAkB,CAExF"}
|
|
@@ -217,6 +217,11 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
217
217
|
}
|
|
218
218
|
});
|
|
219
219
|
const sheetNames = Object.keys(sheets);
|
|
220
|
+
if (sheetNames.length === 0) {
|
|
221
|
+
this._updateStatus('No data found in file', 'error');
|
|
222
|
+
this.state.loading = false;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
220
225
|
await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
|
|
221
226
|
if (sheetNames.length > 1) {
|
|
222
227
|
this._renderMultiSheet(sheets, file.name);
|
|
@@ -251,7 +256,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
251
256
|
const wrapper = document.getElementById(this._id);
|
|
252
257
|
if (!wrapper)
|
|
253
258
|
return;
|
|
254
|
-
// Clean up existing content
|
|
255
259
|
const existingTable = wrapper.querySelector('.jux-table-wrapper');
|
|
256
260
|
if (existingTable)
|
|
257
261
|
existingTable.remove();
|
|
@@ -262,11 +266,10 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
262
266
|
this._sheets.set(name, df);
|
|
263
267
|
});
|
|
264
268
|
const sheetNames = Object.keys(sheets);
|
|
265
|
-
// Build tabs using the Tabs component
|
|
266
269
|
const tabDefs = sheetNames.map(name => ({
|
|
267
270
|
id: name,
|
|
268
271
|
label: name,
|
|
269
|
-
content: ''
|
|
272
|
+
content: ''
|
|
270
273
|
}));
|
|
271
274
|
this._tabs = new Tabs(`${this._id}-tabs`, {
|
|
272
275
|
tabs: tabDefs,
|
|
@@ -275,15 +278,12 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
275
278
|
this._tabs.bind('tabChange', (tabId) => {
|
|
276
279
|
this._df = this._sheets.get(tabId) || null;
|
|
277
280
|
});
|
|
278
|
-
// Create container for tabs
|
|
279
281
|
const tabsContainer = document.createElement('div');
|
|
280
282
|
tabsContainer.className = 'jux-dataframe-tabs';
|
|
281
283
|
wrapper.appendChild(tabsContainer);
|
|
282
284
|
this._tabs.render(tabsContainer);
|
|
283
|
-
|
|
284
|
-
sheetNames.forEach((sheetName, idx) => {
|
|
285
|
+
sheetNames.forEach((sheetName) => {
|
|
285
286
|
const df = sheets[sheetName];
|
|
286
|
-
const panelId = `${this._id}-tabs-${sheetName}-panel`;
|
|
287
287
|
const table = new Table(`${this._id}-table-${sheetName}`, {
|
|
288
288
|
striped: this._tableOptions.striped,
|
|
289
289
|
hoverable: this._tableOptions.hoverable,
|
|
@@ -294,48 +294,13 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
294
294
|
});
|
|
295
295
|
const columnDefs = df.columns.map(col => ({ key: col, label: col }));
|
|
296
296
|
table.columns(columnDefs).rows(df.toRows());
|
|
297
|
-
// Add settings button to tab panel
|
|
298
297
|
const settingsBtn = new Button(`${this._id}-settings-${sheetName}`, {
|
|
299
298
|
label: '⚙️ Import Settings',
|
|
300
299
|
variant: 'ghost',
|
|
301
300
|
size: 'small'
|
|
302
301
|
});
|
|
303
302
|
settingsBtn.bind('click', () => this._showReshapeModal());
|
|
304
|
-
// Use addTabContent to add components
|
|
305
303
|
this._tabs.addTabContent(sheetName, [settingsBtn, table]);
|
|
306
|
-
if (this._tableOptions.filterable) {
|
|
307
|
-
// Add filter input above table
|
|
308
|
-
const panel = document.getElementById(panelId);
|
|
309
|
-
if (panel) {
|
|
310
|
-
const filterContainer = document.createElement('div');
|
|
311
|
-
filterContainer.className = 'jux-dataframe-filter';
|
|
312
|
-
const input = document.createElement('input');
|
|
313
|
-
input.type = 'text';
|
|
314
|
-
input.placeholder = `Filter ${sheetName}...`;
|
|
315
|
-
input.className = 'jux-input-element jux-dataframe-filter-input';
|
|
316
|
-
const iconEl = renderIcon('search');
|
|
317
|
-
iconEl.style.width = '16px';
|
|
318
|
-
iconEl.style.height = '16px';
|
|
319
|
-
const iconWrap = document.createElement('span');
|
|
320
|
-
iconWrap.className = 'jux-dataframe-filter-icon';
|
|
321
|
-
iconWrap.appendChild(iconEl);
|
|
322
|
-
filterContainer.appendChild(iconWrap);
|
|
323
|
-
filterContainer.appendChild(input);
|
|
324
|
-
input.addEventListener('input', () => {
|
|
325
|
-
const text = input.value.toLowerCase();
|
|
326
|
-
if (!text) {
|
|
327
|
-
table.rows(df.toRows());
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
const filtered = df.filter((row) => Object.values(row).some(v => v !== null && v !== undefined && String(v).toLowerCase().includes(text)));
|
|
331
|
-
table.rows(filtered.toRows());
|
|
332
|
-
});
|
|
333
|
-
const tableWrapper = panel.querySelector('.jux-table-wrapper');
|
|
334
|
-
if (tableWrapper) {
|
|
335
|
-
panel.insertBefore(filterContainer, tableWrapper);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
304
|
});
|
|
340
305
|
const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
|
|
341
306
|
this._updateStatus(`${sourceName} — ${sheetNames.length} sheets, ${totalRows} total rows`, 'success');
|
|
@@ -378,7 +343,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
378
343
|
const emptyColumns = cols.filter(c => {
|
|
379
344
|
if (!c.startsWith('__EMPTY'))
|
|
380
345
|
return false;
|
|
381
|
-
// Check if ALL values in this column are null/empty
|
|
382
346
|
return rows.every(row => {
|
|
383
347
|
const val = row[c];
|
|
384
348
|
return val === null || val === undefined || String(val).trim() === '';
|
|
@@ -411,7 +375,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
411
375
|
}
|
|
412
376
|
else {
|
|
413
377
|
this._updateStatus(`${sourceName} — ${this._df.height} rows × ${this._df.width} cols`, 'success');
|
|
414
|
-
// Always show settings button if we have raw file data
|
|
415
378
|
if (this._rawFileData) {
|
|
416
379
|
requestAnimationFrame(() => {
|
|
417
380
|
const statusEl = document.getElementById(`${this._id}-status`);
|
|
@@ -455,6 +418,10 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
455
418
|
}
|
|
456
419
|
_detectLikelyHeaderRow(df) {
|
|
457
420
|
const rows = df.toRows();
|
|
421
|
+
const cols = df.columns;
|
|
422
|
+
const colsAreGeneric = cols.some(c => c.startsWith('__EMPTY') || c.match(/^_\d+$/) || c.match(/^col_\d+$/));
|
|
423
|
+
if (!colsAreGeneric)
|
|
424
|
+
return 0;
|
|
458
425
|
for (let i = 0; i < Math.min(rows.length, 10); i++) {
|
|
459
426
|
const row = rows[i];
|
|
460
427
|
const values = Object.values(row);
|
|
@@ -465,10 +432,8 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
465
432
|
const str = String(v).trim();
|
|
466
433
|
return isNaN(Number(str)) && str !== '';
|
|
467
434
|
}).length;
|
|
468
|
-
if (nonNumericCount >= nonEmpty.length * 0.7
|
|
469
|
-
// i
|
|
470
|
-
// as the header during the initial parse, so the actual 0-based file
|
|
471
|
-
// row index is i + 1.
|
|
435
|
+
if (nonNumericCount >= nonEmpty.length * 0.7) {
|
|
436
|
+
// toRows index i = file row (i + 1) since row 0 was used as headers
|
|
472
437
|
return i + 1;
|
|
473
438
|
}
|
|
474
439
|
}
|
|
@@ -500,7 +465,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
500
465
|
if (!this._rawFileData?.file)
|
|
501
466
|
return;
|
|
502
467
|
this._cleanupReshapeModal();
|
|
503
|
-
// Always detect from a fresh raw parse, not from current _df
|
|
504
468
|
let suggestedRow = 0;
|
|
505
469
|
try {
|
|
506
470
|
const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
|
|
@@ -526,22 +490,11 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
526
490
|
<label style="display: block; font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
|
|
527
491
|
Header Row (0-based index)
|
|
528
492
|
</label>
|
|
529
|
-
<input
|
|
530
|
-
|
|
531
|
-
id="${this._id}-header-row"
|
|
532
|
-
class="jux-input-element"
|
|
533
|
-
value="${suggestedRow}"
|
|
534
|
-
min="0"
|
|
535
|
-
max="50"
|
|
536
|
-
style="width: 100%;"
|
|
537
|
-
/>
|
|
538
|
-
<div id="${this._id}-reshape-hint" class="jux-reshape-hint" style="margin-top: 0.5rem; padding: 0.75rem; background: hsl(var(--muted) / 0.5); border-radius: var(--radius); font-size: 0.875rem;">
|
|
539
|
-
</div>
|
|
493
|
+
<input type="number" id="${this._id}-header-row" class="jux-input-element" value="${suggestedRow}" min="0" max="50" style="width: 100%;" />
|
|
494
|
+
<div id="${this._id}-reshape-hint" class="jux-reshape-hint" style="margin-top: 0.5rem; padding: 0.75rem; background: hsl(var(--muted) / 0.5); border-radius: var(--radius); font-size: 0.875rem;"></div>
|
|
540
495
|
</div>
|
|
541
496
|
<div class="jux-reshape-preview-container">
|
|
542
|
-
<div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
|
|
543
|
-
Preview
|
|
544
|
-
</div>
|
|
497
|
+
<div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview</div>
|
|
545
498
|
<div id="${this._id}-preview" style="font-family: ui-monospace, monospace; font-size: 12px; background: hsl(var(--muted) / 0.3); border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 0; overflow: hidden; max-height: 400px; overflow-y: auto;"></div>
|
|
546
499
|
</div>
|
|
547
500
|
`;
|
|
@@ -568,6 +521,11 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
568
521
|
sheetChunkSize: this._sheetChunkSize
|
|
569
522
|
});
|
|
570
523
|
const sheetNames = Object.keys(sheets);
|
|
524
|
+
if (sheetNames.length === 0) {
|
|
525
|
+
this._updateStatus(`No data found with header at row ${headerRow}. Try a different row.`, 'error');
|
|
526
|
+
this.state.loading = false;
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
571
529
|
await this._driver.store(this._rawFileData.file.name, sheets[sheetNames[0]], { source: this._rawFileData.file.name });
|
|
572
530
|
if (sheetNames.length > 1) {
|
|
573
531
|
this._renderMultiSheet(sheets, this._rawFileData.file.name);
|
|
@@ -579,6 +537,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
579
537
|
}
|
|
580
538
|
catch (err) {
|
|
581
539
|
this._updateStatus(`Error: ${err.message}`, 'error');
|
|
540
|
+
this.state.loading = false;
|
|
582
541
|
}
|
|
583
542
|
}
|
|
584
543
|
}
|
|
@@ -593,8 +552,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
593
552
|
if (!hintDiv)
|
|
594
553
|
return;
|
|
595
554
|
if (headerRow > 0) {
|
|
596
|
-
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers.
|
|
597
|
-
`Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
555
|
+
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
598
556
|
}
|
|
599
557
|
else {
|
|
600
558
|
hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
|
|
@@ -604,26 +562,46 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
604
562
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
605
563
|
updateHint(headerRow);
|
|
606
564
|
try {
|
|
607
|
-
//
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
565
|
+
// Read cells directly from XLSX to match parser behavior exactly
|
|
566
|
+
const XLSX = await import('xlsx');
|
|
567
|
+
const buffer = await this._rawFileData.file.arrayBuffer();
|
|
568
|
+
const workbook = XLSX.read(buffer, {
|
|
569
|
+
type: 'array',
|
|
570
|
+
sheetRows: Math.max(headerRow + 12, 15),
|
|
571
|
+
dense: false
|
|
611
572
|
});
|
|
612
|
-
const
|
|
613
|
-
|
|
573
|
+
const sheetName = workbook.SheetNames[0];
|
|
574
|
+
const worksheet = workbook.Sheets[sheetName];
|
|
575
|
+
const ref = worksheet['!ref'];
|
|
576
|
+
if (!ref) {
|
|
614
577
|
if (previewDiv)
|
|
615
578
|
previewDiv.textContent = 'No data found';
|
|
616
579
|
return;
|
|
617
580
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
const
|
|
621
|
-
const
|
|
622
|
-
|
|
581
|
+
const range = XLSX.utils.decode_range(ref);
|
|
582
|
+
const endRow = range.e.r;
|
|
583
|
+
const startCol = range.s.c;
|
|
584
|
+
const endCol = range.e.c;
|
|
585
|
+
const readCellValue = (r, c) => {
|
|
586
|
+
const addr = XLSX.utils.encode_cell({ r, c });
|
|
587
|
+
const cell = worksheet[addr];
|
|
588
|
+
if (!cell)
|
|
589
|
+
return null;
|
|
590
|
+
if (cell.w !== undefined)
|
|
591
|
+
return cell.w;
|
|
592
|
+
if (cell.v !== undefined)
|
|
593
|
+
return cell.v;
|
|
594
|
+
return null;
|
|
595
|
+
};
|
|
596
|
+
const readRow = (r) => {
|
|
597
|
+
const vals = [];
|
|
598
|
+
for (let c = startCol; c <= endCol; c++) {
|
|
599
|
+
vals.push(readCellValue(r, c));
|
|
600
|
+
}
|
|
601
|
+
return vals;
|
|
602
|
+
};
|
|
623
603
|
let html = '<table style="width: 100%; border-collapse: collapse; font-size: 12px;">';
|
|
624
|
-
|
|
625
|
-
// Row 0 = rawCols, Row 1+ = rawRows[i-1]
|
|
626
|
-
const totalRowsToShow = Math.min(headerRow + 8, rawRows.length + 1);
|
|
604
|
+
const totalRowsToShow = Math.min(headerRow + 8, endRow + 1);
|
|
627
605
|
for (let fileRow = 0; fileRow < totalRowsToShow; fileRow++) {
|
|
628
606
|
const isHeader = (fileRow === headerRow);
|
|
629
607
|
const isSkipped = (fileRow < headerRow);
|
|
@@ -635,7 +613,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
635
613
|
rowStyle += 'background: hsl(var(--muted) / 0.4); color: hsl(var(--muted-foreground)); font-style: italic; opacity: 0.7;';
|
|
636
614
|
}
|
|
637
615
|
html += `<tr style="${rowStyle}">`;
|
|
638
|
-
// Row index cell
|
|
639
616
|
html += `<td style="padding: 8px 12px; width: 60px; font-weight: 600; color: hsl(var(--muted-foreground)); border-right: 1px solid hsl(var(--border)); text-align: center;">`;
|
|
640
617
|
if (isHeader) {
|
|
641
618
|
html += `<span style="color: hsl(142 71% 45%);">▶ ${fileRow}</span>`;
|
|
@@ -644,15 +621,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
644
621
|
html += `${fileRow}`;
|
|
645
622
|
}
|
|
646
623
|
html += '</td>';
|
|
647
|
-
|
|
648
|
-
let values;
|
|
649
|
-
if (fileRow === 0) {
|
|
650
|
-
values = rawCols;
|
|
651
|
-
}
|
|
652
|
-
else {
|
|
653
|
-
values = rawRows[fileRow - 1] ? Object.values(rawRows[fileRow - 1]) : [];
|
|
654
|
-
}
|
|
655
|
-
// Show first 6 columns
|
|
624
|
+
const values = readRow(fileRow);
|
|
656
625
|
const displayCols = values.slice(0, 6);
|
|
657
626
|
displayCols.forEach(val => {
|
|
658
627
|
const displayVal = val != null ? String(val).substring(0, 20) : '';
|
|
@@ -662,26 +631,23 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
662
631
|
html += `<td style="${cellStyle}">${this._escapeHtml(displayVal)}</td>`;
|
|
663
632
|
});
|
|
664
633
|
if (values.length > 6) {
|
|
665
|
-
html += `<td style="padding: 8px 12px; color: hsl(var(--muted-foreground));"
|
|
634
|
+
html += `<td style="padding: 8px 12px; color: hsl(var(--muted-foreground));">...</td>`;
|
|
666
635
|
}
|
|
667
|
-
// Status badge cell
|
|
668
636
|
html += `<td style="padding: 8px 12px; text-align: right; white-space: nowrap;">`;
|
|
669
637
|
if (isHeader) {
|
|
670
|
-
html += '<span style="background: hsl(
|
|
638
|
+
html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
|
|
671
639
|
}
|
|
672
640
|
else if (isSkipped) {
|
|
673
|
-
html += '<span style="color: hsl(var(--muted-foreground));
|
|
641
|
+
html += '<span style="color: hsl(var(--muted-foreground));">skipped</span>';
|
|
674
642
|
}
|
|
675
643
|
else {
|
|
676
|
-
html += '<span style="color: hsl(var(--
|
|
644
|
+
html += '<span style="color: hsl(var(--success));">data</span>';
|
|
677
645
|
}
|
|
678
|
-
html += '</td>';
|
|
679
|
-
html += '</tr>';
|
|
646
|
+
html += '</td></tr>';
|
|
680
647
|
}
|
|
681
648
|
html += '</table>';
|
|
682
|
-
if (previewDiv)
|
|
649
|
+
if (previewDiv)
|
|
683
650
|
previewDiv.innerHTML = html;
|
|
684
|
-
}
|
|
685
651
|
}
|
|
686
652
|
catch (err) {
|
|
687
653
|
if (previewDiv)
|
|
@@ -760,6 +726,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
760
726
|
}
|
|
761
727
|
catch (err) {
|
|
762
728
|
this._updateStatus(`Error: ${err.message}`, 'error');
|
|
729
|
+
this.state.loading = false;
|
|
763
730
|
}
|
|
764
731
|
}
|
|
765
732
|
}
|
|
@@ -784,8 +751,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
784
751
|
return;
|
|
785
752
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
786
753
|
if (headerRow > 0) {
|
|
787
|
-
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers.
|
|
788
|
-
`Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
754
|
+
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
789
755
|
}
|
|
790
756
|
else {
|
|
791
757
|
hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
|
|
@@ -798,7 +764,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
798
764
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
799
765
|
updateHint();
|
|
800
766
|
try {
|
|
801
|
-
// Parse raw to show all rows
|
|
802
767
|
const rawDf = this._driver.parseCSV(this._rawFileData.text, {
|
|
803
768
|
delimiter: delim,
|
|
804
769
|
headerRow: 0,
|
|
@@ -807,7 +772,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
807
772
|
});
|
|
808
773
|
const rawCols = rawDf.columns;
|
|
809
774
|
const rawRows = rawDf.toRows();
|
|
810
|
-
// Build HTML table
|
|
811
775
|
let html = '<table style="width: 100%; border-collapse: collapse; font-size: 11px;">';
|
|
812
776
|
const totalRows = Math.min(headerRow + 8, rawRows.length + 1);
|
|
813
777
|
for (let i = 0; i < totalRows; i++) {
|
|
@@ -821,11 +785,9 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
821
785
|
rowStyle += 'background: hsl(var(--muted) / 0.3); color: hsl(var(--muted-foreground)); font-style: italic;';
|
|
822
786
|
}
|
|
823
787
|
html += `<tr style="${rowStyle}">`;
|
|
824
|
-
// Row index
|
|
825
788
|
html += `<td style="padding: 6px 8px; width: 50px; color: hsl(var(--muted-foreground)); font-weight: 500;">`;
|
|
826
789
|
html += isHeader ? `<strong>→ ${i}</strong>` : `${i}`;
|
|
827
790
|
html += '</td>';
|
|
828
|
-
// Data
|
|
829
791
|
let values;
|
|
830
792
|
if (i === 0) {
|
|
831
793
|
values = rawCols;
|
|
@@ -843,7 +805,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
843
805
|
if (values.length > 6) {
|
|
844
806
|
html += `<td style="padding: 6px 8px; color: hsl(var(--muted-foreground));">...</td>`;
|
|
845
807
|
}
|
|
846
|
-
// Status
|
|
847
808
|
html += `<td style="padding: 6px 8px; text-align: right; font-size: 10px;">`;
|
|
848
809
|
if (isHeader) {
|
|
849
810
|
html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
|
|
@@ -854,8 +815,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
854
815
|
else {
|
|
855
816
|
html += '<span style="color: hsl(var(--success));">data</span>';
|
|
856
817
|
}
|
|
857
|
-
html += '</td>';
|
|
858
|
-
html += '</tr>';
|
|
818
|
+
html += '</td></tr>';
|
|
859
819
|
}
|
|
860
820
|
html += '</table>';
|
|
861
821
|
if (previewDiv)
|
|
@@ -275,6 +275,13 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
275
275
|
});
|
|
276
276
|
|
|
277
277
|
const sheetNames = Object.keys(sheets);
|
|
278
|
+
|
|
279
|
+
if (sheetNames.length === 0) {
|
|
280
|
+
this._updateStatus('No data found in file', 'error');
|
|
281
|
+
this.state.loading = false;
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
278
285
|
await this._driver.store(file.name, sheets[sheetNames[0]], { source: file.name });
|
|
279
286
|
|
|
280
287
|
if (sheetNames.length > 1) {
|
|
@@ -312,7 +319,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
312
319
|
const wrapper = document.getElementById(this._id);
|
|
313
320
|
if (!wrapper) return;
|
|
314
321
|
|
|
315
|
-
// Clean up existing content
|
|
316
322
|
const existingTable = wrapper.querySelector('.jux-table-wrapper');
|
|
317
323
|
if (existingTable) existingTable.remove();
|
|
318
324
|
|
|
@@ -325,11 +331,10 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
325
331
|
|
|
326
332
|
const sheetNames = Object.keys(sheets);
|
|
327
333
|
|
|
328
|
-
// Build tabs using the Tabs component
|
|
329
334
|
const tabDefs = sheetNames.map(name => ({
|
|
330
335
|
id: name,
|
|
331
336
|
label: name,
|
|
332
|
-
content: ''
|
|
337
|
+
content: ''
|
|
333
338
|
}));
|
|
334
339
|
|
|
335
340
|
this._tabs = new Tabs(`${this._id}-tabs`, {
|
|
@@ -341,17 +346,14 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
341
346
|
this._df = this._sheets.get(tabId) || null;
|
|
342
347
|
});
|
|
343
348
|
|
|
344
|
-
// Create container for tabs
|
|
345
349
|
const tabsContainer = document.createElement('div');
|
|
346
350
|
tabsContainer.className = 'jux-dataframe-tabs';
|
|
347
351
|
wrapper.appendChild(tabsContainer);
|
|
348
352
|
|
|
349
353
|
this._tabs.render(tabsContainer);
|
|
350
354
|
|
|
351
|
-
|
|
352
|
-
sheetNames.forEach((sheetName, idx) => {
|
|
355
|
+
sheetNames.forEach((sheetName) => {
|
|
353
356
|
const df = sheets[sheetName];
|
|
354
|
-
const panelId = `${this._id}-tabs-${sheetName}-panel`;
|
|
355
357
|
|
|
356
358
|
const table = new Table(`${this._id}-table-${sheetName}`, {
|
|
357
359
|
striped: this._tableOptions.striped,
|
|
@@ -365,7 +367,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
365
367
|
const columnDefs = df.columns.map(col => ({ key: col, label: col }));
|
|
366
368
|
table.columns(columnDefs).rows(df.toRows());
|
|
367
369
|
|
|
368
|
-
// Add settings button to tab panel
|
|
369
370
|
const settingsBtn = new Button(`${this._id}-settings-${sheetName}`, {
|
|
370
371
|
label: '⚙️ Import Settings',
|
|
371
372
|
variant: 'ghost',
|
|
@@ -373,49 +374,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
373
374
|
});
|
|
374
375
|
settingsBtn.bind('click', () => this._showReshapeModal());
|
|
375
376
|
|
|
376
|
-
// Use addTabContent to add components
|
|
377
377
|
this._tabs!.addTabContent(sheetName, [settingsBtn, table]);
|
|
378
|
-
|
|
379
|
-
if (this._tableOptions.filterable) {
|
|
380
|
-
// Add filter input above table
|
|
381
|
-
const panel = document.getElementById(panelId);
|
|
382
|
-
if (panel) {
|
|
383
|
-
const filterContainer = document.createElement('div');
|
|
384
|
-
filterContainer.className = 'jux-dataframe-filter';
|
|
385
|
-
|
|
386
|
-
const input = document.createElement('input');
|
|
387
|
-
input.type = 'text';
|
|
388
|
-
input.placeholder = `Filter ${sheetName}...`;
|
|
389
|
-
input.className = 'jux-input-element jux-dataframe-filter-input';
|
|
390
|
-
|
|
391
|
-
const iconEl = renderIcon('search');
|
|
392
|
-
iconEl.style.width = '16px';
|
|
393
|
-
iconEl.style.height = '16px';
|
|
394
|
-
|
|
395
|
-
const iconWrap = document.createElement('span');
|
|
396
|
-
iconWrap.className = 'jux-dataframe-filter-icon';
|
|
397
|
-
iconWrap.appendChild(iconEl);
|
|
398
|
-
|
|
399
|
-
filterContainer.appendChild(iconWrap);
|
|
400
|
-
filterContainer.appendChild(input);
|
|
401
|
-
|
|
402
|
-
input.addEventListener('input', () => {
|
|
403
|
-
const text = input.value.toLowerCase();
|
|
404
|
-
if (!text) { table.rows(df.toRows()); return; }
|
|
405
|
-
const filtered = df.filter((row) =>
|
|
406
|
-
Object.values(row).some(v =>
|
|
407
|
-
v !== null && v !== undefined && String(v).toLowerCase().includes(text)
|
|
408
|
-
)
|
|
409
|
-
);
|
|
410
|
-
table.rows(filtered.toRows());
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
const tableWrapper = panel.querySelector('.jux-table-wrapper');
|
|
414
|
-
if (tableWrapper) {
|
|
415
|
-
panel.insertBefore(filterContainer, tableWrapper);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
378
|
});
|
|
420
379
|
|
|
421
380
|
const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
|
|
@@ -468,7 +427,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
468
427
|
const rows = df.toRows();
|
|
469
428
|
const emptyColumns = cols.filter(c => {
|
|
470
429
|
if (!c.startsWith('__EMPTY')) return false;
|
|
471
|
-
// Check if ALL values in this column are null/empty
|
|
472
430
|
return rows.every(row => {
|
|
473
431
|
const val = row[c];
|
|
474
432
|
return val === null || val === undefined || String(val).trim() === '';
|
|
@@ -494,7 +452,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
494
452
|
`${sourceName} — ${this._df!.height} rows × ${this._df!.width} cols (Data may need reformatting)`,
|
|
495
453
|
'warning'
|
|
496
454
|
);
|
|
497
|
-
|
|
498
455
|
requestAnimationFrame(() => {
|
|
499
456
|
const statusEl = document.getElementById(`${this._id}-status`);
|
|
500
457
|
if (statusEl) {
|
|
@@ -511,8 +468,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
511
468
|
`${sourceName} — ${this._df!.height} rows × ${this._df!.width} cols`,
|
|
512
469
|
'success'
|
|
513
470
|
);
|
|
514
|
-
|
|
515
|
-
// Always show settings button if we have raw file data
|
|
516
471
|
if (this._rawFileData) {
|
|
517
472
|
requestAnimationFrame(() => {
|
|
518
473
|
const statusEl = document.getElementById(`${this._id}-status`);
|
|
@@ -566,6 +521,13 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
566
521
|
|
|
567
522
|
private _detectLikelyHeaderRow(df: DataFrame): number {
|
|
568
523
|
const rows = df.toRows();
|
|
524
|
+
const cols = df.columns;
|
|
525
|
+
|
|
526
|
+
const colsAreGeneric = cols.some(c =>
|
|
527
|
+
c.startsWith('__EMPTY') || c.match(/^_\d+$/) || c.match(/^col_\d+$/)
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
if (!colsAreGeneric) return 0;
|
|
569
531
|
|
|
570
532
|
for (let i = 0; i < Math.min(rows.length, 10); i++) {
|
|
571
533
|
const row = rows[i];
|
|
@@ -579,10 +541,8 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
579
541
|
return isNaN(Number(str)) && str !== '';
|
|
580
542
|
}).length;
|
|
581
543
|
|
|
582
|
-
if (nonNumericCount >= nonEmpty.length * 0.7
|
|
583
|
-
// i
|
|
584
|
-
// as the header during the initial parse, so the actual 0-based file
|
|
585
|
-
// row index is i + 1.
|
|
544
|
+
if (nonNumericCount >= nonEmpty.length * 0.7) {
|
|
545
|
+
// toRows index i = file row (i + 1) since row 0 was used as headers
|
|
586
546
|
return i + 1;
|
|
587
547
|
}
|
|
588
548
|
}
|
|
@@ -618,7 +578,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
618
578
|
|
|
619
579
|
this._cleanupReshapeModal();
|
|
620
580
|
|
|
621
|
-
// Always detect from a fresh raw parse, not from current _df
|
|
622
581
|
let suggestedRow = 0;
|
|
623
582
|
try {
|
|
624
583
|
const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
|
|
@@ -645,22 +604,11 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
645
604
|
<label style="display: block; font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
|
|
646
605
|
Header Row (0-based index)
|
|
647
606
|
</label>
|
|
648
|
-
<input
|
|
649
|
-
|
|
650
|
-
id="${this._id}-header-row"
|
|
651
|
-
class="jux-input-element"
|
|
652
|
-
value="${suggestedRow}"
|
|
653
|
-
min="0"
|
|
654
|
-
max="50"
|
|
655
|
-
style="width: 100%;"
|
|
656
|
-
/>
|
|
657
|
-
<div id="${this._id}-reshape-hint" class="jux-reshape-hint" style="margin-top: 0.5rem; padding: 0.75rem; background: hsl(var(--muted) / 0.5); border-radius: var(--radius); font-size: 0.875rem;">
|
|
658
|
-
</div>
|
|
607
|
+
<input type="number" id="${this._id}-header-row" class="jux-input-element" value="${suggestedRow}" min="0" max="50" style="width: 100%;" />
|
|
608
|
+
<div id="${this._id}-reshape-hint" class="jux-reshape-hint" style="margin-top: 0.5rem; padding: 0.75rem; background: hsl(var(--muted) / 0.5); border-radius: var(--radius); font-size: 0.875rem;"></div>
|
|
659
609
|
</div>
|
|
660
610
|
<div class="jux-reshape-preview-container">
|
|
661
|
-
<div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
|
|
662
|
-
Preview
|
|
663
|
-
</div>
|
|
611
|
+
<div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview</div>
|
|
664
612
|
<div id="${this._id}-preview" style="font-family: ui-monospace, monospace; font-size: 12px; background: hsl(var(--muted) / 0.3); border: 1px solid hsl(var(--border)); border-radius: var(--radius); padding: 0; overflow: hidden; max-height: 400px; overflow-y: auto;"></div>
|
|
665
613
|
</div>
|
|
666
614
|
`;
|
|
@@ -691,6 +639,13 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
691
639
|
});
|
|
692
640
|
|
|
693
641
|
const sheetNames = Object.keys(sheets);
|
|
642
|
+
|
|
643
|
+
if (sheetNames.length === 0) {
|
|
644
|
+
this._updateStatus(`No data found with header at row ${headerRow}. Try a different row.`, 'error');
|
|
645
|
+
this.state.loading = false;
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
694
649
|
await this._driver.store(this._rawFileData!.file.name, sheets[sheetNames[0]], { source: this._rawFileData!.file.name });
|
|
695
650
|
|
|
696
651
|
if (sheetNames.length > 1) {
|
|
@@ -702,6 +657,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
702
657
|
this._reshapeModal!.closeModal();
|
|
703
658
|
} catch (err: any) {
|
|
704
659
|
this._updateStatus(`Error: ${err.message}`, 'error');
|
|
660
|
+
this.state.loading = false;
|
|
705
661
|
}
|
|
706
662
|
}
|
|
707
663
|
}
|
|
@@ -719,8 +675,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
719
675
|
const updateHint = (headerRow: number) => {
|
|
720
676
|
if (!hintDiv) return;
|
|
721
677
|
if (headerRow > 0) {
|
|
722
|
-
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers.
|
|
723
|
-
`Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
678
|
+
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
724
679
|
} else {
|
|
725
680
|
hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
|
|
726
681
|
}
|
|
@@ -731,36 +686,53 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
731
686
|
updateHint(headerRow);
|
|
732
687
|
|
|
733
688
|
try {
|
|
734
|
-
//
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
689
|
+
// Read cells directly from XLSX to match parser behavior exactly
|
|
690
|
+
const XLSX = await import('xlsx');
|
|
691
|
+
const buffer = await this._rawFileData!.file.arrayBuffer();
|
|
692
|
+
const workbook = XLSX.read(buffer, {
|
|
693
|
+
type: 'array',
|
|
694
|
+
sheetRows: Math.max(headerRow + 12, 15),
|
|
695
|
+
dense: false
|
|
738
696
|
});
|
|
739
|
-
const rawSheet = Object.values(rawSheets)[0];
|
|
740
697
|
|
|
741
|
-
|
|
698
|
+
const sheetName = workbook.SheetNames[0];
|
|
699
|
+
const worksheet = workbook.Sheets[sheetName];
|
|
700
|
+
const ref = worksheet['!ref'];
|
|
701
|
+
if (!ref) {
|
|
742
702
|
if (previewDiv) previewDiv.textContent = 'No data found';
|
|
743
703
|
return;
|
|
744
704
|
}
|
|
745
705
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
const
|
|
749
|
-
const
|
|
706
|
+
const range = XLSX.utils.decode_range(ref);
|
|
707
|
+
const endRow = range.e.r;
|
|
708
|
+
const startCol = range.s.c;
|
|
709
|
+
const endCol = range.e.c;
|
|
710
|
+
|
|
711
|
+
const readCellValue = (r: number, c: number): any => {
|
|
712
|
+
const addr = XLSX.utils.encode_cell({ r, c });
|
|
713
|
+
const cell = worksheet[addr];
|
|
714
|
+
if (!cell) return null;
|
|
715
|
+
if (cell.w !== undefined) return cell.w;
|
|
716
|
+
if (cell.v !== undefined) return cell.v;
|
|
717
|
+
return null;
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
const readRow = (r: number): any[] => {
|
|
721
|
+
const vals: any[] = [];
|
|
722
|
+
for (let c = startCol; c <= endCol; c++) {
|
|
723
|
+
vals.push(readCellValue(r, c));
|
|
724
|
+
}
|
|
725
|
+
return vals;
|
|
726
|
+
};
|
|
750
727
|
|
|
751
|
-
// Build HTML table showing raw file structure
|
|
752
728
|
let html = '<table style="width: 100%; border-collapse: collapse; font-size: 12px;">';
|
|
753
|
-
|
|
754
|
-
// We need to show rows 0 through headerRow+7 (or so)
|
|
755
|
-
// Row 0 = rawCols, Row 1+ = rawRows[i-1]
|
|
756
|
-
const totalRowsToShow = Math.min(headerRow + 8, rawRows.length + 1);
|
|
729
|
+
const totalRowsToShow = Math.min(headerRow + 8, endRow + 1);
|
|
757
730
|
|
|
758
731
|
for (let fileRow = 0; fileRow < totalRowsToShow; fileRow++) {
|
|
759
732
|
const isHeader = (fileRow === headerRow);
|
|
760
733
|
const isSkipped = (fileRow < headerRow);
|
|
761
734
|
|
|
762
735
|
let rowStyle = 'border-bottom: 1px solid hsl(var(--border));';
|
|
763
|
-
|
|
764
736
|
if (isHeader) {
|
|
765
737
|
rowStyle += 'background: hsl(142 71% 45% / 0.15); font-weight: 600;';
|
|
766
738
|
} else if (isSkipped) {
|
|
@@ -768,8 +740,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
768
740
|
}
|
|
769
741
|
|
|
770
742
|
html += `<tr style="${rowStyle}">`;
|
|
771
|
-
|
|
772
|
-
// Row index cell
|
|
773
743
|
html += `<td style="padding: 8px 12px; width: 60px; font-weight: 600; color: hsl(var(--muted-foreground)); border-right: 1px solid hsl(var(--border)); text-align: center;">`;
|
|
774
744
|
if (isHeader) {
|
|
775
745
|
html += `<span style="color: hsl(142 71% 45%);">▶ ${fileRow}</span>`;
|
|
@@ -778,15 +748,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
778
748
|
}
|
|
779
749
|
html += '</td>';
|
|
780
750
|
|
|
781
|
-
|
|
782
|
-
let values: any[];
|
|
783
|
-
if (fileRow === 0) {
|
|
784
|
-
values = rawCols;
|
|
785
|
-
} else {
|
|
786
|
-
values = rawRows[fileRow - 1] ? Object.values(rawRows[fileRow - 1]) : [];
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
// Show first 6 columns
|
|
751
|
+
const values = readRow(fileRow);
|
|
790
752
|
const displayCols = values.slice(0, 6);
|
|
791
753
|
displayCols.forEach(val => {
|
|
792
754
|
const displayVal = val != null ? String(val).substring(0, 20) : '';
|
|
@@ -797,28 +759,22 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
797
759
|
});
|
|
798
760
|
|
|
799
761
|
if (values.length > 6) {
|
|
800
|
-
html += `<td style="padding: 8px 12px; color: hsl(var(--muted-foreground));"
|
|
762
|
+
html += `<td style="padding: 8px 12px; color: hsl(var(--muted-foreground));">...</td>`;
|
|
801
763
|
}
|
|
802
764
|
|
|
803
|
-
// Status badge cell
|
|
804
765
|
html += `<td style="padding: 8px 12px; text-align: right; white-space: nowrap;">`;
|
|
805
766
|
if (isHeader) {
|
|
806
|
-
html += '<span style="background: hsl(
|
|
767
|
+
html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
|
|
807
768
|
} else if (isSkipped) {
|
|
808
|
-
html += '<span style="color: hsl(var(--muted-foreground));
|
|
769
|
+
html += '<span style="color: hsl(var(--muted-foreground));">skipped</span>';
|
|
809
770
|
} else {
|
|
810
|
-
html += '<span style="color: hsl(var(--
|
|
771
|
+
html += '<span style="color: hsl(var(--success));">data</span>';
|
|
811
772
|
}
|
|
812
|
-
html += '</td>';
|
|
813
|
-
|
|
814
|
-
html += '</tr>';
|
|
773
|
+
html += '</td></tr>';
|
|
815
774
|
}
|
|
816
775
|
|
|
817
776
|
html += '</table>';
|
|
818
|
-
|
|
819
|
-
if (previewDiv) {
|
|
820
|
-
previewDiv.innerHTML = html;
|
|
821
|
-
}
|
|
777
|
+
if (previewDiv) previewDiv.innerHTML = html;
|
|
822
778
|
} catch (err: any) {
|
|
823
779
|
if (previewDiv) previewDiv.textContent = `Error: ${err.message}`;
|
|
824
780
|
}
|
|
@@ -826,7 +782,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
826
782
|
|
|
827
783
|
if (headerRowInput) headerRowInput.addEventListener('input', updatePreview);
|
|
828
784
|
updatePreview();
|
|
829
|
-
|
|
830
785
|
this._reshapeModal.open();
|
|
831
786
|
}
|
|
832
787
|
|
|
@@ -901,10 +856,10 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
901
856
|
|
|
902
857
|
await this._driver.store(this._rawFileData.file.name, df, { source: this._rawFileData.file.name });
|
|
903
858
|
this._setDataFrame(df, this._rawFileData.file.name);
|
|
904
|
-
|
|
905
859
|
this._reshapeModal!.closeModal();
|
|
906
860
|
} catch (err: any) {
|
|
907
861
|
this._updateStatus(`Error: ${err.message}`, 'error');
|
|
862
|
+
this.state.loading = false;
|
|
908
863
|
}
|
|
909
864
|
}
|
|
910
865
|
}
|
|
@@ -931,8 +886,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
931
886
|
if (!hintDiv) return;
|
|
932
887
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
933
888
|
if (headerRow > 0) {
|
|
934
|
-
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers.
|
|
935
|
-
`Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
889
|
+
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
936
890
|
} else {
|
|
937
891
|
hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
|
|
938
892
|
}
|
|
@@ -943,11 +897,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
943
897
|
|
|
944
898
|
const delim = delimiterSelect?.value || ',';
|
|
945
899
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
946
|
-
|
|
947
900
|
updateHint();
|
|
948
901
|
|
|
949
902
|
try {
|
|
950
|
-
// Parse raw to show all rows
|
|
951
903
|
const rawDf = this._driver.parseCSV(this._rawFileData.text, {
|
|
952
904
|
delimiter: delim,
|
|
953
905
|
headerRow: 0,
|
|
@@ -958,9 +910,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
958
910
|
const rawCols = rawDf.columns;
|
|
959
911
|
const rawRows = rawDf.toRows();
|
|
960
912
|
|
|
961
|
-
// Build HTML table
|
|
962
913
|
let html = '<table style="width: 100%; border-collapse: collapse; font-size: 11px;">';
|
|
963
|
-
|
|
964
914
|
const totalRows = Math.min(headerRow + 8, rawRows.length + 1);
|
|
965
915
|
|
|
966
916
|
for (let i = 0; i < totalRows; i++) {
|
|
@@ -968,7 +918,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
968
918
|
const isSkipped = (i < headerRow);
|
|
969
919
|
|
|
970
920
|
let rowStyle = 'border-bottom: 1px solid hsl(var(--border));';
|
|
971
|
-
|
|
972
921
|
if (isHeader) {
|
|
973
922
|
rowStyle += 'background: hsl(var(--primary) / 0.15); font-weight: bold;';
|
|
974
923
|
} else if (isSkipped) {
|
|
@@ -976,13 +925,10 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
976
925
|
}
|
|
977
926
|
|
|
978
927
|
html += `<tr style="${rowStyle}">`;
|
|
979
|
-
|
|
980
|
-
// Row index
|
|
981
928
|
html += `<td style="padding: 6px 8px; width: 50px; color: hsl(var(--muted-foreground)); font-weight: 500;">`;
|
|
982
929
|
html += isHeader ? `<strong>→ ${i}</strong>` : `${i}`;
|
|
983
930
|
html += '</td>';
|
|
984
931
|
|
|
985
|
-
// Data
|
|
986
932
|
let values: any[];
|
|
987
933
|
if (i === 0) {
|
|
988
934
|
values = rawCols;
|
|
@@ -1001,7 +947,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
1001
947
|
html += `<td style="padding: 6px 8px; color: hsl(var(--muted-foreground));">...</td>`;
|
|
1002
948
|
}
|
|
1003
949
|
|
|
1004
|
-
// Status
|
|
1005
950
|
html += `<td style="padding: 6px 8px; text-align: right; font-size: 10px;">`;
|
|
1006
951
|
if (isHeader) {
|
|
1007
952
|
html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
|
|
@@ -1010,13 +955,10 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
1010
955
|
} else {
|
|
1011
956
|
html += '<span style="color: hsl(var(--success));">data</span>';
|
|
1012
957
|
}
|
|
1013
|
-
html += '</td>';
|
|
1014
|
-
|
|
1015
|
-
html += '</tr>';
|
|
958
|
+
html += '</td></tr>';
|
|
1016
959
|
}
|
|
1017
960
|
|
|
1018
961
|
html += '</table>';
|
|
1019
|
-
|
|
1020
962
|
if (previewDiv) previewDiv.innerHTML = html;
|
|
1021
963
|
} catch (err: any) {
|
|
1022
964
|
if (previewDiv) previewDiv.textContent = `Error: ${err.message}`;
|
|
@@ -90,6 +90,9 @@ export declare class TabularDriver {
|
|
|
90
90
|
/**
|
|
91
91
|
* ✅ FIXED: Stream Excel file with optional headerRow override
|
|
92
92
|
* headerRow is 0-based: 0 = first row, 1 = second row, etc.
|
|
93
|
+
*
|
|
94
|
+
* Uses direct cell access instead of sheet_to_json to avoid
|
|
95
|
+
* issues with blank row handling and sparse arrays.
|
|
93
96
|
*/
|
|
94
97
|
streamFileMultiSheet(file: File, options?: ParseOptions): Promise<Record<string, DataFrame>>;
|
|
95
98
|
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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"}
|
|
@@ -489,6 +489,9 @@ export class TabularDriver {
|
|
|
489
489
|
/**
|
|
490
490
|
* ✅ FIXED: Stream Excel file with optional headerRow override
|
|
491
491
|
* headerRow is 0-based: 0 = first row, 1 = second row, etc.
|
|
492
|
+
*
|
|
493
|
+
* Uses direct cell access instead of sheet_to_json to avoid
|
|
494
|
+
* issues with blank row handling and sparse arrays.
|
|
492
495
|
*/
|
|
493
496
|
async streamFileMultiSheet(file, options = {}) {
|
|
494
497
|
const { maxSheetSize = 100000, sheetChunkSize = 10000, onProgress, headerRow = 0 } = options;
|
|
@@ -528,80 +531,76 @@ export class TabularDriver {
|
|
|
528
531
|
processedSheets++;
|
|
529
532
|
continue;
|
|
530
533
|
}
|
|
531
|
-
//
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
if (!rawData[i]) {
|
|
548
|
-
rawData[i] = new Array(maxCols).fill(null);
|
|
549
|
-
}
|
|
550
|
-
else {
|
|
551
|
-
while (rawData[i].length < maxCols) {
|
|
552
|
-
rawData[i].push(null);
|
|
553
|
-
}
|
|
534
|
+
// Direct cell reader - bypasses sheet_to_json completely
|
|
535
|
+
const readCellValue = (r, c) => {
|
|
536
|
+
const addr = XLSX.utils.encode_cell({ r, c });
|
|
537
|
+
const cell = worksheet[addr];
|
|
538
|
+
if (!cell)
|
|
539
|
+
return null;
|
|
540
|
+
if (cell.w !== undefined)
|
|
541
|
+
return cell.w;
|
|
542
|
+
if (cell.v !== undefined)
|
|
543
|
+
return cell.v;
|
|
544
|
+
return null;
|
|
545
|
+
};
|
|
546
|
+
const readRow = (r) => {
|
|
547
|
+
const vals = [];
|
|
548
|
+
for (let c = startCol; c <= endCol; c++) {
|
|
549
|
+
vals.push(readCellValue(r, c));
|
|
554
550
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
551
|
+
return vals;
|
|
552
|
+
};
|
|
553
|
+
// ✅ FIX: headerRow is the user's 0-based index counting from
|
|
554
|
+
// the first row of the file. Do NOT add startRow — the user
|
|
555
|
+
// sees row 0 as the first row regardless of where the sheet
|
|
556
|
+
// range begins. We simply use headerRow as the absolute sheet
|
|
557
|
+
// row index.
|
|
558
|
+
const headerSheetRow = headerRow;
|
|
559
|
+
console.log(`[TabularDriver] Sheet "${sheetName}": range=${ref}, startRow=${startRow}, endRow=${endRow}, startCol=${startCol}, endCol=${endCol}`);
|
|
560
|
+
console.log(`[TabularDriver] headerRow=${headerRow}, headerSheetRow=${headerSheetRow} (absolute, no startRow offset)`);
|
|
561
|
+
if (headerSheetRow > endRow) {
|
|
562
|
+
console.warn(`[TabularDriver] headerRow ${headerRow} (sheet row ${headerSheetRow}) exceeds endRow ${endRow}`);
|
|
559
563
|
processedSheets++;
|
|
560
564
|
continue;
|
|
561
565
|
}
|
|
562
|
-
//
|
|
563
|
-
|
|
564
|
-
console.log(`[TabularDriver]
|
|
565
|
-
//
|
|
566
|
-
const
|
|
567
|
-
const headers = [];
|
|
568
|
-
for (let j = 0; j < maxCols; j++) {
|
|
569
|
-
const h = headerRowData[j];
|
|
566
|
+
// Read header values directly from cells
|
|
567
|
+
const headerValues = readRow(headerSheetRow);
|
|
568
|
+
console.log(`[TabularDriver] Raw header values at sheet row ${headerSheetRow}:`, headerValues);
|
|
569
|
+
// Build headers array
|
|
570
|
+
const headers = headerValues.map((h, i) => {
|
|
570
571
|
if (h === null || h === undefined || String(h).trim() === '') {
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
else {
|
|
574
|
-
headers.push(String(h).trim());
|
|
572
|
+
return `__EMPTY${i > 0 ? '_' + i : ''}`;
|
|
575
573
|
}
|
|
576
|
-
|
|
577
|
-
|
|
574
|
+
return String(h).trim();
|
|
575
|
+
});
|
|
576
|
+
// Count valid headers
|
|
578
577
|
const validHeaders = headers.filter(h => !h.startsWith('__EMPTY'));
|
|
579
|
-
console.log(`[TabularDriver] Headers:`, headers);
|
|
580
|
-
console.log(`[TabularDriver] Valid headers: ${validHeaders.length}/${headers.length}`);
|
|
578
|
+
console.log(`[TabularDriver] Headers (${validHeaders.length} valid / ${headers.length} total):`, headers);
|
|
581
579
|
if (validHeaders.length === 0) {
|
|
582
|
-
console.warn(`[TabularDriver] No valid headers found at row ${headerRow} in sheet "${sheetName}"`);
|
|
580
|
+
console.warn(`[TabularDriver] No valid headers found at row ${headerRow} (sheet row ${headerSheetRow}) in sheet "${sheetName}"`);
|
|
581
|
+
// Log surrounding rows for debugging
|
|
582
|
+
for (let debugR = Math.max(startRow, headerSheetRow - 2); debugR <= Math.min(endRow, headerSheetRow + 2); debugR++) {
|
|
583
|
+
console.log(`[TabularDriver] row ${debugR}:`, readRow(debugR));
|
|
584
|
+
}
|
|
583
585
|
processedSheets++;
|
|
584
586
|
continue;
|
|
585
587
|
}
|
|
586
|
-
// Build rows
|
|
588
|
+
// Build data rows: everything after the header row
|
|
587
589
|
const rows = [];
|
|
588
|
-
for (let
|
|
589
|
-
const rowData =
|
|
590
|
+
for (let r = headerSheetRow + 1; r <= endRow; r++) {
|
|
591
|
+
const rowData = readRow(r);
|
|
590
592
|
// Skip completely empty rows
|
|
591
|
-
|
|
592
|
-
continue;
|
|
593
|
-
const hasContent = rowData.some((cell) => cell !== null && cell !== undefined && String(cell).trim() !== '');
|
|
593
|
+
const hasContent = rowData.some(cell => cell !== null && cell !== undefined && String(cell).trim() !== '');
|
|
594
594
|
if (!hasContent)
|
|
595
595
|
continue;
|
|
596
596
|
const row = {};
|
|
597
597
|
for (let j = 0; j < headers.length; j++) {
|
|
598
|
-
row[headers[j]] =
|
|
598
|
+
row[headers[j]] = rowData[j];
|
|
599
599
|
}
|
|
600
600
|
rows.push(row);
|
|
601
601
|
}
|
|
602
|
-
console.log(`[TabularDriver] Built ${rows.length} data rows`);
|
|
602
|
+
console.log(`[TabularDriver] Built ${rows.length} data rows for sheet "${sheetName}"`);
|
|
603
603
|
if (rows.length > 0) {
|
|
604
|
-
console.log(`[TabularDriver] First row keys:`, Object.keys(rows[0]));
|
|
605
604
|
console.log(`[TabularDriver] First row:`, rows[0]);
|
|
606
605
|
}
|
|
607
606
|
if (rows.length > 0) {
|
|
@@ -609,6 +609,9 @@ export class TabularDriver {
|
|
|
609
609
|
/**
|
|
610
610
|
* ✅ FIXED: Stream Excel file with optional headerRow override
|
|
611
611
|
* headerRow is 0-based: 0 = first row, 1 = second row, etc.
|
|
612
|
+
*
|
|
613
|
+
* Uses direct cell access instead of sheet_to_json to avoid
|
|
614
|
+
* issues with blank row handling and sparse arrays.
|
|
612
615
|
*/
|
|
613
616
|
async streamFileMultiSheet(file: File, options: ParseOptions = {}): Promise<Record<string, DataFrame>> {
|
|
614
617
|
const { maxSheetSize = 100000, sheetChunkSize = 10000, onProgress, headerRow = 0 } = options;
|
|
@@ -656,91 +659,86 @@ export class TabularDriver {
|
|
|
656
659
|
continue;
|
|
657
660
|
}
|
|
658
661
|
|
|
659
|
-
//
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
662
|
+
// Direct cell reader - bypasses sheet_to_json completely
|
|
663
|
+
const readCellValue = (r: number, c: number): any => {
|
|
664
|
+
const addr = XLSX.utils.encode_cell({ r, c });
|
|
665
|
+
const cell = worksheet[addr];
|
|
666
|
+
if (!cell) return null;
|
|
667
|
+
if (cell.w !== undefined) return cell.w;
|
|
668
|
+
if (cell.v !== undefined) return cell.v;
|
|
669
|
+
return null;
|
|
670
|
+
};
|
|
667
671
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
+
const readRow = (r: number): any[] => {
|
|
673
|
+
const vals: any[] = [];
|
|
674
|
+
for (let c = startCol; c <= endCol; c++) {
|
|
675
|
+
vals.push(readCellValue(r, c));
|
|
676
|
+
}
|
|
677
|
+
return vals;
|
|
678
|
+
};
|
|
672
679
|
|
|
673
|
-
//
|
|
674
|
-
|
|
680
|
+
// ✅ FIX: headerRow is the user's 0-based index counting from
|
|
681
|
+
// the first row of the file. Do NOT add startRow — the user
|
|
682
|
+
// sees row 0 as the first row regardless of where the sheet
|
|
683
|
+
// range begins. We simply use headerRow as the absolute sheet
|
|
684
|
+
// row index.
|
|
685
|
+
const headerSheetRow = headerRow;
|
|
675
686
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
if (!rawData[i]) {
|
|
679
|
-
rawData[i] = new Array(maxCols).fill(null);
|
|
680
|
-
} else {
|
|
681
|
-
while (rawData[i].length < maxCols) {
|
|
682
|
-
rawData[i].push(null);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
687
|
+
console.log(`[TabularDriver] Sheet "${sheetName}": range=${ref}, startRow=${startRow}, endRow=${endRow}, startCol=${startCol}, endCol=${endCol}`);
|
|
688
|
+
console.log(`[TabularDriver] headerRow=${headerRow}, headerSheetRow=${headerSheetRow} (absolute, no startRow offset)`);
|
|
686
689
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
console.warn(`[TabularDriver] headerRow ${headerRow} exceeds data length ${rawData.length}`);
|
|
690
|
+
if (headerSheetRow > endRow) {
|
|
691
|
+
console.warn(`[TabularDriver] headerRow ${headerRow} (sheet row ${headerSheetRow}) exceeds endRow ${endRow}`);
|
|
690
692
|
processedSheets++;
|
|
691
693
|
continue;
|
|
692
694
|
}
|
|
693
695
|
|
|
694
|
-
//
|
|
695
|
-
|
|
696
|
-
console.log(`[TabularDriver]
|
|
696
|
+
// Read header values directly from cells
|
|
697
|
+
const headerValues = readRow(headerSheetRow);
|
|
698
|
+
console.log(`[TabularDriver] Raw header values at sheet row ${headerSheetRow}:`, headerValues);
|
|
697
699
|
|
|
698
|
-
//
|
|
699
|
-
const
|
|
700
|
-
const headers: string[] = [];
|
|
701
|
-
for (let j = 0; j < maxCols; j++) {
|
|
702
|
-
const h = headerRowData[j];
|
|
700
|
+
// Build headers array
|
|
701
|
+
const headers: string[] = headerValues.map((h: any, i: number) => {
|
|
703
702
|
if (h === null || h === undefined || String(h).trim() === '') {
|
|
704
|
-
|
|
705
|
-
} else {
|
|
706
|
-
headers.push(String(h).trim());
|
|
703
|
+
return `__EMPTY${i > 0 ? '_' + i : ''}`;
|
|
707
704
|
}
|
|
708
|
-
|
|
705
|
+
return String(h).trim();
|
|
706
|
+
});
|
|
709
707
|
|
|
710
|
-
// Count valid
|
|
708
|
+
// Count valid headers
|
|
711
709
|
const validHeaders = headers.filter(h => !h.startsWith('__EMPTY'));
|
|
712
|
-
console.log(`[TabularDriver] Headers:`, headers);
|
|
713
|
-
console.log(`[TabularDriver] Valid headers: ${validHeaders.length}/${headers.length}`);
|
|
710
|
+
console.log(`[TabularDriver] Headers (${validHeaders.length} valid / ${headers.length} total):`, headers);
|
|
714
711
|
|
|
715
712
|
if (validHeaders.length === 0) {
|
|
716
|
-
console.warn(`[TabularDriver] No valid headers found at row ${headerRow} in sheet "${sheetName}"`);
|
|
713
|
+
console.warn(`[TabularDriver] No valid headers found at row ${headerRow} (sheet row ${headerSheetRow}) in sheet "${sheetName}"`);
|
|
714
|
+
// Log surrounding rows for debugging
|
|
715
|
+
for (let debugR = Math.max(startRow, headerSheetRow - 2); debugR <= Math.min(endRow, headerSheetRow + 2); debugR++) {
|
|
716
|
+
console.log(`[TabularDriver] row ${debugR}:`, readRow(debugR));
|
|
717
|
+
}
|
|
717
718
|
processedSheets++;
|
|
718
719
|
continue;
|
|
719
720
|
}
|
|
720
721
|
|
|
721
|
-
// Build rows
|
|
722
|
+
// Build data rows: everything after the header row
|
|
722
723
|
const rows: Record<string, any>[] = [];
|
|
723
|
-
for (let
|
|
724
|
-
const rowData =
|
|
724
|
+
for (let r = headerSheetRow + 1; r <= endRow; r++) {
|
|
725
|
+
const rowData = readRow(r);
|
|
725
726
|
|
|
726
727
|
// Skip completely empty rows
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
const hasContent = rowData.some((cell: any) =>
|
|
728
|
+
const hasContent = rowData.some(cell =>
|
|
730
729
|
cell !== null && cell !== undefined && String(cell).trim() !== ''
|
|
731
730
|
);
|
|
732
731
|
if (!hasContent) continue;
|
|
733
732
|
|
|
734
733
|
const row: Record<string, any> = {};
|
|
735
734
|
for (let j = 0; j < headers.length; j++) {
|
|
736
|
-
row[headers[j]] =
|
|
735
|
+
row[headers[j]] = rowData[j];
|
|
737
736
|
}
|
|
738
737
|
rows.push(row);
|
|
739
738
|
}
|
|
740
739
|
|
|
741
|
-
console.log(`[TabularDriver] Built ${rows.length} data rows`);
|
|
740
|
+
console.log(`[TabularDriver] Built ${rows.length} data rows for sheet "${sheetName}"`);
|
|
742
741
|
if (rows.length > 0) {
|
|
743
|
-
console.log(`[TabularDriver] First row keys:`, Object.keys(rows[0]));
|
|
744
742
|
console.log(`[TabularDriver] First row:`, rows[0]);
|
|
745
743
|
}
|
|
746
744
|
|