juxscript 1.1.195 → 1.1.197
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 +1 -0
- package/lib/components/dataframe.d.ts.map +1 -1
- package/lib/components/dataframe.js +206 -202
- package/lib/components/dataframe.ts +224 -227
- package/package.json +1 -1
|
@@ -91,6 +91,7 @@ export declare class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
91
91
|
private _showReshapeModal;
|
|
92
92
|
private _cleanupReshapeModal;
|
|
93
93
|
private _showExcelReshapeModal;
|
|
94
|
+
private _escapeHtml;
|
|
94
95
|
private _showCSVReshapeModal;
|
|
95
96
|
update(_prop: string, _value: any): void;
|
|
96
97
|
render(targetId?: string | HTMLElement | BaseComponent<any>): this;
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"dataframe.d.ts","sourceRoot":"","sources":["dataframe.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,9 @@ import { DataFrame } from '../storage/DataFrame.js';
|
|
|
3
3
|
import { TabularDriver } from '../storage/TabularDriver.js';
|
|
4
4
|
import { FileUpload } from './fileupload.js';
|
|
5
5
|
import { Table } from './table.js';
|
|
6
|
+
import { Tabs } from './tabs.js';
|
|
6
7
|
import { Modal } from './modal.js';
|
|
8
|
+
import { Button } from './button.js';
|
|
7
9
|
import { renderIcon } from './icons.js';
|
|
8
10
|
const TRIGGER_EVENTS = [];
|
|
9
11
|
const CALLBACK_EVENTS = ['load', 'error', 'transform'];
|
|
@@ -249,65 +251,39 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
249
251
|
const wrapper = document.getElementById(this._id);
|
|
250
252
|
if (!wrapper)
|
|
251
253
|
return;
|
|
254
|
+
// Clean up existing content
|
|
252
255
|
const existingTable = wrapper.querySelector('.jux-table-wrapper');
|
|
253
256
|
if (existingTable)
|
|
254
257
|
existingTable.remove();
|
|
255
|
-
const existingTabs = wrapper.querySelector('.jux-
|
|
258
|
+
const existingTabs = wrapper.querySelector('.jux-tabs');
|
|
256
259
|
if (existingTabs)
|
|
257
260
|
existingTabs.remove();
|
|
258
261
|
Object.entries(sheets).forEach(([name, df]) => {
|
|
259
262
|
this._sheets.set(name, df);
|
|
260
263
|
});
|
|
261
264
|
const sheetNames = Object.keys(sheets);
|
|
262
|
-
// Build
|
|
265
|
+
// Build tabs using the Tabs component
|
|
266
|
+
const tabDefs = sheetNames.map(name => ({
|
|
267
|
+
id: name,
|
|
268
|
+
label: name,
|
|
269
|
+
content: '' // Content will be added after render
|
|
270
|
+
}));
|
|
271
|
+
this._tabs = new Tabs(`${this._id}-tabs`, {
|
|
272
|
+
tabs: tabDefs,
|
|
273
|
+
activeTab: sheetNames[0]
|
|
274
|
+
});
|
|
275
|
+
this._tabs.bind('tabChange', (tabId) => {
|
|
276
|
+
this._df = this._sheets.get(tabId) || null;
|
|
277
|
+
});
|
|
278
|
+
// Create container for tabs
|
|
263
279
|
const tabsContainer = document.createElement('div');
|
|
264
280
|
tabsContainer.className = 'jux-dataframe-tabs';
|
|
265
|
-
const tabList = document.createElement('div');
|
|
266
|
-
tabList.className = 'jux-tabs-list';
|
|
267
|
-
sheetNames.forEach((sheetName, idx) => {
|
|
268
|
-
const tabBtn = document.createElement('button');
|
|
269
|
-
tabBtn.className = 'jux-tabs-button' + (idx === 0 ? ' jux-tabs-button-active' : '');
|
|
270
|
-
tabBtn.setAttribute('data-sheet', sheetName);
|
|
271
|
-
const labelSpan = document.createElement('span');
|
|
272
|
-
labelSpan.textContent = sheetName;
|
|
273
|
-
tabBtn.appendChild(labelSpan);
|
|
274
|
-
// Add settings cog icon
|
|
275
|
-
const cogBtn = document.createElement('button');
|
|
276
|
-
cogBtn.className = 'jux-dataframe-tab-settings';
|
|
277
|
-
cogBtn.title = 'Import settings for ' + sheetName;
|
|
278
|
-
cogBtn.innerHTML = `<span class="iconify" data-icon="lucide:settings" style="width:14px;height:14px;"></span>`;
|
|
279
|
-
cogBtn.addEventListener('click', (e) => {
|
|
280
|
-
e.stopPropagation();
|
|
281
|
-
this._showReshapeModal();
|
|
282
|
-
});
|
|
283
|
-
tabBtn.appendChild(cogBtn);
|
|
284
|
-
tabBtn.addEventListener('click', () => {
|
|
285
|
-
// Switch active tab
|
|
286
|
-
tabList.querySelectorAll('.jux-tabs-button').forEach(btn => {
|
|
287
|
-
btn.classList.remove('jux-tabs-button-active');
|
|
288
|
-
});
|
|
289
|
-
tabBtn.classList.add('jux-tabs-button-active');
|
|
290
|
-
// Show/hide panels
|
|
291
|
-
wrapper.querySelectorAll('.jux-tabs-panel').forEach(panel => {
|
|
292
|
-
panel.style.display = 'none';
|
|
293
|
-
});
|
|
294
|
-
const panel = document.getElementById(`${this._id}-panel-${sheetName}`);
|
|
295
|
-
if (panel)
|
|
296
|
-
panel.style.display = 'block';
|
|
297
|
-
// Update current df reference
|
|
298
|
-
this._df = this._sheets.get(sheetName) || null;
|
|
299
|
-
});
|
|
300
|
-
tabList.appendChild(tabBtn);
|
|
301
|
-
});
|
|
302
|
-
tabsContainer.appendChild(tabList);
|
|
303
281
|
wrapper.appendChild(tabsContainer);
|
|
304
|
-
|
|
282
|
+
this._tabs.render(tabsContainer);
|
|
283
|
+
// Now render tables into each tab panel
|
|
305
284
|
sheetNames.forEach((sheetName, idx) => {
|
|
306
285
|
const df = sheets[sheetName];
|
|
307
|
-
const
|
|
308
|
-
panel.className = 'jux-tabs-panel';
|
|
309
|
-
panel.id = `${this._id}-panel-${sheetName}`;
|
|
310
|
-
panel.style.display = idx === 0 ? 'block' : 'none';
|
|
286
|
+
const panelId = `${this._id}-tabs-${sheetName}-panel`;
|
|
311
287
|
const table = new Table(`${this._id}-table-${sheetName}`, {
|
|
312
288
|
striped: this._tableOptions.striped,
|
|
313
289
|
hoverable: this._tableOptions.hoverable,
|
|
@@ -318,44 +294,49 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
318
294
|
});
|
|
319
295
|
const columnDefs = df.columns.map(col => ({ key: col, label: col }));
|
|
320
296
|
table.columns(columnDefs).rows(df.toRows());
|
|
321
|
-
|
|
322
|
-
|
|
297
|
+
// Add settings button to tab panel
|
|
298
|
+
const settingsBtn = new Button(`${this._id}-settings-${sheetName}`, {
|
|
299
|
+
label: '⚙️ Import Settings',
|
|
300
|
+
variant: 'ghost',
|
|
301
|
+
size: 'small'
|
|
302
|
+
});
|
|
303
|
+
settingsBtn.bind('click', () => this._showReshapeModal());
|
|
304
|
+
// Use addTabContent to add components
|
|
305
|
+
this._tabs.addTabContent(sheetName, [settingsBtn, table]);
|
|
323
306
|
if (this._tableOptions.filterable) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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);
|
|
343
336
|
}
|
|
344
|
-
const filtered = df.filter((row) => Object.values(row).some(v => v !== null && v !== undefined && String(v).toLowerCase().includes(text)));
|
|
345
|
-
table.rows(filtered.toRows());
|
|
346
|
-
});
|
|
347
|
-
const tableWrapper = panel.querySelector('.jux-table-wrapper');
|
|
348
|
-
if (tableWrapper) {
|
|
349
|
-
panel.insertBefore(filterContainer, tableWrapper);
|
|
350
337
|
}
|
|
351
338
|
}
|
|
352
339
|
});
|
|
353
|
-
// Ensure Iconify renders the cog icons
|
|
354
|
-
requestAnimationFrame(() => {
|
|
355
|
-
if (window.Iconify) {
|
|
356
|
-
window.Iconify.scan();
|
|
357
|
-
}
|
|
358
|
-
});
|
|
359
340
|
const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
|
|
360
341
|
this._updateStatus(`${sourceName} — ${sheetNames.length} sheets, ${totalRows} total rows`, 'success');
|
|
361
342
|
this._df = sheets[sheetNames[0]];
|
|
@@ -515,7 +496,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
515
496
|
const modalContentHTML = `
|
|
516
497
|
<div style="margin-bottom: 1rem;">
|
|
517
498
|
<label style="display: block; font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
|
|
518
|
-
Header Row
|
|
499
|
+
Header Row (0-based index)
|
|
519
500
|
</label>
|
|
520
501
|
<input
|
|
521
502
|
type="number"
|
|
@@ -526,14 +507,14 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
526
507
|
max="50"
|
|
527
508
|
style="width: 100%;"
|
|
528
509
|
/>
|
|
529
|
-
<div id="${this._id}-reshape-hint" class="jux-reshape-hint">
|
|
510
|
+
<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;">
|
|
530
511
|
</div>
|
|
531
512
|
</div>
|
|
532
513
|
<div class="jux-reshape-preview-container">
|
|
533
514
|
<div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
|
|
534
515
|
Preview
|
|
535
516
|
</div>
|
|
536
|
-
<div id="${this._id}-preview"
|
|
517
|
+
<div id="${this._id}-preview" style="font-family: 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>
|
|
537
518
|
</div>
|
|
538
519
|
`;
|
|
539
520
|
this._reshapeModal
|
|
@@ -583,73 +564,93 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
583
564
|
const updateHint = (headerRow) => {
|
|
584
565
|
if (!hintDiv)
|
|
585
566
|
return;
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
`
|
|
589
|
-
|
|
567
|
+
if (headerRow > 0) {
|
|
568
|
+
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. ` +
|
|
569
|
+
`Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
|
|
573
|
+
}
|
|
590
574
|
};
|
|
591
575
|
const updatePreview = async () => {
|
|
592
576
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
593
577
|
updateHint(headerRow);
|
|
594
578
|
try {
|
|
595
|
-
//
|
|
579
|
+
// Get raw data to show all rows
|
|
596
580
|
const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
|
|
597
581
|
headerRow: 0,
|
|
598
|
-
maxSheetSize: headerRow +
|
|
582
|
+
maxSheetSize: headerRow + 12
|
|
599
583
|
});
|
|
600
584
|
const rawSheet = Object.values(rawSheets)[0];
|
|
601
|
-
|
|
602
|
-
const parsedSheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
|
|
603
|
-
headerRow,
|
|
604
|
-
maxSheetSize: headerRow + 15
|
|
605
|
-
});
|
|
606
|
-
const parsedSheet = Object.values(parsedSheets)[0];
|
|
607
|
-
if (!rawSheet && !parsedSheet) {
|
|
585
|
+
if (!rawSheet) {
|
|
608
586
|
if (previewDiv)
|
|
609
587
|
previewDiv.textContent = 'No data found';
|
|
610
588
|
return;
|
|
611
589
|
}
|
|
612
|
-
const
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
lines.push(`${'[0]'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('│ ')} ← skipped`);
|
|
626
|
-
const skippedCount = Math.min(headerRow - 1, rawRows.length);
|
|
627
|
-
for (let i = 0; i < skippedCount; i++) {
|
|
628
|
-
const row = rawRows[i];
|
|
629
|
-
const rowIdx = `[${i + 1}]`.padEnd(idxWidth);
|
|
630
|
-
const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
|
|
631
|
-
lines.push(`${rowIdx}${cols} ← skipped`);
|
|
590
|
+
const rawCols = rawSheet.columns;
|
|
591
|
+
const rawRows = rawSheet.toRows();
|
|
592
|
+
// Build a simple HTML table for clarity
|
|
593
|
+
let html = '<table style="width: 100%; border-collapse: collapse; font-size: 11px;">';
|
|
594
|
+
// Render each row
|
|
595
|
+
const totalRows = Math.min(headerRow + 8, rawRows.length + 1);
|
|
596
|
+
for (let i = 0; i < totalRows; i++) {
|
|
597
|
+
const isHeader = (i === headerRow);
|
|
598
|
+
const isSkipped = (i < headerRow);
|
|
599
|
+
let rowStyle = 'border-bottom: 1px solid hsl(var(--border));';
|
|
600
|
+
let cellStyle = 'padding: 6px 8px; text-align: left;';
|
|
601
|
+
if (isHeader) {
|
|
602
|
+
rowStyle += 'background: hsl(var(--primary) / 0.15); font-weight: bold;';
|
|
632
603
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
604
|
+
else if (isSkipped) {
|
|
605
|
+
rowStyle += 'background: hsl(var(--muted) / 0.3); color: hsl(var(--muted-foreground)); font-style: italic;';
|
|
606
|
+
}
|
|
607
|
+
html += `<tr style="${rowStyle}">`;
|
|
608
|
+
// Row index cell
|
|
609
|
+
html += `<td style="${cellStyle} width: 50px; color: hsl(var(--muted-foreground)); font-weight: 500;">`;
|
|
610
|
+
if (isHeader) {
|
|
611
|
+
html += `<strong>→ ${i}</strong>`;
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
html += `${i}`;
|
|
615
|
+
}
|
|
616
|
+
html += '</td>';
|
|
617
|
+
// Data cells
|
|
618
|
+
let values;
|
|
619
|
+
if (i === 0) {
|
|
620
|
+
values = rawCols;
|
|
621
|
+
}
|
|
622
|
+
else if (i - 1 < rawRows.length) {
|
|
623
|
+
values = Object.values(rawRows[i - 1]);
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
values = [];
|
|
627
|
+
}
|
|
628
|
+
// Show first 6 columns
|
|
629
|
+
const displayCols = values.slice(0, 6);
|
|
630
|
+
displayCols.forEach(val => {
|
|
631
|
+
const displayVal = val != null ? String(val).substring(0, 25) : '';
|
|
632
|
+
html += `<td style="${cellStyle}">${this._escapeHtml(displayVal)}</td>`;
|
|
649
633
|
});
|
|
634
|
+
if (values.length > 6) {
|
|
635
|
+
html += `<td style="${cellStyle} color: hsl(var(--muted-foreground));">...</td>`;
|
|
636
|
+
}
|
|
637
|
+
// Status cell
|
|
638
|
+
html += `<td style="${cellStyle} text-align: right; font-size: 10px;">`;
|
|
639
|
+
if (isHeader) {
|
|
640
|
+
html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
|
|
641
|
+
}
|
|
642
|
+
else if (isSkipped) {
|
|
643
|
+
html += '<span style="color: hsl(var(--muted-foreground));">skipped</span>';
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
html += '<span style="color: hsl(var(--success));">data</span>';
|
|
647
|
+
}
|
|
648
|
+
html += '</td>';
|
|
649
|
+
html += '</tr>';
|
|
650
650
|
}
|
|
651
|
+
html += '</table>';
|
|
651
652
|
if (previewDiv) {
|
|
652
|
-
previewDiv.
|
|
653
|
+
previewDiv.innerHTML = html;
|
|
653
654
|
}
|
|
654
655
|
}
|
|
655
656
|
catch (err) {
|
|
@@ -662,6 +663,11 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
662
663
|
updatePreview();
|
|
663
664
|
this._reshapeModal.open();
|
|
664
665
|
}
|
|
666
|
+
_escapeHtml(text) {
|
|
667
|
+
const div = document.createElement('div');
|
|
668
|
+
div.textContent = text;
|
|
669
|
+
return div.innerHTML;
|
|
670
|
+
}
|
|
665
671
|
_showCSVReshapeModal() {
|
|
666
672
|
if (!this._rawFileData)
|
|
667
673
|
return;
|
|
@@ -683,17 +689,13 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
683
689
|
</select>
|
|
684
690
|
</div>
|
|
685
691
|
<div style="margin-bottom: 1rem;">
|
|
686
|
-
<label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Header Row</label>
|
|
692
|
+
<label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Header Row (0-based index)</label>
|
|
687
693
|
<input type="number" id="${this._id}-header-row" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
|
|
688
694
|
</div>
|
|
689
|
-
<div style="margin-bottom: 1rem;">
|
|
690
|
-
|
|
691
|
-
<input type="number" id="${this._id}-skip-rows" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
|
|
692
|
-
</div>
|
|
693
|
-
<div id="${this._id}-reshape-hint" class="jux-reshape-hint"></div>
|
|
694
|
-
<div class="jux-reshape-preview-container" style="margin-top: 1rem;">
|
|
695
|
+
<div id="${this._id}-reshape-hint" class="jux-reshape-hint" style="margin-top: 0.5rem; margin-bottom: 1rem; padding: 0.75rem; background: hsl(var(--muted) / 0.5); border-radius: var(--radius); font-size: 0.875rem;"></div>
|
|
696
|
+
<div class="jux-reshape-preview-container">
|
|
695
697
|
<div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview</div>
|
|
696
|
-
<div id="${this._id}-preview"
|
|
698
|
+
<div id="${this._id}-preview" style="font-family: 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>
|
|
697
699
|
</div>
|
|
698
700
|
`;
|
|
699
701
|
this._reshapeModal
|
|
@@ -712,17 +714,14 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
712
714
|
return;
|
|
713
715
|
const delimiterSelect = document.getElementById(`${this._id}-delimiter`);
|
|
714
716
|
const headerRowInput = document.getElementById(`${this._id}-header-row`);
|
|
715
|
-
const skipRowsInput = document.getElementById(`${this._id}-skip-rows`);
|
|
716
717
|
const delim = delimiterSelect.value;
|
|
717
718
|
const headerRow = parseInt(headerRowInput.value) || 0;
|
|
718
|
-
const skipRows = parseInt(skipRowsInput.value) || 0;
|
|
719
719
|
this.state.loading = true;
|
|
720
720
|
this._updateStatus('Re-parsing with new settings...', 'loading');
|
|
721
721
|
try {
|
|
722
722
|
const df = this._driver.parseCSV(this._rawFileData.text, {
|
|
723
723
|
delimiter: delim,
|
|
724
724
|
headerRow,
|
|
725
|
-
skipRows,
|
|
726
725
|
hasHeader: true
|
|
727
726
|
});
|
|
728
727
|
await this._driver.store(this._rawFileData.file.name, df, { source: this._rawFileData.file.name });
|
|
@@ -740,7 +739,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
740
739
|
requestAnimationFrame(() => {
|
|
741
740
|
const delimiterSelect = document.getElementById(`${this._id}-delimiter`);
|
|
742
741
|
const headerRowInput = document.getElementById(`${this._id}-header-row`);
|
|
743
|
-
const skipRowsInput = document.getElementById(`${this._id}-skip-rows`);
|
|
744
742
|
const previewDiv = document.getElementById(`${this._id}-preview`);
|
|
745
743
|
const hintDiv = document.getElementById(`${this._id}-reshape-hint`);
|
|
746
744
|
if (this._rawFileData?.text) {
|
|
@@ -755,75 +753,83 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
755
753
|
if (!hintDiv)
|
|
756
754
|
return;
|
|
757
755
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
756
|
+
if (headerRow > 0) {
|
|
757
|
+
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. ` +
|
|
758
|
+
`Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
|
|
762
|
+
}
|
|
764
763
|
};
|
|
765
764
|
const updatePreview = () => {
|
|
766
765
|
if (!this._rawFileData?.text)
|
|
767
766
|
return;
|
|
768
767
|
const delim = delimiterSelect?.value || ',';
|
|
769
768
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
770
|
-
const skipRows = parseInt(skipRowsInput?.value) || 0;
|
|
771
769
|
updateHint();
|
|
772
770
|
try {
|
|
773
|
-
|
|
774
|
-
const
|
|
775
|
-
const lines = [];
|
|
776
|
-
const totalOffset = headerRow + skipRows;
|
|
777
|
-
// Parse raw (no header offset) to show skipped rows
|
|
778
|
-
if (totalOffset > 0) {
|
|
779
|
-
const rawDf = this._driver.parseCSV(this._rawFileData.text, {
|
|
780
|
-
delimiter: delim,
|
|
781
|
-
headerRow: 0,
|
|
782
|
-
skipRows: 0,
|
|
783
|
-
hasHeader: true,
|
|
784
|
-
maxRows: totalOffset + 1
|
|
785
|
-
});
|
|
786
|
-
lines.push('');
|
|
787
|
-
lines.push(` ┌${'─'.repeat(100)}┐`);
|
|
788
|
-
lines.push(` │ ROWS 0-${totalOffset - 1} WILL BE SKIPPED (not imported)`.padEnd(101) + '│');
|
|
789
|
-
lines.push(` └${'─'.repeat(100)}┘`);
|
|
790
|
-
lines.push('');
|
|
791
|
-
const rawCols = rawDf.columns;
|
|
792
|
-
lines.push(`${'[0]'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('│ ')} ← skipped`);
|
|
793
|
-
const rawRows = rawDf.toRows();
|
|
794
|
-
const skippedCount = Math.min(totalOffset - 1, rawRows.length);
|
|
795
|
-
for (let i = 0; i < skippedCount; i++) {
|
|
796
|
-
const row = rawRows[i];
|
|
797
|
-
const rowIdx = `[${i + 1}]`.padEnd(idxWidth);
|
|
798
|
-
const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
|
|
799
|
-
lines.push(`${rowIdx}${cols} ← skipped`);
|
|
800
|
-
}
|
|
801
|
-
lines.push('');
|
|
802
|
-
lines.push(` ╔${'═'.repeat(100)}╗`);
|
|
803
|
-
lines.push(` ║ ▼ DATA STARTS HERE (Row ${totalOffset} = Column Headers)`.padEnd(101) + '║');
|
|
804
|
-
lines.push(` ╚${'═'.repeat(100)}╝`);
|
|
805
|
-
lines.push('');
|
|
806
|
-
}
|
|
807
|
-
// Parse with actual settings for header + data rows
|
|
808
|
-
const df = this._driver.parseCSV(this._rawFileData.text, {
|
|
771
|
+
// Parse raw to show all rows
|
|
772
|
+
const rawDf = this._driver.parseCSV(this._rawFileData.text, {
|
|
809
773
|
delimiter: delim,
|
|
810
|
-
headerRow,
|
|
811
|
-
skipRows,
|
|
774
|
+
headerRow: 0,
|
|
812
775
|
hasHeader: true,
|
|
813
|
-
maxRows:
|
|
776
|
+
maxRows: headerRow + 10
|
|
814
777
|
});
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
778
|
+
const rawCols = rawDf.columns;
|
|
779
|
+
const rawRows = rawDf.toRows();
|
|
780
|
+
// Build HTML table
|
|
781
|
+
let html = '<table style="width: 100%; border-collapse: collapse; font-size: 11px;">';
|
|
782
|
+
const totalRows = Math.min(headerRow + 8, rawRows.length + 1);
|
|
783
|
+
for (let i = 0; i < totalRows; i++) {
|
|
784
|
+
const isHeader = (i === headerRow);
|
|
785
|
+
const isSkipped = (i < headerRow);
|
|
786
|
+
let rowStyle = 'border-bottom: 1px solid hsl(var(--border));';
|
|
787
|
+
if (isHeader) {
|
|
788
|
+
rowStyle += 'background: hsl(var(--primary) / 0.15); font-weight: bold;';
|
|
789
|
+
}
|
|
790
|
+
else if (isSkipped) {
|
|
791
|
+
rowStyle += 'background: hsl(var(--muted) / 0.3); color: hsl(var(--muted-foreground)); font-style: italic;';
|
|
792
|
+
}
|
|
793
|
+
html += `<tr style="${rowStyle}">`;
|
|
794
|
+
// Row index
|
|
795
|
+
html += `<td style="padding: 6px 8px; width: 50px; color: hsl(var(--muted-foreground)); font-weight: 500;">`;
|
|
796
|
+
html += isHeader ? `<strong>→ ${i}</strong>` : `${i}`;
|
|
797
|
+
html += '</td>';
|
|
798
|
+
// Data
|
|
799
|
+
let values;
|
|
800
|
+
if (i === 0) {
|
|
801
|
+
values = rawCols;
|
|
802
|
+
}
|
|
803
|
+
else if (i - 1 < rawRows.length) {
|
|
804
|
+
values = Object.values(rawRows[i - 1]);
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
values = [];
|
|
808
|
+
}
|
|
809
|
+
values.slice(0, 6).forEach(val => {
|
|
810
|
+
const displayVal = val != null ? String(val).substring(0, 25) : '';
|
|
811
|
+
html += `<td style="padding: 6px 8px;">${this._escapeHtml(displayVal)}</td>`;
|
|
812
|
+
});
|
|
813
|
+
if (values.length > 6) {
|
|
814
|
+
html += `<td style="padding: 6px 8px; color: hsl(var(--muted-foreground));">...</td>`;
|
|
815
|
+
}
|
|
816
|
+
// Status
|
|
817
|
+
html += `<td style="padding: 6px 8px; text-align: right; font-size: 10px;">`;
|
|
818
|
+
if (isHeader) {
|
|
819
|
+
html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
|
|
820
|
+
}
|
|
821
|
+
else if (isSkipped) {
|
|
822
|
+
html += '<span style="color: hsl(var(--muted-foreground));">skipped</span>';
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
html += '<span style="color: hsl(var(--success));">data</span>';
|
|
826
|
+
}
|
|
827
|
+
html += '</td>';
|
|
828
|
+
html += '</tr>';
|
|
826
829
|
}
|
|
830
|
+
html += '</table>';
|
|
831
|
+
if (previewDiv)
|
|
832
|
+
previewDiv.innerHTML = html;
|
|
827
833
|
}
|
|
828
834
|
catch (err) {
|
|
829
835
|
if (previewDiv)
|
|
@@ -834,8 +840,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
834
840
|
delimiterSelect.addEventListener('change', updatePreview);
|
|
835
841
|
if (headerRowInput)
|
|
836
842
|
headerRowInput.addEventListener('input', updatePreview);
|
|
837
|
-
if (skipRowsInput)
|
|
838
|
-
skipRowsInput.addEventListener('input', updatePreview);
|
|
839
843
|
updatePreview();
|
|
840
844
|
this._reshapeModal.open();
|
|
841
845
|
});
|
|
@@ -5,6 +5,7 @@ import { FileUpload } from './fileupload.js';
|
|
|
5
5
|
import { Table } from './table.js';
|
|
6
6
|
import { Tabs } from './tabs.js';
|
|
7
7
|
import { Modal } from './modal.js';
|
|
8
|
+
import { Button } from './button.js';
|
|
8
9
|
import { renderIcon } from './icons.js';
|
|
9
10
|
|
|
10
11
|
const TRIGGER_EVENTS = [] as const;
|
|
@@ -311,10 +312,11 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
311
312
|
const wrapper = document.getElementById(this._id);
|
|
312
313
|
if (!wrapper) return;
|
|
313
314
|
|
|
315
|
+
// Clean up existing content
|
|
314
316
|
const existingTable = wrapper.querySelector('.jux-table-wrapper');
|
|
315
317
|
if (existingTable) existingTable.remove();
|
|
316
318
|
|
|
317
|
-
const existingTabs = wrapper.querySelector('.jux-
|
|
319
|
+
const existingTabs = wrapper.querySelector('.jux-tabs');
|
|
318
320
|
if (existingTabs) existingTabs.remove();
|
|
319
321
|
|
|
320
322
|
Object.entries(sheets).forEach(([name, df]) => {
|
|
@@ -323,65 +325,33 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
323
325
|
|
|
324
326
|
const sheetNames = Object.keys(sheets);
|
|
325
327
|
|
|
326
|
-
// Build
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
sheetNames.forEach((sheetName, idx) => {
|
|
334
|
-
const tabBtn = document.createElement('button');
|
|
335
|
-
tabBtn.className = 'jux-tabs-button' + (idx === 0 ? ' jux-tabs-button-active' : '');
|
|
336
|
-
tabBtn.setAttribute('data-sheet', sheetName);
|
|
337
|
-
|
|
338
|
-
const labelSpan = document.createElement('span');
|
|
339
|
-
labelSpan.textContent = sheetName;
|
|
340
|
-
tabBtn.appendChild(labelSpan);
|
|
341
|
-
|
|
342
|
-
// Add settings cog icon
|
|
343
|
-
const cogBtn = document.createElement('button');
|
|
344
|
-
cogBtn.className = 'jux-dataframe-tab-settings';
|
|
345
|
-
cogBtn.title = 'Import settings for ' + sheetName;
|
|
346
|
-
cogBtn.innerHTML = `<span class="iconify" data-icon="lucide:settings" style="width:14px;height:14px;"></span>`;
|
|
347
|
-
cogBtn.addEventListener('click', (e) => {
|
|
348
|
-
e.stopPropagation();
|
|
349
|
-
this._showReshapeModal();
|
|
350
|
-
});
|
|
351
|
-
tabBtn.appendChild(cogBtn);
|
|
352
|
-
|
|
353
|
-
tabBtn.addEventListener('click', () => {
|
|
354
|
-
// Switch active tab
|
|
355
|
-
tabList.querySelectorAll('.jux-tabs-button').forEach(btn => {
|
|
356
|
-
btn.classList.remove('jux-tabs-button-active');
|
|
357
|
-
});
|
|
358
|
-
tabBtn.classList.add('jux-tabs-button-active');
|
|
359
|
-
|
|
360
|
-
// Show/hide panels
|
|
361
|
-
wrapper.querySelectorAll('.jux-tabs-panel').forEach(panel => {
|
|
362
|
-
(panel as HTMLElement).style.display = 'none';
|
|
363
|
-
});
|
|
364
|
-
const panel = document.getElementById(`${this._id}-panel-${sheetName}`);
|
|
365
|
-
if (panel) panel.style.display = 'block';
|
|
328
|
+
// Build tabs using the Tabs component
|
|
329
|
+
const tabDefs = sheetNames.map(name => ({
|
|
330
|
+
id: name,
|
|
331
|
+
label: name,
|
|
332
|
+
content: '' // Content will be added after render
|
|
333
|
+
}));
|
|
366
334
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
335
|
+
this._tabs = new Tabs(`${this._id}-tabs`, {
|
|
336
|
+
tabs: tabDefs,
|
|
337
|
+
activeTab: sheetNames[0]
|
|
338
|
+
});
|
|
370
339
|
|
|
371
|
-
|
|
340
|
+
this._tabs.bind('tabChange', (tabId: string) => {
|
|
341
|
+
this._df = this._sheets.get(tabId) || null;
|
|
372
342
|
});
|
|
373
343
|
|
|
374
|
-
|
|
344
|
+
// Create container for tabs
|
|
345
|
+
const tabsContainer = document.createElement('div');
|
|
346
|
+
tabsContainer.className = 'jux-dataframe-tabs';
|
|
375
347
|
wrapper.appendChild(tabsContainer);
|
|
376
348
|
|
|
377
|
-
|
|
349
|
+
this._tabs.render(tabsContainer);
|
|
350
|
+
|
|
351
|
+
// Now render tables into each tab panel
|
|
378
352
|
sheetNames.forEach((sheetName, idx) => {
|
|
379
353
|
const df = sheets[sheetName];
|
|
380
|
-
|
|
381
|
-
const panel = document.createElement('div');
|
|
382
|
-
panel.className = 'jux-tabs-panel';
|
|
383
|
-
panel.id = `${this._id}-panel-${sheetName}`;
|
|
384
|
-
panel.style.display = idx === 0 ? 'block' : 'none';
|
|
354
|
+
const panelId = `${this._id}-tabs-${sheetName}-panel`;
|
|
385
355
|
|
|
386
356
|
const table = new Table(`${this._id}-table-${sheetName}`, {
|
|
387
357
|
striped: this._tableOptions.striped,
|
|
@@ -395,54 +365,59 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
395
365
|
const columnDefs = df.columns.map(col => ({ key: col, label: col }));
|
|
396
366
|
table.columns(columnDefs).rows(df.toRows());
|
|
397
367
|
|
|
398
|
-
|
|
399
|
-
|
|
368
|
+
// Add settings button to tab panel
|
|
369
|
+
const settingsBtn = new Button(`${this._id}-settings-${sheetName}`, {
|
|
370
|
+
label: '⚙️ Import Settings',
|
|
371
|
+
variant: 'ghost',
|
|
372
|
+
size: 'small'
|
|
373
|
+
});
|
|
374
|
+
settingsBtn.bind('click', () => this._showReshapeModal());
|
|
375
|
+
|
|
376
|
+
// Use addTabContent to add components
|
|
377
|
+
this._tabs!.addTabContent(sheetName, [settingsBtn, table]);
|
|
400
378
|
|
|
401
379
|
if (this._tableOptions.filterable) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
)
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
+
});
|
|
431
412
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
413
|
+
const tableWrapper = panel.querySelector('.jux-table-wrapper');
|
|
414
|
+
if (tableWrapper) {
|
|
415
|
+
panel.insertBefore(filterContainer, tableWrapper);
|
|
416
|
+
}
|
|
435
417
|
}
|
|
436
418
|
}
|
|
437
419
|
});
|
|
438
420
|
|
|
439
|
-
// Ensure Iconify renders the cog icons
|
|
440
|
-
requestAnimationFrame(() => {
|
|
441
|
-
if ((window as any).Iconify) {
|
|
442
|
-
(window as any).Iconify.scan();
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
|
|
446
421
|
const totalRows = Object.values(sheets).reduce((sum, df) => sum + df.height, 0);
|
|
447
422
|
this._updateStatus(
|
|
448
423
|
`${sourceName} — ${sheetNames.length} sheets, ${totalRows} total rows`,
|
|
@@ -641,7 +616,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
641
616
|
const modalContentHTML = `
|
|
642
617
|
<div style="margin-bottom: 1rem;">
|
|
643
618
|
<label style="display: block; font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
|
|
644
|
-
Header Row
|
|
619
|
+
Header Row (0-based index)
|
|
645
620
|
</label>
|
|
646
621
|
<input
|
|
647
622
|
type="number"
|
|
@@ -652,14 +627,14 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
652
627
|
max="50"
|
|
653
628
|
style="width: 100%;"
|
|
654
629
|
/>
|
|
655
|
-
<div id="${this._id}-reshape-hint" class="jux-reshape-hint">
|
|
630
|
+
<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;">
|
|
656
631
|
</div>
|
|
657
632
|
</div>
|
|
658
633
|
<div class="jux-reshape-preview-container">
|
|
659
634
|
<div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">
|
|
660
635
|
Preview
|
|
661
636
|
</div>
|
|
662
|
-
<div id="${this._id}-preview"
|
|
637
|
+
<div id="${this._id}-preview" style="font-family: 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>
|
|
663
638
|
</div>
|
|
664
639
|
`;
|
|
665
640
|
|
|
@@ -716,10 +691,12 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
716
691
|
|
|
717
692
|
const updateHint = (headerRow: number) => {
|
|
718
693
|
if (!hintDiv) return;
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
694
|
+
if (headerRow > 0) {
|
|
695
|
+
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. ` +
|
|
696
|
+
`Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
697
|
+
} else {
|
|
698
|
+
hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
|
|
699
|
+
}
|
|
723
700
|
};
|
|
724
701
|
|
|
725
702
|
const updatePreview = async () => {
|
|
@@ -727,74 +704,90 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
727
704
|
updateHint(headerRow);
|
|
728
705
|
|
|
729
706
|
try {
|
|
730
|
-
//
|
|
707
|
+
// Get raw data to show all rows
|
|
731
708
|
const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
|
|
732
709
|
headerRow: 0,
|
|
733
|
-
maxSheetSize: headerRow +
|
|
710
|
+
maxSheetSize: headerRow + 12
|
|
734
711
|
});
|
|
735
712
|
const rawSheet = Object.values(rawSheets)[0];
|
|
736
713
|
|
|
737
|
-
|
|
738
|
-
const parsedSheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
|
|
739
|
-
headerRow,
|
|
740
|
-
maxSheetSize: headerRow + 15
|
|
741
|
-
});
|
|
742
|
-
const parsedSheet = Object.values(parsedSheets)[0];
|
|
743
|
-
|
|
744
|
-
if (!rawSheet && !parsedSheet) {
|
|
714
|
+
if (!rawSheet) {
|
|
745
715
|
if (previewDiv) previewDiv.textContent = 'No data found';
|
|
746
716
|
return;
|
|
747
717
|
}
|
|
748
718
|
|
|
749
|
-
const
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const row = rawRows[i];
|
|
770
|
-
const rowIdx = `[${i + 1}]`.padEnd(idxWidth);
|
|
771
|
-
const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
|
|
772
|
-
lines.push(`${rowIdx}${cols} ← skipped`);
|
|
719
|
+
const rawCols = rawSheet.columns;
|
|
720
|
+
const rawRows = rawSheet.toRows();
|
|
721
|
+
|
|
722
|
+
// Build a simple HTML table for clarity
|
|
723
|
+
let html = '<table style="width: 100%; border-collapse: collapse; font-size: 11px;">';
|
|
724
|
+
|
|
725
|
+
// Render each row
|
|
726
|
+
const totalRows = Math.min(headerRow + 8, rawRows.length + 1);
|
|
727
|
+
|
|
728
|
+
for (let i = 0; i < totalRows; i++) {
|
|
729
|
+
const isHeader = (i === headerRow);
|
|
730
|
+
const isSkipped = (i < headerRow);
|
|
731
|
+
|
|
732
|
+
let rowStyle = 'border-bottom: 1px solid hsl(var(--border));';
|
|
733
|
+
let cellStyle = 'padding: 6px 8px; text-align: left;';
|
|
734
|
+
|
|
735
|
+
if (isHeader) {
|
|
736
|
+
rowStyle += 'background: hsl(var(--primary) / 0.15); font-weight: bold;';
|
|
737
|
+
} else if (isSkipped) {
|
|
738
|
+
rowStyle += 'background: hsl(var(--muted) / 0.3); color: hsl(var(--muted-foreground)); font-style: italic;';
|
|
773
739
|
}
|
|
774
740
|
|
|
775
|
-
|
|
776
|
-
lines.push(` ╔${'═'.repeat(100)}╗`);
|
|
777
|
-
lines.push(` ║ ▼ DATA STARTS HERE (Row ${headerRow} = Column Headers)`.padEnd(101) + '║');
|
|
778
|
-
lines.push(` ╚${'═'.repeat(100)}╝`);
|
|
779
|
-
lines.push('');
|
|
780
|
-
}
|
|
741
|
+
html += `<tr style="${rowStyle}">`;
|
|
781
742
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
743
|
+
// Row index cell
|
|
744
|
+
html += `<td style="${cellStyle} width: 50px; color: hsl(var(--muted-foreground)); font-weight: 500;">`;
|
|
745
|
+
if (isHeader) {
|
|
746
|
+
html += `<strong>→ ${i}</strong>`;
|
|
747
|
+
} else {
|
|
748
|
+
html += `${i}`;
|
|
749
|
+
}
|
|
750
|
+
html += '</td>';
|
|
751
|
+
|
|
752
|
+
// Data cells
|
|
753
|
+
let values: any[];
|
|
754
|
+
if (i === 0) {
|
|
755
|
+
values = rawCols;
|
|
756
|
+
} else if (i - 1 < rawRows.length) {
|
|
757
|
+
values = Object.values(rawRows[i - 1]);
|
|
758
|
+
} else {
|
|
759
|
+
values = [];
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Show first 6 columns
|
|
763
|
+
const displayCols = values.slice(0, 6);
|
|
764
|
+
displayCols.forEach(val => {
|
|
765
|
+
const displayVal = val != null ? String(val).substring(0, 25) : '';
|
|
766
|
+
html += `<td style="${cellStyle}">${this._escapeHtml(displayVal)}</td>`;
|
|
793
767
|
});
|
|
768
|
+
|
|
769
|
+
if (values.length > 6) {
|
|
770
|
+
html += `<td style="${cellStyle} color: hsl(var(--muted-foreground));">...</td>`;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Status cell
|
|
774
|
+
html += `<td style="${cellStyle} text-align: right; font-size: 10px;">`;
|
|
775
|
+
if (isHeader) {
|
|
776
|
+
html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
|
|
777
|
+
} else if (isSkipped) {
|
|
778
|
+
html += '<span style="color: hsl(var(--muted-foreground));">skipped</span>';
|
|
779
|
+
} else {
|
|
780
|
+
html += '<span style="color: hsl(var(--success));">data</span>';
|
|
781
|
+
}
|
|
782
|
+
html += '</td>';
|
|
783
|
+
|
|
784
|
+
html += '</tr>';
|
|
794
785
|
}
|
|
795
786
|
|
|
787
|
+
html += '</table>';
|
|
788
|
+
|
|
796
789
|
if (previewDiv) {
|
|
797
|
-
previewDiv.
|
|
790
|
+
previewDiv.innerHTML = html;
|
|
798
791
|
}
|
|
799
792
|
} catch (err: any) {
|
|
800
793
|
if (previewDiv) previewDiv.textContent = `Error: ${err.message}`;
|
|
@@ -807,6 +800,12 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
807
800
|
this._reshapeModal.open();
|
|
808
801
|
}
|
|
809
802
|
|
|
803
|
+
private _escapeHtml(text: string): string {
|
|
804
|
+
const div = document.createElement('div');
|
|
805
|
+
div.textContent = text;
|
|
806
|
+
return div.innerHTML;
|
|
807
|
+
}
|
|
808
|
+
|
|
810
809
|
private _showCSVReshapeModal(): void {
|
|
811
810
|
if (!this._rawFileData) return;
|
|
812
811
|
|
|
@@ -830,17 +829,13 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
830
829
|
</select>
|
|
831
830
|
</div>
|
|
832
831
|
<div style="margin-bottom: 1rem;">
|
|
833
|
-
<label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Header Row</label>
|
|
832
|
+
<label style="display: block; font-weight: 600; margin-bottom: 0.5rem;">Header Row (0-based index)</label>
|
|
834
833
|
<input type="number" id="${this._id}-header-row" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
|
|
835
834
|
</div>
|
|
836
|
-
<div style="margin-bottom: 1rem;">
|
|
837
|
-
|
|
838
|
-
<input type="number" id="${this._id}-skip-rows" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
|
|
839
|
-
</div>
|
|
840
|
-
<div id="${this._id}-reshape-hint" class="jux-reshape-hint"></div>
|
|
841
|
-
<div class="jux-reshape-preview-container" style="margin-top: 1rem;">
|
|
835
|
+
<div id="${this._id}-reshape-hint" class="jux-reshape-hint" style="margin-top: 0.5rem; margin-bottom: 1rem; padding: 0.75rem; background: hsl(var(--muted) / 0.5); border-radius: var(--radius); font-size: 0.875rem;"></div>
|
|
836
|
+
<div class="jux-reshape-preview-container">
|
|
842
837
|
<div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview</div>
|
|
843
|
-
<div id="${this._id}-preview"
|
|
838
|
+
<div id="${this._id}-preview" style="font-family: 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>
|
|
844
839
|
</div>
|
|
845
840
|
`;
|
|
846
841
|
|
|
@@ -860,11 +855,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
860
855
|
|
|
861
856
|
const delimiterSelect = document.getElementById(`${this._id}-delimiter`) as HTMLSelectElement;
|
|
862
857
|
const headerRowInput = document.getElementById(`${this._id}-header-row`) as HTMLInputElement;
|
|
863
|
-
const skipRowsInput = document.getElementById(`${this._id}-skip-rows`) as HTMLInputElement;
|
|
864
858
|
|
|
865
859
|
const delim = delimiterSelect.value;
|
|
866
860
|
const headerRow = parseInt(headerRowInput.value) || 0;
|
|
867
|
-
const skipRows = parseInt(skipRowsInput.value) || 0;
|
|
868
861
|
|
|
869
862
|
this.state.loading = true;
|
|
870
863
|
this._updateStatus('Re-parsing with new settings...', 'loading');
|
|
@@ -873,7 +866,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
873
866
|
const df = this._driver.parseCSV(this._rawFileData.text, {
|
|
874
867
|
delimiter: delim,
|
|
875
868
|
headerRow,
|
|
876
|
-
skipRows,
|
|
877
869
|
hasHeader: true
|
|
878
870
|
});
|
|
879
871
|
|
|
@@ -894,7 +886,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
894
886
|
requestAnimationFrame(() => {
|
|
895
887
|
const delimiterSelect = document.getElementById(`${this._id}-delimiter`) as HTMLSelectElement;
|
|
896
888
|
const headerRowInput = document.getElementById(`${this._id}-header-row`) as HTMLInputElement;
|
|
897
|
-
const skipRowsInput = document.getElementById(`${this._id}-skip-rows`) as HTMLInputElement;
|
|
898
889
|
const previewDiv = document.getElementById(`${this._id}-preview`)!;
|
|
899
890
|
const hintDiv = document.getElementById(`${this._id}-reshape-hint`)!;
|
|
900
891
|
|
|
@@ -909,12 +900,12 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
909
900
|
const updateHint = () => {
|
|
910
901
|
if (!hintDiv) return;
|
|
911
902
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
903
|
+
if (headerRow > 0) {
|
|
904
|
+
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. ` +
|
|
905
|
+
`Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
906
|
+
} else {
|
|
907
|
+
hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
|
|
908
|
+
}
|
|
918
909
|
};
|
|
919
910
|
|
|
920
911
|
const updatePreview = () => {
|
|
@@ -922,74 +913,81 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
922
913
|
|
|
923
914
|
const delim = delimiterSelect?.value || ',';
|
|
924
915
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
925
|
-
const skipRows = parseInt(skipRowsInput?.value) || 0;
|
|
926
916
|
|
|
927
917
|
updateHint();
|
|
928
918
|
|
|
929
919
|
try {
|
|
930
|
-
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
920
|
+
// Parse raw to show all rows
|
|
921
|
+
const rawDf = this._driver.parseCSV(this._rawFileData.text, {
|
|
922
|
+
delimiter: delim,
|
|
923
|
+
headerRow: 0,
|
|
924
|
+
hasHeader: true,
|
|
925
|
+
maxRows: headerRow + 10
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
const rawCols = rawDf.columns;
|
|
929
|
+
const rawRows = rawDf.toRows();
|
|
930
|
+
|
|
931
|
+
// Build HTML table
|
|
932
|
+
let html = '<table style="width: 100%; border-collapse: collapse; font-size: 11px;">';
|
|
933
|
+
|
|
934
|
+
const totalRows = Math.min(headerRow + 8, rawRows.length + 1);
|
|
935
|
+
|
|
936
|
+
for (let i = 0; i < totalRows; i++) {
|
|
937
|
+
const isHeader = (i === headerRow);
|
|
938
|
+
const isSkipped = (i < headerRow);
|
|
944
939
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
const rawCols = rawDf.columns;
|
|
952
|
-
lines.push(`${'[0]'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('│ ')} ← skipped`);
|
|
953
|
-
|
|
954
|
-
const rawRows = rawDf.toRows();
|
|
955
|
-
const skippedCount = Math.min(totalOffset - 1, rawRows.length);
|
|
956
|
-
for (let i = 0; i < skippedCount; i++) {
|
|
957
|
-
const row = rawRows[i];
|
|
958
|
-
const rowIdx = `[${i + 1}]`.padEnd(idxWidth);
|
|
959
|
-
const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
|
|
960
|
-
lines.push(`${rowIdx}${cols} ← skipped`);
|
|
940
|
+
let rowStyle = 'border-bottom: 1px solid hsl(var(--border));';
|
|
941
|
+
|
|
942
|
+
if (isHeader) {
|
|
943
|
+
rowStyle += 'background: hsl(var(--primary) / 0.15); font-weight: bold;';
|
|
944
|
+
} else if (isSkipped) {
|
|
945
|
+
rowStyle += 'background: hsl(var(--muted) / 0.3); color: hsl(var(--muted-foreground)); font-style: italic;';
|
|
961
946
|
}
|
|
962
947
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
948
|
+
html += `<tr style="${rowStyle}">`;
|
|
949
|
+
|
|
950
|
+
// Row index
|
|
951
|
+
html += `<td style="padding: 6px 8px; width: 50px; color: hsl(var(--muted-foreground)); font-weight: 500;">`;
|
|
952
|
+
html += isHeader ? `<strong>→ ${i}</strong>` : `${i}`;
|
|
953
|
+
html += '</td>';
|
|
954
|
+
|
|
955
|
+
// Data
|
|
956
|
+
let values: any[];
|
|
957
|
+
if (i === 0) {
|
|
958
|
+
values = rawCols;
|
|
959
|
+
} else if (i - 1 < rawRows.length) {
|
|
960
|
+
values = Object.values(rawRows[i - 1]);
|
|
961
|
+
} else {
|
|
962
|
+
values = [];
|
|
963
|
+
}
|
|
969
964
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
skipRows,
|
|
975
|
-
hasHeader: true,
|
|
976
|
-
maxRows: 8
|
|
977
|
-
});
|
|
965
|
+
values.slice(0, 6).forEach(val => {
|
|
966
|
+
const displayVal = val != null ? String(val).substring(0, 25) : '';
|
|
967
|
+
html += `<td style="padding: 6px 8px;">${this._escapeHtml(displayVal)}</td>`;
|
|
968
|
+
});
|
|
978
969
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
970
|
+
if (values.length > 6) {
|
|
971
|
+
html += `<td style="padding: 6px 8px; color: hsl(var(--muted-foreground));">...</td>`;
|
|
972
|
+
}
|
|
982
973
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
974
|
+
// Status
|
|
975
|
+
html += `<td style="padding: 6px 8px; text-align: right; font-size: 10px;">`;
|
|
976
|
+
if (isHeader) {
|
|
977
|
+
html += '<span style="background: hsl(var(--primary)); color: white; padding: 2px 6px; border-radius: 4px;">HEADER</span>';
|
|
978
|
+
} else if (isSkipped) {
|
|
979
|
+
html += '<span style="color: hsl(var(--muted-foreground));">skipped</span>';
|
|
980
|
+
} else {
|
|
981
|
+
html += '<span style="color: hsl(var(--success));">data</span>';
|
|
982
|
+
}
|
|
983
|
+
html += '</td>';
|
|
989
984
|
|
|
990
|
-
|
|
991
|
-
previewDiv.textContent = lines.join('\n');
|
|
985
|
+
html += '</tr>';
|
|
992
986
|
}
|
|
987
|
+
|
|
988
|
+
html += '</table>';
|
|
989
|
+
|
|
990
|
+
if (previewDiv) previewDiv.innerHTML = html;
|
|
993
991
|
} catch (err: any) {
|
|
994
992
|
if (previewDiv) previewDiv.textContent = `Error: ${err.message}`;
|
|
995
993
|
}
|
|
@@ -997,7 +995,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
997
995
|
|
|
998
996
|
if (delimiterSelect) delimiterSelect.addEventListener('change', updatePreview);
|
|
999
997
|
if (headerRowInput) headerRowInput.addEventListener('input', updatePreview);
|
|
1000
|
-
if (skipRowsInput) skipRowsInput.addEventListener('input', updatePreview);
|
|
1001
998
|
|
|
1002
999
|
updatePreview();
|
|
1003
1000
|
this._reshapeModal!.open();
|