UPDATE: Adds Web browser wasm bindings (#30)
* UPDATE: Adds Web browser wasm bindings * FIX: install wasm-pack in the GitHub actions
This commit is contained in:
committed by
GitHub
parent
d445553d85
commit
489027991c
@@ -16,14 +16,14 @@ serde_json = "1.0"
|
||||
serde_repr = "0.1"
|
||||
ryu = "1.0"
|
||||
chrono = "0.4"
|
||||
chrono-tz = "0.7.0"
|
||||
chrono-tz = "0.9"
|
||||
regex = "1.0"
|
||||
once_cell = "1.16.0"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
js-sys = { version = "0.3.60" }
|
||||
js-sys = { version = "0.3.69" }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
rand = "0.8.4"
|
||||
rand = "0.8.5"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use chrono::DateTime;
|
||||
use chrono::Datelike;
|
||||
use chrono::Months;
|
||||
use chrono::NaiveDateTime;
|
||||
use chrono::TimeZone;
|
||||
use chrono::Timelike;
|
||||
|
||||
use crate::expressions::types::CellReferenceIndex;
|
||||
@@ -258,8 +257,8 @@ impl Model {
|
||||
// milliseconds since January 1, 1970 00:00:00 UTC.
|
||||
let milliseconds = get_milliseconds_since_epoch();
|
||||
let seconds = milliseconds / 1000;
|
||||
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
|
||||
Some(dt) => dt,
|
||||
let local_time = match DateTime::from_timestamp(seconds, 0) {
|
||||
Some(dt) => dt.with_timezone(&self.tz),
|
||||
None => {
|
||||
return CalcResult::Error {
|
||||
error: Error::ERROR,
|
||||
@@ -268,7 +267,6 @@ impl Model {
|
||||
}
|
||||
}
|
||||
};
|
||||
let local_time = self.tz.from_utc_datetime(&dt);
|
||||
// 693_594 is computed as:
|
||||
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
|
||||
// The 2 days offset is because of Excel 1900 bug
|
||||
@@ -289,8 +287,8 @@ impl Model {
|
||||
// milliseconds since January 1, 1970 00:00:00 UTC.
|
||||
let milliseconds = get_milliseconds_since_epoch();
|
||||
let seconds = milliseconds / 1000;
|
||||
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
|
||||
Some(dt) => dt,
|
||||
let local_time = match DateTime::from_timestamp(seconds, 0) {
|
||||
Some(dt) => dt.with_timezone(&self.tz),
|
||||
None => {
|
||||
return CalcResult::Error {
|
||||
error: Error::ERROR,
|
||||
@@ -299,7 +297,6 @@ impl Model {
|
||||
}
|
||||
}
|
||||
};
|
||||
let local_time = self.tz.from_utc_datetime(&dt);
|
||||
// 693_594 is computed as:
|
||||
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
|
||||
// The 2 days offset is because of Excel 1900 bug
|
||||
|
||||
@@ -55,6 +55,11 @@ pub fn get_milliseconds_since_epoch() -> i64 {
|
||||
.as_millis() as i64
|
||||
}
|
||||
|
||||
/// Number of milliseconds since January 1, 1970
|
||||
/// Used by time and date functions. It takes the value from the environment:
|
||||
/// * The Operative System
|
||||
/// * The JavaScript environment
|
||||
/// * Or mocked for tests
|
||||
#[cfg(not(test))]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn get_milliseconds_since_epoch() -> i64 {
|
||||
@@ -1761,11 +1766,11 @@ impl Model {
|
||||
}
|
||||
|
||||
/// Returns data about the worksheets
|
||||
pub fn get_sheets_info(&self) -> Vec<SheetInfo> {
|
||||
pub fn get_worksheets_properties(&self) -> Vec<SheetProperties> {
|
||||
self.workbook
|
||||
.worksheets
|
||||
.iter()
|
||||
.map(|worksheet| SheetInfo {
|
||||
.map(|worksheet| SheetProperties {
|
||||
name: worksheet.get_name(),
|
||||
state: worksheet.state.to_string(),
|
||||
color: worksheet.color.clone(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use chrono::DateTime;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -324,7 +324,7 @@ impl Model {
|
||||
|
||||
let milliseconds = get_milliseconds_since_epoch();
|
||||
let seconds = milliseconds / 1000;
|
||||
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
|
||||
let dt = match DateTime::from_timestamp(seconds, 0) {
|
||||
Some(s) => s,
|
||||
None => return Err(format!("Invalid timestamp: {}", milliseconds)),
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{test::util::new_empty_model, types::SheetInfo};
|
||||
use crate::{test::util::new_empty_model, types::SheetProperties};
|
||||
|
||||
#[test]
|
||||
fn workbook_worksheets_info() {
|
||||
let model = new_empty_model();
|
||||
let sheets_info = model.get_sheets_info();
|
||||
let sheets_info = model.get_worksheets_properties();
|
||||
assert_eq!(
|
||||
sheets_info[0],
|
||||
SheetInfo {
|
||||
SheetProperties {
|
||||
name: "Sheet1".to_string(),
|
||||
state: "visible".to_string(),
|
||||
sheet_id: 1,
|
||||
|
||||
@@ -6,4 +6,5 @@ mod test_general;
|
||||
mod test_rename_sheet;
|
||||
mod test_row_column;
|
||||
mod test_styles;
|
||||
mod test_to_from_bytes;
|
||||
mod test_undo_redo;
|
||||
|
||||
@@ -30,6 +30,32 @@ fn add_undo_redo() {
|
||||
assert!(!model.can_redo());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_sheet_color() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
model.set_sheet_color(0, "#343434").unwrap();
|
||||
let worksheets_properties = model.get_worksheets_properties();
|
||||
assert_eq!(worksheets_properties.len(), 1);
|
||||
assert_eq!(worksheets_properties[0].color, Some("#343434".to_owned()));
|
||||
model.undo().unwrap();
|
||||
assert_eq!(model.get_worksheets_properties()[0].color, None);
|
||||
|
||||
model.redo().unwrap();
|
||||
assert_eq!(
|
||||
model.get_worksheets_properties()[0].color,
|
||||
Some("#343434".to_owned())
|
||||
);
|
||||
// changes the color if there is one
|
||||
model.set_sheet_color(0, "#2534FF").unwrap();
|
||||
assert_eq!(
|
||||
model.get_worksheets_properties()[0].color,
|
||||
Some("#2534FF".to_owned())
|
||||
);
|
||||
// Setting it back to none
|
||||
model.set_sheet_color(0, "").unwrap();
|
||||
assert_eq!(model.get_worksheets_properties()[0].color, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_sheet_propagates() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
@@ -39,8 +65,8 @@ fn new_sheet_propagates() {
|
||||
|
||||
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
model2.apply_external_diffs(&send_queue).unwrap();
|
||||
let sheets_info = model2.get_sheets_info();
|
||||
assert_eq!(sheets_info.len(), 2);
|
||||
let worksheets_properties = model2.get_worksheets_properties();
|
||||
assert_eq!(worksheets_properties.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -53,6 +79,6 @@ fn delete_sheet_propagates() {
|
||||
|
||||
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
model2.apply_external_diffs(&send_queue).unwrap();
|
||||
let sheets_info = model2.get_sheets_info();
|
||||
let sheets_info = model2.get_worksheets_properties();
|
||||
assert_eq!(sheets_info.len(), 1);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,13 @@ fn set_user_input_errors() {
|
||||
assert!(model.set_user_input(0, 1, LAST_COLUMN + 1, "1").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_model_debug_message() {
|
||||
let model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
let s = &format!("{:?}", model);
|
||||
assert_eq!(s, "UserModel");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_remove_rows() {
|
||||
let model = new_empty_model();
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::UserModel;
|
||||
fn basic_rename() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
model.rename_sheet(0, "NewSheet").unwrap();
|
||||
assert_eq!(model.get_sheets_info()[0].name, "NewSheet");
|
||||
assert_eq!(model.get_worksheets_properties()[0].name, "NewSheet");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -14,15 +14,15 @@ fn undo_redo() {
|
||||
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
model.rename_sheet(0, "NewSheet").unwrap();
|
||||
model.undo().unwrap();
|
||||
assert_eq!(model.get_sheets_info()[0].name, "Sheet1");
|
||||
assert_eq!(model.get_worksheets_properties()[0].name, "Sheet1");
|
||||
model.redo().unwrap();
|
||||
assert_eq!(model.get_sheets_info()[0].name, "NewSheet");
|
||||
assert_eq!(model.get_worksheets_properties()[0].name, "NewSheet");
|
||||
|
||||
let send_queue = model.flush_send_queue();
|
||||
|
||||
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
|
||||
model2.apply_external_diffs(&send_queue).unwrap();
|
||||
assert_eq!(model.get_sheets_info()[0].name, "NewSheet");
|
||||
assert_eq!(model.get_worksheets_properties()[0].name, "NewSheet");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
30
base/src/test/user_model/test_to_from_bytes.rs
Normal file
30
base/src/test/user_model/test_to_from_bytes.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use crate::{test::util::new_empty_model, UserModel};
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let mut model1 = UserModel::from_model(new_empty_model());
|
||||
let width = model1.get_column_width(0, 3).unwrap() * 3.0;
|
||||
model1.set_column_width(0, 3, width).unwrap();
|
||||
model1.set_user_input(0, 1, 2, "Hello IronCalc!").unwrap();
|
||||
|
||||
let model_bytes = model1.to_bytes();
|
||||
|
||||
let model2 = UserModel::from_bytes(&model_bytes).unwrap();
|
||||
|
||||
assert_eq!(model2.get_column_width(0, 3), Ok(width));
|
||||
assert_eq!(
|
||||
model2.get_formatted_cell_value(0, 1, 2),
|
||||
Ok("Hello IronCalc!".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors() {
|
||||
let model_bytes = "Early in the morning, late in the century, Cricklewood Broadway.";
|
||||
assert_eq!(
|
||||
&UserModel::from_bytes(model_bytes).unwrap_err(),
|
||||
"Error parsing workbook"
|
||||
);
|
||||
}
|
||||
@@ -316,6 +316,7 @@ impl Default for Styles {
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Style {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub alignment: Option<Alignment>,
|
||||
pub num_fmt: String,
|
||||
pub fill: Fill,
|
||||
@@ -665,9 +666,10 @@ pub struct Border {
|
||||
/// Information need to show a sheet tab in the UI
|
||||
/// The color is serialized only if it is not Color::None
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub struct SheetInfo {
|
||||
pub struct SheetProperties {
|
||||
pub name: String,
|
||||
pub state: String,
|
||||
pub sheet_id: u32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, fmt::Debug};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -12,8 +12,8 @@ use crate::{
|
||||
},
|
||||
model::Model,
|
||||
types::{
|
||||
Alignment, BorderItem, BorderStyle, Cell, Col, HorizontalAlignment, Row, SheetInfo, Style,
|
||||
VerticalAlignment,
|
||||
Alignment, BorderItem, BorderStyle, Cell, Col, HorizontalAlignment, Row, SheetProperties,
|
||||
Style, VerticalAlignment,
|
||||
},
|
||||
utils::is_valid_hex_color,
|
||||
};
|
||||
@@ -113,6 +113,11 @@ enum Diff {
|
||||
old_value: String,
|
||||
new_value: String,
|
||||
},
|
||||
SetSheetColor {
|
||||
index: u32,
|
||||
old_value: String,
|
||||
new_value: String,
|
||||
},
|
||||
}
|
||||
|
||||
type DiffList = Vec<Diff>;
|
||||
@@ -276,6 +281,12 @@ pub struct UserModel {
|
||||
pause_evaluation: bool,
|
||||
}
|
||||
|
||||
impl Debug for UserModel {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("UserModel").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl UserModel {
|
||||
/// Creates a user model from an existing model
|
||||
pub fn from_model(model: Model) -> UserModel {
|
||||
@@ -301,7 +312,32 @@ impl UserModel {
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a model from it's internal representation
|
||||
///
|
||||
/// See also:
|
||||
/// * [Model::from_json]
|
||||
pub fn from_bytes(s: &str) -> Result<UserModel, String> {
|
||||
let model = Model::from_json(s)?;
|
||||
Ok(UserModel {
|
||||
model,
|
||||
history: History::default(),
|
||||
send_queue: vec![],
|
||||
pause_evaluation: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the internal representation of a model
|
||||
///
|
||||
/// See also:
|
||||
/// * [Model::to_json_str]
|
||||
pub fn to_bytes(&self) -> String {
|
||||
self.model.to_json_str()
|
||||
}
|
||||
|
||||
/// Undoes last change if any, places the change in the redo list and evaluates the model if needed
|
||||
///
|
||||
/// See also:
|
||||
/// * [UserModel::redo]
|
||||
pub fn undo(&mut self) -> Result<(), String> {
|
||||
if let Some(diff_list) = self.history.undo() {
|
||||
self.apply_undo_diff_list(&diff_list)?;
|
||||
@@ -314,6 +350,9 @@ impl UserModel {
|
||||
}
|
||||
|
||||
/// Redoes the last undone change, places the change in the undo list and evaluates the model if needed
|
||||
///
|
||||
/// See also:
|
||||
/// * [UserModel::redo]
|
||||
pub fn redo(&mut self) -> Result<(), String> {
|
||||
if let Some(diff_list) = self.history.redo() {
|
||||
self.apply_diff_list(&diff_list)?;
|
||||
@@ -495,6 +534,27 @@ impl UserModel {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets sheet color
|
||||
///
|
||||
/// Note: an empty string will remove the color
|
||||
///
|
||||
/// See also
|
||||
/// * [Model::set_sheet_color]
|
||||
/// * [UserModel::get_worksheets_properties]
|
||||
pub fn set_sheet_color(&mut self, sheet: u32, color: &str) -> Result<(), String> {
|
||||
let old_value = match &self.model.workbook.worksheet(sheet)?.color {
|
||||
Some(c) => c.clone(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
self.model.set_sheet_color(sheet, color)?;
|
||||
self.push_diff_list(vec![Diff::SetSheetColor {
|
||||
index: sheet,
|
||||
old_value,
|
||||
new_value: color.to_string(),
|
||||
}]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes cells contents and style
|
||||
///
|
||||
/// See also:
|
||||
@@ -858,10 +918,10 @@ impl UserModel {
|
||||
/// Returns information about the sheets
|
||||
///
|
||||
/// See also:
|
||||
/// * [Model::get_sheets_info]
|
||||
/// * [Model::get_worksheets_properties]
|
||||
#[inline]
|
||||
pub fn get_sheets_info(&self) -> Vec<SheetInfo> {
|
||||
self.model.get_sheets_info()
|
||||
pub fn get_worksheets_properties(&self) -> Vec<SheetProperties> {
|
||||
self.model.get_worksheets_properties()
|
||||
}
|
||||
|
||||
// **** Private methods ****** //
|
||||
@@ -1020,6 +1080,13 @@ impl UserModel {
|
||||
} => {
|
||||
self.model.rename_sheet_by_index(*index, old_value)?;
|
||||
}
|
||||
Diff::SetSheetColor {
|
||||
index,
|
||||
old_value,
|
||||
new_value: _,
|
||||
} => {
|
||||
self.model.set_sheet_color(*index, old_value)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if needs_evaluation {
|
||||
@@ -1133,6 +1200,13 @@ impl UserModel {
|
||||
} => {
|
||||
self.model.rename_sheet_by_index(*index, new_value)?;
|
||||
}
|
||||
Diff::SetSheetColor {
|
||||
index,
|
||||
old_value: _,
|
||||
new_value,
|
||||
} => {
|
||||
self.model.set_sheet_color(*index, new_value)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user