This commit is contained in:
SheetJS 2023-07-23 17:01:30 -04:00
parent be15eb3620
commit 4dd53501ff
18 changed files with 381 additions and 128 deletions

@ -36,8 +36,8 @@ new versions are released!
## NetSuite
After downloading the script, it can be referenced directly in `define` calls
in SuiteScripts:
After uploading the script to the File Cabinet, it can be referenced directly in
`define` calls in SuiteScripts:
```js
define(['N/file', './xlsx.full.min'], function(file, XLSX) {
@ -45,8 +45,8 @@ define(['N/file', './xlsx.full.min'], function(file, XLSX) {
})
```
As explained in the [NetSuite demo](/docs/demos/cloud/netsuite), module
aliases are created in config files referenced via `@NAmdConfig` comments.
As explained in the [NetSuite demo](/docs/demos/cloud/netsuite), module aliases
can be created in config files referenced via `@NAmdConfig` comments.
## SAP UI5

@ -54,13 +54,13 @@ module.exports = {
NextJS collects telemetry by default. The `telemetry` subcommand can disable it:
```js
npx next@13.4.4 telemetry disable
npx next@13.4.12 telemetry disable
```
The setting can be verified by running
```js
npx next@13.4.4 telemetry status
npx next@13.4.12 telemetry status
```
:::
@ -69,10 +69,11 @@ npx next@13.4.4 telemetry status
The following deployments were tested:
| NextJS | Date |
|:-------|:-----------|
| 13.1.1 | 2023-01-14 |
| 13.4.4 | 2023-05-26 |
| NextJS | NodeJS | Date |
|:--------|:----------|:-----------|
| 11.1.4 | `16.20.1` | 2023-07-23 |
| 12.3.4 | `18.17.0` | 2023-07-23 |
| 13.4.12 | `18.17.0` | 2023-07-23 |
:::
@ -503,6 +504,9 @@ This demo showcases the following SheetJS + NextJS flows:
| `/sheets/[id]` | asset module | `getStaticPaths` | `sheet_to_html` |
| `/getServerSideProps` | lifecycle | `getServerSideProps` | `sheet_to_html` |
The commands in this demo use `next@13.4.12`. Other versions were tested by
replacing the version number in the relevant commands.
:::
### Initial Setup
@ -510,13 +514,13 @@ This demo showcases the following SheetJS + NextJS flows:
0) Disable NextJS telemetry:
```js
npx next@13.4.4 telemetry disable
npx next@13.4.12 telemetry disable
```
Confirm it is disabled by running
```js
npx next@13.4.4 telemetry status
npx next@13.4.12 telemetry status
```
1) Set up folder structure. At the end, a `pages` folder with a `sheets`
@ -538,7 +542,7 @@ curl -LO https://docs.sheetjs.com/next/sheetjs.xlsx
3) Install dependencies:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz next@13.4.4`}
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz next@13.4.12`}
</CodeBlock>
4) Download NextJS config scripts and place in the root folder:
@ -596,7 +600,7 @@ cd ../..
6) Test the deployment:
```bash
npx next@13.4.4
npx next@13.4.12
```
Open a web browser and access:
@ -622,20 +626,20 @@ After saving the file, the website should refresh with the new row.
8) Stop the server and run a production build:
```bash
npx next@13.4.4 build
npx next@13.4.12 build
```
The final output will show a list of the routes and types:
```
Route (pages) Size First Load JS
┌ ○ / 563 B 74.4 kB
├ /_app 0 B 73.9 kB
├ ○ /404 182 B 74.1 kB
├ λ /getServerSideProps 522 B 74.4 kB
├ ● /getStaticPaths 2.89 kB 76.8 kB
├ ● /getStaticProps 586 B 74.5 kB
└ ● /sheets/[id] 522 B 74.4 kB
┌ ○ / 563 B 75.3 kB
├ /_app 0 B 74.8 kB
├ ○ /404 182 B 75 kB
├ λ /getServerSideProps 522 B 75.3 kB
├ ● /getStaticPaths 2.91 kB 77.7 kB
├ ● /getStaticProps 586 B 75.4 kB
└ ● /sheets/[id] (303 ms) 522 B 75.3 kB
├ /sheets/0
└ /sheets/1
```
@ -647,7 +651,7 @@ worksheets in the file. `/getServerSideProps` is server-rendered.
9) Try to build a static site:
```bash
npx next@13.4.4 export
npx next@13.4.12 export
```
:::note The static export will fail!
@ -663,19 +667,19 @@ is still server-rendered.
```bash
rm -f pages/getServerSideProps.js
npx next@13.4.4 build
npx next@13.4.12 build
```
Inspecting the output, there should be no lines with the `λ` symbol:
```
Route (pages) Size First Load JS
┌ ○ / 563 B 74.4 kB
├ /_app 0 B 73.9 kB
├ ○ /404 182 B 74.1 kB
├ ● /getStaticPaths 2.89 kB 76.8 kB
├ ● /getStaticProps 586 B 74.5 kB
└ ● /sheets/[id] 522 B 74.4 kB
┌ ○ / 563 B 75.3 kB
├ /_app 0 B 74.8 kB
├ ○ /404 182 B 75 kB
├ ● /getStaticPaths 2.91 kB 77.7 kB
├ ● /getStaticProps 586 B 75.4 kB
└ ● /sheets/[id] 522 B 75.3 kB
├ /sheets/0
└ /sheets/1
```
@ -683,7 +687,7 @@ Route (pages) Size First Load JS
11) Generate the static site:
```bash
npx next@13.4.4 export
npx next@13.4.12 export
```
The static site will be written to the `out` subfolder
@ -694,5 +698,6 @@ The static site will be written to the `out` subfolder
npx http-server out
```
The command will start a local HTTP server for testing the generated site. Note
that `/getServerSideProps` will 404 since the page was removed.
The command will start a local HTTP server at `http://localhost:8080/` for
testing the generated site. Note that `/getServerSideProps` will 404 since the
page was removed.

@ -20,7 +20,6 @@ The following deployments were tested:
| Nuxt Content | Nuxt | Date |
|:-------------|:---------|:-----------|
| `1.15.1` | `2.16.3` | 2023-06-01 |
| `2.3.0` | `3.0.0` | 2023-01-19 |
| `2.6.0` | `3.5.2` | 2023-06-01 |
:::

@ -1,5 +1,7 @@
---
title: React Native
sidebar_label: React Native
description: Build data-intensive desktop apps with React Native. Seamlessly integrate spreadsheets into your app using SheetJS. Securely process and generate Excel files at the desk.
pagination_prev: demos/mobile/index
pagination_next: demos/data/index
sidebar_position: 6
@ -7,26 +9,25 @@ sidebar_custom_props:
summary: Native Components with React
---
# Sheets on the Desktop with React Native
import current from '/version.js';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
:::note
This section covers React Native for desktop applications. For iOS and Android
applications, [check the mobile demo](/docs/demos/mobile/reactnative)
:::
React Native for Windows + macOS is a backend for React Native that supports
React Native for Windows + macOS[^1] 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.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
The "Complete Example" creates an app that looks like the screenshots below:
This demo uses React Native for Windows + macOS and SheetJS to process
spreadsheets. We'll explore how to load SheetJS in a React Native deskktop app
and create native modules for selecting and reading files from the computer.
The Windows and macOS demos create apps that look like the screenshots below:
<table><thead><tr>
<th><a href="#windows-demo">Win10</a></th>
@ -41,8 +42,6 @@ The "Complete Example" creates an app that looks like the screenshots below:
</td></tr></tbody></table>
<details><summary><b>Tested Environments</b> (click to show)</summary>
:::note
This demo was tested in the following environments:
@ -51,13 +50,169 @@ This demo was tested in the following environments:
|:---------------|:-----|:------------|:-----------|
| Windows 10 | x64 | `v0.70.10` | 2023-01-04 |
| Windows 11 | x64 | `v0.71.11` | 2023-05-11 |
| MacOS 12.4 | x64 | `v0.64.30` | 2023-01-04 |
| MacOS 12.6 | x64 | `v0.71.26` | 2023-07-23 |
| MacOS 13.4 | arm | `v0.71.18` | 2023-07-06 |
:::
:::info pass
This section covers React Native for desktop applications. For iOS and Android
applications, [check the mobile demo](/docs/demos/mobile/reactnative)
:::
## Integration Details
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
imported from the main `App.js` entrypoint or any script in the project.
### Internal State
For simplicity, this demo uses an "Array of Arrays"[^2] as the internal state.
<table><thead><tr><th>Spreadsheet</th><th>Array of Arrays</th></tr></thead><tbody><tr><td>
![`pres.xlsx` data](pathname:///pres.png)
</td><td>
```js
[
["Name", "Index"],
["Bill Clinton", 42],
["GeorgeW Bush", 43],
["Barack Obama", 44],
["Donald Trump", 45],
["Joseph Biden", 46]
]
```
</td></tr></tbody></table>
Each array within the structure corresponds to one row.
The state is initialized with the following snippet:
```js
const [ aoa, setAoA ] = useState(["SheetJS".split(""), "5433795".split("")]);
```
#### Updating State
Starting from a SheetJS worksheet object, `sheet_to_json`[^3] with the `header`
option can generate an array of arrays:
```js
/* assuming `wb` is a SheetJS workbook */
function update_state(wb) {
/* convert first worksheet to AOA */
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const data = utils.sheet_to_json(ws, {header:1});
/* update state */
setAoA(data);
}
```
### Displaying Data
The demos use native `View` elements from `react-native` to display data.
<details><summary><b>Explanation</b> (click to show)</summary>
Since some spreadsheets may have empty cells between cells containing data,
looping over the rows may skip values!
This example explicitly loops over the row and column indices.
**Determining the Row Indices**
The first row index is `0` and the last row index is `aoa.length - 1`. This
corresponds to the `for` loop:
```js
for(var R = 0; R < aoa.length; ++R) {/* ... */}
```
**Determining the Column Indices**
The first column index is `0` and the last column index must be calculated from
the maximum column index across every row.
Traditionally this would be implemented in a `for` loop:
```js
var max_col_index = 0;
for(var R = 0; R < aoa.length; ++R) {
if(!aoa[R]) continue;
max_col_index = Math.max(max_col_index, aoa[R].length - 1);
}
```
`Array#reduce` simplifies this calculation:
```js
const max_col_index = aoa.reduce((C,row) => Math.max(C,row.length), 1) - 1;
```
**Looping from 0 to N-1**
Traditionally a `for` loop would be used:
```js
var data = [];
for(var R = 0; R < max_row; ++R) data[R] = func(R);
```
For creating an array of React Native components, `Array.from` should be used:
```jsx
var children = Array.from({length: max_row}, (_,R) => ( <Row key={R} /> ));
```
</details>
The relevant parts for rendering data are shown below:
```jsx
import React, { useState, type FC } from 'react';
import { SafeAreaView, ScrollView, Text, View } from 'react-native';
const App: FC = () => {
const [ aoa, setAoA ] = useState(["SheetJS".split(""), "5433795".split("")]);
const max_cols = aoa.reduce((acc,row) => Math.max(acc,row.length),1);
return (
<SafeAreaView>
<ScrollView contentInsetAdjustmentBehavior="automatic">
{/* Table Container */}
<View>{
/* Loop over the row indices */
// highlight-next-line
Array.from({length: aoa.length}, (_, R) => (
/* Table Row */
<View key={R}>{
/* Loop over the column indices */
// highlight-next-line
Array.from({length: max_cols}, (_, C) => (
/* Table Cell */
<View key={C}>
// highlight-next-line
<Text>{String(aoa?.[R]?.[C]??"")}</Text>
</View>
))
}</View>
))
}</View>
</ScrollView>
</SafeAreaView>
);
};
export default App;
```
## Native Modules
:::caution
@ -70,8 +225,7 @@ 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.
React Native for Windows + macOS use Turbo Modules[^4] for native integrations.
The demos define a native module named `DocumentPicker`.
@ -357,20 +511,20 @@ curl -Lo windows/SheetJSWin/DocumentPicker.h https://docs.sheetjs.com/reactnativ
Now the native module will be added to the app.
4) Remove `App.js` (if it exists) and save the following to `App.tsx`:
4) Remove `App.js` (if it exists) and download [`App.tsx`](https://docs.sheetjs.com/reactnative/rnw/App.tsx):
<Tabs groupId="shell">
<TabItem value="pwsh" label="PowerShell">
```bash
iwr -Uri https://docs.sheetjs.com/reactnative/desktop/App.tsx -OutFile App.tsx
iwr -Uri https://docs.sheetjs.com/reactnative/rnw/App.tsx -OutFile App.tsx
```
</TabItem>
<TabItem value="bash" label="WSL Bash">
```bash
curl -LO https://docs.sheetjs.com/reactnative/desktop/App.tsx
curl -LO https://docs.sheetjs.com/reactnative/rnw/App.tsx
```
</TabItem>
@ -389,29 +543,31 @@ file picker to select the `pres.xlsx` file and the app will show the data.
## macOS Demo
0) Follow the [React Native](https://reactnative.dev/docs/environment-setup)
guide for React Native CLI on MacOS.
0) Follow the "Setting up the development environment"[^5] guide in the React
Native documentation for "React Native CLI Quickstart" + "macOS" + "iOS".
1) Create a new project using React Native `0.71`:
### Project Setup
1) Create a new React Native project using React Native `0.71`:
```bash
npx react-native init SheetJSmacOS --template react-native@^0.71.0
npx -y react-native init SheetJSmacOS --template react-native@^0.71.0
cd SheetJSmacOS
```
Create the MacOS part of the application:
2) Create the MacOS part of the application:
```bash
npx react-native-macos-init --no-telemetry
npx -y react-native-macos-init --no-telemetry
```
Install the SheetJS library:
3) Install the SheetJS library:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
To ensure that the app works, launch the app:
4) To ensure that the app works, launch the app:
```bash
npx react-native run-macos
@ -419,7 +575,10 @@ 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`:
### Native Module
5) Create the file `macos/SheetJSmacOS-macOS/RCTDocumentPicker.h` with the
following contents:
```objc title="macos/SheetJSmacOS-macOS/RCTDocumentPicker.h"
#import <React/RCTBridgeModule.h>
@ -427,7 +586,8 @@ Close the running app from the dock and close the Metro terminal window.
@end
```
Create the file `macos/SheetJSmacOS-macOS/RCTDocumentPicker.m`:
6) Create the file `macos/SheetJSmacOS-macOS/RCTDocumentPicker.m` with the
following contents:
```objc title="macos/SheetJSmacOS-macOS/RCTDocumentPicker.m"
#import <Foundation/Foundation.h>
@ -462,20 +622,26 @@ RCT_EXPORT_METHOD(PickAndRead:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromi
@end
```
3) Edit the project file `macos/SheetJSmacOS.xcodeproj/project.pbxproj`.
7) Edit the project file `macos/SheetJSmacOS.xcodeproj/project.pbxproj`.
There are four places where lines must be added:
A) Immediately after `/* Begin PBXBuildFile section */`
:::note pass
A) Copy the highlighted line and paste under `/* 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 */; };
5142014D2437B4B30078DB4F /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5142014C2437B4B30078DB4F /* AppDelegate.mm */; };
```
B) Immediately after `/* Begin PBXFileReference section */`
:::
:::note pass
B) Copy the highlighted lines and paste under `/* Begin PBXFileReference section */`:
```plist
/* Begin PBXFileReference section */
@ -486,6 +652,10 @@ B) Immediately after `/* Begin PBXFileReference section */`
008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = "<group>"; };
```
:::
:::note pass
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`:
@ -505,6 +675,9 @@ a block with the comment `SheetJSmacOS-macOS`:
Within the block, look for `buildPhases` and find the hex string for `Sources`:
```plist
514201482437B4B30078DB4F /* SheetJSmacOS-macOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 5142015A2437B4B40078DB4F /* Build configuration list for PBXNativeTarget "SheetJSmacOS-macOS" */;
buildPhases = (
1A938104A937498D81B3BD3B /* [CP] Check Pods Manifest.lock */,
381D8A6F24576A6C00465D17 /* Start Packager */,
@ -528,14 +701,17 @@ add the highlighted line:
files = (
// highlight-next-line
4717DC6A28CC499A00A9BE56 /* RCTDocumentPicker.m in Sources */,
514201502437B4B30078DB4F /* ViewController.m in Sources */,
514201582437B4B40078DB4F /* main.m in Sources */,
5142014D2437B4B30078DB4F /* AppDelegate.m in Sources */,
5142014D2437B4B30078DB4F /* AppDelegate.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
```
:::
:::note pass
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`:
@ -570,63 +746,57 @@ highlighted lines:
13B07FAE1A68108700A75B9A /* SheetJSmacOS-iOS */,
```
4) Replace `App.tsx` with the following:
:::
```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 (
<SafeAreaView style={styles.outer}>
<Text style={styles.title}>SheetJS × React Native MacOS {version}</Text>
<TouchableHighlight onPress={async() => {
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}`); }
}}><Text style={styles.button}>Click here to Open File!</Text></TouchableHighlight>
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={styles.table}>{aoa.map((row,R) => (
<View style={styles.row} key={R}>{row.map((cell,C) => (
<View style={styles.cell} key={C}><Text>{cell}</Text></View>
))}</View>
))}</View>
</ScrollView>
</SafeAreaView>
);
};
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:
8) To ensure that the app still works, launch the app again:
```bash
npx react-native run-macos
```
Download <https://sheetjs.com/pres.xlsx>, then click on "open file". Use the
file picker to select the `pres.xlsx` file and the app will show the data.
Close the running app from the dock and close the Metro terminal window.
6) Make a release build:
### Application
9) Download [`App.tsx`](https://docs.sheetjs.com/reactnative/rnm/App.tsx) and
replace the file in the project:
```bash
curl -LO https://docs.sheetjs.com/reactnative/rnm/App.tsx
```
10) Test the app:
```bash
npx react-native run-macos
```
Download <https://sheetjs.com/pres.xlsx>.
Click "Click here to Open File!" and use the file picker to select `pres.xlsx` .
The app will refresh and display the data from the file.
11) Make a release build:
```bash
xcodebuild -workspace macos/SheetJSmacOS.xcworkspace -scheme SheetJSmacOS-macOS -config Release
```
The last line of the output will include the path to the app. If it is not
displayed, the app path can be found in the `DerivedData` folder:
```bash
find ~/Library/Developer/Xcode/DerivedData -name SheetJSmacOS.app | grep Release
```
12) Run the release app:
```bash
open -a "$(find ~/Library/Developer/Xcode/DerivedData -name SheetJSmacOS.app | grep Release | head -n 1)"
```
[^1]: The [official website](https://microsoft.github.io/react-native-windows/) covers both platforms, but there are separate repositories for [Windows](https://github.com/microsoft/react-native-windows) and [macOS](https://github.com/microsoft/react-native-macos)
[^2]: See ["Array of Arrays" in the API reference](/docs/api/utilities/array#array-of-arrays)
[^3]: See ["Array Output" in "Utility Functions"](/docs/api/utilities/array#array-output)
[^4]: See ["Turbo Native Modules"](https://reactnative.dev/docs/the-new-architecture/pillars-turbomodules) in the React Native documentation.
[^5]: See ["Setting up the development environment"](https://reactnative.dev/docs/environment-setup) in the React Native documentation. Select the "React Native CLI Quickstart" tab and choose the Development OS "macOS" and the Target OS "iOS".

@ -15,7 +15,7 @@ changes. Git can also store and track binary data artifacts.
GitHub is a popular host for Git repositories. GitHub's "Flat Data" project
explores storing and comparing versions of structured CSV and JSON data. The
official "Excel to CSV" example uses SheetJS to generate CSV data from files:
official "Excel to CSV"[^1] example uses SheetJS to generate CSV data from files:
```mermaid
sequenceDiagram
@ -273,3 +273,5 @@ jobs:
The update process will run once an hour. If you return in a few hours and
refresh the page, there should be more commits in the selection list.
[^1]: See ["Excel to CSV"](https://githubnext.com/projects/flat-data#:~:text=View%20code-,Excel,-to%20CSV) in the "Flat Data" writeup

@ -28,6 +28,8 @@ form data. This can be disabled with cloud-specific configuration:
- [AWS Lambda Functions](/docs/demos/cloud/aws#aws-lambda-functions)
- [Azure Functions](/docs/demos/cloud/azure#azure-functions)
- [GitHub Actions](/docs/demos/cloud/github)
- [Deno Deploy](/docs/demos/cloud/deno)
## Cloud Storage
@ -44,7 +46,6 @@ File hosting services provide simple solutions for storing data, synchronizing
files across devices, and sharing with specific users or customers. Demos:
- [Dropbox](/docs/demos/cloud/dropbox)
- [GitHub](/docs/demos/cloud/github)
## Cloud Data

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 108 KiB

@ -0,0 +1,42 @@
import React, { useState, type FC } from 'react';
import { SafeAreaView, ScrollView, StyleSheet, Text, TouchableHighlight, View, Alert } from 'react-native';
import { read, utils, version } from 'xlsx';
import { getEnforcing } from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
const DocumentPicker = getEnforcing('DocumentPicker');
const App: FC = () => {
const [ aoa, setAoA ] = useState(["SheetJS".split(""), "5433795".split("")]);
const max_cols = aoa.reduce((acc,row) => Math.max(acc,row.length),1);
return (
<SafeAreaView style={styles.outer}>
<Text style={styles.title}>SheetJS × React Native MacOS {version}</Text>
<TouchableHighlight onPress={async() => {
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.alert(`Read Error`, `${err instanceof Error ? err.message : err}`); }
}}><Text style={styles.button}>Click here to Open File!</Text></TouchableHighlight>
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={styles.table}>{Array.from({length: aoa.length}, (_, R) => (
<View style={styles.row} key={R}>{Array.from({length: max_cols}, (_, C) => (
<View style={styles.cell} key={C}><Text>{String(aoa?.[R]?.[C]??"")}</Text></View>
))}</View>
))}</View>
</ScrollView>
</SafeAreaView>
);
};
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;

@ -0,0 +1,3 @@
#import <React/RCTBridgeModule.h>
@interface RCTDocumentPicker : NSObject <RCTBridgeModule>
@end

@ -0,0 +1,30 @@
#import <Foundation/Foundation.h>
#import <React/RCTUtils.h>
#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

@ -1,12 +1,13 @@
import React, { useState, type Node } from 'react';
import { SafeAreaView, ScrollView, StyleSheet, Text, TouchableHighlight, View } from 'react-native';
import React, { useState, type FC } from 'react';
import { SafeAreaView, ScrollView, StyleSheet, Text, TouchableHighlight, View, Alert } 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 App: FC = () => {
const [ aoa, setAoA ] = useState(["SheetJS".split(""), "5433795".split("")]);
const max_cols = aoa.reduce((acc,row) => Math.max(acc,row.length),1);
return (
<SafeAreaView style={styles.outer}>
@ -16,12 +17,12 @@ const App: () => Node = () => {
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}`); }
} catch(err) { Alert.alert(`Read Error`, `${err instanceof Error ? err.message : err}`); }
}}><Text style={styles.button}>Click here to Open File!</Text></TouchableHighlight>
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={styles.table}>{aoa.map((row,R) => (
<View style={styles.row} key={R}>{row.map((cell,C) => (
<View style={styles.cell} key={C}><Text>{cell}</Text></View>
<View style={styles.table}>{Array.from({length: aoa.length}, (_, R) => (
<View style={styles.row} key={R}>{Array.from({length: max_cols}, (_, C) => (
<View style={styles.cell} key={C}><Text>{String(aoa?.[R]?.[C]??"")}</Text></View>
))}</View>
))}</View>
</ScrollView>
@ -38,4 +39,4 @@ const styles = StyleSheet.create({
button: { marginTop: 8, fontSize: 18, fontWeight: '400', },
});
export default App;
export default App;