juxscript 1.1.196 → 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 +196 -207
- package/lib/components/dataframe.ts +211 -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"
|
|
@@ -533,7 +514,7 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
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" style="font-family: monospace; font-size:
|
|
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
|
|
@@ -585,17 +566,17 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
585
566
|
return;
|
|
586
567
|
if (headerRow > 0) {
|
|
587
568
|
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. ` +
|
|
588
|
-
`Rows <strong>0–${headerRow - 1}</strong> will be skipped
|
|
569
|
+
`Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
589
570
|
}
|
|
590
571
|
else {
|
|
591
|
-
hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers
|
|
572
|
+
hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
|
|
592
573
|
}
|
|
593
574
|
};
|
|
594
575
|
const updatePreview = async () => {
|
|
595
576
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
596
577
|
updateHint(headerRow);
|
|
597
578
|
try {
|
|
598
|
-
// Get raw data
|
|
579
|
+
// Get raw data to show all rows
|
|
599
580
|
const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData.file, {
|
|
600
581
|
headerRow: 0,
|
|
601
582
|
maxSheetSize: headerRow + 12
|
|
@@ -608,63 +589,68 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
608
589
|
}
|
|
609
590
|
const rawCols = rawSheet.columns;
|
|
610
591
|
const rawRows = rawSheet.toRows();
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
//
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
// Rows 1 to headerRow-1 are skipped data rows
|
|
623
|
-
for (let i = 0; i < Math.min(headerRow - 1, rawRows.length); i++) {
|
|
624
|
-
const row = rawRows[i];
|
|
625
|
-
const vals = Object.values(row).slice(0, 5).map(v => String(v ?? '').substring(0, colWidth - 1).padEnd(colWidth)).join('│');
|
|
626
|
-
lines.push(` [${i + 1}] ${vals}${Object.values(row).length > 5 ? ' ...' : ''}`);
|
|
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;';
|
|
627
603
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
lines.push(' (No data rows found after header)');
|
|
658
|
-
}
|
|
659
|
-
else {
|
|
660
|
-
dataRows.forEach((row, i) => {
|
|
661
|
-
const rowIdx = headerRow + 1 + i;
|
|
662
|
-
const vals = Object.values(row).slice(0, 5).map(v => String(v ?? '').substring(0, colWidth - 1).padEnd(colWidth)).join('│');
|
|
663
|
-
lines.push(` [${rowIdx}] ${vals}${Object.values(row).length > 5 ? ' ...' : ''}`);
|
|
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>`;
|
|
664
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>';
|
|
665
650
|
}
|
|
651
|
+
html += '</table>';
|
|
666
652
|
if (previewDiv) {
|
|
667
|
-
previewDiv.
|
|
653
|
+
previewDiv.innerHTML = html;
|
|
668
654
|
}
|
|
669
655
|
}
|
|
670
656
|
catch (err) {
|
|
@@ -677,6 +663,11 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
677
663
|
updatePreview();
|
|
678
664
|
this._reshapeModal.open();
|
|
679
665
|
}
|
|
666
|
+
_escapeHtml(text) {
|
|
667
|
+
const div = document.createElement('div');
|
|
668
|
+
div.textContent = text;
|
|
669
|
+
return div.innerHTML;
|
|
670
|
+
}
|
|
680
671
|
_showCSVReshapeModal() {
|
|
681
672
|
if (!this._rawFileData)
|
|
682
673
|
return;
|
|
@@ -698,17 +689,13 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
698
689
|
</select>
|
|
699
690
|
</div>
|
|
700
691
|
<div style="margin-bottom: 1rem;">
|
|
701
|
-
<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>
|
|
702
693
|
<input type="number" id="${this._id}-header-row" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
|
|
703
694
|
</div>
|
|
704
|
-
<div style="margin-bottom: 1rem;">
|
|
705
|
-
|
|
706
|
-
<input type="number" id="${this._id}-skip-rows" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
|
|
707
|
-
</div>
|
|
708
|
-
<div id="${this._id}-reshape-hint" class="jux-reshape-hint"></div>
|
|
709
|
-
<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">
|
|
710
697
|
<div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview</div>
|
|
711
|
-
<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>
|
|
712
699
|
</div>
|
|
713
700
|
`;
|
|
714
701
|
this._reshapeModal
|
|
@@ -727,17 +714,14 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
727
714
|
return;
|
|
728
715
|
const delimiterSelect = document.getElementById(`${this._id}-delimiter`);
|
|
729
716
|
const headerRowInput = document.getElementById(`${this._id}-header-row`);
|
|
730
|
-
const skipRowsInput = document.getElementById(`${this._id}-skip-rows`);
|
|
731
717
|
const delim = delimiterSelect.value;
|
|
732
718
|
const headerRow = parseInt(headerRowInput.value) || 0;
|
|
733
|
-
const skipRows = parseInt(skipRowsInput.value) || 0;
|
|
734
719
|
this.state.loading = true;
|
|
735
720
|
this._updateStatus('Re-parsing with new settings...', 'loading');
|
|
736
721
|
try {
|
|
737
722
|
const df = this._driver.parseCSV(this._rawFileData.text, {
|
|
738
723
|
delimiter: delim,
|
|
739
724
|
headerRow,
|
|
740
|
-
skipRows,
|
|
741
725
|
hasHeader: true
|
|
742
726
|
});
|
|
743
727
|
await this._driver.store(this._rawFileData.file.name, df, { source: this._rawFileData.file.name });
|
|
@@ -755,7 +739,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
755
739
|
requestAnimationFrame(() => {
|
|
756
740
|
const delimiterSelect = document.getElementById(`${this._id}-delimiter`);
|
|
757
741
|
const headerRowInput = document.getElementById(`${this._id}-header-row`);
|
|
758
|
-
const skipRowsInput = document.getElementById(`${this._id}-skip-rows`);
|
|
759
742
|
const previewDiv = document.getElementById(`${this._id}-preview`);
|
|
760
743
|
const hintDiv = document.getElementById(`${this._id}-reshape-hint`);
|
|
761
744
|
if (this._rawFileData?.text) {
|
|
@@ -770,75 +753,83 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
770
753
|
if (!hintDiv)
|
|
771
754
|
return;
|
|
772
755
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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
|
+
}
|
|
779
763
|
};
|
|
780
764
|
const updatePreview = () => {
|
|
781
765
|
if (!this._rawFileData?.text)
|
|
782
766
|
return;
|
|
783
767
|
const delim = delimiterSelect?.value || ',';
|
|
784
768
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
785
|
-
const skipRows = parseInt(skipRowsInput?.value) || 0;
|
|
786
769
|
updateHint();
|
|
787
770
|
try {
|
|
788
|
-
|
|
789
|
-
const
|
|
790
|
-
const lines = [];
|
|
791
|
-
const totalOffset = headerRow + skipRows;
|
|
792
|
-
// Parse raw (no header offset) to show skipped rows
|
|
793
|
-
if (totalOffset > 0) {
|
|
794
|
-
const rawDf = this._driver.parseCSV(this._rawFileData.text, {
|
|
795
|
-
delimiter: delim,
|
|
796
|
-
headerRow: 0,
|
|
797
|
-
skipRows: 0,
|
|
798
|
-
hasHeader: true,
|
|
799
|
-
maxRows: totalOffset + 1
|
|
800
|
-
});
|
|
801
|
-
lines.push('');
|
|
802
|
-
lines.push(` ┌${'─'.repeat(100)}┐`);
|
|
803
|
-
lines.push(` │ ROWS 0-${totalOffset - 1} WILL BE SKIPPED (not imported)`.padEnd(101) + '│');
|
|
804
|
-
lines.push(` └${'─'.repeat(100)}┘`);
|
|
805
|
-
lines.push('');
|
|
806
|
-
const rawCols = rawDf.columns;
|
|
807
|
-
lines.push(`${'[0]'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('│ ')} ← skipped`);
|
|
808
|
-
const rawRows = rawDf.toRows();
|
|
809
|
-
const skippedCount = Math.min(totalOffset - 1, rawRows.length);
|
|
810
|
-
for (let i = 0; i < skippedCount; i++) {
|
|
811
|
-
const row = rawRows[i];
|
|
812
|
-
const rowIdx = `[${i + 1}]`.padEnd(idxWidth);
|
|
813
|
-
const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
|
|
814
|
-
lines.push(`${rowIdx}${cols} ← skipped`);
|
|
815
|
-
}
|
|
816
|
-
lines.push('');
|
|
817
|
-
lines.push(` ╔${'═'.repeat(100)}╗`);
|
|
818
|
-
lines.push(` ║ ▼ DATA STARTS HERE (Row ${totalOffset} = Column Headers)`.padEnd(101) + '║');
|
|
819
|
-
lines.push(` ╚${'═'.repeat(100)}╝`);
|
|
820
|
-
lines.push('');
|
|
821
|
-
}
|
|
822
|
-
// Parse with actual settings for header + data rows
|
|
823
|
-
const df = this._driver.parseCSV(this._rawFileData.text, {
|
|
771
|
+
// Parse raw to show all rows
|
|
772
|
+
const rawDf = this._driver.parseCSV(this._rawFileData.text, {
|
|
824
773
|
delimiter: delim,
|
|
825
|
-
headerRow,
|
|
826
|
-
skipRows,
|
|
774
|
+
headerRow: 0,
|
|
827
775
|
hasHeader: true,
|
|
828
|
-
maxRows:
|
|
829
|
-
});
|
|
830
|
-
const headerLine = `${'HDR'.padEnd(idxWidth)}${df.columns.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('│ ')} ◀ HEADERS`;
|
|
831
|
-
lines.push(headerLine);
|
|
832
|
-
lines.push(`${'───'.padEnd(idxWidth)}${'─'.repeat(Math.min(colWidth * df.columns.length, 120))}`);
|
|
833
|
-
const dataRows = df.toRows();
|
|
834
|
-
dataRows.forEach((row, i) => {
|
|
835
|
-
const rowIdx = `[${totalOffset + 1 + i}]`.padEnd(idxWidth);
|
|
836
|
-
const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
|
|
837
|
-
lines.push(`${rowIdx}${cols}`);
|
|
776
|
+
maxRows: headerRow + 10
|
|
838
777
|
});
|
|
839
|
-
|
|
840
|
-
|
|
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>';
|
|
841
829
|
}
|
|
830
|
+
html += '</table>';
|
|
831
|
+
if (previewDiv)
|
|
832
|
+
previewDiv.innerHTML = html;
|
|
842
833
|
}
|
|
843
834
|
catch (err) {
|
|
844
835
|
if (previewDiv)
|
|
@@ -849,8 +840,6 @@ export class DataFrameComponent extends BaseComponent {
|
|
|
849
840
|
delimiterSelect.addEventListener('change', updatePreview);
|
|
850
841
|
if (headerRowInput)
|
|
851
842
|
headerRowInput.addEventListener('input', updatePreview);
|
|
852
|
-
if (skipRowsInput)
|
|
853
|
-
skipRowsInput.addEventListener('input', updatePreview);
|
|
854
843
|
updatePreview();
|
|
855
844
|
this._reshapeModal.open();
|
|
856
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"
|
|
@@ -659,7 +634,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
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" style="font-family: monospace; font-size:
|
|
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
|
|
|
@@ -718,9 +693,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
718
693
|
if (!hintDiv) return;
|
|
719
694
|
if (headerRow > 0) {
|
|
720
695
|
hintDiv.innerHTML = `Row <strong>${headerRow}</strong> will be used as column headers. ` +
|
|
721
|
-
`Rows <strong>0–${headerRow - 1}</strong> will be skipped
|
|
696
|
+
`Rows <strong>0–${headerRow - 1}</strong> will be skipped.`;
|
|
722
697
|
} else {
|
|
723
|
-
hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers
|
|
698
|
+
hintDiv.innerHTML = `Row <strong>0</strong> (first row) will be used as column headers.`;
|
|
724
699
|
}
|
|
725
700
|
};
|
|
726
701
|
|
|
@@ -729,7 +704,7 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
729
704
|
updateHint(headerRow);
|
|
730
705
|
|
|
731
706
|
try {
|
|
732
|
-
// Get raw data
|
|
707
|
+
// Get raw data to show all rows
|
|
733
708
|
const rawSheets = await this._driver.streamFileMultiSheet(this._rawFileData!.file, {
|
|
734
709
|
headerRow: 0,
|
|
735
710
|
maxSheetSize: headerRow + 12
|
|
@@ -743,71 +718,76 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
743
718
|
|
|
744
719
|
const rawCols = rawSheet.columns;
|
|
745
720
|
const rawRows = rawSheet.toRows();
|
|
746
|
-
const colWidth = 20;
|
|
747
|
-
const lines: string[] = [];
|
|
748
721
|
|
|
749
|
-
//
|
|
750
|
-
|
|
751
|
-
lines.push('┌─────────────────────────────────────────────────────────────────────────────────┐');
|
|
752
|
-
lines.push(`│ SKIPPED: Rows 0–${headerRow - 1} will NOT be imported │`);
|
|
753
|
-
lines.push('└─────────────────────────────────────────────────────────────────────────────────┘');
|
|
754
|
-
lines.push('');
|
|
755
|
-
|
|
756
|
-
// Row 0 is the raw file header (becomes column names when headerRow=0)
|
|
757
|
-
const row0Line = rawCols.slice(0, 5).map(c => String(c ?? '').substring(0, colWidth - 1).padEnd(colWidth)).join('│');
|
|
758
|
-
lines.push(` [0] ${row0Line}${rawCols.length > 5 ? ' ...' : ''}`);
|
|
759
|
-
|
|
760
|
-
// Rows 1 to headerRow-1 are skipped data rows
|
|
761
|
-
for (let i = 0; i < Math.min(headerRow - 1, rawRows.length); i++) {
|
|
762
|
-
const row = rawRows[i];
|
|
763
|
-
const vals = Object.values(row).slice(0, 5).map(v => String(v ?? '').substring(0, colWidth - 1).padEnd(colWidth)).join('│');
|
|
764
|
-
lines.push(` [${i + 1}] ${vals}${Object.values(row).length > 5 ? ' ...' : ''}`);
|
|
765
|
-
}
|
|
722
|
+
// Build a simple HTML table for clarity
|
|
723
|
+
let html = '<table style="width: 100%; border-collapse: collapse; font-size: 11px;">';
|
|
766
724
|
|
|
767
|
-
|
|
768
|
-
|
|
725
|
+
// Render each row
|
|
726
|
+
const totalRows = Math.min(headerRow + 8, rawRows.length + 1);
|
|
769
727
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
// The header row content - if headerRow=0, it's rawCols, else it's rawRows[headerRow-1]
|
|
777
|
-
let headerValues: string[];
|
|
778
|
-
if (headerRow === 0) {
|
|
779
|
-
headerValues = rawCols;
|
|
780
|
-
} else if (headerRow - 1 < rawRows.length) {
|
|
781
|
-
headerValues = Object.values(rawRows[headerRow - 1]).map(v => String(v ?? ''));
|
|
782
|
-
} else {
|
|
783
|
-
headerValues = rawCols; // fallback
|
|
784
|
-
}
|
|
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;';
|
|
785
734
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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;';
|
|
739
|
+
}
|
|
789
740
|
|
|
790
|
-
|
|
791
|
-
lines.push('────────────────────────────────────────────────────────────────────────────────────');
|
|
792
|
-
lines.push(' DATA ROWS (will be imported):');
|
|
793
|
-
lines.push('');
|
|
741
|
+
html += `<tr style="${rowStyle}">`;
|
|
794
742
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
+
}
|
|
798
761
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
const vals = Object.values(row).slice(0, 5).map(v => String(v ?? '').substring(0, colWidth - 1).padEnd(colWidth)).join('│');
|
|
805
|
-
lines.push(` [${rowIdx}] ${vals}${Object.values(row).length > 5 ? ' ...' : ''}`);
|
|
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>`;
|
|
806
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>';
|
|
807
785
|
}
|
|
808
786
|
|
|
787
|
+
html += '</table>';
|
|
788
|
+
|
|
809
789
|
if (previewDiv) {
|
|
810
|
-
previewDiv.
|
|
790
|
+
previewDiv.innerHTML = html;
|
|
811
791
|
}
|
|
812
792
|
} catch (err: any) {
|
|
813
793
|
if (previewDiv) previewDiv.textContent = `Error: ${err.message}`;
|
|
@@ -820,6 +800,12 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
820
800
|
this._reshapeModal.open();
|
|
821
801
|
}
|
|
822
802
|
|
|
803
|
+
private _escapeHtml(text: string): string {
|
|
804
|
+
const div = document.createElement('div');
|
|
805
|
+
div.textContent = text;
|
|
806
|
+
return div.innerHTML;
|
|
807
|
+
}
|
|
808
|
+
|
|
823
809
|
private _showCSVReshapeModal(): void {
|
|
824
810
|
if (!this._rawFileData) return;
|
|
825
811
|
|
|
@@ -843,17 +829,13 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
843
829
|
</select>
|
|
844
830
|
</div>
|
|
845
831
|
<div style="margin-bottom: 1rem;">
|
|
846
|
-
<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>
|
|
847
833
|
<input type="number" id="${this._id}-header-row" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
|
|
848
834
|
</div>
|
|
849
|
-
<div style="margin-bottom: 1rem;">
|
|
850
|
-
|
|
851
|
-
<input type="number" id="${this._id}-skip-rows" class="jux-input-element" value="0" min="0" max="50" style="width: 100%;" />
|
|
852
|
-
</div>
|
|
853
|
-
<div id="${this._id}-reshape-hint" class="jux-reshape-hint"></div>
|
|
854
|
-
<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">
|
|
855
837
|
<div style="font-weight: 600; margin-bottom: 0.5rem; color: hsl(var(--foreground));">Preview</div>
|
|
856
|
-
<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>
|
|
857
839
|
</div>
|
|
858
840
|
`;
|
|
859
841
|
|
|
@@ -873,11 +855,9 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
873
855
|
|
|
874
856
|
const delimiterSelect = document.getElementById(`${this._id}-delimiter`) as HTMLSelectElement;
|
|
875
857
|
const headerRowInput = document.getElementById(`${this._id}-header-row`) as HTMLInputElement;
|
|
876
|
-
const skipRowsInput = document.getElementById(`${this._id}-skip-rows`) as HTMLInputElement;
|
|
877
858
|
|
|
878
859
|
const delim = delimiterSelect.value;
|
|
879
860
|
const headerRow = parseInt(headerRowInput.value) || 0;
|
|
880
|
-
const skipRows = parseInt(skipRowsInput.value) || 0;
|
|
881
861
|
|
|
882
862
|
this.state.loading = true;
|
|
883
863
|
this._updateStatus('Re-parsing with new settings...', 'loading');
|
|
@@ -886,7 +866,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
886
866
|
const df = this._driver.parseCSV(this._rawFileData.text, {
|
|
887
867
|
delimiter: delim,
|
|
888
868
|
headerRow,
|
|
889
|
-
skipRows,
|
|
890
869
|
hasHeader: true
|
|
891
870
|
});
|
|
892
871
|
|
|
@@ -907,7 +886,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
907
886
|
requestAnimationFrame(() => {
|
|
908
887
|
const delimiterSelect = document.getElementById(`${this._id}-delimiter`) as HTMLSelectElement;
|
|
909
888
|
const headerRowInput = document.getElementById(`${this._id}-header-row`) as HTMLInputElement;
|
|
910
|
-
const skipRowsInput = document.getElementById(`${this._id}-skip-rows`) as HTMLInputElement;
|
|
911
889
|
const previewDiv = document.getElementById(`${this._id}-preview`)!;
|
|
912
890
|
const hintDiv = document.getElementById(`${this._id}-reshape-hint`)!;
|
|
913
891
|
|
|
@@ -922,12 +900,12 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
922
900
|
const updateHint = () => {
|
|
923
901
|
if (!hintDiv) return;
|
|
924
902
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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
|
+
}
|
|
931
909
|
};
|
|
932
910
|
|
|
933
911
|
const updatePreview = () => {
|
|
@@ -935,74 +913,81 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
935
913
|
|
|
936
914
|
const delim = delimiterSelect?.value || ',';
|
|
937
915
|
const headerRow = parseInt(headerRowInput?.value) || 0;
|
|
938
|
-
const skipRows = parseInt(skipRowsInput?.value) || 0;
|
|
939
916
|
|
|
940
917
|
updateHint();
|
|
941
918
|
|
|
942
919
|
try {
|
|
943
|
-
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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);
|
|
957
939
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const rawCols = rawDf.columns;
|
|
965
|
-
lines.push(`${'[0]'.padEnd(idxWidth)}${rawCols.map(c => String(c).substring(0, colWidth - 2).padEnd(colWidth)).join('│ ')} ← skipped`);
|
|
966
|
-
|
|
967
|
-
const rawRows = rawDf.toRows();
|
|
968
|
-
const skippedCount = Math.min(totalOffset - 1, rawRows.length);
|
|
969
|
-
for (let i = 0; i < skippedCount; i++) {
|
|
970
|
-
const row = rawRows[i];
|
|
971
|
-
const rowIdx = `[${i + 1}]`.padEnd(idxWidth);
|
|
972
|
-
const cols = Object.values(row).map(v => String(v ?? '').substring(0, colWidth - 2).padEnd(colWidth)).join('│ ');
|
|
973
|
-
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;';
|
|
974
946
|
}
|
|
975
947
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
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
|
+
}
|
|
982
964
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
skipRows,
|
|
988
|
-
hasHeader: true,
|
|
989
|
-
maxRows: 8
|
|
990
|
-
});
|
|
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
|
+
});
|
|
991
969
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
970
|
+
if (values.length > 6) {
|
|
971
|
+
html += `<td style="padding: 6px 8px; color: hsl(var(--muted-foreground));">...</td>`;
|
|
972
|
+
}
|
|
995
973
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
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>';
|
|
1002
984
|
|
|
1003
|
-
|
|
1004
|
-
previewDiv.textContent = lines.join('\n');
|
|
985
|
+
html += '</tr>';
|
|
1005
986
|
}
|
|
987
|
+
|
|
988
|
+
html += '</table>';
|
|
989
|
+
|
|
990
|
+
if (previewDiv) previewDiv.innerHTML = html;
|
|
1006
991
|
} catch (err: any) {
|
|
1007
992
|
if (previewDiv) previewDiv.textContent = `Error: ${err.message}`;
|
|
1008
993
|
}
|
|
@@ -1010,7 +995,6 @@ export class DataFrameComponent extends BaseComponent<DataFrameState> {
|
|
|
1010
995
|
|
|
1011
996
|
if (delimiterSelect) delimiterSelect.addEventListener('change', updatePreview);
|
|
1012
997
|
if (headerRowInput) headerRowInput.addEventListener('input', updatePreview);
|
|
1013
|
-
if (skipRowsInput) skipRowsInput.addEventListener('input', updatePreview);
|
|
1014
998
|
|
|
1015
999
|
updatePreview();
|
|
1016
1000
|
this._reshapeModal!.open();
|