From 610b899f6621acbf85abc7f361a1ea59357a2714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Tue, 28 Jan 2025 20:08:16 +0100 Subject: [PATCH] UPDATE: Add raw API to nodejs bindings --- Cargo.lock | 2 +- base/src/workbook.rs | 2 +- bindings/nodejs/Cargo.toml | 2 +- bindings/nodejs/__test__/index.spec.mjs | 15 +- bindings/nodejs/example.xlsx | Bin 0 -> 41197 bytes bindings/nodejs/index.d.ts | 40 +- bindings/nodejs/index.js | 3 +- bindings/nodejs/main.mjs | 33 +- bindings/nodejs/package.json | 2 +- bindings/nodejs/src/lib.rs | 623 +----------------------- bindings/nodejs/src/model.rs | 343 +++++++++++++ bindings/nodejs/src/user_model.rs | 620 +++++++++++++++++++++++ 12 files changed, 1049 insertions(+), 636 deletions(-) create mode 100644 bindings/nodejs/example.xlsx create mode 100644 bindings/nodejs/src/model.rs create mode 100644 bindings/nodejs/src/user_model.rs diff --git a/Cargo.lock b/Cargo.lock index 89ddd52..0f61571 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,7 +448,7 @@ dependencies = [ [[package]] name = "ironcalc_nodejs" -version = "0.3.0" +version = "0.3.1" dependencies = [ "ironcalc", "napi", diff --git a/base/src/workbook.rs b/base/src/workbook.rs index 05e05e9..841537d 100644 --- a/base/src/workbook.rs +++ b/base/src/workbook.rs @@ -29,7 +29,7 @@ impl Workbook { } /// Returns the a list of defined names in the workbook with their scope - pub(crate) fn get_defined_names_with_scope(&self) -> Vec<(String, Option, String)> { + pub fn get_defined_names_with_scope(&self) -> Vec<(String, Option, String)> { let sheet_id_index: Vec = self.worksheets.iter().map(|s| s.sheet_id).collect(); let defined_names = self diff --git a/bindings/nodejs/Cargo.toml b/bindings/nodejs/Cargo.toml index fecdb92..ac1f61b 100644 --- a/bindings/nodejs/Cargo.toml +++ b/bindings/nodejs/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "ironcalc_nodejs" -version = "0.3.0" +version = "0.3.1" [lib] crate-type = ["cdylib"] diff --git a/bindings/nodejs/__test__/index.spec.mjs b/bindings/nodejs/__test__/index.spec.mjs index 486aba0..b52a85b 100644 --- a/bindings/nodejs/__test__/index.spec.mjs +++ b/bindings/nodejs/__test__/index.spec.mjs @@ -1,11 +1,20 @@ import test from 'ava' -import { Model } from '../index.js'; +import { UserModel, Model } from '../index.js'; -test('sum from native', (t) => { - const model = new Model("Workbook1", "en", "UTC"); +test('User Model smoke test', (t) => { + const model = new UserModel("Workbook1", "en", "UTC"); model.setUserInput(0, 1, 1, "=1+1"); t.is(model.getFormattedCellValue(0, 1, 1), '2'); }); + +test('Raw API smoke test', (t) => { + const model = new Model("Workbook1", "en", "UTC"); + + model.setUserInput(0, 1, 1, "=1+1"); + model.evaluate(); + t.is(model.getFormattedCellValue(0, 1, 1), '2'); +}); + diff --git a/bindings/nodejs/example.xlsx b/bindings/nodejs/example.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..99c93f4c40415c87a2c21c7630710efda1d47834 GIT binary patch literal 41197 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 literal 0 HcmV?d00001 diff --git a/bindings/nodejs/index.d.ts b/bindings/nodejs/index.d.ts index d84b8a4..8cd16e4 100644 --- a/bindings/nodejs/index.d.ts +++ b/bindings/nodejs/index.d.ts @@ -5,7 +5,45 @@ export declare class Model { constructor(name: string, locale: string, timezone: string) - static fromBytes(bytes: Uint8Array): Model + static fromXlsx(filePath: string, locale: string, tz: string): Model + static fromIcalc(fileName: string): Model + saveToXlsx(file: string): void + saveToIcalc(file: string): void + evaluate(): void + setUserInput(sheet: number, row: number, column: number, value: string): void + clearCellContents(sheet: number, row: number, column: number): void + getCellContent(sheet: number, row: number, column: number): string + getCellType(sheet: number, row: number, column: number): number + getFormattedCellValue(sheet: number, row: number, column: number): string + setCellStyle(sheet: number, row: number, column: number, style: unknown): void + getCellStyle(sheet: number, row: number, column: number): unknown + insertRows(sheet: number, row: number, rowCount: number): void + insertColumns(sheet: number, column: number, columnCount: number): void + deleteRows(sheet: number, row: number, rowCount: number): void + deleteColumns(sheet: number, column: number, columnCount: number): void + getColumnWidth(sheet: number, column: number): number + getRowHeight(sheet: number, row: number): number + setColumnWidth(sheet: number, column: number, width: number): void + setRowHeight(sheet: number, row: number, height: number): void + getFrozenColumnsCount(sheet: number): number + getFrozenRowsCount(sheet: number): number + setFrozenColumnsCount(sheet: number, columnCount: number): void + setFrozenRowsCount(sheet: number, rowCount: number): void + getWorksheetsProperties(): unknown + setSheetColor(sheet: number, color: string): void + addSheet(sheetName: string): void + newSheet(): void + deleteSheet(sheet: number): void + renameSheet(sheet: number, newName: string): void + getDefinedNameList(): unknown + newDefinedName(name: string, scope: number | undefined | null, formula: string): void + updateDefinedName(name: string, scope: number | undefined | null, newName: string, newScope: number | undefined | null, newFormula: string): void + deleteDefinedName(name: string, scope?: number | undefined | null): void + testPanic(): void +} +export declare class UserModel { + constructor(name: string, locale: string, timezone: string) + static fromBytes(bytes: Uint8Array): UserModel canUndo(): boolean canRedo(): boolean pauseEvaluation(): void diff --git a/bindings/nodejs/index.js b/bindings/nodejs/index.js index 80c7287..df67155 100644 --- a/bindings/nodejs/index.js +++ b/bindings/nodejs/index.js @@ -310,6 +310,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { Model } = nativeBinding +const { Model, UserModel } = nativeBinding module.exports.Model = Model +module.exports.UserModel = UserModel diff --git a/bindings/nodejs/main.mjs b/bindings/nodejs/main.mjs index 7efa969..b2f09b0 100644 --- a/bindings/nodejs/main.mjs +++ b/bindings/nodejs/main.mjs @@ -1,11 +1,28 @@ -import { Model } from './index.js' - -const model = new Model("Workbook1", "en", "UTC"); +import { UserModel, Model } from './index.js' -model.setUserInput(0, 1, 1, "=1+1"); -let t = model.getFormattedCellValue(0, 1, 1); -console.log('From native', t); +function testUserModel() { + const model = new UserModel("Workbook1", "en", "UTC"); -let t2 = model.getCellStyle(0, 1, 1); -console.log('From native', t2); \ No newline at end of file + model.setUserInput(0, 1, 1, "=1+1"); + let t = model.getFormattedCellValue(0, 1, 1); + + console.log('From native', t); + + let t2 = model.getCellStyle(0, 1, 1); + console.log('From native', t2); +} + +function testModel() { + const model = Model.fromXlsx("example.xlsx", "en", "UTC"); + const style = model.getCellStyle(0, 1, 6); + console.log(style); + + const quantum = model.getFormattedCellValue(0, 14, 4); + console.log(quantum); + +} + + +testUserModel(); +testModel(); \ No newline at end of file diff --git a/bindings/nodejs/package.json b/bindings/nodejs/package.json index 5b001e1..4ea747c 100644 --- a/bindings/nodejs/package.json +++ b/bindings/nodejs/package.json @@ -1,6 +1,6 @@ { "name": "@ironcalc/nodejs", - "version": "0.3.0", + "version": "0.3.1", "main": "index.js", "types": "index.d.ts", "napi": { diff --git a/bindings/nodejs/src/lib.rs b/bindings/nodejs/src/lib.rs index 57b7c38..5d3fe21 100644 --- a/bindings/nodejs/src/lib.rs +++ b/bindings/nodejs/src/lib.rs @@ -1,623 +1,8 @@ -#![deny(clippy::all)] - -use serde::Serialize; - #[macro_use] extern crate napi_derive; -use napi::{self, bindgen_prelude::*, JsUnknown, Result}; +mod model; +mod user_model; -use ironcalc::base::{ - expressions::types::Area, - types::{CellType, Style}, - BorderArea, ClipboardData, UserModel as BaseModel, -}; - -#[derive(Serialize)] -struct DefinedName { - name: String, - scope: Option, - formula: String, -} - -fn to_js_error(error: String) -> Error { - Error::new(Status::Unknown, error) -} - -#[napi] -pub struct Model { - model: BaseModel, -} - -#[napi] -impl Model { - #[napi(constructor)] - pub fn new(name: String, locale: String, timezone: String) -> Result { - let model = BaseModel::new_empty(&name, &locale, &timezone).map_err(to_js_error)?; - Ok(Self { model }) - } - - #[napi(factory)] - pub fn from_bytes(bytes: &[u8]) -> Result { - let model = BaseModel::from_bytes(bytes).map_err(to_js_error)?; - Ok(Model { model }) - } - - pub fn undo(&mut self) -> Result<()> { - self.model.undo().map_err(to_js_error) - } - - pub fn redo(&mut self) -> Result<()> { - self.model.redo().map_err(to_js_error) - } - - #[napi(js_name = "canUndo")] - pub fn can_undo(&self) -> bool { - self.model.can_undo() - } - - #[napi(js_name = "canRedo")] - pub fn can_redo(&self) -> bool { - self.model.can_redo() - } - - #[napi(js_name = "pauseEvaluation")] - pub fn pause_evaluation(&mut self) { - self.model.pause_evaluation() - } - - #[napi(js_name = "resumeEvaluation")] - pub fn resume_evaluation(&mut self) { - self.model.resume_evaluation() - } - - pub fn evaluate(&mut self) { - self.model.evaluate(); - } - - #[napi(js_name = "flushSendQueue")] - pub fn flush_send_queue(&mut self) -> Vec { - self.model.flush_send_queue() - } - - #[napi(js_name = "applyExternalDiffs")] - pub fn apply_external_diffs(&mut self, diffs: &[u8]) -> Result<()> { - self.model.apply_external_diffs(diffs).map_err(to_js_error) - } - - #[napi(js_name = "getCellContent")] - pub fn get_cell_content(&self, sheet: u32, row: i32, column: i32) -> Result { - self - .model - .get_cell_content(sheet, row, column) - .map_err(to_js_error) - } - - #[napi(js_name = "newSheet")] - pub fn new_sheet(&mut self) -> Result<()> { - self.model.new_sheet().map_err(to_js_error) - } - - #[napi(js_name = "deleteSheet")] - pub fn delete_sheet(&mut self, sheet: u32) -> Result<()> { - self.model.delete_sheet(sheet).map_err(to_js_error) - } - - #[napi(js_name = "hideSheet")] - pub fn hide_sheet(&mut self, sheet: u32) -> Result<()> { - self.model.hide_sheet(sheet).map_err(to_js_error) - } - - #[napi(js_name = "unhideSheet")] - pub fn unhide_sheet(&mut self, sheet: u32) -> Result<()> { - self.model.unhide_sheet(sheet).map_err(to_js_error) - } - - #[napi(js_name = "renameSheet")] - pub fn rename_sheet(&mut self, sheet: u32, name: String) -> Result<()> { - self.model.rename_sheet(sheet, &name).map_err(to_js_error) - } - - #[napi(js_name = "setSheetColor")] - pub fn set_sheet_color(&mut self, sheet: u32, color: String) -> Result<()> { - self - .model - .set_sheet_color(sheet, &color) - .map_err(to_js_error) - } - - #[napi(js_name = "rangeClearAll")] - pub fn range_clear_all( - &mut self, - sheet: u32, - start_row: i32, - start_column: i32, - end_row: i32, - end_column: i32, - ) -> Result<()> { - let range = Area { - sheet, - row: start_row, - column: start_column, - width: end_column - start_column + 1, - height: end_row - start_row + 1, - }; - self.model.range_clear_all(&range).map_err(to_js_error) - } - - #[napi(js_name = "rangeClearContents")] - pub fn range_clear_contents( - &mut self, - sheet: u32, - start_row: i32, - start_column: i32, - end_row: i32, - end_column: i32, - ) -> Result<()> { - let range = Area { - sheet, - row: start_row, - column: start_column, - width: end_column - start_column + 1, - height: end_row - start_row + 1, - }; - self.model.range_clear_contents(&range).map_err(to_js_error) - } - - #[napi(js_name = "insertRow")] - pub fn insert_row(&mut self, sheet: u32, row: i32) -> Result<()> { - self.model.insert_row(sheet, row).map_err(to_js_error) - } - - #[napi(js_name = "insertColumn")] - pub fn insert_column(&mut self, sheet: u32, column: i32) -> Result<()> { - self.model.insert_column(sheet, column).map_err(to_js_error) - } - - #[napi(js_name = "deleteRow")] - pub fn delete_row(&mut self, sheet: u32, row: i32) -> Result<()> { - self.model.delete_row(sheet, row).map_err(to_js_error) - } - - #[napi(js_name = "deleteColumn")] - pub fn delete_column(&mut self, sheet: u32, column: i32) -> Result<()> { - self.model.delete_column(sheet, column).map_err(to_js_error) - } - - #[napi(js_name = "setRowHeight")] - pub fn set_row_height(&mut self, sheet: u32, row: i32, height: f64) -> Result<()> { - self - .model - .set_row_height(sheet, row, height) - .map_err(to_js_error) - } - - #[napi(js_name = "setColumnWidth")] - pub fn set_column_width(&mut self, sheet: u32, column: i32, width: f64) -> Result<()> { - self - .model - .set_column_width(sheet, column, width) - .map_err(to_js_error) - } - - #[napi(js_name = "getRowHeight")] - pub fn get_row_height(&mut self, sheet: u32, row: i32) -> Result { - self.model.get_row_height(sheet, row).map_err(to_js_error) - } - - #[napi(js_name = "getColumnWidth")] - pub fn get_column_width(&mut self, sheet: u32, column: i32) -> Result { - self - .model - .get_column_width(sheet, column) - .map_err(to_js_error) - } - - #[napi(js_name = "setUserInput")] - pub fn set_user_input(&mut self, sheet: u32, row: i32, column: i32, input: String) -> Result<()> { - self - .model - .set_user_input(sheet, row, column, &input) - .map_err(to_js_error) - } - - #[napi(js_name = "getFormattedCellValue")] - pub fn get_formatted_cell_value(&self, sheet: u32, row: i32, column: i32) -> Result { - self - .model - .get_formatted_cell_value(sheet, row, column) - .map_err(to_js_error) - } - - #[napi(js_name = "getFrozenRowsCount")] - pub fn get_frozen_rows_count(&self, sheet: u32) -> Result { - self.model.get_frozen_rows_count(sheet).map_err(to_js_error) - } - - #[napi(js_name = "getFrozenColumnsCount")] - pub fn get_frozen_columns_count(&self, sheet: u32) -> Result { - self - .model - .get_frozen_columns_count(sheet) - .map_err(to_js_error) - } - - #[napi(js_name = "setFrozenRowsCount")] - pub fn set_frozen_rows_count(&mut self, sheet: u32, count: i32) -> Result<()> { - self - .model - .set_frozen_rows_count(sheet, count) - .map_err(to_js_error) - } - - #[napi(js_name = "setFrozenColumnsCount")] - pub fn set_frozen_columns_count(&mut self, sheet: u32, count: i32) -> Result<()> { - self - .model - .set_frozen_columns_count(sheet, count) - .map_err(to_js_error) - } - - #[napi(js_name = "updateRangeStyle")] - pub fn update_range_style( - &mut self, - env: Env, - range: JsUnknown, - style_path: String, - value: String, - ) -> Result<()> { - let range: Area = env - .from_js_value(range) - .map_err(|e| to_js_error(e.to_string()))?; - self - .model - .update_range_style(&range, &style_path, &value) - .map_err(to_js_error) - } - - #[napi(js_name = "getCellStyle")] - pub fn get_cell_style( - &mut self, - env: Env, - sheet: u32, - row: i32, - column: i32, - ) -> Result { - let style = self - .model - .get_cell_style(sheet, row, column) - .map_err(to_js_error)?; - - env - .to_js_value(&style) - .map_err(|e| to_js_error(e.to_string())) - } - - #[napi(js_name = "onPasteStyles")] - pub fn on_paste_styles(&mut self, env: Env, styles: JsUnknown) -> Result<()> { - let styles: &Vec> = &env - .from_js_value(styles) - .map_err(|e| to_js_error(e.to_string()))?; - self.model.on_paste_styles(styles).map_err(to_js_error) - } - - #[napi(js_name = "getCellType")] - pub fn get_cell_type(&self, sheet: u32, row: i32, column: i32) -> Result { - Ok( - match self - .model - .get_cell_type(sheet, row, column) - .map_err(to_js_error)? - { - CellType::Number => 1, - CellType::Text => 2, - CellType::LogicalValue => 4, - CellType::ErrorValue => 16, - CellType::Array => 64, - CellType::CompoundData => 128, - }, - ) - } - - // I don't _think_ serializing to JsUnknown can't fail - // FIXME: Remove this clippy directive - #[napi(js_name = "getWorksheetsProperties")] - #[allow(clippy::unwrap_used)] - pub fn get_worksheets_properties(&self, env: Env) -> JsUnknown { - env - .to_js_value(&self.model.get_worksheets_properties()) - .unwrap() - } - - #[napi(js_name = "getSelectedSheet")] - pub fn get_selected_sheet(&self) -> u32 { - self.model.get_selected_sheet() - } - - #[napi(js_name = "getSelectedCell")] - pub fn get_selected_cell(&self) -> Vec { - let (sheet, row, column) = self.model.get_selected_cell(); - vec![sheet as i32, row, column] - } - - // I don't _think_ serializing to JsUnknown can't fail - // FIXME: Remove this clippy directive - #[napi(js_name = "getSelectedView")] - #[allow(clippy::unwrap_used)] - pub fn get_selected_view(&self, env: Env) -> JsUnknown { - env.to_js_value(&self.model.get_selected_view()).unwrap() - } - - #[napi(js_name = "setSelectedSheet")] - pub fn set_selected_sheet(&mut self, sheet: u32) -> Result<()> { - self.model.set_selected_sheet(sheet).map_err(to_js_error) - } - - #[napi(js_name = "setSelectedCell")] - pub fn set_selected_cell(&mut self, row: i32, column: i32) -> Result<()> { - self - .model - .set_selected_cell(row, column) - .map_err(to_js_error) - } - - #[napi(js_name = "setSelectedRange")] - pub fn set_selected_range( - &mut self, - start_row: i32, - start_column: i32, - end_row: i32, - end_column: i32, - ) -> Result<()> { - self - .model - .set_selected_range(start_row, start_column, end_row, end_column) - .map_err(to_js_error) - } - - #[napi(js_name = "setTopLeftVisibleCell")] - pub fn set_top_left_visible_cell(&mut self, top_row: i32, top_column: i32) -> Result<()> { - self - .model - .set_top_left_visible_cell(top_row, top_column) - .map_err(to_js_error) - } - - #[napi(js_name = "setShowGridLines")] - pub fn set_show_grid_lines(&mut self, sheet: u32, show_grid_lines: bool) -> Result<()> { - self - .model - .set_show_grid_lines(sheet, show_grid_lines) - .map_err(to_js_error) - } - - #[napi(js_name = "getShowGridLines")] - pub fn get_show_grid_lines(&mut self, sheet: u32) -> Result { - self.model.get_show_grid_lines(sheet).map_err(to_js_error) - } - - #[napi(js_name = "autoFillRows")] - pub fn auto_fill_rows(&mut self, env: Env, source_area: JsUnknown, to_row: i32) -> Result<()> { - let area: Area = env - .from_js_value(source_area) - .map_err(|e| to_js_error(e.to_string()))?; - self - .model - .auto_fill_rows(&area, to_row) - .map_err(to_js_error) - } - - #[napi(js_name = "autoFillColumns")] - pub fn auto_fill_columns( - &mut self, - env: Env, - source_area: JsUnknown, - to_column: i32, - ) -> Result<()> { - let area: Area = env - .from_js_value(source_area) - .map_err(|e| to_js_error(e.to_string()))?; - self - .model - .auto_fill_columns(&area, to_column) - .map_err(to_js_error) - } - - #[napi(js_name = "onArrowRight")] - pub fn on_arrow_right(&mut self) -> Result<()> { - self.model.on_arrow_right().map_err(to_js_error) - } - - #[napi(js_name = "onArrowLeft")] - pub fn on_arrow_left(&mut self) -> Result<()> { - self.model.on_arrow_left().map_err(to_js_error) - } - - #[napi(js_name = "onArrowUp")] - pub fn on_arrow_up(&mut self) -> Result<()> { - self.model.on_arrow_up().map_err(to_js_error) - } - - #[napi(js_name = "onArrowDown")] - pub fn on_arrow_down(&mut self) -> Result<()> { - self.model.on_arrow_down().map_err(to_js_error) - } - - #[napi(js_name = "onPageDown")] - pub fn on_page_down(&mut self) -> Result<()> { - self.model.on_page_down().map_err(to_js_error) - } - - #[napi(js_name = "onPageUp")] - pub fn on_page_up(&mut self) -> Result<()> { - self.model.on_page_up().map_err(to_js_error) - } - - #[napi(js_name = "setWindowWidth")] - pub fn set_window_width(&mut self, window_width: f64) { - self.model.set_window_width(window_width); - } - - #[napi(js_name = "setWindowHeight")] - pub fn set_window_height(&mut self, window_height: f64) { - self.model.set_window_height(window_height); - } - - #[napi(js_name = "getScrollX")] - pub fn get_scroll_x(&self) -> Result { - self.model.get_scroll_x().map_err(to_js_error) - } - - #[napi(js_name = "getScrollY")] - pub fn get_scroll_y(&self) -> Result { - self.model.get_scroll_y().map_err(to_js_error) - } - - #[napi(js_name = "onExpandSelectedRange")] - pub fn on_expand_selected_range(&mut self, key: String) -> Result<()> { - self - .model - .on_expand_selected_range(&key) - .map_err(to_js_error) - } - - #[napi(js_name = "onAreaSelecting")] - pub fn on_area_selecting(&mut self, target_row: i32, target_column: i32) -> Result<()> { - self - .model - .on_area_selecting(target_row, target_column) - .map_err(to_js_error) - } - - #[napi(js_name = "setAreaWithBorder")] - pub fn set_area_with_border( - &mut self, - env: Env, - area: JsUnknown, - border_area: JsUnknown, - ) -> Result<()> { - let range: Area = env - .from_js_value(area) - .map_err(|e| to_js_error(e.to_string()))?; - let border: BorderArea = env - .from_js_value(border_area) - .map_err(|e| to_js_error(e.to_string()))?; - self - .model - .set_area_with_border(&range, &border) - .map_err(|e| to_js_error(e.to_string()))?; - Ok(()) - } - - #[napi(js_name = "toBytes")] - pub fn to_bytes(&self) -> Vec { - self.model.to_bytes() - } - - #[napi(js_name = "getName")] - pub fn get_name(&self) -> String { - self.model.get_name() - } - - #[napi(js_name = "setName")] - pub fn set_name(&mut self, name: String) { - self.model.set_name(&name); - } - - #[napi(js_name = "copyToClipboard")] - pub fn copy_to_clipboard(&self, env: Env) -> Result { - let data = self - .model - .copy_to_clipboard() - .map_err(|e| to_js_error(e.to_string()))?; - - env - .to_js_value(&data) - .map_err(|e| to_js_error(e.to_string())) - } - - #[napi(js_name = "pasteFromClipboard")] - pub fn paste_from_clipboard( - &mut self, - env: Env, - source_sheet: u32, - source_range: JsUnknown, - clipboard: JsUnknown, - is_cut: bool, - ) -> Result<()> { - let source_range: (i32, i32, i32, i32) = env - .from_js_value(source_range) - .map_err(|e| to_js_error(e.to_string()))?; - let clipboard: ClipboardData = env - .from_js_value(clipboard) - .map_err(|e| to_js_error(e.to_string()))?; - self - .model - .paste_from_clipboard(source_sheet, source_range, &clipboard, is_cut) - .map_err(|e| to_js_error(e.to_string())) - } - - #[napi(js_name = "pasteCsvText")] - pub fn paste_csv_string(&mut self, env: Env, area: JsUnknown, csv: String) -> Result<()> { - let range: Area = env - .from_js_value(area) - .map_err(|e| to_js_error(e.to_string()))?; - self - .model - .paste_csv_string(&range, &csv) - .map_err(|e| to_js_error(e.to_string())) - } - - #[napi(js_name = "getDefinedNameList")] - pub fn get_defined_name_list(&self, env: Env) -> Result { - let data: Vec = self - .model - .get_defined_name_list() - .iter() - .map(|s| DefinedName { - name: s.0.to_owned(), - scope: s.1, - formula: s.2.to_owned(), - }) - .collect(); - env - .to_js_value(&data) - .map_err(|e| to_js_error(e.to_string())) - } - - #[napi(js_name = "newDefinedName")] - pub fn new_defined_name( - &mut self, - name: String, - scope: Option, - formula: String, - ) -> Result<()> { - self - .model - .new_defined_name(&name, scope, &formula) - .map_err(|e| to_js_error(e.to_string())) - } - - #[napi(js_name = "updateDefinedName")] - pub fn update_defined_name( - &mut self, - name: String, - scope: Option, - new_name: String, - new_scope: Option, - new_formula: String, - ) -> Result<()> { - self - .model - .update_defined_name(&name, scope, &new_name, new_scope, &new_formula) - .map_err(|e| to_js_error(e.to_string())) - } - - #[napi(js_name = "deleteDefinedName")] - pub fn delete_definedname(&mut self, name: String, scope: Option) -> Result<()> { - self - .model - .delete_defined_name(&name, scope) - .map_err(|e| to_js_error(e.to_string())) - } -} +pub use model::Model; +pub use user_model::UserModel; diff --git a/bindings/nodejs/src/model.rs b/bindings/nodejs/src/model.rs new file mode 100644 index 0000000..d0c19a7 --- /dev/null +++ b/bindings/nodejs/src/model.rs @@ -0,0 +1,343 @@ +#![deny(clippy::all)] + +use napi::{self, bindgen_prelude::*, JsUnknown, Result}; +use serde::Serialize; + +use ironcalc::{ + base::{ + types::{CellType, Style}, + Model as BaseModel, + }, + error::XlsxError, + export::{save_to_icalc, save_to_xlsx}, + import::{load_from_icalc, load_from_xlsx}, +}; + +#[derive(Serialize)] +struct DefinedName { + name: String, + scope: Option, + formula: String, +} + +fn to_js_error(error: String) -> Error { + Error::new(Status::Unknown, error) +} + +fn to_node_error(error: XlsxError) -> Error { + Error::new(Status::Unknown, error.to_string()) +} + +#[napi] +pub struct Model { + model: BaseModel, +} + +#[napi] +impl Model { + #[napi(constructor)] + pub fn new(name: String, locale: String, timezone: String) -> Result { + let model = BaseModel::new_empty(&name, &locale, &timezone).map_err(to_js_error)?; + Ok(Self { model }) + } + + #[napi(factory)] + pub fn from_xlsx(file_path: String, locale: String, tz: String) -> Result { + let model = load_from_xlsx(&file_path, &locale, &tz) + .map_err(|error| Error::new(Status::Unknown, error.to_string()))?; + Ok(Self { model }) + } + + #[napi(factory)] + pub fn from_icalc(file_name: String) -> Result { + let model = load_from_icalc(&file_name) + .map_err(|error| Error::new(Status::Unknown, error.to_string()))?; + Ok(Self { model }) + } + + #[napi] + pub fn save_to_xlsx(&self, file: String) -> Result<()> { + save_to_xlsx(&self.model, &file).map_err(to_node_error) + } + + #[napi] + pub fn save_to_icalc(&self, file: String) -> Result<()> { + save_to_icalc(&self.model, &file).map_err(to_node_error) + } + + #[napi] + pub fn evaluate(&mut self) { + self.model.evaluate(); + } + + #[napi] + pub fn set_user_input(&mut self, sheet: u32, row: i32, column: i32, value: String) -> Result<()> { + self + .model + .set_user_input(sheet, row, column, value) + .map_err(to_js_error) + } + + #[napi] + pub fn clear_cell_contents(&mut self, sheet: u32, row: i32, column: i32) -> Result<()> { + self + .model + .cell_clear_contents(sheet, row, column) + .map_err(to_js_error) + } + + #[napi] + pub fn get_cell_content(&self, sheet: u32, row: i32, column: i32) -> Result { + self + .model + .get_cell_content(sheet, row, column) + .map_err(to_js_error) + } + + #[napi(js_name = "getCellType")] + pub fn get_cell_type(&self, sheet: u32, row: i32, column: i32) -> Result { + Ok( + match self + .model + .get_cell_type(sheet, row, column) + .map_err(to_js_error)? + { + CellType::Number => 1, + CellType::Text => 2, + CellType::LogicalValue => 4, + CellType::ErrorValue => 16, + CellType::Array => 64, + CellType::CompoundData => 128, + }, + ) + } + + #[napi] + pub fn get_formatted_cell_value(&self, sheet: u32, row: i32, column: i32) -> Result { + self + .model + .get_formatted_cell_value(sheet, row, column) + .map_err(to_js_error) + } + + #[napi] + pub fn set_cell_style( + &mut self, + env: Env, + sheet: u32, + row: i32, + column: i32, + style: JsUnknown, + ) -> Result<()> { + let style: Style = env + .from_js_value(style) + .map_err(|e| to_js_error(e.to_string()))?; + self + .model + .set_cell_style(sheet, row, column, &style) + .map_err(to_js_error) + } + + #[napi(js_name = "getCellStyle")] + pub fn get_cell_style( + &mut self, + env: Env, + sheet: u32, + row: i32, + column: i32, + ) -> Result { + let style = self + .model + .get_style_for_cell(sheet, row, column) + .map_err(to_js_error)?; + + env + .to_js_value(&style) + .map_err(|e| to_js_error(e.to_string())) + } + + #[napi] + pub fn insert_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<()> { + self + .model + .insert_rows(sheet, row, row_count) + .map_err(to_js_error) + } + + #[napi] + pub fn insert_columns(&mut self, sheet: u32, column: i32, column_count: i32) -> Result<()> { + self + .model + .insert_columns(sheet, column, column_count) + .map_err(to_js_error) + } + + #[napi] + pub fn delete_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<()> { + self + .model + .delete_rows(sheet, row, row_count) + .map_err(to_js_error) + } + + #[napi] + pub fn delete_columns(&mut self, sheet: u32, column: i32, column_count: i32) -> Result<()> { + self + .model + .delete_columns(sheet, column, column_count) + .map_err(to_js_error) + } + + #[napi] + pub fn get_column_width(&self, sheet: u32, column: i32) -> Result { + self + .model + .get_column_width(sheet, column) + .map_err(to_js_error) + } + + #[napi] + pub fn get_row_height(&self, sheet: u32, row: i32) -> Result { + self.model.get_row_height(sheet, row).map_err(to_js_error) + } + + #[napi] + pub fn set_column_width(&mut self, sheet: u32, column: i32, width: f64) -> Result<()> { + self + .model + .set_column_width(sheet, column, width) + .map_err(to_js_error) + } + + #[napi] + pub fn set_row_height(&mut self, sheet: u32, row: i32, height: f64) -> Result<()> { + self + .model + .set_row_height(sheet, row, height) + .map_err(to_js_error) + } + + #[napi] + pub fn get_frozen_columns_count(&self, sheet: u32) -> Result { + self + .model + .get_frozen_columns_count(sheet) + .map_err(to_js_error) + } + + #[napi] + pub fn get_frozen_rows_count(&self, sheet: u32) -> Result { + self.model.get_frozen_rows_count(sheet).map_err(to_js_error) + } + + #[napi] + pub fn set_frozen_columns_count(&mut self, sheet: u32, column_count: i32) -> Result<()> { + self + .model + .set_frozen_columns(sheet, column_count) + .map_err(to_js_error) + } + + #[napi] + pub fn set_frozen_rows_count(&mut self, sheet: u32, row_count: i32) -> Result<()> { + self + .model + .set_frozen_rows(sheet, row_count) + .map_err(to_js_error) + } + + // I don't _think_ serializing to JsUnknown can't fail + // FIXME: Remove this clippy directive + #[napi(js_name = "getWorksheetsProperties")] + #[allow(clippy::unwrap_used)] + pub fn get_worksheets_properties(&self, env: Env) -> JsUnknown { + env + .to_js_value(&self.model.get_worksheets_properties()) + .unwrap() + } + + #[napi] + pub fn set_sheet_color(&mut self, sheet: u32, color: String) -> Result<()> { + self + .model + .set_sheet_color(sheet, &color) + .map_err(to_js_error) + } + + #[napi] + pub fn add_sheet(&mut self, sheet_name: String) -> Result<()> { + self.model.add_sheet(&sheet_name).map_err(to_js_error) + } + + #[napi] + pub fn new_sheet(&mut self) { + self.model.new_sheet(); + } + + #[napi] + pub fn delete_sheet(&mut self, sheet: u32) -> Result<()> { + self.model.delete_sheet(sheet).map_err(to_js_error) + } + + #[napi] + pub fn rename_sheet(&mut self, sheet: u32, new_name: String) -> Result<()> { + self + .model + .rename_sheet_by_index(sheet, &new_name) + .map_err(to_js_error) + } + + #[napi(js_name = "getDefinedNameList")] + pub fn get_defined_name_list(&self, env: Env) -> Result { + let data: Vec = self + .model + .workbook + .get_defined_names_with_scope() + .iter() + .map(|s| DefinedName { + name: s.0.to_owned(), + scope: s.1, + formula: s.2.to_owned(), + }) + .collect(); + env + .to_js_value(&data) + .map_err(|e| to_js_error(e.to_string())) + } + + #[napi(js_name = "newDefinedName")] + pub fn new_defined_name( + &mut self, + name: String, + scope: Option, + formula: String, + ) -> Result<()> { + self + .model + .new_defined_name(&name, scope, &formula) + .map_err(|e| to_js_error(e.to_string())) + } + + #[napi(js_name = "updateDefinedName")] + pub fn update_defined_name( + &mut self, + name: String, + scope: Option, + new_name: String, + new_scope: Option, + new_formula: String, + ) -> Result<()> { + self + .model + .update_defined_name(&name, scope, &new_name, new_scope, &new_formula) + .map_err(|e| to_js_error(e.to_string())) + } + + #[napi(js_name = "deleteDefinedName")] + pub fn delete_definedname(&mut self, name: String, scope: Option) -> Result<()> { + self + .model + .delete_defined_name(&name, scope) + .map_err(|e| to_js_error(e.to_string())) + } +} diff --git a/bindings/nodejs/src/user_model.rs b/bindings/nodejs/src/user_model.rs new file mode 100644 index 0000000..b7c0d3d --- /dev/null +++ b/bindings/nodejs/src/user_model.rs @@ -0,0 +1,620 @@ +#![deny(clippy::all)] + +use serde::Serialize; + +use napi::{self, bindgen_prelude::*, JsUnknown, Result}; + +use ironcalc::base::{ + expressions::types::Area, + types::{CellType, Style}, + BorderArea, ClipboardData, UserModel as BaseModel, +}; + +#[derive(Serialize)] +struct DefinedName { + name: String, + scope: Option, + formula: String, +} + +fn to_js_error(error: String) -> Error { + Error::new(Status::Unknown, error) +} + +#[napi] +pub struct UserModel { + model: BaseModel, +} + +#[napi] +impl UserModel { + #[napi(constructor)] + pub fn new(name: String, locale: String, timezone: String) -> Result { + let model = BaseModel::new_empty(&name, &locale, &timezone).map_err(to_js_error)?; + Ok(Self { model }) + } + + #[napi(factory)] + pub fn from_bytes(bytes: &[u8]) -> Result { + let model = BaseModel::from_bytes(bytes).map_err(to_js_error)?; + Ok(UserModel { model }) + } + + pub fn undo(&mut self) -> Result<()> { + self.model.undo().map_err(to_js_error) + } + + pub fn redo(&mut self) -> Result<()> { + self.model.redo().map_err(to_js_error) + } + + #[napi(js_name = "canUndo")] + pub fn can_undo(&self) -> bool { + self.model.can_undo() + } + + #[napi(js_name = "canRedo")] + pub fn can_redo(&self) -> bool { + self.model.can_redo() + } + + #[napi(js_name = "pauseEvaluation")] + pub fn pause_evaluation(&mut self) { + self.model.pause_evaluation() + } + + #[napi(js_name = "resumeEvaluation")] + pub fn resume_evaluation(&mut self) { + self.model.resume_evaluation() + } + + pub fn evaluate(&mut self) { + self.model.evaluate(); + } + + #[napi(js_name = "flushSendQueue")] + pub fn flush_send_queue(&mut self) -> Vec { + self.model.flush_send_queue() + } + + #[napi(js_name = "applyExternalDiffs")] + pub fn apply_external_diffs(&mut self, diffs: &[u8]) -> Result<()> { + self.model.apply_external_diffs(diffs).map_err(to_js_error) + } + + #[napi(js_name = "getCellContent")] + pub fn get_cell_content(&self, sheet: u32, row: i32, column: i32) -> Result { + self + .model + .get_cell_content(sheet, row, column) + .map_err(to_js_error) + } + + #[napi(js_name = "newSheet")] + pub fn new_sheet(&mut self) -> Result<()> { + self.model.new_sheet().map_err(to_js_error) + } + + #[napi(js_name = "deleteSheet")] + pub fn delete_sheet(&mut self, sheet: u32) -> Result<()> { + self.model.delete_sheet(sheet).map_err(to_js_error) + } + + #[napi(js_name = "hideSheet")] + pub fn hide_sheet(&mut self, sheet: u32) -> Result<()> { + self.model.hide_sheet(sheet).map_err(to_js_error) + } + + #[napi(js_name = "unhideSheet")] + pub fn unhide_sheet(&mut self, sheet: u32) -> Result<()> { + self.model.unhide_sheet(sheet).map_err(to_js_error) + } + + #[napi(js_name = "renameSheet")] + pub fn rename_sheet(&mut self, sheet: u32, name: String) -> Result<()> { + self.model.rename_sheet(sheet, &name).map_err(to_js_error) + } + + #[napi(js_name = "setSheetColor")] + pub fn set_sheet_color(&mut self, sheet: u32, color: String) -> Result<()> { + self + .model + .set_sheet_color(sheet, &color) + .map_err(to_js_error) + } + + #[napi(js_name = "rangeClearAll")] + pub fn range_clear_all( + &mut self, + sheet: u32, + start_row: i32, + start_column: i32, + end_row: i32, + end_column: i32, + ) -> Result<()> { + let range = Area { + sheet, + row: start_row, + column: start_column, + width: end_column - start_column + 1, + height: end_row - start_row + 1, + }; + self.model.range_clear_all(&range).map_err(to_js_error) + } + + #[napi(js_name = "rangeClearContents")] + pub fn range_clear_contents( + &mut self, + sheet: u32, + start_row: i32, + start_column: i32, + end_row: i32, + end_column: i32, + ) -> Result<()> { + let range = Area { + sheet, + row: start_row, + column: start_column, + width: end_column - start_column + 1, + height: end_row - start_row + 1, + }; + self.model.range_clear_contents(&range).map_err(to_js_error) + } + + #[napi(js_name = "insertRow")] + pub fn insert_row(&mut self, sheet: u32, row: i32) -> Result<()> { + self.model.insert_row(sheet, row).map_err(to_js_error) + } + + #[napi(js_name = "insertColumn")] + pub fn insert_column(&mut self, sheet: u32, column: i32) -> Result<()> { + self.model.insert_column(sheet, column).map_err(to_js_error) + } + + #[napi(js_name = "deleteRow")] + pub fn delete_row(&mut self, sheet: u32, row: i32) -> Result<()> { + self.model.delete_row(sheet, row).map_err(to_js_error) + } + + #[napi(js_name = "deleteColumn")] + pub fn delete_column(&mut self, sheet: u32, column: i32) -> Result<()> { + self.model.delete_column(sheet, column).map_err(to_js_error) + } + + #[napi(js_name = "setRowHeight")] + pub fn set_row_height(&mut self, sheet: u32, row: i32, height: f64) -> Result<()> { + self + .model + .set_row_height(sheet, row, height) + .map_err(to_js_error) + } + + #[napi(js_name = "setColumnWidth")] + pub fn set_column_width(&mut self, sheet: u32, column: i32, width: f64) -> Result<()> { + self + .model + .set_column_width(sheet, column, width) + .map_err(to_js_error) + } + + #[napi(js_name = "getRowHeight")] + pub fn get_row_height(&mut self, sheet: u32, row: i32) -> Result { + self.model.get_row_height(sheet, row).map_err(to_js_error) + } + + #[napi(js_name = "getColumnWidth")] + pub fn get_column_width(&mut self, sheet: u32, column: i32) -> Result { + self + .model + .get_column_width(sheet, column) + .map_err(to_js_error) + } + + #[napi(js_name = "setUserInput")] + pub fn set_user_input(&mut self, sheet: u32, row: i32, column: i32, input: String) -> Result<()> { + self + .model + .set_user_input(sheet, row, column, &input) + .map_err(to_js_error) + } + + #[napi(js_name = "getFormattedCellValue")] + pub fn get_formatted_cell_value(&self, sheet: u32, row: i32, column: i32) -> Result { + self + .model + .get_formatted_cell_value(sheet, row, column) + .map_err(to_js_error) + } + + #[napi(js_name = "getFrozenRowsCount")] + pub fn get_frozen_rows_count(&self, sheet: u32) -> Result { + self.model.get_frozen_rows_count(sheet).map_err(to_js_error) + } + + #[napi(js_name = "getFrozenColumnsCount")] + pub fn get_frozen_columns_count(&self, sheet: u32) -> Result { + self + .model + .get_frozen_columns_count(sheet) + .map_err(to_js_error) + } + + #[napi(js_name = "setFrozenRowsCount")] + pub fn set_frozen_rows_count(&mut self, sheet: u32, count: i32) -> Result<()> { + self + .model + .set_frozen_rows_count(sheet, count) + .map_err(to_js_error) + } + + #[napi(js_name = "setFrozenColumnsCount")] + pub fn set_frozen_columns_count(&mut self, sheet: u32, count: i32) -> Result<()> { + self + .model + .set_frozen_columns_count(sheet, count) + .map_err(to_js_error) + } + + #[napi(js_name = "updateRangeStyle")] + pub fn update_range_style( + &mut self, + env: Env, + range: JsUnknown, + style_path: String, + value: String, + ) -> Result<()> { + let range: Area = env + .from_js_value(range) + .map_err(|e| to_js_error(e.to_string()))?; + self + .model + .update_range_style(&range, &style_path, &value) + .map_err(to_js_error) + } + + #[napi(js_name = "getCellStyle")] + pub fn get_cell_style( + &mut self, + env: Env, + sheet: u32, + row: i32, + column: i32, + ) -> Result { + let style = self + .model + .get_cell_style(sheet, row, column) + .map_err(to_js_error)?; + + env + .to_js_value(&style) + .map_err(|e| to_js_error(e.to_string())) + } + + #[napi(js_name = "onPasteStyles")] + pub fn on_paste_styles(&mut self, env: Env, styles: JsUnknown) -> Result<()> { + let styles: &Vec> = &env + .from_js_value(styles) + .map_err(|e| to_js_error(e.to_string()))?; + self.model.on_paste_styles(styles).map_err(to_js_error) + } + + #[napi(js_name = "getCellType")] + pub fn get_cell_type(&self, sheet: u32, row: i32, column: i32) -> Result { + Ok( + match self + .model + .get_cell_type(sheet, row, column) + .map_err(to_js_error)? + { + CellType::Number => 1, + CellType::Text => 2, + CellType::LogicalValue => 4, + CellType::ErrorValue => 16, + CellType::Array => 64, + CellType::CompoundData => 128, + }, + ) + } + + // I don't _think_ serializing to JsUnknown can't fail + // FIXME: Remove this clippy directive + #[napi(js_name = "getWorksheetsProperties")] + #[allow(clippy::unwrap_used)] + pub fn get_worksheets_properties(&self, env: Env) -> JsUnknown { + env + .to_js_value(&self.model.get_worksheets_properties()) + .unwrap() + } + + #[napi(js_name = "getSelectedSheet")] + pub fn get_selected_sheet(&self) -> u32 { + self.model.get_selected_sheet() + } + + #[napi(js_name = "getSelectedCell")] + pub fn get_selected_cell(&self) -> Vec { + let (sheet, row, column) = self.model.get_selected_cell(); + vec![sheet as i32, row, column] + } + + // I don't _think_ serializing to JsUnknown can't fail + // FIXME: Remove this clippy directive + #[napi(js_name = "getSelectedView")] + #[allow(clippy::unwrap_used)] + pub fn get_selected_view(&self, env: Env) -> JsUnknown { + env.to_js_value(&self.model.get_selected_view()).unwrap() + } + + #[napi(js_name = "setSelectedSheet")] + pub fn set_selected_sheet(&mut self, sheet: u32) -> Result<()> { + self.model.set_selected_sheet(sheet).map_err(to_js_error) + } + + #[napi(js_name = "setSelectedCell")] + pub fn set_selected_cell(&mut self, row: i32, column: i32) -> Result<()> { + self + .model + .set_selected_cell(row, column) + .map_err(to_js_error) + } + + #[napi(js_name = "setSelectedRange")] + pub fn set_selected_range( + &mut self, + start_row: i32, + start_column: i32, + end_row: i32, + end_column: i32, + ) -> Result<()> { + self + .model + .set_selected_range(start_row, start_column, end_row, end_column) + .map_err(to_js_error) + } + + #[napi(js_name = "setTopLeftVisibleCell")] + pub fn set_top_left_visible_cell(&mut self, top_row: i32, top_column: i32) -> Result<()> { + self + .model + .set_top_left_visible_cell(top_row, top_column) + .map_err(to_js_error) + } + + #[napi(js_name = "setShowGridLines")] + pub fn set_show_grid_lines(&mut self, sheet: u32, show_grid_lines: bool) -> Result<()> { + self + .model + .set_show_grid_lines(sheet, show_grid_lines) + .map_err(to_js_error) + } + + #[napi(js_name = "getShowGridLines")] + pub fn get_show_grid_lines(&mut self, sheet: u32) -> Result { + self.model.get_show_grid_lines(sheet).map_err(to_js_error) + } + + #[napi(js_name = "autoFillRows")] + pub fn auto_fill_rows(&mut self, env: Env, source_area: JsUnknown, to_row: i32) -> Result<()> { + let area: Area = env + .from_js_value(source_area) + .map_err(|e| to_js_error(e.to_string()))?; + self + .model + .auto_fill_rows(&area, to_row) + .map_err(to_js_error) + } + + #[napi(js_name = "autoFillColumns")] + pub fn auto_fill_columns( + &mut self, + env: Env, + source_area: JsUnknown, + to_column: i32, + ) -> Result<()> { + let area: Area = env + .from_js_value(source_area) + .map_err(|e| to_js_error(e.to_string()))?; + self + .model + .auto_fill_columns(&area, to_column) + .map_err(to_js_error) + } + + #[napi(js_name = "onArrowRight")] + pub fn on_arrow_right(&mut self) -> Result<()> { + self.model.on_arrow_right().map_err(to_js_error) + } + + #[napi(js_name = "onArrowLeft")] + pub fn on_arrow_left(&mut self) -> Result<()> { + self.model.on_arrow_left().map_err(to_js_error) + } + + #[napi(js_name = "onArrowUp")] + pub fn on_arrow_up(&mut self) -> Result<()> { + self.model.on_arrow_up().map_err(to_js_error) + } + + #[napi(js_name = "onArrowDown")] + pub fn on_arrow_down(&mut self) -> Result<()> { + self.model.on_arrow_down().map_err(to_js_error) + } + + #[napi(js_name = "onPageDown")] + pub fn on_page_down(&mut self) -> Result<()> { + self.model.on_page_down().map_err(to_js_error) + } + + #[napi(js_name = "onPageUp")] + pub fn on_page_up(&mut self) -> Result<()> { + self.model.on_page_up().map_err(to_js_error) + } + + #[napi(js_name = "setWindowWidth")] + pub fn set_window_width(&mut self, window_width: f64) { + self.model.set_window_width(window_width); + } + + #[napi(js_name = "setWindowHeight")] + pub fn set_window_height(&mut self, window_height: f64) { + self.model.set_window_height(window_height); + } + + #[napi(js_name = "getScrollX")] + pub fn get_scroll_x(&self) -> Result { + self.model.get_scroll_x().map_err(to_js_error) + } + + #[napi(js_name = "getScrollY")] + pub fn get_scroll_y(&self) -> Result { + self.model.get_scroll_y().map_err(to_js_error) + } + + #[napi(js_name = "onExpandSelectedRange")] + pub fn on_expand_selected_range(&mut self, key: String) -> Result<()> { + self + .model + .on_expand_selected_range(&key) + .map_err(to_js_error) + } + + #[napi(js_name = "onAreaSelecting")] + pub fn on_area_selecting(&mut self, target_row: i32, target_column: i32) -> Result<()> { + self + .model + .on_area_selecting(target_row, target_column) + .map_err(to_js_error) + } + + #[napi(js_name = "setAreaWithBorder")] + pub fn set_area_with_border( + &mut self, + env: Env, + area: JsUnknown, + border_area: JsUnknown, + ) -> Result<()> { + let range: Area = env + .from_js_value(area) + .map_err(|e| to_js_error(e.to_string()))?; + let border: BorderArea = env + .from_js_value(border_area) + .map_err(|e| to_js_error(e.to_string()))?; + self + .model + .set_area_with_border(&range, &border) + .map_err(|e| to_js_error(e.to_string()))?; + Ok(()) + } + + #[napi(js_name = "toBytes")] + pub fn to_bytes(&self) -> Vec { + self.model.to_bytes() + } + + #[napi(js_name = "getName")] + pub fn get_name(&self) -> String { + self.model.get_name() + } + + #[napi(js_name = "setName")] + pub fn set_name(&mut self, name: String) { + self.model.set_name(&name); + } + + #[napi(js_name = "copyToClipboard")] + pub fn copy_to_clipboard(&self, env: Env) -> Result { + let data = self + .model + .copy_to_clipboard() + .map_err(|e| to_js_error(e.to_string()))?; + + env + .to_js_value(&data) + .map_err(|e| to_js_error(e.to_string())) + } + + #[napi(js_name = "pasteFromClipboard")] + pub fn paste_from_clipboard( + &mut self, + env: Env, + source_sheet: u32, + source_range: JsUnknown, + clipboard: JsUnknown, + is_cut: bool, + ) -> Result<()> { + let source_range: (i32, i32, i32, i32) = env + .from_js_value(source_range) + .map_err(|e| to_js_error(e.to_string()))?; + let clipboard: ClipboardData = env + .from_js_value(clipboard) + .map_err(|e| to_js_error(e.to_string()))?; + self + .model + .paste_from_clipboard(source_sheet, source_range, &clipboard, is_cut) + .map_err(|e| to_js_error(e.to_string())) + } + + #[napi(js_name = "pasteCsvText")] + pub fn paste_csv_string(&mut self, env: Env, area: JsUnknown, csv: String) -> Result<()> { + let range: Area = env + .from_js_value(area) + .map_err(|e| to_js_error(e.to_string()))?; + self + .model + .paste_csv_string(&range, &csv) + .map_err(|e| to_js_error(e.to_string())) + } + + #[napi(js_name = "getDefinedNameList")] + pub fn get_defined_name_list(&self, env: Env) -> Result { + let data: Vec = self + .model + .get_defined_name_list() + .iter() + .map(|s| DefinedName { + name: s.0.to_owned(), + scope: s.1, + formula: s.2.to_owned(), + }) + .collect(); + env + .to_js_value(&data) + .map_err(|e| to_js_error(e.to_string())) + } + + #[napi(js_name = "newDefinedName")] + pub fn new_defined_name( + &mut self, + name: String, + scope: Option, + formula: String, + ) -> Result<()> { + self + .model + .new_defined_name(&name, scope, &formula) + .map_err(|e| to_js_error(e.to_string())) + } + + #[napi(js_name = "updateDefinedName")] + pub fn update_defined_name( + &mut self, + name: String, + scope: Option, + new_name: String, + new_scope: Option, + new_formula: String, + ) -> Result<()> { + self + .model + .update_defined_name(&name, scope, &new_name, new_scope, &new_formula) + .map_err(|e| to_js_error(e.to_string())) + } + + #[napi(js_name = "deleteDefinedName")] + pub fn delete_definedname(&mut self, name: String, scope: Option) -> Result<()> { + self + .model + .delete_defined_name(&name, scope) + .map_err(|e| to_js_error(e.to_string())) + } +}