docs.sheetjs.com/docz/static/electron/index.js
syntaxbullet fd0d5a6ad0 chore: fix spelling error.
refactor: replace tabs with `details` element, hide dropzone on file-uploads.
2025-04-30 13:53:56 +02:00

206 lines
5.7 KiB
JavaScript

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(
"|"
);
const dropContainer = document.getElementById("drop-container");
const dropzone = document.getElementById("drop");
const fileStatus = document.getElementById("fileStatus");
const exportBtn = document.getElementById("exportBtn");
const spinnerOverlay = document.getElementById("spinner-overlay");
const htmlout = document.getElementById("htmlout");
// open external links in default browser
document.addEventListener("click", (e) => {
if (e.target.tagName === "A" && e.target.href.startsWith("http")) {
e.preventDefault();
electron.shell.openExternal(e.target.href);
}
});
/**
* Export current HTML table as a spreadsheet file using Electron API.
*/
async function exportFile() {
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"],
});
}
exportBtn.addEventListener("click", exportFile, false);
function renderWorkbookToTables(wb) {
htmlout.innerHTML = "";
const sheetNames = wb.SheetNames;
sheetNames.forEach((sheetName) => {
const sheet = wb.Sheets[sheetName];
const sheetname = sheetName;
const table = XLSX.utils.sheet_to_html(sheet);
htmlout.innerHTML += `<details class="sheetjs-sheet-container">
<summary class="sheetjs-sheet-name">${sheetname}</summary>
<div class="sheetjs-tab-content">${table}</div>
</details>`;
});
}
// --- 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);
showExportBtn();
} finally {
hideSpinner();
hideDropUI();
}
}
// --- UI Templates and Helpers ---
const getLoadedFileUI = (fileName) => {
return `<div class="file-loaded">
<span class="file-name text-muted text-small">${fileName}</span>
<button type="button" id="unloadBtn" class="unload-btn">Unload</button>
</div>`;
};
const hideDropUI = () => {
if (dropContainer) dropContainer.style.display = "none";
};
const showDropUI = () => {
if (dropContainer) dropContainer.style.display = "block";
};
const hideLoadedFileUI = () => {
if (fileStatus) fileStatus.innerHTML = "";
};
const hideOutputUI = () => {
if (htmlout) htmlout.innerHTML = "";
};
const showLoadedFileUI = (fileName) => {
const loadedFileUI = getLoadedFileUI(fileName);
fileStatus.innerHTML = loadedFileUI;
const unloadBtn = fileStatus.querySelector("#unloadBtn");
if (unloadBtn) {
unloadBtn.addEventListener("click", () => {
hideLoadedFileUI();
hideExportBtn();
showDropUI();
hideOutputUI();
});
}
hideDropUI();
showExportBtn();
};
const hideExportBtn = () => {
if (exportBtn) exportBtn.disabled = true;
};
const showExportBtn = () => {
if (exportBtn) exportBtn.disabled = false;
};
function showSpinner() {
if (spinnerOverlay) spinnerOverlay.style.display = "flex";
}
function hideSpinner() {
if (spinnerOverlay) spinnerOverlay.style.display = "none";
}
// --- 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);
if (!dropzone || !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");
};
dropzone.addEventListener(
"drop",
(e) => {
e.stopPropagation();
e.preventDefault();
dropContainer.classList.remove("drag-over");
readFile(e.dataTransfer.files);
},
false
);
dropzone.addEventListener("dragenter", handleDrag, false);
dropzone.addEventListener("dragover", handleDrag, false);
dropzone.addEventListener("dragleave", handleDragLeave, false);
dropzone.addEventListener("dragend", handleDragLeave, false);
}
// --- 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();