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 0000000..99c93f4 Binary files /dev/null and b/bindings/nodejs/example.xlsx differ 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())) + } +}