forked from sheetjs/docs.sheetjs.com
		
	legacy
This commit is contained in:
		
							parent
							
								
									fbf32ffb0a
								
							
						
					
					
						commit
						cf739027b7
					
				| @ -47,6 +47,8 @@ new versions are released! | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### Legacy Endpoints | ||||
| 
 | ||||
| :::warning | ||||
| 
 | ||||
| Older releases are technically available on the public npm registry as `xlsx`, | ||||
|  | ||||
| @ -14,14 +14,20 @@ With a familiar UI, `x-spreadsheet` is an excellent choice for a modern editor. | ||||
| 
 | ||||
| [Click here for a live standalone integration demo.](pathname:///xspreadsheet/) | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was last verified on 2023 September 03. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## Live Demo | ||||
| 
 | ||||
| :::note | ||||
| :::note pass | ||||
| 
 | ||||
| Due to CSS conflicts between the data grid and the documentation generator, | ||||
| features like scrolling may not work as expected. | ||||
| 
 | ||||
| [The linked demo uses a simple HTML page.](pathname:///xspreadsheet/) | ||||
| [The standalone demo uses a simple HTML page.](pathname:///xspreadsheet/) | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -114,5 +120,9 @@ var grid = x_spreadsheet(document.getElementById("gridctr")); | ||||
| 
 | ||||
| #### Additional Features | ||||
| 
 | ||||
| :::tip pass | ||||
| 
 | ||||
| This demo barely scratches the surface.  The underlying grid component includes | ||||
| many additional features that work with [SheetJS Pro](https://sheetjs.com/pro). | ||||
| 
 | ||||
| ::: | ||||
| @ -8,11 +8,17 @@ pagination_next: demos/net/index | ||||
|   <script src="https://unpkg.com/canvas-datagrid/dist/canvas-datagrid.js"></script> | ||||
| </head> | ||||
| 
 | ||||
| After extensive testing, `canvas-datagrid` stood out as a high-performance grid | ||||
| [Canvas Datagrid](https://canvas-datagrid.js.org/) is a high-performance grid | ||||
| with a straightforward API. | ||||
| 
 | ||||
| [Click here for a live standalone integration demo.](pathname:///cdg/) | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was last verified on 2023 September 03. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## Live Demo | ||||
| 
 | ||||
| :::note | ||||
| @ -133,5 +139,9 @@ XLSX.writeFile(wb, "SheetJS.xlsx"); | ||||
| 
 | ||||
| #### Additional Features | ||||
| 
 | ||||
| :::tip pass | ||||
| 
 | ||||
| This demo barely scratches the surface.  The underlying grid component includes | ||||
| many additional features that work with [SheetJS Pro](https://sheetjs.com/pro). | ||||
| 
 | ||||
| ::: | ||||
| @ -773,6 +773,6 @@ Other demos show network operations in special platforms: | ||||
| - [React Native "Fetching Remote Data"](/docs/demos/mobile/reactnative#fetching-remote-data) | ||||
| - [NativeScript "Fetching Remote Files"](/docs/demos/mobile/nativescript#fetching-remote-files) | ||||
| 
 | ||||
| [^1]: See [`dataType` in `jQuery.ajax`](https://api.jquery.com/jQuery.ajax/#:~:text=the%20%27dataType%27%20parameter.-,dataType,-(default%3A%20Intelligent) in the official jQuery documentation. | ||||
| [^1]: See [`dataType` in `jQuery.ajax`](https://api.jquery.com/jQuery.ajax/) in the official jQuery documentation. | ||||
| [^2]: See [the official `jquery.binarytransport.js` repo](https://github.com/henrya/js-jquery/tree/master/BinaryTransport) for more details. | ||||
| [^3]: See ["Fluent interface"](https://en.wikipedia.org/wiki/Fluent_interface) on Wikipedia. | ||||
| @ -215,11 +215,11 @@ const wb = XLSX.read(ab); | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was tested on an Intel Mac on 2023 August 20 with RN `0.72.4`. | ||||
| The Android demo was last tested on 2023 September 03 with RN `0.72.4`. The | ||||
| simulator used Android 13 ("Tiramisu") API 33 on a Pixel 3. | ||||
| 
 | ||||
| The iOS simulator runs iOS 16.2 on an iPhone SE (3rd generation). | ||||
| 
 | ||||
| The Android simulator runs Android 12.0 (S) API 31 on a Pixel 3. | ||||
| The iOS demo was last tested on 2023 September 03 with RN `0.72.4`. The | ||||
| simulator used iOS 16.4 on an iPhone SE (3rd generation). | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
|  | ||||
| @ -178,7 +178,7 @@ cordova plugin add cordova-plugin-wkwebview-engine | ||||
| cordova plugin add cordova-plugin-file | ||||
| ``` | ||||
| 
 | ||||
| :::note | ||||
| :::note pass | ||||
| 
 | ||||
| If there is an error `Could not load API for iOS project`, it needs to be reset: | ||||
| 
 | ||||
|  | ||||
| @ -57,7 +57,7 @@ npx @capacitor/cli telemetry | ||||
| 
 | ||||
| ## Integration Details | ||||
| 
 | ||||
| :::caution | ||||
| :::caution pass | ||||
| 
 | ||||
| The latest version of Ionic uses CapacitorJS. These notes are for Cordova apps. | ||||
| The [CapacitorJS demo](/docs/demos/mobile/capacitor) covers CapacitorJS apps. | ||||
|  | ||||
| @ -115,17 +115,31 @@ async function exportFile() { | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was tested on an Intel Mac on 2023 April 01 with Capacitor 4.7.3 and | ||||
| `@capacitor/filesystem` 4.1.4 | ||||
| The Android demo was last tested on 2023 September 03 with CapacitorJS `5.3.0` | ||||
| and filesystem `5.1.3` on an emulated Pixel 3 + Android 13 ("Tiramisu") API 33. | ||||
| 
 | ||||
| The iOS simulator runs iOS 16.2 on an iPhone 14 Pro Max. | ||||
| 
 | ||||
| The Android simulator runs Android 12.0 (S) API 31 on a Pixel 3. | ||||
| The iOS demo was last tested on 2023 September 03 with CapacitorJS `5.3.0` | ||||
| and filesystem `5.1.3` on an emulated iPhone SE (3rd generation) + iOS 16.4. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### Base Project | ||||
| 
 | ||||
| 0) Follow the official "Environment Setup"[^1] instructions to set up Android | ||||
| and iOS targets | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| iOS development is only supported on macOS. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| <details><summary><b>Installation Notes</b> (click to show)</summary> | ||||
| 
 | ||||
| CapacitorJS requires Java 17. | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| 1) Disable telemetry. | ||||
| 
 | ||||
| ```bash | ||||
| @ -167,22 +181,63 @@ npm run build | ||||
| curl -o src/App.svelte -L https://docs.sheetjs.com/cap/App.svelte | ||||
| ``` | ||||
| 
 | ||||
| ### Android | ||||
| 
 | ||||
| 6) Create Android app | ||||
| 
 | ||||
| ```bash | ||||
| npm i --save @capacitor/android | ||||
| npx cap add android | ||||
| ``` | ||||
| 
 | ||||
| 7) Enable file reading and writing in the Android app. | ||||
| 
 | ||||
| The following lines must be added to `android/app/src/main/AndroidManifest.xml` | ||||
| after the `Permissions` comment: | ||||
| 
 | ||||
| ```xml title="android/app/src/main/AndroidManifest.xml (add to file)" | ||||
|     <!-- Permissions --> | ||||
| 
 | ||||
| <!-- highlight-start --> | ||||
|     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> | ||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||||
| <!-- highlight-end --> | ||||
| 
 | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
| ``` | ||||
| 
 | ||||
| 8) Run the app in the simulator | ||||
| 
 | ||||
| ```bash | ||||
| npm run build | ||||
| npx cap sync | ||||
| npx cap run android | ||||
| ``` | ||||
| 
 | ||||
| 9) Test the app | ||||
| 
 | ||||
| Open the app and observe that presidents are listed in the table. | ||||
| 
 | ||||
| Touch "Export XLSX" and the emulator will ask for permission: | ||||
| 
 | ||||
| Tap "Allow" and a popup will be displayed with a path. | ||||
| 
 | ||||
| To see the generated file, switch to the "Files" app in the simulator, tap the | ||||
| `≡` icon and tap "Documents". Tap "Documents" folder to find `SheetJSCap.xlsx`. | ||||
| 
 | ||||
| ### iOS | ||||
| 
 | ||||
| 6) Follow the [React Native demo](/docs/demos/mobile/reactnative#demo) to | ||||
| ensure the iOS simulator is ready. | ||||
| 
 | ||||
| 7) Create iOS app | ||||
| 10) Create iOS app | ||||
| 
 | ||||
| ```bash | ||||
| npm i --save @capacitor/ios | ||||
| npx cap add ios | ||||
| ``` | ||||
| 
 | ||||
| 8) Enable file sharing and make the documents folder visible in the iOS app. | ||||
| 11) Enable file sharing and make the documents folder visible in the iOS app. | ||||
| The following lines must be added to `ios/App/App/Info.plist`: | ||||
| 
 | ||||
| ```xml title="ios/App/App/Info.plist" | ||||
| ```xml title="ios/App/App/Info.plist (add to file)" | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
| <!-- highlight-start --> | ||||
| @ -196,7 +251,7 @@ The following lines must be added to `ios/App/App/Info.plist`: | ||||
| 
 | ||||
| (The root element of the document is `plist` and it contains one `dict` child) | ||||
| 
 | ||||
| 9) Run the app in the simulator | ||||
| 12) Run the app in the simulator | ||||
| 
 | ||||
| ```bash | ||||
| npm run build | ||||
| @ -204,7 +259,7 @@ npx cap sync | ||||
| npx cap run ios | ||||
| ``` | ||||
| 
 | ||||
| 10) Test the app | ||||
| 13) Test the app | ||||
| 
 | ||||
| Open the app and observe that presidents are listed in the table. | ||||
| 
 | ||||
| @ -213,48 +268,4 @@ Touch "Export XLSX" and a popup will be displayed. | ||||
| To see the generated file, switch to the "Files" app in the simulator and look | ||||
| for `SheetJSCap.xlsx` in "On My iPhone" > "`sheetjs-cap`" | ||||
| 
 | ||||
| ### Android | ||||
| 
 | ||||
| 11) Follow the [React Native demo](/docs/demos/mobile/reactnative#demo) to | ||||
| ensure the Android simulator is ready. | ||||
| 
 | ||||
| 12) Create Android app | ||||
| 
 | ||||
| ```bash | ||||
| npm i --save @capacitor/android | ||||
| npx cap add android | ||||
| ``` | ||||
| 
 | ||||
| 13) Enable file reading and writing in the Android app. | ||||
| The following lines must be added to `android/app/src/main/AndroidManifest.xml` | ||||
| after the `Permissions` comment: | ||||
| 
 | ||||
| ```xml title="android/app/src/main/AndroidManifest.xml" | ||||
|     <!-- Permissions --> | ||||
| 
 | ||||
| <!-- highlight-start --> | ||||
|     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> | ||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||||
| <!-- highlight-end --> | ||||
| 
 | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
| ``` | ||||
| 
 | ||||
| 14) Run the app in the simulator | ||||
| 
 | ||||
| ```bash | ||||
| npm run build | ||||
| npx cap sync | ||||
| npx cap run android | ||||
| ``` | ||||
| 
 | ||||
| 15) Test the app | ||||
| 
 | ||||
| Open the app and observe that presidents are listed in the table. | ||||
| 
 | ||||
| Touch "Export XLSX" and the emulator will ask for permission: | ||||
| 
 | ||||
| Tap "Allow" and a popup will be displayed with a path. | ||||
| 
 | ||||
| To see the generated file, switch to the "Files" app in the simulator, tap the | ||||
| `≡` icon and tap "Documents". Tap "Documents" folder to find `SheetJSCap.xlsx`. | ||||
| [^1]: See ["Environment Setup"](https://capacitorjs.com/docs/getting-started/environment-setup) in the CapacitorJS documentation. | ||||
| @ -197,12 +197,13 @@ class SheetJSFlutterState extends State<SheetJSFlutter> { | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was tested on an Intel Mac on 2023-08-18 with Flutter 3.13.0, Dart | ||||
| 3.1.0, and `flutter_js` 0.8.0. | ||||
| The Android demo was last tested on 2023 September 03 with Flutter `3.13.2`. The | ||||
| simulator used Android 13 ("Tiramisu") API 33 on a Pixel 3. | ||||
| 
 | ||||
| The iOS simulator runs iOS 16.2 on an iPhone 14 Pro Max. | ||||
| The iOS demo was last tested on 2023 September 03 with Flutter `3.13.2`. The | ||||
| simulator used iOS 16.4 on an iPhone SE (3rd generation). | ||||
| 
 | ||||
| The Android simulator runs Android 12.0 (S) API 31 on a Pixel 3. | ||||
| Both tests used Dart 3.1.0 and Flutter JS plugin version `0.8.0`. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -257,7 +258,7 @@ Run `flutter emulators` and check for both `ios` and `android` emulators: | ||||
| 
 | ||||
| ``` | ||||
| apple_ios_simulator • iOS Simulator  • Apple  • ios | ||||
| Pixel_3_API_31      • Pixel 3 API 31 • Google • android | ||||
| Pixel_3_API_33      • Pixel 3 API 33 • Google • android | ||||
| ``` | ||||
| 
 | ||||
| 1) Disable telemetry. | ||||
| @ -278,14 +279,76 @@ flutter create sheetjs_flutter | ||||
| cd sheetjs_flutter | ||||
| ``` | ||||
| 
 | ||||
| 3) Open the iOS simulator. | ||||
| 3) Start the Android emulator. | ||||
| 
 | ||||
| 4) While the iOS simulator is open, start the application: | ||||
| <details open><summary><b>Details</b> (click to hide)</summary> | ||||
| 
 | ||||
| **Android Studio** | ||||
| 
 | ||||
| In Android Studio, click "More actions" > "Virtual Device Manager". Look for the | ||||
| emulated device in the list and click the ▶ button to play. | ||||
| 
 | ||||
| **Command Line** | ||||
| 
 | ||||
| List the available emulators with `flutter emulators`: | ||||
| 
 | ||||
| ``` | ||||
| % flutter emulators | ||||
| 2 available emulators: | ||||
| 
 | ||||
| apple_ios_simulator • iOS Simulator  • Apple  • ios | ||||
| Pixel_3_API_33      • Pixel 3 API 33 • Google • android | ||||
| ^^^^^^^^^^^^^^--- the first column is the name | ||||
| ``` | ||||
| 
 | ||||
| The first column shows the name that should be passed to `emulator -avd`. In a | ||||
| previous test, the name was `Pixel_3_API_33` and the launch command was: | ||||
| 
 | ||||
| ``` | ||||
| % emulator -avd Pixel_3_API_33 | ||||
| ``` | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| 4) While the Android emulator is open, start the application: | ||||
| 
 | ||||
| ```bash | ||||
| flutter run | ||||
| ``` | ||||
| 
 | ||||
| <details><summary><b>If emulator is not detected</b> (click to show)</summary> | ||||
| 
 | ||||
| In some test runs, `flutter run` did not automatically detect the emulator. | ||||
| 
 | ||||
| Run `flutter -v -d sheetjs run` and the command will fail. Inspect the output: | ||||
| 
 | ||||
| ```text title="Command output" | ||||
| // highlight-next-line | ||||
| [   +6 ms] No supported devices found with name or id matching 'sheetjs'. | ||||
| [        ] The following devices were found: | ||||
| ... | ||||
| // highlight-next-line | ||||
| [  +26 ms] sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64  • Android 13 (API 33) (emulator) | ||||
| [        ] macOS (desktop)             • macos         • darwin-arm64   • macOS 13.5.1 22G90 darwin-arm64 | ||||
| ... | ||||
| ``` | ||||
| 
 | ||||
| Search the output for `sheetjs`. After that line, search for the emulator list. | ||||
| One of the lines will correspond to the running emulator: | ||||
| 
 | ||||
| ``` | ||||
| [  +26 ms] sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64  • Android 13 (API 33) (emulator) | ||||
|                                          ^^^^^^^^^^^^^--- the second column is the name | ||||
| ``` | ||||
| 
 | ||||
| The second column is the device name. Assuming the name is `emulator-5554`, run: | ||||
| 
 | ||||
| ```bash | ||||
| flutter -v -d emulator-5554 run | ||||
| ``` | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| Once the app loads, stop the terminal process and close the simulator. | ||||
| 
 | ||||
| 5) Install Flutter / Dart dependencies: | ||||
| @ -323,38 +386,11 @@ cd ..`} | ||||
| curl -L -o lib/main.dart https://docs.sheetjs.com/flutter/main.dart | ||||
| ``` | ||||
| 
 | ||||
| ### iOS | ||||
| 
 | ||||
| 9) Start the iOS simulator again. | ||||
| 
 | ||||
| 10) Launch the app: | ||||
| 
 | ||||
| ```bash | ||||
| flutter run | ||||
| ``` | ||||
| 
 | ||||
| The app fetches <https://sheetjs.com/pres.numbers>, parses, converts data to an | ||||
| array of arrays, and presents the data in a Flutter `Table` widget. | ||||
| 
 | ||||
| 11) Close the iOS simulator. | ||||
| 
 | ||||
| ### Android | ||||
| 
 | ||||
| 12) Start the Android emulator with `emulator -avd name_of_device`. The actual | ||||
| emulator name can be found with `flutter emulators`: | ||||
| 9) Start the Android emulator using the same instructions as Step 3. | ||||
| 
 | ||||
| ``` | ||||
| % flutter emulators | ||||
| 2 available emulators: | ||||
| 
 | ||||
| apple_ios_simulator • iOS Simulator  • Apple  • ios | ||||
| Pixel_3_API_31      • Pixel 3 API 31 • Google • android | ||||
| ^^^^^^^^^^^^^^--- the first column is the name | ||||
| 
 | ||||
| % emulator -avd Pixel_3_API_31 | ||||
| ``` | ||||
| 
 | ||||
| 13) Launch the app: | ||||
| 10) Launch the app: | ||||
| 
 | ||||
| ```bash | ||||
| flutter run | ||||
| @ -387,13 +423,27 @@ Searching for `minSdkVersion` should reveal the following line: | ||||
| 
 | ||||
| `flutter.minSdkVersion` should be replaced with `21`: | ||||
| 
 | ||||
| ```diff title="android\app\build.gradle (diff)" | ||||
| -        minSdkVersion flutter.minSdkVersion | ||||
| +        minSdkVersion 21 | ||||
| ```text title="android\app\build.gradle" | ||||
|         minSdkVersion 21 | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 11) Close the Android emulator. | ||||
| 
 | ||||
| ### iOS | ||||
| 
 | ||||
| 12) Start the iOS simulator. | ||||
| 
 | ||||
| 13) Launch the app: | ||||
| 
 | ||||
| ```bash | ||||
| flutter run | ||||
| ``` | ||||
| 
 | ||||
| The app fetches <https://sheetjs.com/pres.numbers>, parses, converts data to an | ||||
| array of arrays, and presents the data in a Flutter `Table` widget. | ||||
| 
 | ||||
| [^1]: <https://dart.dev/> is the official site for the Dart Programming Language. | ||||
| [^2]: <https://flutter.dev/> is the official site for the Flutter Framework. | ||||
| [^3]: [The `flutter_js` package](https://pub.dev/packages/flutter_js) is hosted on the Dart package repository. | ||||
|  | ||||
| @ -180,7 +180,15 @@ function exportFile(workbook) { | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was tested on 2023 February 20 with Electron 23.0.0. | ||||
| This demo was tested in the following environments: | ||||
| 
 | ||||
| | OS and Version | Arch | Electron | Date       | | ||||
| |:---------------|:-----|:---------|:-----------| | ||||
| | macOS 13.5.1   | ARM  | `26.1.0` | 2023-09-03 | | ||||
| | macOS 13.5.1   | x64  | `26.1.0` | 2023-09-03 | | ||||
| | Windows 10     | x64  | `26.1.0` | 2023-09-03 | | ||||
| | Linux (HoloOS) | x64  | `26.1.0` | 2023-09-03 | | ||||
| | Linux (Debian) | ARM  | `26.1.0` | 2023-09-03 | | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -218,7 +226,11 @@ curl -LO https://docs.sheetjs.com/electron/index.html | ||||
| curl -LO https://docs.sheetjs.com/electron/index.js | ||||
| ``` | ||||
| 
 | ||||
| 2) Run `npm i` to install dependencies. | ||||
| 2) Install dependencies: | ||||
| 
 | ||||
| ```bash | ||||
| npm install | ||||
| ``` | ||||
| 
 | ||||
| 3) To verify the app works, run in the test environment: | ||||
| 
 | ||||
| @ -226,8 +238,7 @@ curl -LO https://docs.sheetjs.com/electron/index.js | ||||
| 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. | ||||
| The app will show. | ||||
| 
 | ||||
| 4) To build a standalone app, run the builder: | ||||
| 
 | ||||
| @ -235,8 +246,52 @@ the relevant buttons to open files and clicking the export button. | ||||
| 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/` | ||||
| This will create a package in the `out\make` folder. | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| On Linux, the packaging step may require additional dependencies[^1] | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| **Testing** | ||||
| 
 | ||||
| 5) Download [the test file `pres.numbers`](https://sheetjs.com/pres.numbers) | ||||
| 
 | ||||
| 6) Re-launch the application in the test environment: | ||||
| 
 | ||||
| ```bash | ||||
| npx -y electron . | ||||
| ``` | ||||
| 
 | ||||
| _Electron API_ | ||||
| 
 | ||||
| 7) Click "Click here to select a file from your computer". With the file picker, | ||||
| navigate to the Downloads folder and select `pres.numbers`. | ||||
| 
 | ||||
| The application should show data in a table. | ||||
| 
 | ||||
| 8) Click "Export Data!" and click "Save" in the popup. By default, it will try | ||||
| to write to `Untitled.xls` in the Downloads folder. | ||||
| 
 | ||||
| The app will show a popup once the data is exported. Open the file in a | ||||
| spreadsheet editor and compare the data to the table shown in the application. | ||||
| 
 | ||||
| _Drag and Drop_ | ||||
| 
 | ||||
| 9) Close the application, end the terminal process and re-launch (see step 6) | ||||
| 
 | ||||
| 10) Open the Downloads folder in a file explorer or finder window. | ||||
| 
 | ||||
| 11) Click and drag the `pres.numbers` file from the Downloads folder to the | ||||
| bordered "Drop a spreadsheet file" box. The file data should be displayed. | ||||
| 
 | ||||
| _File Input Element_ | ||||
| 
 | ||||
| 12) Close the application, end the terminal process and re-launch (see step 6) | ||||
| 
 | ||||
| 13) Click "Choose File". With the file picker, navigate to the Downloads folder | ||||
| and select `pres.numbers`. | ||||
| 
 | ||||
| 
 | ||||
| ## Electron Breaking Changes | ||||
| @ -263,3 +318,5 @@ Electron 14+ must use `@electron/remote` instead of `remote`.  An `initialize` | ||||
| call is required to enable Developer Tools in the window. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| [^1]: See ["Makers"](https://www.electronforge.io/config/makers) in the Electron Forge documentation. | ||||
| @ -1,5 +1,6 @@ | ||||
| --- | ||||
| title: PouchDB | ||||
| title: Sheets in PouchDB | ||||
| sidebar_label: PouchDB | ||||
| pagination_prev: demos/desktop/index | ||||
| pagination_next: demos/local/index | ||||
| sidebar_custom_props: | ||||
| @ -9,15 +10,79 @@ sidebar_custom_props: | ||||
| import current from '/version.js'; | ||||
| import CodeBlock from '@theme/CodeBlock'; | ||||
| 
 | ||||
| PouchDB is a pure JS database with built-in synchronization features. | ||||
| [PouchDB](https://pouchdb.com/) is a pure JavaScript database with built-in | ||||
| synchronization features and offline support. | ||||
| 
 | ||||
| [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing | ||||
| data from spreadsheets. | ||||
| 
 | ||||
| This demo uses PouchDB and SheetJS to export database snapshots to spreadsheets | ||||
| and import bulk data from workbooks. We'll explore the subtleties of processing | ||||
| arrays of objects to mesh with both libraries. | ||||
| 
 | ||||
| The ["Complete Example"](#complete-example) section imbues the official "Todos" | ||||
| demo with the ability to export the list to XLSX workbooks. | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was last tested on 2023 September 04 against PouchDB 8.0.1 standalone | ||||
| browser script in Chrome 116. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## Integration Details | ||||
| 
 | ||||
| `Database#allDocs` is the standard approach for bulk data export. The generated | ||||
| SheetJS CE offers standalone scripts, NodeJS modules, ESM modules, and other | ||||
| scripts. The ["Installation"](/docs/getting-started/installation) section covers | ||||
| a number of common deployment scenarios. | ||||
| 
 | ||||
| PouchDB ships with standalone scripts for browser use and NodeJS modules for use | ||||
| in server-side scripts[^1]. | ||||
| 
 | ||||
| The `PouchDB` constructor returns a `Database` object. | ||||
| 
 | ||||
| #### Importing Data | ||||
| 
 | ||||
| `Database#bulkDocs`[^2] is the standard approach for bulk data import. The method | ||||
| accepts "arrays of objects" that can be generated through the SheetJS | ||||
| `sheet_to_json`[^3] method. | ||||
| 
 | ||||
| If rows do not include the `_id` parameter, the database will automatically | ||||
| assign an ID per row. It is strongly recommended to generate the `_id` directly. | ||||
| 
 | ||||
| This method starts from a SheetJS workbook object[^4] and uses data from the | ||||
| first sheet. `read` and `readFile`[^5] can generate workbook objects from files. | ||||
| 
 | ||||
| ```js | ||||
| async function push_first_sheet_to_pouchdb(db, wb, _id_) { | ||||
|   /* get first worksheet */ | ||||
|   const ws = wb.Sheets[wb.SheetNames[0]]; | ||||
| 
 | ||||
|   /* generate array of arrays */ | ||||
|   const aoo = XLSX.utils.sheet_to_json(ws); | ||||
| 
 | ||||
|   /* if a prefix is specified, add a unique _id to each row based on index */ | ||||
|   if(typeof _id_ == "string") aoo.forEach((row, idx) => row._id = _id_ + idx); | ||||
| 
 | ||||
|   /* perform query */ | ||||
|   return await db.bulkDocs(aoo); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| Existing data can be erased with `Database#destroy`. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| #### Exporting Data | ||||
| 
 | ||||
| `Database#allDocs`[^6] is the standard approach for bulk data export. Generated | ||||
| row objects have additional `_id` and `_rev` keys that should be removed. | ||||
| 
 | ||||
| Nested objects must be flattened. The ["Export Tutorial"](/docs/getting-started/examples/export) | ||||
| includes an example of constructing a simple array. | ||||
| After removing the PouchDB internal fields, the SheetJS `json_to_sheet`[^7] | ||||
| method can generate a worksheet. Other utility functions[^8] can construct a | ||||
| workbook. The workbook can be exported with the SheetJS `writeFile`[^9] method: | ||||
| 
 | ||||
| ```js | ||||
| function export_pouchdb_to_xlsx(db) { | ||||
| @ -42,6 +107,17 @@ function export_pouchdb_to_xlsx(db) { | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| `json_to_sheet` expects an array of "flattened" objects where each value is a | ||||
| simple data type that can be stored in a spreadsheet cell. If document objects | ||||
| have a nested structure, integration code should post-process the data. | ||||
| 
 | ||||
| ["Export Tutorial"](/docs/getting-started/examples/export#reshaping-the-array) | ||||
| processes data from an API and computes a few text values from the nested data. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## Complete Example | ||||
| 
 | ||||
| 0) Download the "Working Version" from the Getting Started guide. | ||||
| @ -75,16 +151,28 @@ cd getting-started-todo-master | ||||
|     <section id="todoapp">`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 3) Just before the end of `app.js`, add a `click` event listener: | ||||
| 3) Near the end of `index.html`, look for a script tag referencing a CDN: | ||||
| 
 | ||||
| ```js title="app.js" | ||||
| ```html title="index.html" | ||||
|     <script src="//cdn.jsdelivr.net/pouchdb/3.2.0/pouchdb.min.js"></script> | ||||
| ``` | ||||
| 
 | ||||
| Upgrade PouchDB by changing the `src` attribute to the production build[^10]: | ||||
| 
 | ||||
| ```html | ||||
|     <script src="//cdn.jsdelivr.net/npm/pouchdb@8.0.1/dist/pouchdb.min.js"></script> | ||||
| ``` | ||||
| 
 | ||||
| 4) Just before the end of `js/app.js`, add a `click` event listener: | ||||
| 
 | ||||
| ```js title="js/app.js" | ||||
|   if (remoteCouch) { | ||||
|     sync(); | ||||
|   } | ||||
| 
 | ||||
|   // highlight-start | ||||
|   document.getElementById("xport").addEventListener("click", function() { | ||||
|     db.allDocs({include_docs: true}, function(err, doc) { | ||||
|     db.allDocs({include_docs: true, descending: true}, function(err, doc) { | ||||
|       const aoo = doc.rows.map(r => { | ||||
|         const { _id, _rev, ... rest } = r.doc; | ||||
|         return rest; | ||||
| @ -98,11 +186,57 @@ cd getting-started-todo-master | ||||
| })(); | ||||
| ``` | ||||
| 
 | ||||
| 4) Start a local web server: | ||||
| :::info pass | ||||
| 
 | ||||
| The demo UI reads the todo items in descending order: | ||||
| 
 | ||||
| ```js title="js/app.js" | ||||
|     //------------------------------VVVVVVVVVVVVVVVV (descending order) | ||||
|     db.allDocs({include_docs: true, descending: true}, function(err, doc) { | ||||
|       redrawTodosUI(doc.rows); | ||||
|     }); | ||||
| ``` | ||||
| 
 | ||||
| The new callback function also specifies `descending: true` to ensure that the | ||||
| order of todo items in the export matches the list displayed in the webpage. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 5) Start a local web server: | ||||
| 
 | ||||
| ```bash | ||||
| npx http-server . | ||||
| ``` | ||||
| 
 | ||||
| Access `http://localhost:8080` from your browser.  Add a few items and click | ||||
| the "Export!" button to generate a new file. | ||||
| The command will display a URL (typically `http://localhost:8080`) which can be | ||||
| opened in a web browser. | ||||
| 
 | ||||
| **Testing** | ||||
| 
 | ||||
| 6) Access the URL from step 5 with a web browser. | ||||
| 
 | ||||
| 7) Add two items "Sheet" and "js". Mark "Sheet" as completed. The page should | ||||
| look like the following screenshot: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| 8) Click the "Export!" text at the top of the page. The site should create an | ||||
| export named "SheetJSPouch.xlsx" | ||||
| 
 | ||||
| 9) Open the file in a spreadsheet editor. It should match the following table: | ||||
| 
 | ||||
| | title | completed | | ||||
| |:------|:----------| | ||||
| | Sheet |   TRUE    | | ||||
| | js    |   FALSE   | | ||||
| 
 | ||||
| [^1]: See ["Setting up PouchDB"](https://pouchdb.com/guides/setup-pouchdb.html) in the PouchDB documentation. | ||||
| [^2]: See ["Create/update a batch of documents"](https://pouchdb.com/api.html#batch_create) in the PouchDB API documentation | ||||
| [^3]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output) | ||||
| [^4]: See ["SheetJS Data Model"](/docs/csf) | ||||
| [^5]: See [`read` in "Reading Files"](/docs/api/parse-options) | ||||
| [^6]: See ["Fetch a batch of documents"](https://pouchdb.com/api.html#batch_fetch) in the PouchDB API documentation | ||||
| [^7]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input) | ||||
| [^8]: See ["Workbook Helpers" in "Utilities"](/docs/api/utilities/wb) for details on `book_new` and `book_append_sheet`. | ||||
| [^9]: See [`writeFile` in "Writing Files"](/docs/api/write-options) | ||||
| [^10]: The ["Quick Start" section of "Download"](https://pouchdb.com/download.html#file) in the PouchDB website describes the recommended CDN for PouchDB scripts. | ||||
| @ -7,13 +7,28 @@ pagination_next: demos/extensions/index | ||||
| import current from '/version.js'; | ||||
| import CodeBlock from '@theme/CodeBlock'; | ||||
| 
 | ||||
| [Airtable](https://airtable.com/) is a collaborative dataset hosting service. | ||||
| 
 | ||||
| [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing | ||||
| data from spreadsheets. | ||||
| 
 | ||||
| This demo uses SheetJS to properly exchange data with spreadsheet files. We'll | ||||
| explore how to use the `airtable` NodeJS library and SheetJS in two data flows: | ||||
| 
 | ||||
| - "Exporting data": Data in Airtable will be pulled into an array of objects and | ||||
| exported to XLSB spreadsheets using SheetJS libraries. | ||||
| 
 | ||||
| - "Importing data": Data in an XLSX spreadsheet will be parsed using SheetJS | ||||
| libraries and appended to a dataset in Airtable | ||||
| 
 | ||||
| 
 | ||||
| ## NodeJS Integration | ||||
| 
 | ||||
| Airtable recommends Personal Access Tokens for interacting with their API. When | ||||
| fetching data from the API, the result will include an array of row objects that | ||||
| can be converted to a worksheet with `XLSX.utils.json_to_sheet`. The API methods | ||||
| to write data will accept row objects generated by `XLSX.utils.sheet_to_json`. | ||||
| 
 | ||||
| ## NodeJS Integration | ||||
| 
 | ||||
| The main module is `airtable` and can be installed with `npm`: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| @ -52,7 +67,7 @@ async function airtable_to_worksheet(table) { | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| :::caution | ||||
| :::caution pass | ||||
| 
 | ||||
| The results are not guaranteed to be sorted.  The official API includes options | ||||
| for sorting by fields. | ||||
| @ -83,16 +98,16 @@ async function airtable_load_worksheet(table, worksheet) { | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was last tested on 2023 February 15.  At the time, it was possible to | ||||
| create a free account with API access. | ||||
| This demo was last tested on 2023 September 03. At the time, free accounts | ||||
| included limited API access. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 0) Create a free Airtable account. | ||||
| 0) Create a free Airtable account and verify the email address. | ||||
| 
 | ||||
| ### Personal Access Token | ||||
| 
 | ||||
| :::note | ||||
| :::note pass | ||||
| 
 | ||||
| In the past, Airtable offered API keys.  They are slated to deprecate API keys | ||||
| in January 2024. They recommend "Personal Access Tokens" for operations. | ||||
| @ -137,9 +152,9 @@ the first part after the `.com` will be the workspace name. | ||||
| 
 | ||||
| ### Exporting Data | ||||
| 
 | ||||
| 8) Save the following to `read.js`: | ||||
| 8) Save the following to `SheetJSAirtableRead.js`: | ||||
| 
 | ||||
| ```js title="read.js" | ||||
| ```js title="SheetJSAirtableRead.js" | ||||
| const Airtable = require("airtable"), XLSX = require("xlsx"); | ||||
| // highlight-start | ||||
| /* replace the value with the personal access token */ | ||||
| @ -161,12 +176,23 @@ const base = "app..."; | ||||
| 
 | ||||
| 9) Replace the values in the highlighted lines with the PAT and workspace name. | ||||
| 
 | ||||
| 10) Run `node read.js`.  The script should write `SheetJSAirtable.xlsb`. The file | ||||
| can be opened in Excel. | ||||
| 10) Install dependencies: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz airtable`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 11) Run the script: | ||||
| 
 | ||||
| ```bash | ||||
| node SheetJSAirtableRead.js | ||||
| ``` | ||||
| 
 | ||||
| The script should write `SheetJSAirtable.xlsb`. The file can be opened in Excel. | ||||
| 
 | ||||
| ### Importing Data | ||||
| 
 | ||||
| 11) Create a file `SheetJSAirpend.xlsx` with some new records in sheet `Sheet1`: | ||||
| 12) Create a file `SheetJSAirpend.xlsx` with some new records in sheet `Sheet1`: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| @ -178,9 +204,9 @@ Name,Index | ||||
| Someone Else,47 | ||||
| ``` | ||||
| 
 | ||||
| 12) Save the following to `write.js`: | ||||
| 13) Save the following to `SheetJSAirtableWrite.js`: | ||||
| 
 | ||||
| ```js title="write.js" | ||||
| ```js title="SheetJSAirtableWrite.js" | ||||
| const Airtable = require("airtable"), XLSX = require("xlsx"); | ||||
| // highlight-start | ||||
| /* replace the value with the personal access token */ | ||||
| @ -198,8 +224,20 @@ const base = "app..."; | ||||
| })(); | ||||
| ``` | ||||
| 
 | ||||
| 13) Replace the values in the highlighted lines with the PAT and workspace name. | ||||
| 14) Replace the values in the highlighted lines with the PAT and workspace name. | ||||
| 
 | ||||
| 14) Run `node write.js`.  Open Airtable and verify the new row: | ||||
| 15) Install dependencies: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz airtable`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 16) Run the script: | ||||
| 
 | ||||
| ```bash | ||||
| node SheetJSAirtableWrite.js | ||||
| ``` | ||||
| 
 | ||||
| Open Airtable and verify the new row was added: | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										22
									
								
								docz/docs/03-demos/10-extensions/03-excelapi.md → docz/docs/03-demos/32-extensions/03-excelapi.md
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										22
									
								
								docz/docs/03-demos/10-extensions/03-excelapi.md → docz/docs/03-demos/32-extensions/03-excelapi.md
									
									
									
									
									
								
							| @ -7,7 +7,7 @@ pagination_next: demos/bigdata/index | ||||
| import current from '/version.js'; | ||||
| import CodeBlock from '@theme/CodeBlock'; | ||||
| 
 | ||||
| :::info | ||||
| :::info pass | ||||
| 
 | ||||
| This demo focuses on the JavaScript API included with Excel. For reading and | ||||
| writing Excel files, [other demos](/docs/demos/) cover a wide variety of use cases | ||||
| @ -30,7 +30,7 @@ Function parameters are covered in the official Office JavaScript API docs. | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was last tested on 2023 April 20 against Excel 365 (version 2303) | ||||
| This demo was last tested on 2023 September 03 against Excel 365 (version 2308) | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -87,7 +87,17 @@ async function extern(url) { | ||||
| 
 | ||||
| - Open File Explorer | ||||
| - Select the address bar and enter `%LOCALAPPDATA%\Microsoft\Office\16.0\Wef` | ||||
| - Delete `CustomFunctions` and empty Recycle Bin. | ||||
| - Delete the `CustomFunctions` folder (if it exists) and empty Recycle Bin. | ||||
| 
 | ||||
| :::caution pass | ||||
| 
 | ||||
| **This will delete all custom functions associated with the user account!** | ||||
| 
 | ||||
| To preserve the custom functions on the user account, rename the existing folder | ||||
| to `CustomFunctionsBackup` before testing and rename back to `CustomFunctions` | ||||
| after testing is finished. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 1) Install [NodeJS LTS](https://nodejs.org/en/download/). | ||||
| 
 | ||||
| @ -158,7 +168,7 @@ function version() { | ||||
| Excel window (do not save the Excel file).  Re-run `npm start`. | ||||
| 
 | ||||
| 10) In the new Excel window, enter the formula `=SHEETJS.VERSION()` in cell | ||||
| `E1`. You should see something similar to the following screenshot: | ||||
| `D1`. You should see something similar to the following screenshot: | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| @ -168,7 +178,7 @@ This indicates that the SheetJS library has been loaded. | ||||
| 
 | ||||
| 11) Add the following code snippet to `src\functions\functions.js`: | ||||
| 
 | ||||
| ```js src\functions\functions.js | ||||
| ```js title="src\functions\functions.js (add to end)" | ||||
| /** | ||||
|  * Download file and write data | ||||
|  * @customfunction | ||||
| @ -201,7 +211,7 @@ Excel window (do not save the Excel file).  Re-run `npm start`. | ||||
| formula `=SHEETJS.EXTERN(D1)` in cell `D2` and press Enter.  Excel should pull | ||||
| in the data and generate a dynamic array. | ||||
| 
 | ||||
| :::note | ||||
| :::tip pass | ||||
| 
 | ||||
| [SheetJS Pro](https://sheetjs.com/pro) offers additional features that can be | ||||
| used in Excel Custom Functions and Add-ins | ||||
| @ -20,7 +20,7 @@ available as a global. A JS stub can expose methods from AppleScript scripts. | ||||
| 
 | ||||
| :::note | ||||
| 
 | ||||
| This demo was last tested on 2023 April 18 in macOS Monterey. | ||||
| This demo was last tested on 2023-09-03 in macOS Ventura. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
							
								
								
									
										2
									
								
								docz/docs/03-demos/10-extensions/_category_.json → docz/docs/03-demos/32-extensions/_category_.json
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								docz/docs/03-demos/10-extensions/_category_.json → docz/docs/03-demos/32-extensions/_category_.json
									
									
									
									
									
								
							| @ -1,4 +1,4 @@ | ||||
| { | ||||
|   "label": "App Extensions", | ||||
|   "position": 10 | ||||
|   "position": 32 | ||||
| } | ||||
| @ -679,7 +679,7 @@ cl /MT /I..\v8\v8\ /I..\v8\v8\include sheetjs.v8.cc /GR- v8_monolith.lib Advapi3 | ||||
|   <TabItem value="win" label="Windows"> | ||||
| 
 | ||||
| ```bash | ||||
| cl /MT /I..\v8\v8\ /I..\v8\v8\include sheetjs.v8.cc /GR- v8_monolith.lib Advapi32.lib Winmm.lib Dbghelp.lib /std:c++17 /DV8_COMPRESS_POINTERS=1 /DV8_ENABLE_SANDBOX /link /out:sheetjs.v8.exe /LIBPATH:..\v8\v8\out.gn\x64.release.sample\obj\ | ||||
| .\sheetjs.v8.exe pres.numbers | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|  | ||||
| @ -1,10 +1,12 @@ | ||||
| { | ||||
|   "name": "sheetjs-electron", | ||||
|   "author": "sheetjs", | ||||
|   "description": "Spreadsheet Data Import and Export with SheetJS + Electron", | ||||
|   "license": "Apache-2.0", | ||||
|   "version": "0.0.0", | ||||
|   "main": "main.js", | ||||
|   "dependencies": { | ||||
|     "@electron/remote": "2.0.9", | ||||
|     "@electron/remote": "2.0.11", | ||||
|     "xlsx": "https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz" | ||||
|   }, | ||||
|   "scripts": { | ||||
| @ -13,12 +15,12 @@ | ||||
|     "make": "electron-forge make" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@electron-forge/cli": "6.0.5", | ||||
|     "@electron-forge/maker-deb": "6.0.5", | ||||
|     "@electron-forge/maker-rpm": "6.0.5", | ||||
|     "@electron-forge/maker-squirrel": "6.0.5", | ||||
|     "@electron-forge/maker-zip": "6.0.5", | ||||
|     "electron": "23.0.0" | ||||
|     "@electron-forge/cli": "6.4.1", | ||||
|     "@electron-forge/maker-deb": "6.4.1", | ||||
|     "@electron-forge/maker-rpm": "6.4.1", | ||||
|     "@electron-forge/maker-squirrel": "6.4.1", | ||||
|     "@electron-forge/maker-zip": "6.4.1", | ||||
|     "electron": "26.1.0" | ||||
|   }, | ||||
|   "config": { | ||||
|     "forge": { | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								docz/static/pouchdb/todos.png
									
									
									
									
									
										Normal file
									
								
							
							
								
									
								
								
								
								
								
									
									
								
							
						
						
									
										
											BIN
										
									
								
								docz/static/pouchdb/todos.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 310 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 6.5 KiB | 
		Loading…
	
		Reference in New Issue
	
	Block a user