UPDATE: Adds get/set views to the user model API (#69)
This commit is contained in:
committed by
GitHub
parent
d2cba48f8e
commit
49c3b14bf0
@@ -118,6 +118,8 @@ pub struct Model {
|
||||
pub(crate) language: Language,
|
||||
/// The timezone used to evaluate the model
|
||||
pub(crate) tz: Tz,
|
||||
/// The view id. A view consist of a selected sheet and ranges.
|
||||
pub(crate) view_id: u32,
|
||||
}
|
||||
|
||||
// FIXME: Maybe this should be the same as CellReference
|
||||
@@ -886,6 +888,7 @@ impl Model {
|
||||
language,
|
||||
locale,
|
||||
tz,
|
||||
view_id: 0,
|
||||
};
|
||||
|
||||
model.parse_formulas();
|
||||
|
||||
@@ -15,7 +15,9 @@ use crate::{
|
||||
language::get_language,
|
||||
locale::get_locale,
|
||||
model::{get_milliseconds_since_epoch, Model, ParsedDefinedName},
|
||||
types::{Metadata, Selection, SheetState, Workbook, WorkbookSettings, Worksheet},
|
||||
types::{
|
||||
Metadata, SheetState, Workbook, WorkbookSettings, WorkbookView, Worksheet, WorksheetView,
|
||||
},
|
||||
utils::ParsedReference,
|
||||
};
|
||||
|
||||
@@ -35,7 +37,20 @@ fn is_valid_sheet_name(name: &str) -> bool {
|
||||
|
||||
impl Model {
|
||||
/// Creates a new worksheet. Note that it does not check if the name or the sheet_id exists
|
||||
fn new_empty_worksheet(name: &str, sheet_id: u32) -> Worksheet {
|
||||
fn new_empty_worksheet(name: &str, sheet_id: u32, view_ids: &[&u32]) -> Worksheet {
|
||||
let mut views = HashMap::new();
|
||||
for id in view_ids {
|
||||
views.insert(
|
||||
**id,
|
||||
WorksheetView {
|
||||
row: 1,
|
||||
column: 1,
|
||||
range: [1, 1, 1, 1],
|
||||
top_row: 1,
|
||||
left_column: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
Worksheet {
|
||||
cols: vec![],
|
||||
rows: vec![],
|
||||
@@ -50,12 +65,7 @@ impl Model {
|
||||
color: Default::default(),
|
||||
frozen_columns: 0,
|
||||
frozen_rows: 0,
|
||||
selection: Selection {
|
||||
is_selected: false,
|
||||
row: 1,
|
||||
column: 1,
|
||||
range: [1, 1, 1, 1],
|
||||
},
|
||||
views,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +140,7 @@ impl Model {
|
||||
self.parsed_defined_names = parsed_defined_names;
|
||||
}
|
||||
|
||||
// Reparses all formulas and defined names
|
||||
/// Reparses all formulas and defined names
|
||||
pub(crate) fn reset_parsed_structures(&mut self) {
|
||||
self.parser
|
||||
.set_worksheets(self.workbook.get_worksheet_names());
|
||||
@@ -161,7 +171,8 @@ impl Model {
|
||||
let sheet_name = format!("{}{}", base_name, index);
|
||||
// Now we need a sheet_id
|
||||
let sheet_id = self.get_new_sheet_id();
|
||||
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id);
|
||||
let view_ids: Vec<&u32> = self.workbook.views.keys().collect();
|
||||
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id, &view_ids);
|
||||
self.workbook.worksheets.push(worksheet);
|
||||
self.reset_parsed_structures();
|
||||
(sheet_name, self.workbook.worksheets.len() as u32 - 1)
|
||||
@@ -192,7 +203,8 @@ impl Model {
|
||||
Some(id) => id,
|
||||
None => self.get_new_sheet_id(),
|
||||
};
|
||||
let worksheet = Model::new_empty_worksheet(sheet_name, sheet_id);
|
||||
let view_ids: Vec<&u32> = self.workbook.views.keys().collect();
|
||||
let worksheet = Model::new_empty_worksheet(sheet_name, sheet_id, &view_ids);
|
||||
if sheet_index as usize > self.workbook.worksheets.len() {
|
||||
return Err("Sheet index out of range".to_string());
|
||||
}
|
||||
@@ -339,11 +351,14 @@ impl Model {
|
||||
// "2020-08-06T21:20:53Z
|
||||
let now = dt.format("%Y-%m-%dT%H:%M:%SZ").to_string();
|
||||
|
||||
let mut views = HashMap::new();
|
||||
views.insert(0, WorkbookView { sheet: 0 });
|
||||
|
||||
// String versions of the locale are added here to simplify the serialize/deserialize logic
|
||||
let workbook = Workbook {
|
||||
shared_strings: vec![],
|
||||
defined_names: vec![],
|
||||
worksheets: vec![Model::new_empty_worksheet("Sheet1", 1)],
|
||||
worksheets: vec![Model::new_empty_worksheet("Sheet1", 1, &[&0])],
|
||||
styles: Default::default(),
|
||||
name: name.to_string(),
|
||||
settings: WorkbookSettings {
|
||||
@@ -359,6 +374,7 @@ impl Model {
|
||||
last_modified: now,
|
||||
},
|
||||
tables: HashMap::new(),
|
||||
views,
|
||||
};
|
||||
let parsed_formulas = Vec::new();
|
||||
let worksheets = &workbook.worksheets;
|
||||
@@ -379,6 +395,7 @@ impl Model {
|
||||
locale,
|
||||
language,
|
||||
tz,
|
||||
view_id: 0,
|
||||
};
|
||||
model.parse_formulas();
|
||||
Ok(model)
|
||||
|
||||
@@ -8,3 +8,4 @@ mod test_row_column;
|
||||
mod test_styles;
|
||||
mod test_to_from_bytes;
|
||||
mod test_undo_redo;
|
||||
mod test_view;
|
||||
|
||||
216
base/src/test/user_model/test_view.rs
Normal file
216
base/src/test/user_model/test_view.rs
Normal file
@@ -0,0 +1,216 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
constants::{LAST_COLUMN, LAST_ROW},
|
||||
test::util::new_empty_model,
|
||||
user_model::SelectedView,
|
||||
UserModel,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn initial_view() {
|
||||
let model = new_empty_model();
|
||||
let model = UserModel::from_model(model);
|
||||
assert_eq!(model.get_selected_sheet(), 0);
|
||||
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
||||
assert_eq!(
|
||||
model.get_selected_view(),
|
||||
SelectedView {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 1,
|
||||
range: [1, 1, 1, 1],
|
||||
top_row: 1,
|
||||
left_column: 1
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_the_cell_sets_the_range() {
|
||||
let model = new_empty_model();
|
||||
let mut model = UserModel::from_model(model);
|
||||
model.set_selected_cell(5, 4).unwrap();
|
||||
assert_eq!(model.get_selected_sheet(), 0);
|
||||
assert_eq!(model.get_selected_cell(), (0, 5, 4));
|
||||
assert_eq!(
|
||||
model.get_selected_view(),
|
||||
SelectedView {
|
||||
sheet: 0,
|
||||
row: 5,
|
||||
column: 4,
|
||||
range: [5, 4, 5, 4],
|
||||
top_row: 1,
|
||||
left_column: 1
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_the_range_does_not_set_the_cell() {
|
||||
let model = new_empty_model();
|
||||
let mut model = UserModel::from_model(model);
|
||||
model.set_selected_range(5, 4, 10, 6).unwrap();
|
||||
assert_eq!(model.get_selected_sheet(), 0);
|
||||
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
||||
assert_eq!(
|
||||
model.get_selected_view(),
|
||||
SelectedView {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 1,
|
||||
range: [5, 4, 10, 6],
|
||||
top_row: 1,
|
||||
left_column: 1
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_new_sheet_and_back() {
|
||||
let model = new_empty_model();
|
||||
let mut model = UserModel::from_model(model);
|
||||
model.new_sheet();
|
||||
assert_eq!(model.get_selected_sheet(), 0);
|
||||
model.set_selected_cell(5, 4).unwrap();
|
||||
model.set_selected_sheet(1).unwrap();
|
||||
assert_eq!(model.get_selected_cell(), (1, 1, 1));
|
||||
model.set_selected_sheet(0).unwrap();
|
||||
assert_eq!(model.get_selected_cell(), (0, 5, 4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_selected_cell_errors() {
|
||||
let model = new_empty_model();
|
||||
let mut model = UserModel::from_model(model);
|
||||
assert_eq!(
|
||||
model.set_selected_cell(-5, 4),
|
||||
Err("Invalid row: '-5'".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
model.set_selected_cell(5, -4),
|
||||
Err("Invalid column: '-4'".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
model.set_selected_range(-1, 1, 1, 1),
|
||||
Err("Invalid row: '-1'".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
model.set_selected_range(1, 0, 1, 1),
|
||||
Err("Invalid column: '0'".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
model.set_selected_range(1, 1, LAST_ROW + 1, 1),
|
||||
Err("Invalid row: '1048577'".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
model.set_selected_range(1, 1, 1, LAST_COLUMN + 1),
|
||||
Err("Invalid column: '16385'".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_selected_cell_errors_wrong_sheet() {
|
||||
let mut model = new_empty_model();
|
||||
// forcefully set a wrong index
|
||||
model.workbook.views.get_mut(&0).unwrap().sheet = 2;
|
||||
let mut model = UserModel::from_model(model);
|
||||
// It's returning the wrong number
|
||||
assert_eq!(model.get_selected_sheet(), 2);
|
||||
|
||||
// But we can't set the selected cell anymore
|
||||
assert_eq!(
|
||||
model.set_selected_cell(3, 4),
|
||||
Err("Invalid worksheet index 2".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
model.set_selected_range(3, 4, 5, 6),
|
||||
Err("Invalid worksheet index 2".to_string())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
model.set_top_left_visible_cell(3, 4),
|
||||
Err("Invalid worksheet index 2".to_string())
|
||||
);
|
||||
|
||||
// we can fix it by setting the right cell
|
||||
model.set_selected_sheet(0).unwrap();
|
||||
model.set_selected_cell(3, 4).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_visible_cell() {
|
||||
let model = new_empty_model();
|
||||
let mut model = UserModel::from_model(model);
|
||||
model.set_top_left_visible_cell(100, 12).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
model.get_selected_view(),
|
||||
SelectedView {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 1,
|
||||
range: [1, 1, 1, 1],
|
||||
top_row: 100,
|
||||
left_column: 12
|
||||
}
|
||||
);
|
||||
|
||||
let s = serde_json::to_string(&model.get_selected_view()).unwrap();
|
||||
assert_eq!(
|
||||
serde_json::from_str::<SelectedView>(&s).unwrap(),
|
||||
SelectedView {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 1,
|
||||
range: [1, 1, 1, 1],
|
||||
top_row: 100,
|
||||
left_column: 12
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_visible_cell_errors() {
|
||||
let model = new_empty_model();
|
||||
let mut model = UserModel::from_model(model);
|
||||
assert_eq!(
|
||||
model.set_top_left_visible_cell(-100, 12),
|
||||
Err("Invalid row: '-100'".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
model.set_top_left_visible_cell(100, -12),
|
||||
Err("Invalid column: '-12'".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_no_views() {
|
||||
let mut model = new_empty_model();
|
||||
// forcefully remove the view
|
||||
model.workbook.views = HashMap::new();
|
||||
// also in the sheet
|
||||
model.workbook.worksheets[0].views = HashMap::new();
|
||||
let mut model = UserModel::from_model(model);
|
||||
// get methods will return defaults
|
||||
assert_eq!(model.get_selected_sheet(), 0);
|
||||
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
||||
assert_eq!(
|
||||
model.get_selected_view(),
|
||||
SelectedView {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 1,
|
||||
range: [1, 1, 1, 1],
|
||||
top_row: 1,
|
||||
left_column: 1
|
||||
}
|
||||
);
|
||||
|
||||
// set methods won't complain. but won't work either
|
||||
model.set_selected_sheet(0).unwrap();
|
||||
model.set_selected_cell(5, 6).unwrap();
|
||||
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
||||
}
|
||||
@@ -27,6 +27,14 @@ pub struct WorkbookSettings {
|
||||
pub tz: String,
|
||||
pub locale: String,
|
||||
}
|
||||
|
||||
/// A Workbook View tracks of the selected sheet for each view
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
||||
pub struct WorkbookView {
|
||||
/// The index of the currently selected sheet.
|
||||
pub sheet: u32,
|
||||
}
|
||||
|
||||
/// An internal representation of an IronCalc Workbook
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
||||
pub struct Workbook {
|
||||
@@ -38,6 +46,7 @@ pub struct Workbook {
|
||||
pub settings: WorkbookSettings,
|
||||
pub metadata: Metadata,
|
||||
pub tables: HashMap<String, Table>,
|
||||
pub views: HashMap<u32, WorkbookView>,
|
||||
}
|
||||
|
||||
/// A defined name. The `sheet_id` is the sheet index in case the name is local
|
||||
@@ -48,9 +57,6 @@ pub struct DefinedName {
|
||||
pub sheet_id: Option<u32>,
|
||||
}
|
||||
|
||||
// TODO: Move to worksheet.rs make frozen_rows/columns private and u32
|
||||
/// Internal representation of a worksheet Excel object
|
||||
|
||||
/// * state:
|
||||
/// 18.18.68 ST_SheetState (Sheet Visibility Types)
|
||||
/// hidden, veryHidden, visible
|
||||
@@ -71,12 +77,21 @@ impl Display for SheetState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the state of the worksheet as seen by the user. This includes
|
||||
/// details such as the currently selected cell, the visible range, and the
|
||||
/// position of the viewport.
|
||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
||||
pub struct Selection {
|
||||
pub is_selected: bool,
|
||||
pub struct WorksheetView {
|
||||
/// The row index of the currently selected cell.
|
||||
pub row: i32,
|
||||
/// The column index of the currently selected cell.
|
||||
pub column: i32,
|
||||
/// The selected range in the worksheet, specified as [start_row, start_column, end_row, end_column].
|
||||
pub range: [i32; 4],
|
||||
/// The row index of the topmost visible cell in the worksheet view.
|
||||
pub top_row: i32,
|
||||
/// The column index of the leftmost visible cell in the worksheet view.
|
||||
pub left_column: i32,
|
||||
}
|
||||
|
||||
/// Internal representation of a worksheet Excel object
|
||||
@@ -95,7 +110,7 @@ pub struct Worksheet {
|
||||
pub comments: Vec<Comment>,
|
||||
pub frozen_rows: i32,
|
||||
pub frozen_columns: i32,
|
||||
pub selection: Selection,
|
||||
pub views: HashMap<u32, WorksheetView>,
|
||||
}
|
||||
|
||||
/// Internal representation of Excel's sheet_data
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use std::{collections::HashMap, fmt::Debug};
|
||||
|
||||
use bitcode::{Decode, Encode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
constants,
|
||||
@@ -18,6 +19,17 @@ use crate::{
|
||||
utils::is_valid_hex_color,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||
pub struct SelectedView {
|
||||
pub sheet: u32,
|
||||
pub row: i32,
|
||||
pub column: i32,
|
||||
pub range: [i32; 4],
|
||||
pub top_row: i32,
|
||||
pub left_column: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode)]
|
||||
struct RowData {
|
||||
row: Option<Row>,
|
||||
@@ -118,6 +130,7 @@ enum Diff {
|
||||
old_value: String,
|
||||
new_value: String,
|
||||
},
|
||||
// FIXME: we are missing SetViewDiffs
|
||||
}
|
||||
|
||||
type DiffList = Vec<Diff>;
|
||||
@@ -249,7 +262,7 @@ fn vertical(value: &str) -> Result<VerticalAlignment, String> {
|
||||
}
|
||||
|
||||
/// # A wrapper around [`Model`] for a spreadsheet end user.
|
||||
/// UserModel is a wrapper around Model with undo/redo history, _diffs_ and automatic evaluation.
|
||||
/// UserModel is a wrapper around Model with undo/redo history, _diffs_, automatic evaluation and view management.
|
||||
///
|
||||
/// A diff in this context (or more correctly a _user diff_) is a change created by a user.
|
||||
///
|
||||
@@ -935,6 +948,165 @@ impl UserModel {
|
||||
self.model.get_worksheets_properties()
|
||||
}
|
||||
|
||||
/// Returns the selected sheet index
|
||||
pub fn get_selected_sheet(&self) -> u32 {
|
||||
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
||||
view.sheet
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the selected cell
|
||||
pub fn get_selected_cell(&self) -> (u32, i32, i32) {
|
||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
||||
view.sheet
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
|
||||
if let Some(view) = worksheet.views.get(&self.model.view_id) {
|
||||
return (sheet, view.row, view.column);
|
||||
}
|
||||
}
|
||||
// return a safe default
|
||||
(0, 1, 1)
|
||||
}
|
||||
|
||||
/// Returns selected view
|
||||
pub fn get_selected_view(&self) -> SelectedView {
|
||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
||||
view.sheet
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
|
||||
if let Some(view) = worksheet.views.get(&self.model.view_id) {
|
||||
return SelectedView {
|
||||
sheet,
|
||||
row: view.row,
|
||||
column: view.column,
|
||||
range: view.range,
|
||||
top_row: view.top_row,
|
||||
left_column: view.left_column,
|
||||
};
|
||||
}
|
||||
}
|
||||
// return a safe default
|
||||
SelectedView {
|
||||
sheet: 0,
|
||||
row: 1,
|
||||
column: 1,
|
||||
range: [1, 1, 1, 1],
|
||||
top_row: 1,
|
||||
left_column: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the the selected sheet
|
||||
pub fn set_selected_sheet(&mut self, sheet: u32) -> Result<(), String> {
|
||||
if self.model.workbook.worksheet(sheet).is_err() {
|
||||
return Err(format!("Invalid worksheet index {}", sheet));
|
||||
}
|
||||
if let Some(view) = self.model.workbook.views.get_mut(&0) {
|
||||
view.sheet = sheet;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the selected cell
|
||||
pub fn set_selected_cell(&mut self, row: i32, column: i32) -> Result<(), String> {
|
||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
||||
view.sheet
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if !is_valid_column_number(column) {
|
||||
return Err(format!("Invalid column: '{column}'"));
|
||||
}
|
||||
if !is_valid_column_number(row) {
|
||||
return Err(format!("Invalid row: '{row}'"));
|
||||
}
|
||||
if self.model.workbook.worksheet(sheet).is_err() {
|
||||
return Err(format!("Invalid worksheet index {}", sheet));
|
||||
}
|
||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
||||
if let Some(view) = worksheet.views.get_mut(&0) {
|
||||
view.row = row;
|
||||
view.column = column;
|
||||
view.range = [row, column, row, column];
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the selected range
|
||||
pub fn set_selected_range(
|
||||
&mut self,
|
||||
start_row: i32,
|
||||
start_column: i32,
|
||||
end_row: i32,
|
||||
end_column: i32,
|
||||
) -> Result<(), String> {
|
||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
||||
view.sheet
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if !is_valid_column_number(start_column) {
|
||||
return Err(format!("Invalid column: '{start_column}'"));
|
||||
}
|
||||
if !is_valid_column_number(start_row) {
|
||||
return Err(format!("Invalid row: '{start_row}'"));
|
||||
}
|
||||
|
||||
if !is_valid_column_number(end_column) {
|
||||
return Err(format!("Invalid column: '{end_column}'"));
|
||||
}
|
||||
if !is_valid_column_number(end_row) {
|
||||
return Err(format!("Invalid row: '{end_row}'"));
|
||||
}
|
||||
if self.model.workbook.worksheet(sheet).is_err() {
|
||||
return Err(format!("Invalid worksheet index {}", sheet));
|
||||
}
|
||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
||||
if let Some(view) = worksheet.views.get_mut(&0) {
|
||||
view.range = [start_row, start_column, end_row, end_column];
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the value of the first visible cell
|
||||
pub fn set_top_left_visible_cell(
|
||||
&mut self,
|
||||
top_row: i32,
|
||||
left_column: i32,
|
||||
) -> Result<(), String> {
|
||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
||||
view.sheet
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if !is_valid_column_number(left_column) {
|
||||
return Err(format!("Invalid column: '{left_column}'"));
|
||||
}
|
||||
if !is_valid_column_number(top_row) {
|
||||
return Err(format!("Invalid row: '{top_row}'"));
|
||||
}
|
||||
if self.model.workbook.worksheet(sheet).is_err() {
|
||||
return Err(format!("Invalid worksheet index {}", sheet));
|
||||
}
|
||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
||||
if let Some(view) = worksheet.views.get_mut(&0) {
|
||||
view.top_row = top_row;
|
||||
view.left_column = left_column;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
// **** Private methods ****** //
|
||||
|
||||
fn push_diff_list(&mut self, diff_list: DiffList) {
|
||||
|
||||
@@ -286,4 +286,56 @@ impl Model {
|
||||
pub fn get_worksheets_properties(&self) -> JsValue {
|
||||
serde_wasm_bindgen::to_value(&self.model.get_worksheets_properties()).unwrap()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "getSelectedSheet")]
|
||||
pub fn get_selected_sheet(&self) -> u32 {
|
||||
self.model.get_selected_sheet()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "getSelectedCell")]
|
||||
pub fn get_selected_cell(&self) -> Vec<i32> {
|
||||
let (sheet, row, column) = self.model.get_selected_cell();
|
||||
vec![sheet as i32, row, column]
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "getSelectedView")]
|
||||
pub fn get_selected_view(&self) -> JsValue {
|
||||
serde_wasm_bindgen::to_value(&self.model.get_selected_view()).unwrap()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setSelectedSheet")]
|
||||
pub fn set_selected_sheet(&mut self, sheet: u32) -> Result<(), JsError> {
|
||||
self.model.set_selected_sheet(sheet).map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setSelectedCell")]
|
||||
pub fn set_selected_cell(&mut self, row: i32, column: i32) -> Result<(), JsError> {
|
||||
self.model
|
||||
.set_selected_cell(row, column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setSelectedRange")]
|
||||
pub fn set_selected_range(
|
||||
&mut self,
|
||||
start_row: i32,
|
||||
start_column: i32,
|
||||
end_row: i32,
|
||||
end_column: i32,
|
||||
) -> Result<(), JsError> {
|
||||
self.model
|
||||
.set_selected_range(start_row, start_column, end_row, end_column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setTopLeftVisibleCell")]
|
||||
pub fn set_top_left_visible_cell(
|
||||
&mut self,
|
||||
top_row: i32,
|
||||
top_column: i32,
|
||||
) -> Result<(), JsError> {
|
||||
self.model
|
||||
.set_top_left_visible_cell(top_row, top_column)
|
||||
.map_err(to_js_error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use std::{
|
||||
use roxmltree::Node;
|
||||
|
||||
use ironcalc_base::{
|
||||
types::{Metadata, Workbook, WorkbookSettings},
|
||||
types::{Metadata, Workbook, WorkbookSettings, WorkbookView},
|
||||
Model,
|
||||
};
|
||||
|
||||
@@ -66,7 +66,7 @@ fn load_xlsx_from_reader<R: Read + std::io::Seek>(
|
||||
let workbook = load_workbook(&mut archive)?;
|
||||
let rels = load_relationships(&mut archive)?;
|
||||
let mut tables = HashMap::new();
|
||||
let worksheets = load_sheets(
|
||||
let (worksheets, selected_sheet) = load_sheets(
|
||||
&mut archive,
|
||||
&rels,
|
||||
&workbook,
|
||||
@@ -88,6 +88,13 @@ fn load_xlsx_from_reader<R: Read + std::io::Seek>(
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut views = HashMap::new();
|
||||
views.insert(
|
||||
0,
|
||||
WorkbookView {
|
||||
sheet: selected_sheet,
|
||||
},
|
||||
);
|
||||
Ok(Workbook {
|
||||
shared_strings,
|
||||
defined_names: workbook.defined_names,
|
||||
@@ -100,6 +107,7 @@ fn load_xlsx_from_reader<R: Read + std::io::Seek>(
|
||||
},
|
||||
metadata,
|
||||
tables,
|
||||
views,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ use ironcalc_base::{
|
||||
utils::{column_to_number, parse_reference_a1},
|
||||
},
|
||||
types::{
|
||||
Cell, Col, Comment, DefinedName, Row, Selection, SheetData, SheetState, Table, Worksheet,
|
||||
Cell, Col, Comment, DefinedName, Row, SheetData, SheetState, Table, Worksheet,
|
||||
WorksheetView,
|
||||
},
|
||||
};
|
||||
use roxmltree::Node;
|
||||
@@ -674,7 +675,7 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
|
||||
worksheets: &[String],
|
||||
tables: &HashMap<String, Table>,
|
||||
shared_strings: &mut Vec<String>,
|
||||
) -> Result<Worksheet, XlsxError> {
|
||||
) -> Result<(Worksheet, bool), XlsxError> {
|
||||
let sheet_name = &settings.name;
|
||||
let sheet_id = settings.id;
|
||||
let state = &settings.state;
|
||||
@@ -952,27 +953,37 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
|
||||
// pageSetup
|
||||
// <pageSetup orientation="portrait" r:id="rId1"/>
|
||||
|
||||
Ok(Worksheet {
|
||||
dimension,
|
||||
cols,
|
||||
rows,
|
||||
shared_formulas,
|
||||
sheet_data,
|
||||
name: sheet_name.to_string(),
|
||||
sheet_id,
|
||||
state: state.to_owned(),
|
||||
color,
|
||||
merge_cells,
|
||||
comments: settings.comments,
|
||||
frozen_rows: sheet_view.frozen_rows,
|
||||
frozen_columns: sheet_view.frozen_columns,
|
||||
selection: Selection {
|
||||
is_selected: sheet_view.is_selected,
|
||||
let mut views = HashMap::new();
|
||||
views.insert(
|
||||
0,
|
||||
WorksheetView {
|
||||
row: sheet_view.selected_row,
|
||||
column: sheet_view.selected_column,
|
||||
range: sheet_view.range,
|
||||
top_row: 1,
|
||||
left_column: 1,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
Ok((
|
||||
Worksheet {
|
||||
dimension,
|
||||
cols,
|
||||
rows,
|
||||
shared_formulas,
|
||||
sheet_data,
|
||||
name: sheet_name.to_string(),
|
||||
sheet_id,
|
||||
state: state.to_owned(),
|
||||
color,
|
||||
merge_cells,
|
||||
comments: settings.comments,
|
||||
frozen_rows: sheet_view.frozen_rows,
|
||||
frozen_columns: sheet_view.frozen_columns,
|
||||
views,
|
||||
},
|
||||
sheet_view.is_selected,
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) fn load_sheets<R: Read + std::io::Seek>(
|
||||
@@ -981,7 +992,7 @@ pub(super) fn load_sheets<R: Read + std::io::Seek>(
|
||||
workbook: &WorkbookXML,
|
||||
tables: &mut HashMap<String, Table>,
|
||||
shared_strings: &mut Vec<String>,
|
||||
) -> Result<Vec<Worksheet>, XlsxError> {
|
||||
) -> Result<(Vec<Worksheet>, u32), XlsxError> {
|
||||
// load comments and tables
|
||||
let mut comments = HashMap::new();
|
||||
for sheet in &workbook.worksheets {
|
||||
@@ -1003,6 +1014,8 @@ pub(super) fn load_sheets<R: Read + std::io::Seek>(
|
||||
// load all sheets
|
||||
let worksheets: &Vec<String> = &workbook.worksheets.iter().map(|s| s.name.clone()).collect();
|
||||
let mut sheets = Vec::new();
|
||||
let mut selected_sheet = 0;
|
||||
let mut sheet_index = 0;
|
||||
for sheet in &workbook.worksheets {
|
||||
let sheet_name = &sheet.name;
|
||||
let rel_id = &sheet.id;
|
||||
@@ -1021,15 +1034,14 @@ pub(super) fn load_sheets<R: Read + std::io::Seek>(
|
||||
state: state.clone(),
|
||||
comments: comments.get(rel_id).expect("").to_vec(),
|
||||
};
|
||||
sheets.push(load_sheet(
|
||||
archive,
|
||||
&path,
|
||||
settings,
|
||||
worksheets,
|
||||
tables,
|
||||
shared_strings,
|
||||
)?);
|
||||
let (s, is_selected) =
|
||||
load_sheet(archive, &path, settings, worksheets, tables, shared_strings)?;
|
||||
if is_selected {
|
||||
selected_sheet = sheet_index;
|
||||
}
|
||||
sheets.push(s);
|
||||
sheet_index += 1;
|
||||
}
|
||||
}
|
||||
Ok(sheets)
|
||||
Ok((sheets, selected_sheet))
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -16,33 +16,32 @@ fn test_example() {
|
||||
let workbook = model.workbook;
|
||||
let ws = &workbook.worksheets;
|
||||
let expected_names = vec![
|
||||
("Sheet1".to_string(), false),
|
||||
("Second".to_string(), false),
|
||||
("Sheet4".to_string(), false),
|
||||
("shared".to_string(), false),
|
||||
("Table".to_string(), false),
|
||||
("Sheet2".to_string(), false),
|
||||
("Created fourth".to_string(), false),
|
||||
("Frozen".to_string(), true),
|
||||
("Split".to_string(), false),
|
||||
("Hidden".to_string(), false),
|
||||
"Sheet1".to_string(),
|
||||
"Second".to_string(),
|
||||
"Sheet4".to_string(),
|
||||
"shared".to_string(),
|
||||
"Table".to_string(),
|
||||
"Sheet2".to_string(),
|
||||
"Created fourth".to_string(),
|
||||
"Frozen".to_string(),
|
||||
"Split".to_string(),
|
||||
"Hidden".to_string(),
|
||||
];
|
||||
let names: Vec<(String, bool)> = ws
|
||||
.iter()
|
||||
.map(|s| (s.name.clone(), s.selection.is_selected))
|
||||
.collect();
|
||||
let names: Vec<String> = ws.iter().map(|s| s.name.clone()).collect();
|
||||
|
||||
// One is not not imported and one is hidden
|
||||
assert_eq!(expected_names, names);
|
||||
|
||||
assert_eq!(workbook.views[&0].sheet, 7);
|
||||
|
||||
// Test selection:
|
||||
// First sheet (Sheet1)
|
||||
// E13 and E13:N20
|
||||
assert_eq!(ws[0].frozen_rows, 0);
|
||||
assert_eq!(ws[0].frozen_columns, 0);
|
||||
assert_eq!(ws[0].selection.row, 13);
|
||||
assert_eq!(ws[0].selection.column, 5);
|
||||
assert_eq!(ws[0].selection.range, [13, 5, 20, 14]);
|
||||
assert_eq!(ws[0].views[&0].row, 13);
|
||||
assert_eq!(ws[0].views[&0].column, 5);
|
||||
assert_eq!(ws[0].views[&0].range, [13, 5, 20, 14]);
|
||||
|
||||
let model2 = load_from_icalc("tests/example.ic").unwrap();
|
||||
let s = bitcode::encode(&model2.workbook);
|
||||
|
||||
Reference in New Issue
Block a user