docs.sheetjs.com/docz/static/electron/index.js

233 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const XLSX = window.SheetJSDemoAPI.xlsx;
const basename = window.SheetJSDemoAPI.basename;
const extname = window.SheetJSDemoAPI.extname;
const onFileOpened = window.SheetJSDemoAPI.onFileOpened;
// ---------------------------------------------------------------------------
// Supported file extensions
// ---------------------------------------------------------------------------
const EXTENSIONS =
"xls|xlsx|xlsm|xlsb|xml|csv|txt|dif|sylk|slk|prn|ods|fods|htm|html|numbers".split(
"|"
);
// ---------------------------------------------------------------------------
// DOM references
// ---------------------------------------------------------------------------
const dropContainer = document.getElementById("drop-container");
const fileStatus = document.getElementById("fileStatus");
const exportBtn = document.getElementById("exportBtn");
const spinnerOverlay = document.getElementById("spinner-overlay");
const htmlout = document.getElementById("htmlout");
const onError = document.getElementById("onError");
// ---------------------------------------------------------------------------
// State & helpers
// ---------------------------------------------------------------------------
let currentWorkbook = null; // SheetJS workbook in memory
const isSpreadsheet = (ext) => EXTENSIONS.includes(ext.toLowerCase());
const nextPaint = () => new Promise(requestAnimationFrame);
// ---------------------------------------------------------------------------
// Open external links in default browser (security)
// ---------------------------------------------------------------------------
document.addEventListener("click", (e) => {
if (e.target.tagName === "A" && e.target.href.startsWith("http")) {
e.preventDefault();
window.SheetJSDemoAPI.openExternal(e.target.href);
}
});
// ---------------------------------------------------------------------------
// Export logic uses cached workbook (no DOM traversal)
// ---------------------------------------------------------------------------
async function exportWorkbookAsFile() {
if (!currentWorkbook) return displayError("No workbook loaded!");
// -- 1. use electron save as dialog to get file path
const { filePath, canceled } = await window.SheetJSDemoAPI.saveFile([
{
name: "Spreadsheets",
extensions: EXTENSIONS,
},
]);
if (canceled || !filePath) return;
// -- 2. write workbook to file
try {
XLSX.writeFile(currentWorkbook, filePath);
window.SheetJSDemoAPI.message(`Exported to ${filePath}`);
} catch (err) {
displayError(`Failed to export: ${err.message}`);
}
}
exportBtn.addEventListener("click", exportWorkbookAsFile);
// ---------------------------------------------------------------------------
// Render workbook --> HTML tables
// ---------------------------------------------------------------------------
function renderWorkbookToTables(wb) {
// -- 1. map through each sheet
const html = wb.SheetNames.map((name) => {
const sheet = wb.Sheets[name];
// -- 2. convert sheet to HTML
const table = XLSX.utils.sheet_to_html(sheet, { id: `${name}-tbl` });
return `<details class="sheetjs-sheet-container">
<summary class="sheetjs-sheet-name">${name}</summary>
<div class="sheetjs-tab-content">${table}</div>
</details>`;
}).join("");
// CAUTION!: in production environments please sanitize the HTML output to prevent XSS attacks from maliciously crafted spreadsheets.
htmlout.innerHTML = html; // single write → single reflow of the DOM
}
// ---------------------------------------------------------------------------
// Generic UI helpers
// ---------------------------------------------------------------------------
const displayError = (msg) =>
onError
? ((onError.textContent = msg), (onError.hidden = false))
: console.error(msg);
const hideDropUI = () =>
dropContainer && (dropContainer.style.display = "none");
const showDropUI = () =>
dropContainer && (dropContainer.style.display = "block");
const hideExportBtn = () => (exportBtn.disabled = true);
const showExportBtn = () => (exportBtn.disabled = false);
const showSpinner = () => (spinnerOverlay.style.display = "flex");
const hideSpinner = () => (spinnerOverlay.style.display = "none");
const hideOutputUI = () => (htmlout.innerHTML = "");
const hideLoadedFileUI = () => (fileStatus.innerHTML = "");
const getLoadedFileUI = (fileName) => `<div class="file-loaded">
<span class="file-name text-muted text-small">${fileName}</span>
<button type="button" class="unload-btn">Unload</button>
</div>`;
function showLoadedFileUI(fileName) {
fileStatus.innerHTML = getLoadedFileUI(fileName);
hideDropUI();
showExportBtn();
}
// ---------------------------------------------------------------------------
// Event delegation for unload button avoids perrender listener leaks
// ---------------------------------------------------------------------------
fileStatus.addEventListener("click", (e) => {
if (e.target.classList.contains("unload-btn")) {
hideLoadedFileUI();
hideExportBtn();
showDropUI();
hideOutputUI();
currentWorkbook = null;
}
});
// ---------------------------------------------------------------------------
// Fileopen dialog handler
// ---------------------------------------------------------------------------
async function handleReadBtn() {
// -- 1. show file open dialog to get the file path
const { filePaths, canceled } = await window.SheetJSDemoAPI.openFile([
{
name: "Spreadsheets",
extensions: EXTENSIONS,
},
]);
if (canceled || !filePaths.length) return;
if (filePaths.length !== 1)
return displayError("Please choose a single file.");
showSpinner();
await nextPaint(); // ensure spinner paints
try {
const filePath = filePaths[0];
// -- 2. read the first selected file
currentWorkbook = XLSX.readFile(filePath);
renderWorkbookToTables(currentWorkbook);
showLoadedFileUI(basename(filePath));
} finally {
hideSpinner();
hideDropUI();
onError && (onError.hidden = true);
}
}
// ---------------------------------------------------------------------------
// Draganddrop + file input
// ---------------------------------------------------------------------------
function addListener(id, evt, fn) {
const el = document.getElementById(id);
if (el) el.addEventListener(evt, fn);
}
function attachFileListeners() {
// file input element
addListener("readIn", "change", (e) => {
showSpinner();
nextPaint().then(() => readFile(e.target.files));
});
addListener("readBtn", "click", handleReadBtn);
// draganddrop (applied to whole window for simplicity)
const onDrag = (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = "copy";
};
["dragenter", "dragover"].forEach((t) =>
document.body.addEventListener(t, onDrag, { passive: false })
);
document.body.addEventListener(
"drop",
(e) => {
e.preventDefault();
readFile(e.dataTransfer.files).catch((err) => displayError(err.message));
},
{ passive: false }
);
}
// ---------------------------------------------------------------------------
// Read File from input or DnD
// ---------------------------------------------------------------------------
async function readFile(files) {
// -- 1. if no files, return
if (!files || !files.length) return;
// -- 2. get the first file
const file = files[0];
// -- 3. if not a spreadsheet, return error
const ext = extname(file.name).slice(1);
if (!isSpreadsheet(ext)) return displayError(`Unsupported file type .${ext}`);
showSpinner();
try {
// -- 4. read the file
const data = await file.arrayBuffer();
currentWorkbook = XLSX.read(data);
// -- 5. render the workbook to tables
renderWorkbookToTables(currentWorkbook);
// -- 6. show the loaded file UI
showLoadedFileUI(file.name);
} finally {
hideSpinner();
// reset error UI state
onError && (onError.hidden = true);
}
}
// ---------------------------------------------------------------------------
// Init
// ---------------------------------------------------------------------------
attachFileListeners();
// the file-opened event is sent from the main process when a file is opened using "open with"
onFileOpened(async (_e, filePath) => {
showSpinner();
await nextPaint(); // ensure spinner paints
currentWorkbook = XLSX.readFile(filePath);
renderWorkbookToTables(currentWorkbook);
showLoadedFileUI(path.basename(filePath));
hideSpinner();
hideDropUI();
showExportBtn();
});