From f2e6365604658495f87e27503943b6774497288b Mon Sep 17 00:00:00 2001 From: SheetJS Date: Wed, 3 Aug 2022 23:00:20 -0400 Subject: [PATCH] electron --- .../04-getting-started/03-demos/09-bundler.md | 4 +- .../04-getting-started/03-demos/16-desktop.md | 220 ++++++++++++++++++ .../docs/04-getting-started/03-demos/index.md | 3 +- docz/docs/06-solutions/01-input.md | 2 +- docz/docs/06-solutions/05-output.md | 2 +- docz/docs/index.md | 4 +- docz/package.json | 18 +- docz/static/electron/index.html | 36 +++ docz/static/electron/index.js | 73 ++++++ docz/static/electron/main.js | 29 +++ docz/static/electron/package.json | 51 ++++ 11 files changed, 425 insertions(+), 17 deletions(-) create mode 100644 docz/static/electron/index.html create mode 100644 docz/static/electron/index.js create mode 100644 docz/static/electron/main.js create mode 100644 docz/static/electron/package.json diff --git a/docz/docs/04-getting-started/03-demos/09-bundler.md b/docz/docs/04-getting-started/03-demos/09-bundler.md index 725168c..db1a7e0 100644 --- a/docz/docs/04-getting-started/03-demos/09-bundler.md +++ b/docz/docs/04-getting-started/03-demos/09-bundler.md @@ -433,7 +433,7 @@ click the "Click to Export!" button to generate a file. ## RequireJS -[Standalone scripts](../../installation/standalone) comply with AND `define` +[Standalone scripts](../../installation/standalone) comply with AMD `define` semantics, enabling use in RequireJS out of the box. To enable use of the alias `xlsx`, the RequireJS config should set an alias in @@ -450,7 +450,7 @@ require.config({ }); // highlight-next-line require(["xlsx"], function(XLSX) { - /* use XLSX here */ + /* use XLSX here */ console.log(XLSX.version); }); ``` diff --git a/docz/docs/04-getting-started/03-demos/16-desktop.md b/docz/docs/04-getting-started/03-demos/16-desktop.md index dbe31b0..07cafdf 100644 --- a/docz/docs/04-getting-started/03-demos/16-desktop.md +++ b/docz/docs/04-getting-started/03-demos/16-desktop.md @@ -126,3 +126,223 @@ input.addEventListener('change',function(e){ input.click(); ``` + +## Electron + +The [NodeJS Module](../../installation/nodejs) can be imported from the main or +the renderer thread. + +Electron presents a `fs` module. The `require('xlsx')` call loads the CommonJS +module, so `XLSX.readFile` and `XLSX.writeFile` work in the renderer thread. + +This demo was tested against Electron 19.0.5 on an Intel Mac (`darwin-x64`). + +
Complete Example (click to show) + +This demo includes a drag-and-drop box as well as a file input box, mirroring +the [SheetJS Data Preview Live Demo](http://oss.sheetjs.com/sheetjs/) + +The core data in this demo is an editable HTML table. The readers build up the +table using `sheet_to_html` (with `editable:true` option) and the writers scrape +the table using `table_to_book`. + +The demo project is wired for `electron-forge` to build the standalone binary. + +1) Download the demo files: + +- [`package.json`](pathname:///electron/package.json) : project structure +- [`main.js`](pathname:///electron/main.js) : entrypoint +- [`index.html`](pathname:///electron/index.html) : window page +- [`index.js`](pathname:///electron/index.js) : script loaded in render context + +:::caution + +Right-click each link and select "Save Link As...". Left-clicking a link will +try to load the page in your browser. The goal is to save the file contents. + +::: + +2) Run `npm install` to install dependencies. + +3) To verify the app works, run in the test environment: + +```bash +npx -y electron . +``` + +The app will show and you should be able to verify reading and writing by using +the relevant buttons to open files and clicking the export button. + +4) To build a standalone app, run the builder: + +```bash +npm run make +``` + +This will generate the standalone app in the `out\sheetjs-electron-...` folder. +For a recent Intel Mac, the path will be `out/sheetjs-electron-darwin-x64/` + +
+ +### Writing Files + +[`XLSX.writeFile`](../../api/write-options) writes workbooks to the filesystem. +`showSaveDialog` shows a Save As dialog and returns the selected file name: + +```js +/* from the renderer thread */ +const electron = require('@electron/remote'); + +/* this function will show the save dialog and try to write the workbook */ +async function exportFile(workbook) { + /* show Save As dialog */ + const result = await electron.dialog.showSaveDialog({ + title: 'Save file as', + filters: [{ + name: "Spreadsheets", + extensions: ["xlsx", "xls", "xlsb", /* ... other formats ... */] + }] + }); + /* write file */ + // highlight-next-line + XLSX.writeFile(workbook, result.filePath); +} +``` + +:::note + +In older versions of Electron, `showSaveDialog` returned the path directly: + +```js +var dialog = require('electron').remote.dialog; + +function exportFile(workbook) { + var result = dialog.showSaveDialog(); + XLSX.writeFile(workbook, result); +} +``` + +::: + +### Reading Files + +Electron offers 3 different ways to read files, two of which use Web APIs. + +**File Input Element** + +File input elements automatically map to standard Web APIs. + +For example, assuming a file input element on the page: + +```html + +``` + +The event handler would process the event as if it were a web event: + +```js +async function handleFile(e) { + const file = e.target.files[0]; + const data = await file.arrayBuffer(); + /* data is an ArrayBuffer */ + const workbook = XLSX.read(data); + + /* DO SOMETHING WITH workbook HERE */ +} +document.getElementById("xlf").addEventListener("change", handleFile, false); +``` + +**Drag and Drop** + +The [drag and drop snippet](../../solutions/input#example-user-submissions) +applies to DIV elements on the page. + +For example, assuming a DIV on the page: + +```html +
Drop a spreadsheet file here to see sheet data
+``` + +The event handler would process the event as if it were a web event: + +```js +async function handleDrop(e) { + e.stopPropagation(); + e.preventDefault(); + + const file = e.dataTransfer.files[0]; + const data = await file.arrayBuffer(); + /* data is an ArrayBuffer */ + const workbook = XLSX.read(data); + + /* DO SOMETHING WITH workbook HERE */ +} +document.getElementById("drop").addEventListener("drop", handleDrop, false); +``` + +**Electron API** + +[`XLSX.readFile`](../../api/parse-options) reads workbooks from the filesystem. +`showOpenDialog` shows a Save As dialog and returns the selected file name. +Unlike the Web APIs, the `showOpenDialog` flow can be initiated by app code: + +```js +/* from the renderer thread */ +const electron = require('@electron/remote'); + +/* this function will show the open dialog and try to parse the workbook */ +async function importFile() { + /* show Save As dialog */ + const result = await electron.dialog.showOpenDialog({ + title: 'Select a file', + filters: [{ + name: "Spreadsheets", + extensions: ["xlsx", "xls", "xlsb", /* ... other formats ... */] + }] + }); + /* result.filePaths is an array of selected files */ + if(result.filePaths.length == 0) throw new Error("No file was selected!"); + // highlight-next-line + return XLSX.readFile(result.filePaths[0]); +} +``` + +:::note + +In older versions of Electron, `showOpenDialog` returned the path directly: + +```js +var dialog = require('electron').remote.dialog; + +function importFile(workbook) { + var result = dialog.showOpenDialog({ properties: ['openFile'] }); + return XLSX.readFile(result[0]); +} +``` + +::: + +### Electron Breaking Changes + +The first version of this demo used Electron 1.7.5. The current demo includes +the required changes for Electron 19.0.5. + +There are no Electron-specific workarounds in the library, but Electron broke +backwards compatibility multiple times. A summary of changes is noted below. + +:::caution + +Electron 6.x changed the `dialog` API. Methods like `showSaveDialog` originally +returned an array of strings, but now returns a `Promise`. This change was not +documented. [Electron issue](https://github.com/electron/electron/issues/24438) + +Electron 9.0.0 and later require the preference `nodeIntegration: true` in order +to `require('xlsx')` in the renderer process. + +Electron 12.0.0 and later also require `worldSafeExecuteJavascript: true` and +`contextIsolation: true`. + +Electron 14+ must use `@electron/remote` instead of `remote`. An `initialize` +call is required to enable DevTools in the window. + +::: diff --git a/docz/docs/04-getting-started/03-demos/index.md b/docz/docs/04-getting-started/03-demos/index.md index 855bbd5..2f4cff6 100644 --- a/docz/docs/04-getting-started/03-demos/index.md +++ b/docz/docs/04-getting-started/03-demos/index.md @@ -37,7 +37,7 @@ The demo projects include small runnable examples and short explainers. - [`Command-Line Tools`](./cli) - [`NodeJS Server-Side Processing`](https://github.com/SheetJS/SheetJS/tree/master/demos/server/) -- [`Electron`](https://github.com/SheetJS/SheetJS/tree/master/demos/electron/) +- [`Electron`](./desktop#electron) - [`NW.js`](./desktop#nwjs) - [`Chrome / Chromium Extension`](./chromium) - [`Google Sheets API`](./gsheet) @@ -63,7 +63,6 @@ The demo projects include small runnable examples and short explainers. - [`snowpack`](./bundler#snowpack) - [`swc`](./bundler#swc) - [`systemjs`](https://github.com/SheetJS/SheetJS/tree/master/demos/systemjs/) -- [`typescript`](https://github.com/SheetJS/SheetJS/tree/master/demos/typescript/) - [`vite`](./bundler#vite) - [`webpack 2.x`](https://github.com/SheetJS/SheetJS/tree/master/demos/webpack/) - [`wmr`](./bundler#wmr) diff --git a/docz/docs/06-solutions/01-input.md b/docz/docs/06-solutions/01-input.md index a74bd66..a55b581 100644 --- a/docz/docs/06-solutions/01-input.md +++ b/docz/docs/06-solutions/01-input.md @@ -90,7 +90,7 @@ var XLSX = require("xlsx"); var workbook = XLSX.readFile(path); ``` -Electron APIs have changed over time. The [`electron` demo](https://github.com/SheetJS/SheetJS/tree/master/demos/electron/) +Electron APIs have changed over time. The [`electron` demo](../getting-started/demos/desktop#electron) shows a complete example and details the required version-specific settings. diff --git a/docz/docs/06-solutions/05-output.md b/docz/docs/06-solutions/05-output.md index 02ed1b2..f8fecc3 100644 --- a/docz/docs/06-solutions/05-output.md +++ b/docz/docs/06-solutions/05-output.md @@ -235,7 +235,7 @@ var XLSX = require("xlsx"); XLSX.writeFile(workbook, "out.xlsb"); ``` -Electron APIs have changed over time. The [`electron` demo](https://github.com/SheetJS/SheetJS/tree/master/demos/electron/) +Electron APIs have changed over time. The [`electron` demo](../getting-started/demos/desktop#electron) shows a complete example and details the required version-specific settings. diff --git a/docz/docs/index.md b/docz/docs/index.md index 75d76fb..72a407a 100644 --- a/docz/docs/index.md +++ b/docz/docs/index.md @@ -80,8 +80,8 @@ function Table2XLSX(props) { return (<> - - + + diff --git a/docz/package.json b/docz/package.json index 7d3b8da..86fda6d 100644 --- a/docz/package.json +++ b/docz/package.json @@ -14,20 +14,20 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@cmfcmf/docusaurus-search-local": "^0.10.0", - "@docusaurus/core": "2.0.0-beta.20", - "@docusaurus/preset-classic": "2.0.0-beta.20", - "@docusaurus/theme-common": "2.0.0-beta.20", - "@docusaurus/theme-live-codeblock": "2.0.0-beta.20", - "@mdx-js/react": "1.6.22", - "clsx": "1.1.1", - "prism-react-renderer": "1.3.1", + "@cmfcmf/docusaurus-search-local": "0.11.0", + "@docusaurus/core": "2.0.1", + "@docusaurus/preset-classic": "2.0.1", + "@docusaurus/theme-common": "2.0.1", + "@docusaurus/theme-live-codeblock": "2.0.1", + "@mdx-js/react": "2.1.2", + "clsx": "1.2.1", + "prism-react-renderer": "1.3.5", "react": "17.0.2", "react-dom": "17.0.2", "xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.0.0-beta.20" + "@docusaurus/module-type-aliases": "2.0.1" }, "browserslist": { "production": [ diff --git a/docz/static/electron/index.html b/docz/static/electron/index.html new file mode 100644 index 0000000..aab9086 --- /dev/null +++ b/docz/static/electron/index.html @@ -0,0 +1,36 @@ + + + + + + + +SheetJS Electron Demo + + + +
+SheetJS Electron Demo
+
+
+
Drop a spreadsheet file here to see sheet data
+ ... or click here to select a file + +
+

+
+
+ + + diff --git a/docz/static/electron/index.js b/docz/static/electron/index.js new file mode 100644 index 0000000..28696e0 --- /dev/null +++ b/docz/static/electron/index.js @@ -0,0 +1,73 @@ +/* sheetjs (C) 2013-present SheetJS -- https://sheetjs.com */ +const XLSX = require('xlsx'); +const electron = require('@electron/remote'); + +/* list of supported extensions */ +const EXTENSIONS = "xls|xlsx|xlsm|xlsb|xml|csv|txt|dif|sylk|slk|prn|ods|fods|htm|html|numbers".split("|"); + +/* write file with 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); + +/* common handler to create HTML tables on the page */ +function process_wb(wb) { + const HTMLOUT = document.getElementById('htmlout'); + const XPORT = document.getElementById('exportBtn'); + XPORT.disabled = false; + HTMLOUT.innerHTML = ""; + wb.SheetNames.forEach(function(sheetName) { + const htmlstr = XLSX.utils.sheet_to_html(wb.Sheets[sheetName],{editable:true}); + HTMLOUT.innerHTML += htmlstr; + }); +} + +/* read file with Electron API */ +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!"); + process_wb(XLSX.readFile(o.filePaths[0])); +} +document.getElementById('readBtn').addEventListener('click', handleReadBtn, false); + +/* read file with Web APIs */ +async function readFile(files) { + const f = files[0]; + const data = await f.arrayBuffer(); + process_wb(XLSX.read(data)); +} + +// file input element +document.getElementById('readIn').addEventListener('change', (e) => { readFile(e.target.files); }, false); + +// drag and drop +const drop = document.getElementById('drop'); +drop.addEventListener('drop', (e) => { + e.stopPropagation(); e.preventDefault(); + readFile(e.dataTransfer.files); +}, false); + +const handleDrag = (e) => { + e.stopPropagation(); e.preventDefault(); + e.dataTransfer.dropEffect = 'copy'; +}; +drop.addEventListener('dragenter', handleDrag, false); +drop.addEventListener('dragover', handleDrag, false); diff --git a/docz/static/electron/main.js b/docz/static/electron/main.js new file mode 100644 index 0000000..7090a39 --- /dev/null +++ b/docz/static/electron/main.js @@ -0,0 +1,29 @@ +/* sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */ +var electron = require('electron'); +var XLSX = require('xlsx'); +var app = electron.app; +require('@electron/remote/main').initialize(); // required for Electron 14+ + +var win = null; + +function createWindow() { + if (win) return; + win = new electron.BrowserWindow({ + width: 800, height: 600, + webPreferences: { + worldSafeExecuteJavaScript: true, // required for Electron 12+ + contextIsolation: false, // required for Electron 12+ + nodeIntegration: true, + enableRemoteModule: true + } + }); + win.loadURL("file://" + __dirname + "/index.html"); + require('@electron/remote/main').enable(win.webContents); // required for Electron 14+ + win.webContents.openDevTools(); + win.on('closed', function () { win = null; }); +} +if (app.setAboutPanelOptions) app.setAboutPanelOptions({ applicationName: 'sheetjs-electron', applicationVersion: "XLSX " + XLSX.version, copyright: "(C) 2017-present SheetJS LLC" }); +app.on('open-file', function () { console.log(arguments); }); +app.on('ready', createWindow); +app.on('activate', createWindow); +app.on('window-all-closed', function () { if (process.platform !== 'darwin') app.quit(); }); diff --git a/docz/static/electron/package.json b/docz/static/electron/package.json new file mode 100644 index 0000000..b25da2e --- /dev/null +++ b/docz/static/electron/package.json @@ -0,0 +1,51 @@ +{ + "name": "sheetjs-electron", + "author": "sheetjs", + "version": "0.0.0", + "main": "main.js", + "dependencies": { + "@electron/remote": "2.0.8", + "electron-squirrel-startup": "^1.0.0", + "xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz" + }, + "scripts": { + "start": "electron-forge start", + "package": "electron-forge package", + "make": "electron-forge make" + }, + "devDependencies": { + "@electron-forge/cli": "^6.0.0-beta.64", + "@electron-forge/maker-deb": "^6.0.0-beta.64", + "@electron-forge/maker-rpm": "^6.0.0-beta.64", + "@electron-forge/maker-squirrel": "^6.0.0-beta.64", + "@electron-forge/maker-zip": "^6.0.0-beta.64", + "electron": "19.0.5" + }, + "config": { + "forge": { + "packagerConfig": {}, + "makers": [ + { + "name": "@electron-forge/maker-squirrel", + "config": { + "name": "sheetjs_electron" + } + }, + { + "name": "@electron-forge/maker-zip", + "platforms": [ + "darwin" + ] + }, + { + "name": "@electron-forge/maker-deb", + "config": {} + }, + { + "name": "@electron-forge/maker-rpm", + "config": {} + } + ] + } + } +}
SheetJS Table Export
AuthorIDNote
SheetJS7262Hi!
AuthorID你好!
SheetJS7262வணக்கம்!
Powered by SheetJS