const XLSX = require('xlsx'); const electron = require('@electron/remote'); // --- Supported Extensions --- const EXTENSIONS = "xls|xlsx|xlsm|xlsb|xml|csv|txt|dif|sylk|slk|prn|ods|fods|htm|html|numbers".split("|"); /** * Export current HTML table as a spreadsheet file using Electron API. */ async function exportFile() { const HTMLOUT = document.getElementById('htmlout'); const wb = XLSX.utils.table_to_book(HTMLOUT.getElementsByTagName("TABLE")[0]); const o = await electron.dialog.showSaveDialog({ title: 'Save file as', filters: [{ name: "Spreadsheets", extensions: EXTENSIONS }] }); XLSX.writeFile(wb, o.filePath); electron.dialog.showMessageBox({ message: "Exported data to " + o.filePath, buttons: ["OK"] }); } document.getElementById('exportBtn').addEventListener('click', exportFile, false); /** * Create a element for a table from a header row array. */ function createTableHead(headerRow) { const thead = document.createElement('thead'); const headRow = document.createElement('tr'); headerRow.forEach(cell => { const th = document.createElement('th'); th.textContent = cell !== undefined ? cell : ''; headRow.appendChild(th); }); thead.appendChild(headRow); return thead; } /** * Create a element for a table from a 2D data array. */ function createTableBody(data) { const tbody = document.createElement('tbody'); for (let i = 1; i < data.length; ++i) { const row = data[i]; const tr = document.createElement('tr'); row.forEach(cell => { const td = document.createElement('td'); td.textContent = cell !== undefined ? cell : ''; tr.appendChild(td); }); tbody.appendChild(tr); } return tbody; } /** * Wrap a table in a responsive container div. */ function createResponsiveDiv(table) { const responsiveDiv = document.createElement('div'); responsiveDiv.className = 'table-responsive'; responsiveDiv.appendChild(table); return responsiveDiv; } // --- Main Table Processing --- /** * Render all sheets of a workbook as HTML tables with clickable tabs. */ function renderWorkbookToTables(wb) { const HTMLOUT = document.getElementById('htmlout'); const exportBtn = document.getElementById('exportBtn'); exportBtn.disabled = false; HTMLOUT.innerHTML = ""; // Create tab container const tabContainer = document.createElement('div'); tabContainer.className = 'sheetjs-tab-container'; // Create content container const contentContainer = document.createElement('div'); contentContainer.className = 'sheetjs-tab-content'; // Store tables for each sheet const tables = []; wb.SheetNames.forEach(function(sheetName, idx) { // Create tab button const tab = document.createElement('button'); tab.className = 'sheetjs-tab-btn text-small'; tab.textContent = sheetName; tab.setAttribute('data-sheet-idx', idx); if(idx === 0) tab.classList.add('active'); tab.addEventListener('click', function() { // Remove active from all tabs Array.from(tabContainer.children).forEach(btn => btn.classList.remove('active')); tab.classList.add('active'); // Hide all tables and show the selected one tables.forEach((tableDiv, tIdx) => { tableDiv.style.display = (tIdx === idx) ? '' : 'none'; }); }); tabContainer.appendChild(tab); // Create table for this sheet const sheet = wb.Sheets[sheetName]; const data = XLSX.utils.sheet_to_json(sheet, { header: 1 }); if (data.length === 0) { const emptyDiv = document.createElement('div'); emptyDiv.textContent = `Sheet '${sheetName}' is empty.`; tables.push(emptyDiv); contentContainer.appendChild(emptyDiv); return; } const table = document.createElement('table'); table.className = 'sheetjs-table'; // Remove caption, handled by tab table.appendChild(createTableHead(data[0])); table.appendChild(createTableBody(data)); const responsiveDiv = createResponsiveDiv(table); if(idx !== 0) responsiveDiv.style.display = 'none'; tables.push(responsiveDiv); contentContainer.appendChild(responsiveDiv); }); // Only show tabs if more than one sheet if (wb.SheetNames.length > 1) { HTMLOUT.appendChild(tabContainer); } HTMLOUT.appendChild(contentContainer); } // --- File Import Logic --- /** * Handle file selection dialog and render the selected spreadsheet. */ async function handleReadBtn() { const o = await electron.dialog.showOpenDialog({ title: 'Select a file', filters: [{ name: "Spreadsheets", extensions: EXTENSIONS }], properties: ['openFile'] }); if(o.filePaths.length == 0) throw new Error("No file was selected!"); showSpinner(); // yield to event loop to render spinner await new Promise(resolve => setTimeout(resolve, 200)); try { const filePath = o.filePaths[0]; const fileName = filePath.split(/[/\\]/).pop(); renderWorkbookToTables(XLSX.readFile(filePath)); showLoadedFileUI(fileName); } finally { hideSpinner(); } } // --- UI Templates and Helpers --- /** * Return HTML for the drag-and-drop area. */ function getDropAreaHTML() { return `

Drag and drop a file here

or

`; } /** * Return HTML for the file-loaded UI. */ function getFileLoadedHTML(fileName) { return `
${fileName}
`; } /** * Update the drop-container's inner HTML. */ function updateDropContainer(html) { document.getElementById('drop-container').innerHTML = html; } /** * Restore the original drag-and-drop UI and reset state. */ function restoreDropUI() { updateDropContainer(getDropAreaHTML()); // Remove the table/output const htmlout = document.getElementById('htmlout'); if (htmlout) htmlout.innerHTML = ''; // Reset export button state const exportBtn = document.getElementById('exportBtn'); if (exportBtn) { exportBtn.disabled = true; exportBtn.classList.add('disabled'); } // Re-attach event listeners after restoring UI attachDropListeners(); } /** * Show UI for loaded file and attach unload handler. */ function showLoadedFileUI(fileName) { updateDropContainer(getFileLoadedHTML(fileName)); document.getElementById('unloadBtn').addEventListener('click', restoreDropUI); } // --- Event Listener Helpers --- /** * Add an event listener to an element if it exists. */ function addListener(id, event, handler) { const el = document.getElementById(id); if (el) el.addEventListener(event, handler, false); } /** * Attach drag-and-drop and file input listeners to the UI. */ function attachDropListeners() { addListener('readIn', 'change', (e) => { showSpinner(); // Defer to next tick to ensure spinner renders before heavy work setTimeout(() => readFile(e.target.files), 0); }); addListener('readBtn', 'click', handleReadBtn); const drop = document.getElementById('drop'); const dropContainer = document.getElementById('drop-container'); if (!drop || !dropContainer) return; const handleDrag = (e) => { e.stopPropagation(); e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; dropContainer.classList.add('drag-over'); }; const handleDragLeave = (e) => { e.stopPropagation(); e.preventDefault(); dropContainer.classList.remove('drag-over'); }; drop.addEventListener('drop', (e) => { e.stopPropagation(); e.preventDefault(); dropContainer.classList.remove('drag-over'); readFile(e.dataTransfer.files); }, false); drop.addEventListener('dragenter', handleDrag, false); drop.addEventListener('dragover', handleDrag, false); drop.addEventListener('dragleave', handleDragLeave, false); drop.addEventListener('dragend', handleDragLeave, false); } // --- Spinner Helpers --- function showSpinner() { const spinner = document.getElementById('spinner-overlay'); if (spinner) spinner.style.display = 'flex'; } function hideSpinner() { const spinner = document.getElementById('spinner-overlay'); if (spinner) spinner.style.display = 'none'; } // --- File Reader for Drag-and-Drop and Input --- /** * Read file(s) from input or drag-and-drop and render as table. */ async function readFile(files) { if (!files || files.length === 0) return; const f = files[0]; showSpinner(); try { const data = await f.arrayBuffer(); renderWorkbookToTables(XLSX.read(data)); showLoadedFileUI(f.name); } finally { hideSpinner(); } } // --- Initial Setup --- attachDropListeners();