diff --git a/Cargo.lock b/Cargo.lock index d5e9b9a..48b2d81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1069,7 +1069,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm" -version = "0.5.0" +version = "0.5.3" dependencies = [ "ironcalc_base", "serde", diff --git a/base/src/new_empty.rs b/base/src/new_empty.rs index a07b30e..cc07448 100644 --- a/base/src/new_empty.rs +++ b/base/src/new_empty.rs @@ -405,6 +405,7 @@ impl Model { }, tables: HashMap::new(), views, + users: Vec::new(), }; let parsed_formulas = Vec::new(); let worksheets = &workbook.worksheets; diff --git a/base/src/types.rs b/base/src/types.rs index 3b516af..54b277c 100644 --- a/base/src/types.rs +++ b/base/src/types.rs @@ -39,6 +39,14 @@ pub struct WorkbookView { pub window_height: i64, } +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct WebUser { + pub id: String, + pub sheet: u32, + pub row: i32, + pub column: i32, +} + /// An internal representation of an IronCalc Workbook #[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct Workbook { @@ -51,6 +59,7 @@ pub struct Workbook { pub metadata: Metadata, pub tables: HashMap, pub views: HashMap, + pub users: Vec } /// A defined name. The `sheet_id` is the sheet index in case the name is local diff --git a/base/src/user_model/common.rs b/base/src/user_model/common.rs index 0b4b172..da03010 100644 --- a/base/src/user_model/common.rs +++ b/base/src/user_model/common.rs @@ -14,7 +14,7 @@ use crate::{ model::Model, types::{ Alignment, BorderItem, CellType, Col, HorizontalAlignment, SheetProperties, SheetState, - Style, VerticalAlignment, + Style, VerticalAlignment, WebUser, }, utils::is_valid_hex_color, }; @@ -293,6 +293,11 @@ impl UserModel { self.model.workbook.name = name.to_string(); } + /// Set users + pub fn set_users(&mut self, users: &[WebUser]) { + self.model.workbook.users = users.to_vec(); + } + /// Undoes last change if any, places the change in the redo list and evaluates the model if needed /// /// See also: diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 2315210..1c5a6a4 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm" -version = "0.5.0" +version = "0.5.3" authors = ["Nicolas Hatcher "] description = "IronCalc Web bindings" license = "MIT/Apache-2.0" diff --git a/bindings/wasm/fix_types.py b/bindings/wasm/fix_types.py index fd466ee..8034cc9 100644 --- a/bindings/wasm/fix_types.py +++ b/bindings/wasm/fix_types.py @@ -201,6 +201,36 @@ defined_name_list_types = r""" getDefinedNameList(): DefinedName[]; """ +set_users = r""" +/** +* @param {any} users +*/ + setUsers(users: any): void; +""" + +set_users_types = r""" +/** +* @param {WebUser[]} users +*/ + setUsers(users: WebUser[]): void; +""" + +get_users = r""" +/** +* @returns {any} +*/ + getUsers(): any; +} +""" + +get_users_types = r""" +/** +* @returns {WebUser[]} +*/ + getUsers(): WebUser[]; +} +""" + def fix_types(text): text = text.replace(get_tokens_str, get_tokens_str_types) text = text.replace(update_style_str, update_style_str_types) @@ -215,6 +245,8 @@ def fix_types(text): text = text.replace(clipboard, clipboard_types) text = text.replace(paste_from_clipboard, paste_from_clipboard_types) text = text.replace(defined_name_list, defined_name_list_types) + text = text.replace(set_users, set_users_types) + text = text.replace(get_users, get_users_types) with open("types.ts") as f: types_str = f.read() header_types = "{}\n\n{}".format(header, types_str) diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 5767a2e..3824a86 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -6,7 +6,7 @@ use wasm_bindgen::{ use ironcalc_base::{ expressions::{lexer::util::get_tokens as tokenizer, types::Area, utils::number_to_column}, - types::{CellType, Style}, + types::{CellType, Style, WebUser}, BorderArea, ClipboardData, UserModel as BaseModel, }; @@ -672,4 +672,18 @@ impl Model { .delete_defined_name(name, scope) .map_err(|e| to_js_error(e.to_string())) } + + #[wasm_bindgen(js_name = "setUsers")] + pub fn set_users(&mut self, users: JsValue) -> Result<(), JsError> { + let users: Vec = + serde_wasm_bindgen::from_value(users).map_err(|e| to_js_error(e.to_string()))?; + self.model.set_users(&users); + Ok(()) + } + + #[wasm_bindgen(js_name = "getUsers")] + pub fn get_users(&self) -> Result { + let users = self.model.get_model().workbook.users.clone(); + serde_wasm_bindgen::to_value(&users).map_err(|e| to_js_error(e.to_string())) + } } diff --git a/bindings/wasm/types.ts b/bindings/wasm/types.ts index 7af55b8..e4b8276 100644 --- a/bindings/wasm/types.ts +++ b/bindings/wasm/types.ts @@ -233,4 +233,11 @@ export interface DefinedName { name: string; scope?: number; formula: string; +} + +export interface WebUser { + id: string; + sheet: number; + row: number; + column: number; } \ No newline at end of file diff --git a/webapp/IronCalc/package-lock.json b/webapp/IronCalc/package-lock.json index 3f94081..068ac2b 100644 --- a/webapp/IronCalc/package-lock.json +++ b/webapp/IronCalc/package-lock.json @@ -1,16 +1,16 @@ { "name": "@ironcalc/workbook", - "version": "0.5.1", + "version": "0.5.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ironcalc/workbook", - "version": "0.5.1", + "version": "0.5.4", "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@ironcalc/wasm": "0.5.0", + "@ironcalc/wasm": "0.5.3", "@mui/material": "^6.4", "@mui/system": "^6.4", "i18next": "^23.11.1", @@ -43,11 +43,6 @@ "react-dom": "^18.0.0 || ^19.0.0" } }, - "../../bindings/wasm/pkg": { - "name": "@ironcalc/wasm", - "version": "0.5.0", - "license": "MIT/Apache-2.0" - }, "node_modules/@adobe/css-tools": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz", @@ -1060,8 +1055,10 @@ } }, "node_modules/@ironcalc/wasm": { - "resolved": "../../bindings/wasm/pkg", - "link": true + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@ironcalc/wasm/-/wasm-0.5.3.tgz", + "integrity": "sha512-ryQKR5ISkSQnnsxBYDnrAUN+GDiAQUx0MzkVpJr7VQXiymOSMZbHfpv5geum1eSJV4gw1ft69syuNolIhVZ4Hg==", + "license": "MIT/Apache-2.0" }, "node_modules/@isaacs/cliui": { "version": "8.0.2", diff --git a/webapp/IronCalc/package.json b/webapp/IronCalc/package.json index 228d428..584dc6c 100644 --- a/webapp/IronCalc/package.json +++ b/webapp/IronCalc/package.json @@ -1,6 +1,6 @@ { "name": "@ironcalc/workbook", - "version": "0.5.1", + "version": "0.5.4", "type": "module", "main": "./dist/ironcalc.js", "module": "./dist/ironcalc.js", @@ -17,7 +17,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@ironcalc/wasm": "0.5.0", + "@ironcalc/wasm": "0.5.3", "@mui/material": "^6.4", "@mui/system": "^6.4", "i18next": "^23.11.1", diff --git a/webapp/IronCalc/src/IronCalc.tsx b/webapp/IronCalc/src/IronCalc.tsx index 38d4bad..7efae09 100644 --- a/webapp/IronCalc/src/IronCalc.tsx +++ b/webapp/IronCalc/src/IronCalc.tsx @@ -11,6 +11,10 @@ interface IronCalcProperties { } function IronCalc(properties: IronCalcProperties) { + properties.model.setUsers([ + { id: "john@doe.com", sheet: 0, row: 5, column: 6 }, + { id: "micheal@doe.com", sheet: 0, row: 1, column: 6 }, + ]); return ( diff --git a/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts b/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts index 5a4ea4b..f700c2d 100644 --- a/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts +++ b/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts @@ -19,6 +19,13 @@ import { outlineColor, } from "./constants"; +export interface UserSelection { + userId: string; + color: string; + selection: [number, number, number, number, number]; // [sheet, rowStart, columnStart, rowEnd, columnEnd] + div: HTMLDivElement; +} + export interface CanvasSettings { model: Model; width: number; @@ -1244,6 +1251,34 @@ export default class WorksheetCanvas { editor.style.height = `${height - 1}px`; } + private drawUsersSelection(): void { + const users = this.model.getUsers(); + for (const handle of document.querySelectorAll( + ".user-selection-ironcalc", + )) + handle.remove(); + const colors = []; + users.forEach((user, index) => { + const { sheet, row, column } = user; + if (sheet !== this.model.getSelectedSheet()) { + return; + } + const [x, y] = this.getCoordinatesByCell(row, column); + const width = this.getColumnWidth(sheet, column); + const height = this.getRowHeight(sheet, row); + const div = document.createElement("div"); + const color = getColor(index + 1); + div.className = "user-selection-ironcalc"; + div.style.left = `${x}px`; + div.style.top = `${y}px`; + div.style.width = `${width}px`; + div.style.height = `${height}px`; + div.style.border = `1px solid ${color}`; + div.style.position = "absolute"; + this.canvas.parentElement?.appendChild(div); + }); + } + private drawCellOutline(): void { const { cellOutline, areaOutline, cellOutlineHandle } = this; if (this.workbookState.getEditingCell()) { @@ -1595,6 +1630,7 @@ export default class WorksheetCanvas { context.stroke(); this.drawCellOutline(); + this.drawUsersSelection(); this.drawCellEditor(); this.drawExtendToArea(); this.drawActiveRanges(topLeftCell, bottomRightCell); diff --git a/xlsx/src/import/mod.rs b/xlsx/src/import/mod.rs index 7ca509e..322ecbe 100644 --- a/xlsx/src/import/mod.rs +++ b/xlsx/src/import/mod.rs @@ -110,6 +110,7 @@ fn load_xlsx_from_reader( metadata, tables, views, + users: Vec::new(), }) }