From 3616b043486579ff7e10e920c377ab60c83c3d5c Mon Sep 17 00:00:00 2001 From: SheetJS Date: Wed, 4 Jan 2023 22:57:48 -0500 Subject: [PATCH] rn-desktop --- docz/docs/03-demos/03-desktop.md | 1682 ----------------- docz/docs/03-demos/03-desktop/01-electron.md | 229 +++ docz/docs/03-demos/03-desktop/02-nwjs.md | 129 ++ docz/docs/03-demos/03-desktop/03-wails.md | 221 +++ docz/docs/03-demos/03-desktop/04-tauri.md | 213 +++ .../docs/03-demos/03-desktop/05-neutralino.md | 246 +++ .../03-demos/03-desktop/06-reactnative.md | 683 +++++++ docz/docs/03-demos/03-desktop/_category_.json | 4 + docz/docs/03-demos/03-desktop/index.md | 37 + docz/docs/03-demos/04-grid.md | 1 + docz/docs/03-demos/index.md | 15 +- docz/static/reactnative/rnm.png | Bin 0 -> 87760 bytes docz/static/reactnative/rnw.png | Bin 0 -> 26900 bytes 13 files changed, 1773 insertions(+), 1687 deletions(-) delete mode 100644 docz/docs/03-demos/03-desktop.md create mode 100644 docz/docs/03-demos/03-desktop/01-electron.md create mode 100644 docz/docs/03-demos/03-desktop/02-nwjs.md create mode 100644 docz/docs/03-demos/03-desktop/03-wails.md create mode 100644 docz/docs/03-demos/03-desktop/04-tauri.md create mode 100644 docz/docs/03-demos/03-desktop/05-neutralino.md create mode 100644 docz/docs/03-demos/03-desktop/06-reactnative.md create mode 100644 docz/docs/03-demos/03-desktop/_category_.json create mode 100644 docz/docs/03-demos/03-desktop/index.md create mode 100644 docz/static/reactnative/rnm.png create mode 100644 docz/static/reactnative/rnw.png diff --git a/docz/docs/03-demos/03-desktop.md b/docz/docs/03-demos/03-desktop.md deleted file mode 100644 index 930f28fb..00000000 --- a/docz/docs/03-demos/03-desktop.md +++ /dev/null @@ -1,1682 +0,0 @@ ---- -title: Desktop Applications ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -Web technologies like JavaScript and HTML have been adapted to the traditional -app space. Typically these frameworks bundle a JavaScript engine as well as a -windowing framework. SheetJS is compatible with many app frameworks. - -## NW.js - -The [Standalone scripts](/docs/getting-started/installation/standalone) can be -referenced in a `SCRIPT` tag from the entry point HTML page. - -This demo was tested against NW.js 0.66.0. - -
Complete Example (click to show) - -1) Create a `package.json` file that specifies the entry point: - -```json title="package.json" -{ - "name": "sheetjs-nwjs", - "author": "sheetjs", - "version": "0.0.0", - "main": "index.html", - "dependencies": { - "nw": "~0.66.0", - "xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz" - } -} -``` - -2) Download [`index.html`](pathname:///nwjs/index.html) into the same folder. - -:::caution - -Right-click the link and select "Save Link As...". Left-clicking the link will -try to load the page in your browser. The goal is to save the file contents. - -::: - -3) Run `npm install` to install dependencies - -4) To verify the app works, run in the test environment: - -``` -npx nw . -``` - -The app will show and you should be able to verify reading and writing by using -the file input element to select a spreadsheet and clicking the export button. - -5) To build a standalone app, run the builder: - -``` -npx -p nw-builder nwbuild --mode=build . -``` - -This will generate the standalone app in the `build\sheetjs-nwjs\` folder. - -
- -### Reading data - -The standard HTML5 `FileReader` techniques from the browser apply to NW.js! - -NW.js handles the OS minutiae for dragging files into app windows. The -[drag and drop snippet](/docs/solutions/input#example-user-submissions) apply -to DIV elements on the page. - -Similarly, 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); -``` - -### Writing data - -File input elements with the attribute `nwsaveas` show UI for saving a file. The -standard trick is to generate a hidden file input DOM element and "click" it. -Since NW.js does not present a `writeFileSync` in the `fs` package, a manual -step is required: - -```js -/* pre-build the hidden nwsaveas input element */ -var input = document.createElement('input'); -input.style.display = 'none'; -input.setAttribute('nwsaveas', 'SheetJSNWDemo.xlsx'); -input.setAttribute('type', 'file'); -document.body.appendChild(input); - -/* show a message if the save is canceled */ -input.addEventListener('cancel',function(){ alert("Save was canceled!"); }); - -/* write to a file on the 'change' event */ -input.addEventListener('change',function(e){ - /* the `value` is the path that the program will write */ - var filename = this.value; - - /* use XLSX.write with type "buffer" to generate a buffer" */ - /* highlight-next-line */ - var wbout = XLSX.write(workbook, {type:'buffer', bookType:"xlsx"}); - /* highlight-next-line */ - fs.writeFile(filename, wbout, function(err) { - if(!err) return alert("Saved to " + filename); - alert("Error: " + (err.message || err)); - }); -}); - -input.click(); -``` - -## Electron - -The [NodeJS Module](/docs/getting-started/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 on 2022 November 07 with Electron 21.2.2 on `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](https://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) : main process script -- [`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`](/docs/api/write-options) writes workbooks to the file system. -`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](/docs/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`](/docs/api/parse-options) reads workbooks from the file system. -`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.2.2. - -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 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 Developer Tools in the window. - -::: - -## Tauri - -The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported -from JavaScript code. - -This demo was tested against Tauri 1.0.5 on 2022 August 13. - -:::note - -Tauri currently does not provide the equivalent of NodeJS `fs` module. The raw -`@tauri-apps/api` methods used in the examples are not expected to change. - -::: - -`http` and `dialog` must be explicitly allowed in `tauri.conf.json`: - -```json title="tauri.conf.json" - "allowlist": { - "all": true, - "http": { - "all": true, - "request": true, - "scope": ["https://**"] - }, - "dialog": { - "all": true - } -``` - -The "Complete Example" creates an app that looks like the screenshot: - -![SheetJS Tauri MacOS screenshot](pathname:///tauri/macos.png) - -
Complete Example (click to show) - -0) [Read Tauri "Getting Started" guide and install dependencies.](https://tauri.app/v1/guides/getting-started/prerequisites) - -1) Create a new Tauri app: - -```bash -npm create tauri-app -``` - -When prompted: - -- App Name: `SheetJSTauri` -- Window Title: `SheetJS + Tauri` -- UI recipe: `create-vite` -- Add "@tauri-apps/api": `Y` -- ViteJS template: `vue-ts` - -2) Enter the directory: - -```bash -cd SheetJSTauri -``` - -Open `package.json` with a text editor and add the highlighted lines: - -```json title="package.json" -{ - "name": "SheetJSTauri", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vue-tsc --noEmit && vite build", - "preview": "vite preview", - "tauri": "tauri" - }, - "dependencies": { -// highlight-next-line - "@tauri-apps/api": "^1.0.2", - "vue": "^3.2.37", -// highlight-next-line - "xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz" - }, - "devDependencies": { -// highlight-next-line - "@tauri-apps/cli": "^1.0.5", - "@vitejs/plugin-vue": "^3.0.3", - "typescript": "^4.6.4", - "vite": "^3.0.7", - "vue-tsc": "^0.39.5" - } -} -``` - -3) Install dependencies: - -```bash -npm install --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz -``` - -4) Enable operations by adding the highlighted lines to `tauri.conf.json`: - -```json title="src-tauri/tauri.conf.json" - "tauri": { - "allowlist": { -// highlight-start - "http": { - "all": true, - "request": true, - "scope": ["https://**"] - }, - "dialog": { - "all": true - }, -// highlight-end - "all": true - } -``` - -In the same file, look for the `"identifier"` key and replace the value with `com.sheetjs.tauri`: - -```json title="src-tauri/tauri.conf.json" - "icons/icon.ico" - ], - // highlight-next-line - "identifier": "com.sheetjs.tauri", - "longDescription": "", -``` - - -5) Download [`App.vue`](pathname:///tauri/App.vue) and replace `src/App.vue` - with the downloaded script. - -6) Build the app with - -```bash -npm run tauri build -``` - -At the end, it will print the path to the generated program. Run the program! - -
- -### Reading Files - -There are two steps to reading files: obtaining a path and reading binary data: - -```js -import { read } from 'xlsx'; -import { open } from '@tauri-apps/api/dialog'; -import { readBinaryFile } from '@tauri-apps/api/fs'; - -const filters = [ - {name: "Excel Binary Workbook", extensions: ["xlsb"]}, - {name: "Excel Workbook", extensions: ["xlsx"]}, - {name: "Excel 97-2004 Workbook", extensions: ["xls"]}, - // ... other desired formats ... -]; - -async function openFile() { - /* show open file dialog */ - const selected = await open({ - title: "Open Spreadsheet", - multiple: false, - directory: false, - filters - }); - - /* read data into a Uint8Array */ - const d = await readBinaryFile(selected); - - /* parse with SheetJS */ - const wb = read(d); - return wb; -} -``` - -### Writing Files - -There are two steps to writing files: obtaining a path and writing binary data: - -```js -import { write } from 'xlsx'; -import { save } from '@tauri-apps/api/dialog'; -import { writeBinaryFile } from '@tauri-apps/api/fs'; - -const filters = [ - {name: "Excel Binary Workbook", extensions: ["xlsb"]}, - {name: "Excel Workbook", extensions: ["xlsx"]}, - {name: "Excel 97-2004 Workbook", extensions: ["xls"]}, - // ... other desired formats ... -]; - -async function saveFile(wb) { - /* show save file dialog */ - const selected = await save({ - title: "Save to Spreadsheet", - filters - }); - - /* Generate workbook */ - const bookType = selected.slice(selected.lastIndexOf(".") + 1); - const d = write(wb, {type: "buffer", bookType}); - - /* save data to file */ - await writeBinaryFile(selected, d); -} -``` - -## Wails - -The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported -from JavaScript code. - -This demo was tested against Wails `v2.0.0-beta.44.2` on 2022 August 31 using -the Svelte TypeScript starter. - -:::caution - -Wails currently does not provide the equivalent of NodeJS `fs` module. - -The HTML File Input Element does not show a file picker. This is a known bug. - -All raw file operations must be performed in Go code. - -::: - - -The "Complete Example" creates an app that looks like the screenshot: - -![SheetJS Wails MacOS screenshot](pathname:///wails/macos.png) - -
Complete Example (click to show) - -0) [Read Wails "Getting Started" guide and install dependencies.](https://wails.io/docs/gettingstarted/installation) - -1) Create a new Wails app: - -```bash -wails init -n sheetjs-wails -t svelte-ts -``` - -2) Enter the directory: - -```bash -cd sheetjs-wails -``` - -3) Install front-end dependencies: - -```bash -cd frontend -curl -L -o src/assets/logo.png https://sheetjs.com/sketch1024.png -npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz -cd .. -``` - -4) Download source files: - -- Download [`app.go`](pathname:///wails/app.go) and replace `app.go` -- Download [`App.svelte`](pathname:///wails/App.svelte) and replace - `frontend/src/App.svelte` - -5) Build the app with - -```bash -wails build -``` - -At the end, it will print the path to the generated program. Run the program! - -
- -All operations must be run from Go code. This example passes Base64 strings. - -### Reading Files - -The file picker and reading operations can be combined in one Go function. - -#### Go - -```go -import ( - "context" -// highlight-start - "encoding/base64" - "io/ioutil" - "github.com/wailsapp/wails/v2/pkg/runtime" -// highlight-end -) - -type App struct { - ctx context.Context -} - -// ReadFile shows an open file dialog and returns the data as Base64 string -func (a *App) ReadFile() string { - // highlight-next-line - selection, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ - Title: "Select File", - Filters: []runtime.FileFilter{ - { DisplayName: "Excel Workbooks (*.xlsx)", Pattern: "*.xlsx", }, - // ... more filters for more file types - }, - }) - if err != nil { return "" } // The demo app shows an error message - // highlight-next-line - data, err := ioutil.ReadFile(selection) - if err != nil { return "" } // The demo app shows an error message - // highlight-next-line - return base64.StdEncoding.EncodeToString(data) -} -``` - -#### JS - -Wails will automatically create `window.go.main.App.ReadFile` for use in JS: - -```js title="frontend/src/App.svelte" -import { read, utils } from 'xlsx'; - -async function importFile(evt) { -// highlight-start - const b64 = window['go']['main']['App']['ReadFile'](); - const wb = read(b64, { type: "base64" }); -// highlight-end - const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet - html = utils.sheet_to_html(ws); // generate HTML and update state -} -``` - -### Writing Files - -There is a multi-part dance since the library needs the file extension. - -1) Show the save file picker in Go, pass back to JS - -2) Generate the file data in JS, pass the data back to Go - -3) Write to file in Go - -##### Go - -Two Go functions will be exposed. - -- `SaveFile` will show the file picker and return the path: - -```go -import ( - "context" -// highlight-start - "github.com/wailsapp/wails/v2/pkg/runtime" -// highlight-end -) - -type App struct { - ctx context.Context -} - -func (a *App) SaveFile() string { -// highlight-next-line - selection, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{ - Title: "Select File", - DefaultFilename: "SheetJSWails.xlsx", - Filters: []runtime.FileFilter{ - { DisplayName: "Excel Workbooks (*.xlsx)", Pattern: "*.xlsx", }, - // ... more filters for more file types - }, - }) - if err != nil { return "" } // The demo app shows an error message - return selection -} -``` - -- `WriteFile` performs the file write given a Base64 string and file path: - -```go -import ( - "context" -// highlight-start - "encoding/base64" - "io/ioutil" -// highlight-end -) - -type App struct { - ctx context.Context -} - -func (a *App) WriteFile(b64 string, path string) { - // highlight-start - buf, _ := base64.StdEncoding.DecodeString(b64); - _ = ioutil.WriteFile(path, buf, 0644); - // highlight-end -} -``` - -#### JS - -Wails will automatically create bindings for use in JS: - -```js -import { utils, write } from 'xlsx'; - -async function exportFile(wb) { - /* generate workbook */ - const elt = tbl.getElementsByTagName("TABLE")[0]; - const wb = utils.table_to_book(elt); - - /* show save picker and get path */ - const path = await window['go']['main']['App']['SaveFile'](); - - /* generate base64 string based on the path */ - const b64 = write(wb, { bookType: path.slice(path.lastIndexOf(".")+1), type: "base64" }); - - /* write to file */ - await window['go']['main']['App']['WriteFile'](b64, path); - // The demo shows a success message at this point -} -``` - -## NeutralinoJS - -The [Standalone build](/docs/getting-started/installation/standalone) can be added -to the entry `index.html` - -This demo was tested against "binaries" `4.7.0` and "client" `3.6.0` - -:::note - -NeutralinoJS currently does not provide the equivalent of NodeJS `fs` module. -The raw `Neutralino.filesystem` and `Neutralino.os` methods are used. - -::: - -The `os` and `filesystem` modules must be enabled in `neutralino.conf.json`. -The starter already enables `os` so typically one line must be added: - -```json title="neutralino.config.json" - "nativeAllowList": [ - "app.*", - "os.*", -// highlight-next-line - "filesystem.*", - "debug.log" - ], -``` - -The "Complete Example" creates an app that looks like the screenshot: - -![SheetJS NeutralinoJS MacOS screenshot](pathname:///neu/macos.png) - -:::caution - -At the time of writing, `filters` did not work as expected on MacOS. They have -been omitted in the example and commented in the code snippets - -::: - -
Complete Example (click to show) - -The app core state will be the HTML table. Reading files will add the table to -the window. Writing files will parse the table into a spreadsheet. - -1) Create a new NeutralinoJS app: - -```bash -npx @neutralinojs/neu create sheetjs-neu -cd sheetjs-neu -``` - -2) Download the standalone script and place in `resources/js/main.js`: - -```bash -curl -L -o resources/js/xlsx.full.min.js https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js -``` - -3) Add the highlighted lines to `neutralino.conf.json` in `nativeAllowList`: - -```json title="neutralino.config.json" - "nativeAllowList": [ - "app.*", -// highlight-start - "os.*", - "filesystem.*", -// highlight-end - "debug.log" - ], -``` - -4) Set up skeleton app and print version info: - -- Edit `resources/index.html` and replace the `` with the code below: - -```html title="resources/index.html" - -
-

SheetJS × NeutralinoJS

- - -
-
- - - - - -``` - -- Append the following code to `resources/styles.css` to center the table: - -```css title="resources/styles.css" -#info { - width:100%; - text-align: unset; -} -table { - margin: 0 auto; -} -``` - -- Print the version number in the `showInfo` method of `resources/js/main.js`: - -```js title="resources/js/main.js" - ${NL_APPID} is running on port ${NL_PORT} inside ${NL_OS} -

- server: v${NL_VERSION} . client: v${NL_CVERSION} -// highlight-start -

- SheetJS version ${XLSX.version} -// highlight-end - `; -``` - -5) Run the app: - -```bash -npx @neutralinojs/neu run -``` - -You should see `SheetJS Version ` followed by the library version number. - -6) Add the following code to the bottom of `resources/js/main.js`: - -```js -(async() => { - const ab = await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer(); - const wb = XLSX.read(ab); - const ws = wb.Sheets[wb.SheetNames[0]]; - document.getElementById('info').innerHTML = XLSX.utils.sheet_to_html(ws); -})(); -``` - -Save the source file, close the app and re-run the command from step 5. - -When the app loads, a table should show in the main screen. - -7) Add `importFile` and `exportFile` to the bottom of `resources/js/main.js`: - -```js -async function importData() { - /* show open dialog */ - const [filename] = await Neutralino.os.showOpenDialog('Open a spreadsheet'); - - /* read data */ - const ab = await Neutralino.filesystem.readBinaryFile(filename); - const wb = XLSX.read(ab); - - /* make table */ - const ws = wb.Sheets[wb.SheetNames[0]]; - document.getElementById('info').innerHTML = XLSX.utils.sheet_to_html(ws); -} - -async function exportData() { - /* show save dialog */ - const filename = await Neutralino.os.showSaveDialog('Save to file'); - - /* make workbook */ - const tbl = document.getElementById('info').querySelector("table"); - const wb = XLSX.utils.table_to_book(tbl); - - /* make file */ - const bookType = filename.slice(filename.lastIndexOf(".") + 1); - const data = XLSX.write(wb, { bookType, type: "buffer" }); - await Neutralino.filesystem.writeBinaryFile(filename, data); -} -``` - -Save the source file, close the app and re-run the command from step 5. - -When the app loads, click the "Import File" button and select a spreadsheet to -see the contents. Click "Export File" and enter `SheetJSNeu.xlsx` to write. - -8) Build production apps: - -```bash -npx @neutralinojs/neu run -``` - -Platform-specific programs will be created in the `dist` folder. - -
- -### Reading Files - -There are two steps to reading files: obtaining a path and reading binary data: - -```js -const filters = [ - {name: "Excel Binary Workbook", extensions: ["xlsb"]}, - {name: "Excel Workbook", extensions: ["xlsx"]}, -] - -async function openFile() { - /* show open file dialog */ - const [filename] = await Neutralino.os.showOpenDialog( - 'Open a spreadsheet', - { /* filters, */ multiSelections: false } - ); - - /* read data into an ArrayBuffer */ - const ab = await Neutralino.filesystem.readBinaryFile(filename); - - /* parse with SheetJS */ - const wb = XLSX.read(ab); - return wb; -} -``` - -This method can be called from a button click or other event. - -### Writing Files - -There are two steps to writing files: obtaining a path and writing binary data: - -```js -const filters = [ - {name: "Excel Binary Workbook", extensions: ["xlsb"]}, - {name: "Excel Workbook", extensions: ["xlsx"]}, -] - -async function saveFile(wb) { - /* show save file dialog */ - const filename = await Neutralino.os.showSaveDialog( - 'Save to file', - { /* filters */ } - ); - - /* Generate workbook */ - const bookType = filename.slice(filename.lastIndexOf(".") + 1); - const data = XLSX.write(wb, { bookType, type: "buffer" }); - - /* save data to file */ - await Neutralino.filesystem.writeBinaryFile(filename, data); -} -``` - -## React Native Windows - -The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported -from the main app script. File operations must be written in native code. - -This demo was tested against `v0.69.6` on 2022 September 07 in Windows 10. - -:::warning - -There is no simple standalone executable file at the end of the process. - -[The official documentation describes distribution strategies](https://microsoft.github.io/react-native-windows/docs/native-code#distribution) - -::: - -React Native Windows use [Turbo Modules](https://reactnative.dev/docs/the-new-architecture/pillars-turbomodules) - -
Complete Example (click to show) - -:::note - -React Native Windows supports writing native code in C++ or C#. This demo has -been tested against both application types. - -::: - -0) Follow the ["Getting Started" guide](https://microsoft.github.io/react-native-windows/docs/getting-started) - -1) Create a new project using React Native `0.69`: - -```powershell -npx react-native init SheetJSWin --template react-native@^0.69.0 -cd .\SheetJSWin\ -``` - -Create the Windows part of the application: - - - - -```powershell -npx react-native-windows-init --no-telemetry --overwrite --language=cs -``` - - - - -```powershell -npx react-native-windows-init --no-telemetry --overwrite -``` - - - - -Install library: - -```powershell -npm install --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz -``` - -To ensure that the app works, launch the app: - -```powershell -npx react-native run-windows --no-telemetry -``` - - - - -2) Create the file `windows\SheetJSWin\DocumentPicker.cs` with the following: - -```csharp title="windows\SheetJSWin\DocumentPicker.cs" -using System; -using Microsoft.ReactNative.Managed; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Windows.ApplicationModel.Core; -using Windows.Security.Cryptography; -using Windows.Storage; -using Windows.Storage.Pickers; -using Windows.UI.Core; - -namespace SheetJSWin { - [ReactModule] - class DocumentPicker { - private ReactContext context; - [ReactInitializer] - public void Initialize(ReactContext reactContext) { context = reactContext; } - - [ReactMethod("PickAndRead")] - public async void PickAndRead(IReactPromise result) { - context.Handle.UIDispatcher.Post(async() => { try { - var picker = new FileOpenPicker(); - picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; - picker.FileTypeFilter.Add(".xlsx"); - picker.FileTypeFilter.Add(".xls"); - - var file = await picker.PickSingleFileAsync(); - if(file == null) throw new Exception("File not found"); - - var buf = await FileIO.ReadBufferAsync(file); - result.Resolve(CryptographicBuffer.EncodeToBase64String(buf)); - } catch(Exception e) { result.Reject(new ReactError { Message = e.Message }); }}); - } - } -} -``` - -3) Add the highlighted line to `windows\SheetJSWin\SheetJSWin.csproj`. Look for -the `ItemGroup` that contains `ReactPackageProvider.cs`: - -```xml title="windows\SheetJSWin\SheetJSWin.csproj" - - - - -``` - - - - -4) Create the file `windows\SheetJSWin\DocumentPicker.h` with the following: - -```cpp title="windows\SheetJSWin\DocumentPicker.h" -#pragma once - -#include "pch.h" -#include -#include -#include "JSValue.h" -#include "NativeModules.h" - -using namespace winrt::Microsoft::ReactNative; -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::Storage; -using namespace winrt::Windows::Storage::Pickers; -using namespace winrt::Windows::Security::Cryptography; - -namespace SheetJSWin { - REACT_MODULE(DocumentPicker); - struct DocumentPicker { - REACT_INIT(Initialize); - void Initialize(const ReactContext& reactContext) noexcept { - context = reactContext; - } - - REACT_METHOD(PickAndRead); - void PickAndRead(ReactPromise promise) noexcept { - auto prom = promise; - context.UIDispatcher().Post([prom = std::move(prom)]()->winrt::fire_and_forget { - auto p = prom; - winrt::Windows::Storage::Pickers::FileOpenPicker picker; - picker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary); - picker.FileTypeFilter().Append(L".xlsx"); - picker.FileTypeFilter().Append(L".xls"); - - StorageFile file = co_await picker.PickSingleFileAsync(); - if(file == nullptr) { p.Reject("File not Found"); co_return; } - - auto buf = co_await FileIO::ReadBufferAsync(file); - p.Resolve(CryptographicBuffer::EncodeToBase64String(buf)); - co_return; - }); - } - - private: - ReactContext context{nullptr}; - }; -} -``` - -5) Add the highlighted line to `windows\SheetJSWin\ReactPackageProvider.cpp`: - -```cpp title="windows\SheetJSWin\ReactPackageProvider.cpp" -#include "ReactPackageProvider.h" -// highlight-next-line -#include "DocumentPicker.h" -#include "NativeModules.h" -``` - - - - -Now the native module will be added to the app. - -6) Remove `App.js` and save the following to `App.tsx`: - -```tsx title="App.tsx" -import React, { useState, type Node } from 'react'; -import { SafeAreaView, ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native'; -import { read, utils, version } from 'xlsx'; -import { getEnforcing } from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; -const DocumentPicker = getEnforcing('DocumentPicker'); - -const App: () => Node = () => { - - const [ aoa, setAoA ] = useState(["SheetJS".split(""), "5433795".split("")]); - - return ( - - SheetJS × React Native Windows {version} - { - try { - const b64 = await DocumentPicker.PickAndRead(); - const wb = read(b64); - setAoA(utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { header: 1 } )); - } catch(err) { alert(`Error: ${err.message}`); } - }}>Click here to Open File! - - {aoa.map((row,R) => ( - {row.map((cell,C) => ( - {cell} - ))} - ))} - - - ); -}; - -const styles = StyleSheet.create({ - cell: { flex: 4 }, - row: { flexDirection: 'row', justifyContent: 'space-evenly', padding: 10, backgroundColor: 'white', }, - table: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', }, - outer: { marginTop: 32, paddingHorizontal: 24, }, - title: { fontSize: 24, fontWeight: '600', }, - button: { marginTop: 8, fontSize: 18, fontWeight: '400', }, -}); - -export default App; -``` - -7) Test the app again: - -```powershell -npx react-native run-windows --no-telemetry -``` - -Download , then click on "open file". Use the -file picker to select the `pres.xlsx` file and the app will show the data. - -
- -### Reading Files - -Only the main UI thread can show file pickers. This is similar to Web Worker -DOM access limitations in the Web platform. - -This example defines a `PickAndRead` function that will show the file picker, -read the file contents, and return a Base64 string. - - - - -```csharp -namespace SheetJSWin { - [ReactModule] - class DocumentPicker { - /* The context must be stored when the module is initialized */ - private ReactContext context; - [ReactInitializer] - public void Initialize(ReactContext ctx) { context = ctx; } - - [ReactMethod("PickAndRead")] - public async void PickAndRead(IReactPromise result) { - /* perform file picker action in the UI thread */ - // highlight-next-line - context.Handle.UIDispatcher.Post(async() => { try { - /* create file picker */ - var picker = new FileOpenPicker(); - picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; - picker.FileTypeFilter.Add(".xlsx"); - picker.FileTypeFilter.Add(".xls"); - - /* show file picker */ - // highlight-next-line - var file = await picker.PickSingleFileAsync(); - if(file == null) throw new Exception("File not found"); - - /* read data and return base64 string */ - var buf = await FileIO.ReadBufferAsync(file); - // highlight-next-line - result.Resolve(CryptographicBuffer.EncodeToBase64String(buf)); - } catch(Exception e) { result.Reject(new ReactError { Message = e.Message }); }}); - } - } -} -``` - - - - -```cpp -namespace SheetJSWin -{ - REACT_MODULE(DocumentPicker); - struct DocumentPicker - { - /* The context must be stored when the module is initialized */ - REACT_INIT(Initialize); - void Initialize(const ReactContext& reactContext) noexcept { - context = reactContext; - } - - REACT_METHOD(PickAndRead); - void PickAndRead(ReactPromise promise) noexcept { - auto prom = promise; - /* perform file picker action in the UI thread */ - // highlight-next-line - context.UIDispatcher().Post([prom = std::move(prom)]()->winrt::fire_and_forget { - auto p = prom; // promise -> prom -> p dance avoids promise destruction - - /* create file picker */ - winrt::Windows::Storage::Pickers::FileOpenPicker picker; - picker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary); - picker.FileTypeFilter().Append(L".xlsx"); - picker.FileTypeFilter().Append(L".xls"); - - /* show file picker */ - // highlight-next-line - StorageFile file = co_await picker.PickSingleFileAsync(); - if(file == nullptr) { p.Reject("File not Found"); co_return; } - - /* read data and return base64 string */ - auto buf = co_await FileIO::ReadBufferAsync(file); - // highlight-next-line - p.Resolve(CryptographicBuffer::EncodeToBase64String(buf)); - co_return; - }); - } - - private: - ReactContext context{nullptr}; - }; -} -``` - - - - -This module can be referenced from the Turbo Module Registry: - -```js -import { read } from 'xlsx'; -import { getEnforcing } from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; -const DocumentPicker = getEnforcing('DocumentPicker'); - - -/* ... in some event handler ... */ -async() => { - const b64 = await DocumentPicker.PickAndRead(); - const wb = read(b64); - // DO SOMETHING WITH `wb` HERE -} -``` - -## React Native MacOS - -The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported -from the main app script. File operations must be written in native code. - -This demo was tested against `v0.64.30` on 2022 September 10 in MacOS 12.4 - -:::note - -At the time of writing, the latest supported React Native version was `v0.64.3` - -::: - -
Complete Example (click to show) - -0) Follow the [React Native](https://reactnative.dev/docs/environment-setup) - guide for React Native CLI on MacOS. - -:::caution - -NodeJS `v16` is required. There are OS-specific tools for downgrading: - -- [`nvm-windows`](https://github.com/coreybutler/nvm-windows/releases) Windows -- [`n`](https://github.com/tj/n/) Linux, MacOS, WSL, etc. - -::: - -1) Create a new project using React Native `0.64`: - -```bash -npx react-native init SheetJSmacOS --template react-native@^0.64.0 -cd SheetJSmacOS -``` - -Create the MacOS part of the application: - -```bash -npx react-native-macos-init --no-telemetry -``` - -Install Library: - -``` -npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz -``` - -To ensure that the app works, launch the app: - -```powershell -npx react-native run-macos -``` - -Close the running app from the dock and close the Metro terminal window. - -2) Create the file `macos/SheetJSmacOS-macOS/RCTDocumentPicker.h`: - -```objc title="macos/SheetJSmacOS-macOS/RCTDocumentPicker.h" -#import -@interface RCTDocumentPicker : NSObject -@end -``` - -Create the file `macos/SheetJSmacOS-macOS/RCTDocumentPicker.m`: - -```objc title="macos/SheetJSmacOS-macOS/RCTDocumentPicker.m" -#import -#import - -#import "RCTDocumentPicker.h" - -@implementation RCTDocumentPicker - -RCT_EXPORT_MODULE(); - -RCT_EXPORT_METHOD(PickAndRead:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) -{ - RCTExecuteOnMainQueue(^{ - NSOpenPanel *panel = [NSOpenPanel openPanel]; - [panel setCanChooseDirectories:NO]; - [panel setAllowsMultipleSelection:NO]; - [panel setMessage:@"Select a spreadsheet to read"]; - - [panel beginWithCompletionHandler:^(NSInteger result){ - if (result == NSModalResponseOK) { - NSURL *selected = [[panel URLs] objectAtIndex:0]; - NSFileHandle *hFile = [NSFileHandle fileHandleForReadingFromURL:selected error:nil]; - if(hFile) { - NSData *data = [hFile readDataToEndOfFile]; - resolve([data base64EncodedStringWithOptions:0]); - } else reject(@"read_failure", @"Could not read selected file!", nil); - } else reject(@"select_failure", @"No file selected!", nil); - }]; - }); -} -@end -``` - -3) Edit the project file `macos/SheetJSmacOS.xcodeproj/project.pbxproj`. - -There are four places where lines must be added: - -A) Immediately after `/* Begin PBXBuildFile section */` - -```plist -/* Begin PBXBuildFile section */ -// highlight-next-line - 4717DC6A28CC499A00A9BE56 /* RCTDocumentPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 4717DC6928CC499A00A9BE56 /* RCTDocumentPicker.m */; }; - 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; -``` - -B) Immediately after `/* Begin PBXFileReference section */` - -```plist -/* Begin PBXFileReference section */ -// highlight-start - 4717DC6828CC495400A9BE56 /* RCTDocumentPicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RCTDocumentPicker.h; path = "SheetJSMacOS-macOS/RCTDocumentPicker.h"; sourceTree = ""; }; - 4717DC6928CC499A00A9BE56 /* RCTDocumentPicker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RCTDocumentPicker.m; path = "SheetJSMacOS-macOS/RCTDocumentPicker.m"; sourceTree = ""; }; -// highlight-end - 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; -``` - -C) The goal is to add a reference to the `PBXSourcesBuildPhase` block for the -`macOS` target. To determine this, look in the `PBXNativeTarget section` for -a block with the comment `SheetJSmacOS-macOS`: - -```plist -/* Begin PBXNativeTarget section */ -... - productType = "com.apple.product-type.application"; - }; -// highlight-next-line - 514201482437B4B30078DB4F /* SheetJSmacOS-macOS */ = { - isa = PBXNativeTarget; -... -/* End PBXNativeTarget section */ -``` - -Within the block, look for `buildPhases` and find the hex string for `Sources`: - -```plist - buildPhases = ( - 1A938104A937498D81B3BD3B /* [CP] Check Pods Manifest.lock */, - 381D8A6F24576A6C00465D17 /* Start Packager */, -// highlight-next-line - 514201452437B4B30078DB4F /* Sources */, - 514201462437B4B30078DB4F /* Frameworks */, - 514201472437B4B30078DB4F /* Resources */, - 381D8A6E24576A4E00465D17 /* Bundle React Native code and images */, - 3689826CA944E2EF44FCBC17 /* [CP] Copy Pods Resources */, - ); -``` - -Search for that hex string (`514201452437B4B30078DB4F` in our example) in the -file and it should show up in a `PBXSourcesBuildPhase` section. Within `files`, -add the highlighted line: - -```plist - 514201452437B4B30078DB4F /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( -// highlight-next-line - 4717DC6A28CC499A00A9BE56 /* RCTDocumentPicker.m in Sources */, - 514201502437B4B30078DB4F /* ViewController.m in Sources */, - 514201582437B4B40078DB4F /* main.m in Sources */, - 5142014D2437B4B30078DB4F /* AppDelegate.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -``` - -D) The goal is to add file references to the "main group". Search for -`/* Begin PBXProject section */` and there should be one Project object. -Within the project object, look for `mainGroup`: - -```plist -/* Begin PBXProject section */ - 83CBB9F71A601CBA00E9B192 /* Project object */ = { - isa = PBXProject; -... - Base, - ); -// highlight-next-line - mainGroup = 83CBB9F61A601CBA00E9B192; - productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; -... -/* End PBXProject section */ -``` - -Search for that hex string (`83CBB9F61A601CBA00E9B192` in our example) in the -file and it should show up in a `PBXGroup` section. Within `children`, add the -highlighted lines: - -```plist - 83CBB9F61A601CBA00E9B192 = { - isa = PBXGroup; - children = ( -// highlight-start - 4717DC6828CC495400A9BE56 /* RCTDocumentPicker.h */, - 4717DC6928CC499A00A9BE56 /* RCTDocumentPicker.m */, -// highlight-end - 5142014A2437B4B30078DB4F /* SheetJSmacOS-macOS */, - 13B07FAE1A68108700A75B9A /* SheetJSmacOS-iOS */, -``` - -4) Replace `App.js` with the following: - -```tsx title="App.js" -import React, { useState, type Node } from 'react'; -import { SafeAreaView, ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native'; -import { read, utils, version } from 'xlsx'; -import { getEnforcing } from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; -const DocumentPicker = getEnforcing('DocumentPicker'); - -const App: () => Node = () => { - - const [ aoa, setAoA ] = useState(["SheetJS".split(""), "5433795".split("")]); - - return ( - - SheetJS × React Native MacOS {version} - { - try { - const b64 = await DocumentPicker.PickAndRead(); - const wb = read(b64); - setAoA(utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { header: 1 } )); - } catch(err) { alert(`Error: ${err.message}`); } - }}>Click here to Open File! - - {aoa.map((row,R) => ( - {row.map((cell,C) => ( - {cell} - ))} - ))} - - - ); -}; - -const styles = StyleSheet.create({ - cell: { flex: 4 }, - row: { flexDirection: 'row', justifyContent: 'space-evenly', padding: 10, backgroundColor: 'white', }, - table: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', }, - outer: { marginTop: 32, paddingHorizontal: 24, }, - title: { fontSize: 24, fontWeight: '600', }, - button: { marginTop: 8, fontSize: 18, fontWeight: '400', }, -}); - -export default App; -``` - -5) Test the app: - -```bash -npx react-native run-macos -``` - -Download , then click on "open file". Use the -file picker to select the `pres.xlsx` file and the app will show the data. - -6) Make a release build: - -```bash -xcodebuild -workspace macos/SheetJSmacOS.xcworkspace -scheme SheetJSmacOS-macOS -config Release -``` - -
- -### Reading Files - -Only the main UI thread can show file pickers. This is similar to Web Worker -DOM access limitations in the Web platform. - -This example defines a `PickAndRead` function that will show the file picker, -read the file contents, and return a Base64 string. - -```objc -/* the resolve/reject is projected on the JS side as a Promise */ -RCT_EXPORT_METHOD(PickAndRead:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) -{ - /* perform file picker action in the UI thread */ - // highlight-next-line - RCTExecuteOnMainQueue(^{ - /* create file picker */ - NSOpenPanel *panel = [NSOpenPanel openPanel]; - [panel setCanChooseDirectories:NO]; - [panel setAllowsMultipleSelection:NO]; - [panel setMessage:@"Select a spreadsheet to read"]; - - /* show file picker */ - // highlight-next-line - [panel beginWithCompletionHandler:^(NSInteger result){ - if (result == NSModalResponseOK) { - /* read data and return base64 string */ - NSURL *selected = [[panel URLs] objectAtIndex:0]; - NSFileHandle *hFile = [NSFileHandle fileHandleForReadingFromURL:selected error:nil]; - if(hFile) { - NSData *data = [hFile readDataToEndOfFile]; - // highlight-next-line - resolve([data base64EncodedStringWithOptions:0]); - } else reject(@"read_failure", @"Could not read selected file!", nil); - } else reject(@"select_failure", @"No file selected!", nil); - }]; - }); -} -``` - -This module is referenced in the same way as the React Native Windows example: - -```js -import { read } from 'xlsx'; -import { getEnforcing } from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; -const DocumentPicker = getEnforcing('DocumentPicker'); - - -/* ... in some event handler ... */ -async() => { - const b64 = await DocumentPicker.PickAndRead(); - const wb = read(b64); - // DO SOMETHING WITH `wb` HERE -} -``` \ No newline at end of file diff --git a/docz/docs/03-demos/03-desktop/01-electron.md b/docz/docs/03-demos/03-desktop/01-electron.md new file mode 100644 index 00000000..fd74a510 --- /dev/null +++ b/docz/docs/03-demos/03-desktop/01-electron.md @@ -0,0 +1,229 @@ +--- +title: Electron +pagination_prev: demos/mobile +pagination_next: demos/grid +sidebar_position: 1 +sidebar_custom_props: + summary: Embedded NodeJS + Chromium +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The [NodeJS Module](/docs/getting-started/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 on 2022 November 07 with Electron 21.2.2 on `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](https://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) : main process script +- [`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`](/docs/api/write-options) writes workbooks to the file system. +`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](/docs/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`](/docs/api/parse-options) reads workbooks from the file system. +`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.2.2. + +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 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 Developer Tools in the window. + +::: diff --git a/docz/docs/03-demos/03-desktop/02-nwjs.md b/docz/docs/03-demos/03-desktop/02-nwjs.md new file mode 100644 index 00000000..ff143ae9 --- /dev/null +++ b/docz/docs/03-demos/03-desktop/02-nwjs.md @@ -0,0 +1,129 @@ +--- +title: NW.js +pagination_prev: demos/mobile +pagination_next: demos/grid +sidebar_position: 2 +sidebar_custom_props: + summary: Embedded Chromium + NodeJS +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The [Standalone scripts](/docs/getting-started/installation/standalone) can be +referenced in a `SCRIPT` tag from the entry point HTML page. + +This demo was tested against NW.js 0.66.0. + +
Complete Example (click to show) + +1) Create a `package.json` file that specifies the entry point: + +```json title="package.json" +{ + "name": "sheetjs-nwjs", + "author": "sheetjs", + "version": "0.0.0", + "main": "index.html", + "dependencies": { + "nw": "~0.66.0", + "xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz" + } +} +``` + +2) Download [`index.html`](pathname:///nwjs/index.html) into the same folder. + +:::caution + +Right-click the link and select "Save Link As...". Left-clicking the link will +try to load the page in your browser. The goal is to save the file contents. + +::: + +3) Run `npm install` to install dependencies + +4) To verify the app works, run in the test environment: + +``` +npx nw . +``` + +The app will show and you should be able to verify reading and writing by using +the file input element to select a spreadsheet and clicking the export button. + +5) To build a standalone app, run the builder: + +``` +npx -p nw-builder nwbuild --mode=build . +``` + +This will generate the standalone app in the `build\sheetjs-nwjs\` folder. + +
+ +### Reading data + +The standard HTML5 `FileReader` techniques from the browser apply to NW.js! + +NW.js handles the OS minutiae for dragging files into app windows. The +[drag and drop snippet](/docs/solutions/input#example-user-submissions) apply +to DIV elements on the page. + +Similarly, 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); +``` + +### Writing data + +File input elements with the attribute `nwsaveas` show UI for saving a file. The +standard trick is to generate a hidden file input DOM element and "click" it. +Since NW.js does not present a `writeFileSync` in the `fs` package, a manual +step is required: + +```js +/* pre-build the hidden nwsaveas input element */ +var input = document.createElement('input'); +input.style.display = 'none'; +input.setAttribute('nwsaveas', 'SheetJSNWDemo.xlsx'); +input.setAttribute('type', 'file'); +document.body.appendChild(input); + +/* show a message if the save is canceled */ +input.addEventListener('cancel',function(){ alert("Save was canceled!"); }); + +/* write to a file on the 'change' event */ +input.addEventListener('change',function(e){ + /* the `value` is the path that the program will write */ + var filename = this.value; + + /* use XLSX.write with type "buffer" to generate a buffer" */ + /* highlight-next-line */ + var wbout = XLSX.write(workbook, {type:'buffer', bookType:"xlsx"}); + /* highlight-next-line */ + fs.writeFile(filename, wbout, function(err) { + if(!err) return alert("Saved to " + filename); + alert("Error: " + (err.message || err)); + }); +}); + +input.click(); +``` diff --git a/docz/docs/03-demos/03-desktop/03-wails.md b/docz/docs/03-demos/03-desktop/03-wails.md new file mode 100644 index 00000000..c15dd98d --- /dev/null +++ b/docz/docs/03-demos/03-desktop/03-wails.md @@ -0,0 +1,221 @@ +--- +title: Wails +pagination_prev: demos/mobile +pagination_next: demos/grid +sidebar_position: 3 +sidebar_custom_props: + summary: Webview + Go Backend +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported +from JavaScript code. + +This demo was tested against Wails `v2.0.0-beta.44.2` on 2022 August 31 using +the Svelte TypeScript starter. + +:::caution + +Wails currently does not provide the equivalent of NodeJS `fs` module. + +The HTML File Input Element does not show a file picker. This is a known bug. + +All raw file operations must be performed in Go code. + +::: + + +The "Complete Example" creates an app that looks like the screenshot: + +![SheetJS Wails MacOS screenshot](pathname:///wails/macos.png) + +
Complete Example (click to show) + +0) [Read Wails "Getting Started" guide and install dependencies.](https://wails.io/docs/gettingstarted/installation) + +1) Create a new Wails app: + +```bash +wails init -n sheetjs-wails -t svelte-ts +``` + +2) Enter the directory: + +```bash +cd sheetjs-wails +``` + +3) Install front-end dependencies: + +```bash +cd frontend +curl -L -o src/assets/logo.png https://sheetjs.com/sketch1024.png +npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz +cd .. +``` + +4) Download source files: + +- Download [`app.go`](pathname:///wails/app.go) and replace `app.go` +- Download [`App.svelte`](pathname:///wails/App.svelte) and replace + `frontend/src/App.svelte` + +5) Build the app with + +```bash +wails build +``` + +At the end, it will print the path to the generated program. Run the program! + +
+ +All operations must be run from Go code. This example passes Base64 strings. + +### Reading Files + +The file picker and reading operations can be combined in one Go function. + +#### Go + +```go +import ( + "context" +// highlight-start + "encoding/base64" + "io/ioutil" + "github.com/wailsapp/wails/v2/pkg/runtime" +// highlight-end +) + +type App struct { + ctx context.Context +} + +// ReadFile shows an open file dialog and returns the data as Base64 string +func (a *App) ReadFile() string { + // highlight-next-line + selection, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ + Title: "Select File", + Filters: []runtime.FileFilter{ + { DisplayName: "Excel Workbooks (*.xlsx)", Pattern: "*.xlsx", }, + // ... more filters for more file types + }, + }) + if err != nil { return "" } // The demo app shows an error message + // highlight-next-line + data, err := ioutil.ReadFile(selection) + if err != nil { return "" } // The demo app shows an error message + // highlight-next-line + return base64.StdEncoding.EncodeToString(data) +} +``` + +#### JS + +Wails will automatically create `window.go.main.App.ReadFile` for use in JS: + +```js title="frontend/src/App.svelte" +import { read, utils } from 'xlsx'; + +async function importFile(evt) { +// highlight-start + const b64 = window['go']['main']['App']['ReadFile'](); + const wb = read(b64, { type: "base64" }); +// highlight-end + const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet + html = utils.sheet_to_html(ws); // generate HTML and update state +} +``` + +### Writing Files + +There is a multi-part dance since the library needs the file extension. + +1) Show the save file picker in Go, pass back to JS + +2) Generate the file data in JS, pass the data back to Go + +3) Write to file in Go + +##### Go + +Two Go functions will be exposed. + +- `SaveFile` will show the file picker and return the path: + +```go +import ( + "context" +// highlight-start + "github.com/wailsapp/wails/v2/pkg/runtime" +// highlight-end +) + +type App struct { + ctx context.Context +} + +func (a *App) SaveFile() string { +// highlight-next-line + selection, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{ + Title: "Select File", + DefaultFilename: "SheetJSWails.xlsx", + Filters: []runtime.FileFilter{ + { DisplayName: "Excel Workbooks (*.xlsx)", Pattern: "*.xlsx", }, + // ... more filters for more file types + }, + }) + if err != nil { return "" } // The demo app shows an error message + return selection +} +``` + +- `WriteFile` performs the file write given a Base64 string and file path: + +```go +import ( + "context" +// highlight-start + "encoding/base64" + "io/ioutil" +// highlight-end +) + +type App struct { + ctx context.Context +} + +func (a *App) WriteFile(b64 string, path string) { + // highlight-start + buf, _ := base64.StdEncoding.DecodeString(b64); + _ = ioutil.WriteFile(path, buf, 0644); + // highlight-end +} +``` + +#### JS + +Wails will automatically create bindings for use in JS: + +```js +import { utils, write } from 'xlsx'; + +async function exportFile(wb) { + /* generate workbook */ + const elt = tbl.getElementsByTagName("TABLE")[0]; + const wb = utils.table_to_book(elt); + + /* show save picker and get path */ + const path = await window['go']['main']['App']['SaveFile'](); + + /* generate base64 string based on the path */ + const b64 = write(wb, { bookType: path.slice(path.lastIndexOf(".")+1), type: "base64" }); + + /* write to file */ + await window['go']['main']['App']['WriteFile'](b64, path); + // The demo shows a success message at this point +} +``` diff --git a/docz/docs/03-demos/03-desktop/04-tauri.md b/docz/docs/03-demos/03-desktop/04-tauri.md new file mode 100644 index 00000000..4abbedb6 --- /dev/null +++ b/docz/docs/03-demos/03-desktop/04-tauri.md @@ -0,0 +1,213 @@ +--- +title: Tauri +pagination_prev: demos/mobile +pagination_next: demos/grid +sidebar_position: 4 +sidebar_custom_props: + summary: Webview + Rust Backend +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported +from JavaScript code. + +This demo was tested against Tauri 1.0.5 on 2022 August 13. + +:::note + +Tauri currently does not provide the equivalent of NodeJS `fs` module. The raw +`@tauri-apps/api` methods used in the examples are not expected to change. + +::: + +`http` and `dialog` must be explicitly allowed in `tauri.conf.json`: + +```json title="tauri.conf.json" + "allowlist": { + "all": true, + "http": { + "all": true, + "request": true, + "scope": ["https://**"] + }, + "dialog": { + "all": true + } +``` + +The "Complete Example" creates an app that looks like the screenshot: + +![SheetJS Tauri MacOS screenshot](pathname:///tauri/macos.png) + +
Complete Example (click to show) + +0) [Read Tauri "Getting Started" guide and install dependencies.](https://tauri.app/v1/guides/getting-started/prerequisites) + +1) Create a new Tauri app: + +```bash +npm create tauri-app +``` + +When prompted: + +- App Name: `SheetJSTauri` +- Window Title: `SheetJS + Tauri` +- UI recipe: `create-vite` +- Add "@tauri-apps/api": `Y` +- ViteJS template: `vue-ts` + +2) Enter the directory: + +```bash +cd SheetJSTauri +``` + +Open `package.json` with a text editor and add the highlighted lines: + +```json title="package.json" +{ + "name": "SheetJSTauri", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { +// highlight-next-line + "@tauri-apps/api": "^1.0.2", + "vue": "^3.2.37", +// highlight-next-line + "xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz" + }, + "devDependencies": { +// highlight-next-line + "@tauri-apps/cli": "^1.0.5", + "@vitejs/plugin-vue": "^3.0.3", + "typescript": "^4.6.4", + "vite": "^3.0.7", + "vue-tsc": "^0.39.5" + } +} +``` + +3) Install dependencies: + +```bash +npm install --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz +``` + +4) Enable operations by adding the highlighted lines to `tauri.conf.json`: + +```json title="src-tauri/tauri.conf.json" + "tauri": { + "allowlist": { +// highlight-start + "http": { + "all": true, + "request": true, + "scope": ["https://**"] + }, + "dialog": { + "all": true + }, +// highlight-end + "all": true + } +``` + +In the same file, look for the `"identifier"` key and replace the value with `com.sheetjs.tauri`: + +```json title="src-tauri/tauri.conf.json" + "icons/icon.ico" + ], + // highlight-next-line + "identifier": "com.sheetjs.tauri", + "longDescription": "", +``` + + +5) Download [`App.vue`](pathname:///tauri/App.vue) and replace `src/App.vue` + with the downloaded script. + +6) Build the app with + +```bash +npm run tauri build +``` + +At the end, it will print the path to the generated program. Run the program! + +
+ +### Reading Files + +There are two steps to reading files: obtaining a path and reading binary data: + +```js +import { read } from 'xlsx'; +import { open } from '@tauri-apps/api/dialog'; +import { readBinaryFile } from '@tauri-apps/api/fs'; + +const filters = [ + {name: "Excel Binary Workbook", extensions: ["xlsb"]}, + {name: "Excel Workbook", extensions: ["xlsx"]}, + {name: "Excel 97-2004 Workbook", extensions: ["xls"]}, + // ... other desired formats ... +]; + +async function openFile() { + /* show open file dialog */ + const selected = await open({ + title: "Open Spreadsheet", + multiple: false, + directory: false, + filters + }); + + /* read data into a Uint8Array */ + const d = await readBinaryFile(selected); + + /* parse with SheetJS */ + const wb = read(d); + return wb; +} +``` + +### Writing Files + +There are two steps to writing files: obtaining a path and writing binary data: + +```js +import { write } from 'xlsx'; +import { save } from '@tauri-apps/api/dialog'; +import { writeBinaryFile } from '@tauri-apps/api/fs'; + +const filters = [ + {name: "Excel Binary Workbook", extensions: ["xlsb"]}, + {name: "Excel Workbook", extensions: ["xlsx"]}, + {name: "Excel 97-2004 Workbook", extensions: ["xls"]}, + // ... other desired formats ... +]; + +async function saveFile(wb) { + /* show save file dialog */ + const selected = await save({ + title: "Save to Spreadsheet", + filters + }); + + /* Generate workbook */ + const bookType = selected.slice(selected.lastIndexOf(".") + 1); + const d = write(wb, {type: "buffer", bookType}); + + /* save data to file */ + await writeBinaryFile(selected, d); +} +``` diff --git a/docz/docs/03-demos/03-desktop/05-neutralino.md b/docz/docs/03-demos/03-desktop/05-neutralino.md new file mode 100644 index 00000000..9fc8fd17 --- /dev/null +++ b/docz/docs/03-demos/03-desktop/05-neutralino.md @@ -0,0 +1,246 @@ +--- +title: NeutralinoJS +pagination_prev: demos/mobile +pagination_next: demos/grid +sidebar_position: 5 +sidebar_custom_props: + summary: Webview + Lightweight Extensions +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The [Standalone build](/docs/getting-started/installation/standalone) can be added +to the entry `index.html` + +This demo was tested against "binaries" `4.7.0` and "client" `3.6.0` + +:::note + +NeutralinoJS currently does not provide the equivalent of NodeJS `fs` module. +The raw `Neutralino.filesystem` and `Neutralino.os` methods are used. + +::: + +The `os` and `filesystem` modules must be enabled in `neutralino.conf.json`. +The starter already enables `os` so typically one line must be added: + +```json title="neutralino.config.json" + "nativeAllowList": [ + "app.*", + "os.*", +// highlight-next-line + "filesystem.*", + "debug.log" + ], +``` + +The "Complete Example" creates an app that looks like the screenshot: + +![SheetJS NeutralinoJS MacOS screenshot](pathname:///neu/macos.png) + +:::caution + +At the time of writing, `filters` did not work as expected on MacOS. They have +been omitted in the example and commented in the code snippets + +::: + +
Complete Example (click to show) + +The app core state will be the HTML table. Reading files will add the table to +the window. Writing files will parse the table into a spreadsheet. + +1) Create a new NeutralinoJS app: + +```bash +npx @neutralinojs/neu create sheetjs-neu +cd sheetjs-neu +``` + +2) Download the standalone script and place in `resources/js/main.js`: + +```bash +curl -L -o resources/js/xlsx.full.min.js https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js +``` + +3) Add the highlighted lines to `neutralino.conf.json` in `nativeAllowList`: + +```json title="neutralino.config.json" + "nativeAllowList": [ + "app.*", +// highlight-start + "os.*", + "filesystem.*", +// highlight-end + "debug.log" + ], +``` + +4) Set up skeleton app and print version info: + +- Edit `resources/index.html` and replace the `` with the code below: + +```html title="resources/index.html" + +
+

SheetJS × NeutralinoJS

+ + +
+
+ + + + + +``` + +- Append the following code to `resources/styles.css` to center the table: + +```css title="resources/styles.css" +#info { + width:100%; + text-align: unset; +} +table { + margin: 0 auto; +} +``` + +- Print the version number in the `showInfo` method of `resources/js/main.js`: + +```js title="resources/js/main.js" + ${NL_APPID} is running on port ${NL_PORT} inside ${NL_OS} +

+ server: v${NL_VERSION} . client: v${NL_CVERSION} +// highlight-start +

+ SheetJS version ${XLSX.version} +// highlight-end + `; +``` + +5) Run the app: + +```bash +npx @neutralinojs/neu run +``` + +You should see `SheetJS Version ` followed by the library version number. + +6) Add the following code to the bottom of `resources/js/main.js`: + +```js +(async() => { + const ab = await (await fetch("https://sheetjs.com/pres.numbers")).arrayBuffer(); + const wb = XLSX.read(ab); + const ws = wb.Sheets[wb.SheetNames[0]]; + document.getElementById('info').innerHTML = XLSX.utils.sheet_to_html(ws); +})(); +``` + +Save the source file, close the app and re-run the command from step 5. + +When the app loads, a table should show in the main screen. + +7) Add `importFile` and `exportFile` to the bottom of `resources/js/main.js`: + +```js +async function importData() { + /* show open dialog */ + const [filename] = await Neutralino.os.showOpenDialog('Open a spreadsheet'); + + /* read data */ + const ab = await Neutralino.filesystem.readBinaryFile(filename); + const wb = XLSX.read(ab); + + /* make table */ + const ws = wb.Sheets[wb.SheetNames[0]]; + document.getElementById('info').innerHTML = XLSX.utils.sheet_to_html(ws); +} + +async function exportData() { + /* show save dialog */ + const filename = await Neutralino.os.showSaveDialog('Save to file'); + + /* make workbook */ + const tbl = document.getElementById('info').querySelector("table"); + const wb = XLSX.utils.table_to_book(tbl); + + /* make file */ + const bookType = filename.slice(filename.lastIndexOf(".") + 1); + const data = XLSX.write(wb, { bookType, type: "buffer" }); + await Neutralino.filesystem.writeBinaryFile(filename, data); +} +``` + +Save the source file, close the app and re-run the command from step 5. + +When the app loads, click the "Import File" button and select a spreadsheet to +see the contents. Click "Export File" and enter `SheetJSNeu.xlsx` to write. + +8) Build production apps: + +```bash +npx @neutralinojs/neu run +``` + +Platform-specific programs will be created in the `dist` folder. + +
+ +### Reading Files + +There are two steps to reading files: obtaining a path and reading binary data: + +```js +const filters = [ + {name: "Excel Binary Workbook", extensions: ["xlsb"]}, + {name: "Excel Workbook", extensions: ["xlsx"]}, +] + +async function openFile() { + /* show open file dialog */ + const [filename] = await Neutralino.os.showOpenDialog( + 'Open a spreadsheet', + { /* filters, */ multiSelections: false } + ); + + /* read data into an ArrayBuffer */ + const ab = await Neutralino.filesystem.readBinaryFile(filename); + + /* parse with SheetJS */ + const wb = XLSX.read(ab); + return wb; +} +``` + +This method can be called from a button click or other event. + +### Writing Files + +There are two steps to writing files: obtaining a path and writing binary data: + +```js +const filters = [ + {name: "Excel Binary Workbook", extensions: ["xlsb"]}, + {name: "Excel Workbook", extensions: ["xlsx"]}, +] + +async function saveFile(wb) { + /* show save file dialog */ + const filename = await Neutralino.os.showSaveDialog( + 'Save to file', + { /* filters */ } + ); + + /* Generate workbook */ + const bookType = filename.slice(filename.lastIndexOf(".") + 1); + const data = XLSX.write(wb, { bookType, type: "buffer" }); + + /* save data to file */ + await Neutralino.filesystem.writeBinaryFile(filename, data); +} +``` + diff --git a/docz/docs/03-demos/03-desktop/06-reactnative.md b/docz/docs/03-demos/03-desktop/06-reactnative.md new file mode 100644 index 00000000..10c107fc --- /dev/null +++ b/docz/docs/03-demos/03-desktop/06-reactnative.md @@ -0,0 +1,683 @@ +--- +title: React Native for Desktop +pagination_prev: demos/mobile +pagination_next: demos/grid +sidebar_position: 6 +sidebar_custom_props: + summary: Native Components with React +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +:::note + +This section covers React Native for desktop applications. For iOS and Android +applications, [check the mobile demo](/docs/demos/mobile) + +::: + +React Native for Windows + macOS is a backend for React Native that supports +native apps. The Windows backend builds apps for use on Windows 10 / 11, Xbox, +and other supported platforms. The macOS backend supports macOS 10.14 SDK + +The [NodeJS Module](/docs/getting-started/installation/nodejs) can be imported +from the main app script. File operations must be written in native code. + +The "Complete Example" creates an app that looks like the screenshots below: + + + + +
WindowsmacOS
+ +![Windows screenshot](pathname:///reactnative/rnw.png) + + + +![macOS screenshot](pathname:///reactnative/rnm.png) + +
+ +## Native Modules + +:::caution + +As with the mobile versions of React Native, file operations are not provided +by the base SDK. The examples include native code for both Windows and macOS. + +The Windows demo assumes some familiarity with C++ / C# and the macOS demo +assumes some familiarity with Objective-C. + +::: + +React Native for Windows + macOS use [Turbo Modules](https://reactnative.dev/docs/the-new-architecture/pillars-turbomodules) +for effortless integration with native libraries and code. + +The demos define a native module named `DocumentPicker`. + +### Reading Files + +The native modules in the demos define a `PickAndRead` function that will show +the file picker, read the file contents, and return a Base64 string. + +Only the main UI thread can show file pickers. This is similar to Web Worker +DOM access limitations in the Web platform. + +_Integration_ + +This module can be referenced from the Turbo Module Registry: + +```js +import { read } from 'xlsx'; +import { getEnforcing } from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; +const DocumentPicker = getEnforcing('DocumentPicker'); + + +/* ... in some event handler ... */ +async() => { + const b64 = await DocumentPicker.PickAndRead(); + const wb = read(b64); + // DO SOMETHING WITH `wb` HERE +} +``` + +_Native Module_ + + + + +React Native Windows supports C++ and C# projects. + + + + +```csharp +[ReactMethod("PickAndRead")] +public async void PickAndRead(IReactPromise result) { + /* perform file picker action in the UI thread */ + // highlight-next-line + context.Handle.UIDispatcher.Post(async() => { try { + /* create file picker */ + var picker = new FileOpenPicker(); + picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; + picker.FileTypeFilter.Add(".xlsx"); + picker.FileTypeFilter.Add(".xls"); + + /* show file picker */ + // highlight-next-line + var file = await picker.PickSingleFileAsync(); + if(file == null) throw new Exception("File not found"); + + /* read data and return base64 string */ + var buf = await FileIO.ReadBufferAsync(file); + // highlight-next-line + result.Resolve(CryptographicBuffer.EncodeToBase64String(buf)); + } catch(Exception e) { result.Reject(new ReactError { Message = e.Message }); }}); +} +``` + + + + +```cpp +REACT_METHOD(PickAndRead); +void PickAndRead(ReactPromise promise) noexcept { + auto prom = promise; + /* perform file picker action in the UI thread */ + // highlight-next-line + context.UIDispatcher().Post([prom = std::move(prom)]()->winrt::fire_and_forget { + auto p = prom; // promise -> prom -> p dance avoids promise destruction + + /* create file picker */ + FileOpenPicker picker; + picker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary); + picker.FileTypeFilter().Append(L".xlsx"); + picker.FileTypeFilter().Append(L".xls"); + + /* show file picker */ + // highlight-next-line + StorageFile file = co_await picker.PickSingleFileAsync(); + if(file == nullptr) { p.Reject("File not Found"); co_return; } + + /* read data and return base64 string */ + auto buf = co_await FileIO::ReadBufferAsync(file); + // highlight-next-line + p.Resolve(CryptographicBuffer::EncodeToBase64String(buf)); + co_return; + }); +} +``` + + + + + + + +React Native macOS supports Objective-C modules + +```objc +/* the resolve/reject is projected on the JS side as a Promise */ +RCT_EXPORT_METHOD(PickAndRead:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + /* perform file picker action in the UI thread */ + // highlight-next-line + RCTExecuteOnMainQueue(^{ + /* create file picker */ + NSOpenPanel *panel = [NSOpenPanel openPanel]; + [panel setCanChooseDirectories:NO]; + [panel setAllowsMultipleSelection:NO]; + [panel setMessage:@"Select a spreadsheet to read"]; + + /* show file picker */ + // highlight-next-line + [panel beginWithCompletionHandler:^(NSInteger result){ + if (result == NSModalResponseOK) { + /* read data and return base64 string */ + NSURL *selected = [[panel URLs] objectAtIndex:0]; + NSFileHandle *hFile = [NSFileHandle fileHandleForReadingFromURL:selected error:nil]; + if(hFile) { + NSData *data = [hFile readDataToEndOfFile]; + // highlight-next-line + resolve([data base64EncodedStringWithOptions:0]); + } else reject(@"read_failure", @"Could not read selected file!", nil); + } else reject(@"select_failure", @"No file selected!", nil); + }]; + }); +} +``` + + + + + +## Windows Demo + +This demo was tested against `v0.70.10` on 2023 January 04 in Windows 10. + +:::warning + +There is no simple standalone executable file at the end of the process. + +[The official documentation describes distribution strategies](https://microsoft.github.io/react-native-windows/docs/native-code#distribution) + +::: + +:::note + +React Native Windows supports writing native code in C++ or C#. This demo has +been tested against both application types. + +::: + +0) Follow the ["Getting Started" guide](https://microsoft.github.io/react-native-windows/docs/getting-started) + +:::caution + +NodeJS `v16` is required. There are OS-specific tools for downgrading: + +- [`nvm-windows`](https://github.com/coreybutler/nvm-windows/releases) Windows +- [`n`](https://github.com/tj/n/) Linux, MacOS, WSL, etc. + +::: + +1) Create a new project using React Native `0.70`: + +```powershell +npx react-native init SheetJSWin --template react-native@^0.70.0 +cd .\SheetJSWin\ +``` + +Create the Windows part of the application: + + + + +```powershell +npx react-native-windows-init --no-telemetry --overwrite --language=cs +``` + + + + +```powershell +npx react-native-windows-init --no-telemetry --overwrite +``` + + + + +Install library: + +```powershell +npm install --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz +``` + +To ensure that the app works, launch the app: + +```powershell +npx react-native run-windows --no-telemetry +``` + + + + +2) Create the file `windows\SheetJSWin\DocumentPicker.cs` with the following: + +```csharp title="windows\SheetJSWin\DocumentPicker.cs" +using System; +using System.Threading.Tasks; +using Windows.Security.Cryptography; +using Windows.Storage; +using Windows.Storage.Pickers; +using Microsoft.ReactNative.Managed; + +namespace SheetJSWin { + [ReactModule] + class DocumentPicker { + private ReactContext context; + [ReactInitializer] + public void Initialize(ReactContext reactContext) { context = reactContext; } + + [ReactMethod("PickAndRead")] + public async void PickAndRead(IReactPromise result) { + context.Handle.UIDispatcher.Post(async() => { try { + var picker = new FileOpenPicker(); + picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; + picker.FileTypeFilter.Add(".xlsx"); + picker.FileTypeFilter.Add(".xls"); + + var file = await picker.PickSingleFileAsync(); + if(file == null) throw new Exception("File not found"); + + var buf = await FileIO.ReadBufferAsync(file); + result.Resolve(CryptographicBuffer.EncodeToBase64String(buf)); + } catch(Exception e) { result.Reject(new ReactError { Message = e.Message }); }}); + } + } +} +``` + +3) Add the highlighted line to `windows\SheetJSWin\SheetJSWin.csproj`. Look for +the `ItemGroup` that contains `ReactPackageProvider.cs`: + +```xml title="windows\SheetJSWin\SheetJSWin.csproj" + + + + +``` + + + + +2) Create the file `windows\SheetJSWin\DocumentPicker.h` with the following: + +```cpp title="windows\SheetJSWin\DocumentPicker.h" +#pragma once + +#include +#include +#include "NativeModules.h" + +using namespace winrt::Microsoft::ReactNative; +using namespace winrt::Windows::Storage; +using namespace winrt::Windows::Storage::Pickers; +using namespace winrt::Windows::Security::Cryptography; + +namespace SheetJSWin { + REACT_MODULE(DocumentPicker); + struct DocumentPicker { + REACT_INIT(Initialize); + void Initialize(const ReactContext& reactContext) noexcept { + context = reactContext; + } + + REACT_METHOD(PickAndRead); + void PickAndRead(ReactPromise promise) noexcept { + auto prom = promise; + context.UIDispatcher().Post([prom = std::move(prom)]()->winrt::fire_and_forget { + auto p = prom; + FileOpenPicker picker; + picker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary); + picker.FileTypeFilter().Append(L".xlsx"); + picker.FileTypeFilter().Append(L".xls"); + + StorageFile file = co_await picker.PickSingleFileAsync(); + if(file == nullptr) { p.Reject("File not Found"); co_return; } + + auto buf = co_await FileIO::ReadBufferAsync(file); + p.Resolve(CryptographicBuffer::EncodeToBase64String(buf)); + co_return; + }); + } + + private: + ReactContext context{nullptr}; + }; +} +``` + +3) Add the highlighted line to `windows\SheetJSWin\ReactPackageProvider.cpp`: + +```cpp title="windows\SheetJSWin\ReactPackageProvider.cpp" +#include "ReactPackageProvider.h" +// highlight-next-line +#include "DocumentPicker.h" +#include "NativeModules.h" +``` + + + + +Now the native module will be added to the app. + +4) Remove `App.js` and save the following to `App.tsx`: + +```tsx title="App.tsx" +import React, { useState, type Node } from 'react'; +import { SafeAreaView, ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native'; +import { read, utils, version } from 'xlsx'; +import { getEnforcing } from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; +const DocumentPicker = getEnforcing('DocumentPicker'); + +const App: () => Node = () => { + + const [ aoa, setAoA ] = useState(["SheetJS".split(""), "5433795".split("")]); + + return ( + + SheetJS × React Native Windows {version} + { + try { + const b64 = await DocumentPicker.PickAndRead(); + const wb = read(b64); + setAoA(utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { header: 1 } )); + } catch(err) { alert(`Error: ${err.message}`); } + }}>Click here to Open File! + + {aoa.map((row,R) => ( + {row.map((cell,C) => ( + {cell} + ))} + ))} + + + ); +}; + +const styles = StyleSheet.create({ + cell: { flex: 4 }, + row: { flexDirection: 'row', justifyContent: 'space-evenly', padding: 10, backgroundColor: 'white', }, + table: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', }, + outer: { marginTop: 32, paddingHorizontal: 24, }, + title: { fontSize: 24, fontWeight: '600', }, + button: { marginTop: 8, fontSize: 18, fontWeight: '400', }, +}); + +export default App; +``` + +5) Test the app again: + +```powershell +npx react-native run-windows --no-telemetry +``` + +Download , then click on "open file". Use the +file picker to select the `pres.xlsx` file and the app will show the data. + +## macOS Demo + +This demo was tested against `v0.64.30` on 2023 January 04 in MacOS 12.4 + +0) Follow the [React Native](https://reactnative.dev/docs/environment-setup) + guide for React Native CLI on MacOS. + +:::caution + +NodeJS `v16` is required. There are OS-specific tools for downgrading: + +- [`nvm-windows`](https://github.com/coreybutler/nvm-windows/releases) Windows +- [`n`](https://github.com/tj/n/) Linux, MacOS, WSL, etc. + +::: + +1) Create a new project using React Native `0.64`: + +```bash +npx react-native init SheetJSmacOS --template react-native@^0.64.0 +cd SheetJSmacOS +``` + +Create the MacOS part of the application: + +```bash +npx react-native-macos-init --no-telemetry +``` + +Install Library: + +``` +npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz +``` + +To ensure that the app works, launch the app: + +```powershell +npx react-native run-macos +``` + +Close the running app from the dock and close the Metro terminal window. + +2) Create the file `macos/SheetJSmacOS-macOS/RCTDocumentPicker.h`: + +```objc title="macos/SheetJSmacOS-macOS/RCTDocumentPicker.h" +#import +@interface RCTDocumentPicker : NSObject +@end +``` + +Create the file `macos/SheetJSmacOS-macOS/RCTDocumentPicker.m`: + +```objc title="macos/SheetJSmacOS-macOS/RCTDocumentPicker.m" +#import +#import + +#import "RCTDocumentPicker.h" + +@implementation RCTDocumentPicker + +RCT_EXPORT_MODULE(); + +RCT_EXPORT_METHOD(PickAndRead:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) +{ + RCTExecuteOnMainQueue(^{ + NSOpenPanel *panel = [NSOpenPanel openPanel]; + [panel setCanChooseDirectories:NO]; + [panel setAllowsMultipleSelection:NO]; + [panel setMessage:@"Select a spreadsheet to read"]; + + [panel beginWithCompletionHandler:^(NSInteger result){ + if (result == NSModalResponseOK) { + NSURL *selected = [[panel URLs] objectAtIndex:0]; + NSFileHandle *hFile = [NSFileHandle fileHandleForReadingFromURL:selected error:nil]; + if(hFile) { + NSData *data = [hFile readDataToEndOfFile]; + resolve([data base64EncodedStringWithOptions:0]); + } else reject(@"read_failure", @"Could not read selected file!", nil); + } else reject(@"select_failure", @"No file selected!", nil); + }]; + }); +} +@end +``` + +3) Edit the project file `macos/SheetJSmacOS.xcodeproj/project.pbxproj`. + +There are four places where lines must be added: + +A) Immediately after `/* Begin PBXBuildFile section */` + +```plist +/* Begin PBXBuildFile section */ +// highlight-next-line + 4717DC6A28CC499A00A9BE56 /* RCTDocumentPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 4717DC6928CC499A00A9BE56 /* RCTDocumentPicker.m */; }; + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; +``` + +B) Immediately after `/* Begin PBXFileReference section */` + +```plist +/* Begin PBXFileReference section */ +// highlight-start + 4717DC6828CC495400A9BE56 /* RCTDocumentPicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RCTDocumentPicker.h; path = "SheetJSMacOS-macOS/RCTDocumentPicker.h"; sourceTree = ""; }; + 4717DC6928CC499A00A9BE56 /* RCTDocumentPicker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = RCTDocumentPicker.m; path = "SheetJSMacOS-macOS/RCTDocumentPicker.m"; sourceTree = ""; }; +// highlight-end + 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; +``` + +C) The goal is to add a reference to the `PBXSourcesBuildPhase` block for the +`macOS` target. To determine this, look in the `PBXNativeTarget section` for +a block with the comment `SheetJSmacOS-macOS`: + +```plist +/* Begin PBXNativeTarget section */ +... + productType = "com.apple.product-type.application"; + }; +// highlight-next-line + 514201482437B4B30078DB4F /* SheetJSmacOS-macOS */ = { + isa = PBXNativeTarget; +... +/* End PBXNativeTarget section */ +``` + +Within the block, look for `buildPhases` and find the hex string for `Sources`: + +```plist + buildPhases = ( + 1A938104A937498D81B3BD3B /* [CP] Check Pods Manifest.lock */, + 381D8A6F24576A6C00465D17 /* Start Packager */, +// highlight-next-line + 514201452437B4B30078DB4F /* Sources */, + 514201462437B4B30078DB4F /* Frameworks */, + 514201472437B4B30078DB4F /* Resources */, + 381D8A6E24576A4E00465D17 /* Bundle React Native code and images */, + 3689826CA944E2EF44FCBC17 /* [CP] Copy Pods Resources */, + ); +``` + +Search for that hex string (`514201452437B4B30078DB4F` in our example) in the +file and it should show up in a `PBXSourcesBuildPhase` section. Within `files`, +add the highlighted line: + +```plist + 514201452437B4B30078DB4F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( +// highlight-next-line + 4717DC6A28CC499A00A9BE56 /* RCTDocumentPicker.m in Sources */, + 514201502437B4B30078DB4F /* ViewController.m in Sources */, + 514201582437B4B40078DB4F /* main.m in Sources */, + 5142014D2437B4B30078DB4F /* AppDelegate.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +``` + +D) The goal is to add file references to the "main group". Search for +`/* Begin PBXProject section */` and there should be one Project object. +Within the project object, look for `mainGroup`: + +```plist +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; +... + Base, + ); +// highlight-next-line + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; +... +/* End PBXProject section */ +``` + +Search for that hex string (`83CBB9F61A601CBA00E9B192` in our example) in the +file and it should show up in a `PBXGroup` section. Within `children`, add the +highlighted lines: + +```plist + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( +// highlight-start + 4717DC6828CC495400A9BE56 /* RCTDocumentPicker.h */, + 4717DC6928CC499A00A9BE56 /* RCTDocumentPicker.m */, +// highlight-end + 5142014A2437B4B30078DB4F /* SheetJSmacOS-macOS */, + 13B07FAE1A68108700A75B9A /* SheetJSmacOS-iOS */, +``` + +4) Replace `App.js` with the following: + +```tsx title="App.js" +import React, { useState, type Node } from 'react'; +import { SafeAreaView, ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native'; +import { read, utils, version } from 'xlsx'; +import { getEnforcing } from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; +const DocumentPicker = getEnforcing('DocumentPicker'); + +const App: () => Node = () => { + + const [ aoa, setAoA ] = useState(["SheetJS".split(""), "5433795".split("")]); + + return ( + + SheetJS × React Native MacOS {version} + { + try { + const b64 = await DocumentPicker.PickAndRead(); + const wb = read(b64); + setAoA(utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { header: 1 } )); + } catch(err) { alert(`Error: ${err.message}`); } + }}>Click here to Open File! + + {aoa.map((row,R) => ( + {row.map((cell,C) => ( + {cell} + ))} + ))} + + + ); +}; + +const styles = StyleSheet.create({ + cell: { flex: 4 }, + row: { flexDirection: 'row', justifyContent: 'space-evenly', padding: 10, backgroundColor: 'white', }, + table: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', }, + outer: { marginTop: 32, paddingHorizontal: 24, }, + title: { fontSize: 24, fontWeight: '600', }, + button: { marginTop: 8, fontSize: 18, fontWeight: '400', }, +}); + +export default App; +``` + +5) Test the app: + +```bash +npx react-native run-macos +``` + +Download , then click on "open file". Use the +file picker to select the `pres.xlsx` file and the app will show the data. + +6) Make a release build: + +```bash +xcodebuild -workspace macos/SheetJSmacOS.xcworkspace -scheme SheetJSmacOS-macOS -config Release +``` diff --git a/docz/docs/03-demos/03-desktop/_category_.json b/docz/docs/03-demos/03-desktop/_category_.json new file mode 100644 index 00000000..74aac8f8 --- /dev/null +++ b/docz/docs/03-demos/03-desktop/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Desktop Applications", + "position": 3 +} diff --git a/docz/docs/03-demos/03-desktop/index.md b/docz/docs/03-demos/03-desktop/index.md new file mode 100644 index 00000000..54007b1c --- /dev/null +++ b/docz/docs/03-demos/03-desktop/index.md @@ -0,0 +1,37 @@ +--- +title: Desktop Applications +pagination_prev: demos/mobile +pagination_next: demos/grid +--- + +import DocCardList from '@theme/DocCardList'; +import {useCurrentSidebarCategory} from '@docusaurus/theme-common'; + +Web technologies like JavaScript and HTML have been adapted to the traditional +app space. Typically these frameworks bundle a JavaScript engine as well as a +windowing framework. SheetJS is compatible with many app frameworks. + +Demos for common tools are included in separate pages: + +
    {useCurrentSidebarCategory().items.map((item, index) => { + const listyle = (item.customProps?.icon) ? { + listStyleImage: `url("${item.customProps.icon}")` + } : {}; + return (
  • + {item.label}{item.customProps?.summary && (" - " + item.customProps.summary)} +
  • ); +})}
+ +:::note Recommendation + +Electron is the most established and widely-used framework. With deep support +for NodeJS modules and consistent user interfaces, it is the recommended choice +for new projects and for web developers. + +Frameworks like Wails are compelling alternatives for teams with experience in +other programming languages. + +Frameworks like React Native generate applications that use native UI elements. + +::: + diff --git a/docz/docs/03-demos/04-grid.md b/docz/docs/03-demos/04-grid.md index ffbcf2b5..b3624381 100644 --- a/docz/docs/03-demos/04-grid.md +++ b/docz/docs/03-demos/04-grid.md @@ -1,5 +1,6 @@ --- title: Data Grids and Tables +pagination_prev: demos/desktop/index --- Various JavaScript UI components provide a more interactive editing experience. diff --git a/docz/docs/03-demos/index.md b/docz/docs/03-demos/index.md index ff851a94..18139f2a 100644 --- a/docz/docs/03-demos/index.md +++ b/docz/docs/03-demos/index.md @@ -19,7 +19,7 @@ run in the web browser, demos will include interactive examples. - [`Web SQL Database`](/docs/demos/database#websql) - [`IndexedDB`](/docs/demos/database#indexeddb) -### Frameworks +### Web Frameworks - [`Angular`](/docs/demos/angular) - [`React`](/docs/demos/react) @@ -38,6 +38,15 @@ run in the web browser, demos will include interactive examples. - [`angular-ui-grid`](/docs/demos/grid#angular-ui-grid) - [`material ui`](/docs/demos/grid#material-ui-table) +### Desktop App Frameworks + +- [`Electron`](/docs/demos/desktop/electron) +- [`NW.js`](/docs/demos/desktop/nwjs) +- [`Wails`](/docs/demos/desktop/wails) +- [`Tauri`](/docs/demos/desktop/tauri) +- [`NeutralinoJS`](/docs/demos/desktop/neutralino) +- [`React Native for Desktop`](/docs/demos/desktop/reactnative) + ### Platforms and Integrations - [`Command-Line Tools`](/docs/demos/cli) @@ -45,10 +54,6 @@ run in the web browser, demos will include interactive examples. - [`NodeJS Server-Side Processing`](/docs/demos/server#nodejs) - [`Deno Server-Side Processing`](/docs/demos/server#deno) - [`Content Management and Static Sites`](/docs/demos/content) -- [`Electron`](/docs/demos/desktop#electron) -- [`NW.js`](/docs/demos/desktop#nwjs) -- [`Tauri`](/docs/demos/desktop#tauri) -- [`Wails`](/docs/demos/desktop#wails) - [`Chrome and Chromium Extensions`](/docs/demos/chromium) - [`Google Sheets API`](/docs/demos/gsheet) - [`ExtendScript for Adobe Apps`](/docs/demos/extendscript) diff --git a/docz/static/reactnative/rnm.png b/docz/static/reactnative/rnm.png new file mode 100644 index 0000000000000000000000000000000000000000..375911459acc5bd2a378a2f7e20a37656d965241 GIT binary patch literal 87760 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�Fs1J)2qGf8BQ5T@lLx6nwOgKai-;fo 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 literal 0 HcmV?d00001 diff --git a/docz/static/reactnative/rnw.png b/docz/static/reactnative/rnw.png new file mode 100644 index 0000000000000000000000000000000000000000..b6e40a4fe09207cfe43fe89997d7c50dcc4c0680 GIT binary patch literal 26900 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 IyZG<_03;@ttpET3 literal 0 HcmV?d00001