diff --git a/base/src/model.rs b/base/src/model.rs
index 422b068..5ed01af 100644
--- a/base/src/model.rs
+++ b/base/src/model.rs
@@ -682,6 +682,13 @@ impl Model {
Err(format!("Invalid color: {}", color))
}
+ /// Changes the visibility of a sheet
+ pub fn set_sheet_state(&mut self, sheet: u32, state: SheetState) -> Result<(), String> {
+ let worksheet = self.workbook.worksheet_mut(sheet)?;
+ worksheet.state = state;
+ Ok(())
+ }
+
/// Makes the grid lines in the sheet visible (`true`) or hidden (`false`)
pub fn set_show_grid_lines(&mut self, sheet: u32, show_grid_lines: bool) -> Result<(), String> {
let worksheet = self.workbook.worksheet_mut(sheet)?;
diff --git a/base/src/test/user_model/mod.rs b/base/src/test/user_model/mod.rs
index c557104..0dcc989 100644
--- a/base/src/test/user_model/mod.rs
+++ b/base/src/test/user_model/mod.rs
@@ -14,6 +14,7 @@ mod test_on_paste_styles;
mod test_paste_csv;
mod test_rename_sheet;
mod test_row_column;
+mod test_sheet_state;
mod test_styles;
mod test_to_from_bytes;
mod test_undo_redo;
diff --git a/base/src/test/user_model/test_sheet_state.rs b/base/src/test/user_model/test_sheet_state.rs
new file mode 100644
index 0000000..bf9164f
--- /dev/null
+++ b/base/src/test/user_model/test_sheet_state.rs
@@ -0,0 +1,57 @@
+#![allow(clippy::unwrap_used)]
+
+use crate::test::util::new_empty_model;
+use crate::UserModel;
+
+#[test]
+fn basic_tests() {
+ let model = new_empty_model();
+ let mut model = UserModel::from_model(model);
+
+ // add three more sheets
+ model.new_sheet().unwrap();
+ model.new_sheet().unwrap();
+ model.new_sheet().unwrap();
+
+ let info = model.get_worksheets_properties();
+ assert_eq!(info.len(), 4);
+ for sheet in &info {
+ assert_eq!(sheet.state, "visible".to_string());
+ }
+
+ model.set_selected_sheet(2).unwrap();
+ assert_eq!(info.get(2).unwrap().name, "Sheet3".to_string());
+
+ model.hide_sheet(2).unwrap();
+
+ let info = model.get_worksheets_properties();
+ assert_eq!(model.get_selected_sheet(), 3);
+ assert_eq!(info.get(2).unwrap().state, "hidden".to_string());
+
+ model.undo().unwrap();
+ let info = model.get_worksheets_properties();
+ assert_eq!(info.get(2).unwrap().state, "visible".to_string());
+ model.redo().unwrap();
+ let info = model.get_worksheets_properties();
+ assert_eq!(info.get(2).unwrap().state, "hidden".to_string());
+
+ model.set_selected_sheet(3).unwrap();
+ model.hide_sheet(3).unwrap();
+ assert_eq!(model.get_selected_sheet(), 0);
+
+ model.unhide_sheet(2).unwrap();
+ model.unhide_sheet(3).unwrap();
+
+ let info = model.get_worksheets_properties();
+ assert_eq!(info.len(), 4);
+ for sheet in &info {
+ assert_eq!(sheet.state, "visible".to_string());
+ }
+
+ model.undo().unwrap();
+ let info = model.get_worksheets_properties();
+ assert_eq!(info.get(3).unwrap().state, "hidden".to_string());
+ model.redo().unwrap();
+ let info = model.get_worksheets_properties();
+ assert_eq!(info.get(3).unwrap().state, "visible".to_string());
+}
diff --git a/base/src/user_model/common.rs b/base/src/user_model/common.rs
index 7306c44..def3771 100644
--- a/base/src/user_model/common.rs
+++ b/base/src/user_model/common.rs
@@ -13,8 +13,8 @@ use crate::{
},
model::Model,
types::{
- Alignment, BorderItem, CellType, Col, HorizontalAlignment, SheetProperties, Style,
- VerticalAlignment,
+ Alignment, BorderItem, CellType, Col, HorizontalAlignment, SheetProperties, SheetState,
+ Style, VerticalAlignment,
},
utils::is_valid_hex_color,
};
@@ -440,6 +440,48 @@ impl UserModel {
Ok(())
}
+ /// Hides sheet by index
+ ///
+ /// See also:
+ /// * [Model::set_sheet_state]
+ /// * [UserModel::unhide_sheet]
+ pub fn hide_sheet(&mut self, sheet: u32) -> Result<(), String> {
+ let sheet_count = self.model.workbook.worksheets.len() as u32;
+ for index in 1..sheet_count {
+ let sheet_index = (sheet + index) % sheet_count;
+ if self.model.workbook.worksheet(sheet_index)?.state == SheetState::Visible {
+ if let Some(view) = self.model.workbook.views.get_mut(&self.model.view_id) {
+ view.sheet = sheet_index;
+ };
+ break;
+ }
+ }
+ let old_value = self.model.workbook.worksheet(sheet)?.state.clone();
+ self.push_diff_list(vec![Diff::SetSheetState {
+ index: sheet,
+ new_value: SheetState::Hidden,
+ old_value,
+ }]);
+ self.model.set_sheet_state(sheet, SheetState::Hidden)?;
+ Ok(())
+ }
+
+ /// Un hides sheet by index
+ ///
+ /// See also:
+ /// * [Model::set_sheet_state]
+ /// * [UserModel::hide_sheet]
+ pub fn unhide_sheet(&mut self, sheet: u32) -> Result<(), String> {
+ let old_value = self.model.workbook.worksheet(sheet)?.state.clone();
+ self.push_diff_list(vec![Diff::SetSheetState {
+ index: sheet,
+ new_value: SheetState::Visible,
+ old_value,
+ }]);
+ self.model.set_sheet_state(sheet, SheetState::Visible)?;
+ Ok(())
+ }
+
/// Sets sheet color
///
/// Note: an empty string will remove the color
@@ -1862,6 +1904,11 @@ impl UserModel {
} => {
self.model.set_show_grid_lines(*sheet, *old_value)?;
}
+ Diff::SetSheetState {
+ index,
+ old_value,
+ new_value: _,
+ } => self.model.set_sheet_state(*index, old_value.clone())?,
}
}
if needs_evaluation {
@@ -1989,6 +2036,11 @@ impl UserModel {
} => {
self.model.set_show_grid_lines(*sheet, *new_value)?;
}
+ Diff::SetSheetState {
+ index,
+ old_value: _,
+ new_value,
+ } => self.model.set_sheet_state(*index, new_value.clone())?,
}
}
diff --git a/base/src/user_model/history.rs b/base/src/user_model/history.rs
index 4403e43..e9b3490 100644
--- a/base/src/user_model/history.rs
+++ b/base/src/user_model/history.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap;
use bitcode::{Decode, Encode};
-use crate::types::{Cell, Col, Row, Style};
+use crate::types::{Cell, Col, Row, SheetState, Style};
#[derive(Clone, Encode, Decode)]
pub(crate) struct RowData {
@@ -104,6 +104,11 @@ pub(crate) enum Diff {
old_value: String,
new_value: String,
},
+ SetSheetState {
+ index: u32,
+ old_value: SheetState,
+ new_value: SheetState,
+ },
SetShowGridLines {
sheet: u32,
old_value: bool,
diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs
index 2d26f1b..17f25ee 100644
--- a/bindings/wasm/src/lib.rs
+++ b/bindings/wasm/src/lib.rs
@@ -106,6 +106,16 @@ impl Model {
self.model.delete_sheet(sheet).map_err(to_js_error)
}
+ #[wasm_bindgen(js_name = "hideSheet")]
+ pub fn hide_sheet(&mut self, sheet: u32) -> Result<(), JsError> {
+ self.model.hide_sheet(sheet).map_err(to_js_error)
+ }
+
+ #[wasm_bindgen(js_name = "unhideSheet")]
+ pub fn unhide_sheet(&mut self, sheet: u32) -> Result<(), JsError> {
+ self.model.unhide_sheet(sheet).map_err(to_js_error)
+ }
+
#[wasm_bindgen(js_name = "renameSheet")]
pub fn rename_sheet(&mut self, sheet: u32, name: &str) -> Result<(), JsError> {
self.model.rename_sheet(sheet, name).map_err(to_js_error)
diff --git a/bindings/wasm/types.ts b/bindings/wasm/types.ts
index 76d1a79..c66d168 100644
--- a/bindings/wasm/types.ts
+++ b/bindings/wasm/types.ts
@@ -113,6 +113,7 @@ export interface WorksheetProperties {
name: string;
color: string;
sheet_id: number;
+ state: string;
}
interface CellStyleFill {
diff --git a/webapp/src/components/SheetTabBar/SheetListMenu.tsx b/webapp/src/components/SheetTabBar/SheetListMenu.tsx
index 50c6f15..f22a959 100644
--- a/webapp/src/components/SheetTabBar/SheetListMenu.tsx
+++ b/webapp/src/components/SheetTabBar/SheetListMenu.tsx
@@ -59,7 +59,10 @@ const SheetListMenu = (properties: SheetListMenuProps) => {
)}
{hasColors && }
{tab.name}
diff --git a/webapp/src/components/SheetTabBar/SheetTab.tsx b/webapp/src/components/SheetTabBar/SheetTab.tsx
index c662ece..e8db493 100644
--- a/webapp/src/components/SheetTabBar/SheetTab.tsx
+++ b/webapp/src/components/SheetTabBar/SheetTab.tsx
@@ -15,8 +15,9 @@ interface SheetTabProps {
onSelected: () => void;
onColorChanged: (hex: string) => void;
onRenamed: (name: string) => void;
- canDelete: () => boolean;
+ canDelete: boolean;
onDeleted: () => void;
+ onHideSheet: () => void;
workbookState: WorkbookState;
}
@@ -94,15 +95,23 @@ function SheetTab(props: SheetTabProps) {
Change Color
{
props.onDeleted();
handleClose();
}}
>
- {" "}
Delete
+ {
+ props.onHideSheet();
+ handleClose();
+ }}
+ >
+ Hide sheet
+
void;
onSheetRenamed: (name: string) => void;
onSheetDeleted: () => void;
+ onHideSheet: () => void;
}
function SheetTabBar(props: SheetTabBarProps) {
@@ -33,6 +34,18 @@ function SheetTabBar(props: SheetTabBarProps) {
setAnchorEl(null);
};
+ const nonHidenSheets = sheets
+ .map((s, index) => {
+ return {
+ state: s.state,
+ index,
+ name: s.name,
+ color: s.color,
+ sheetId: s.sheetId,
+ };
+ })
+ .filter((s) => s.state === "visible");
+
return (
@@ -54,25 +67,24 @@ function SheetTabBar(props: SheetTabBarProps) {
- {sheets.map((tab, index) => (
+ {nonHidenSheets.map((tab) => (
onSheetSelected(index)}
+ selected={tab.index === selectedIndex}
+ onSelected={() => onSheetSelected(tab.index)}
onColorChanged={(hex: string): void => {
props.onSheetColorChanged(hex);
}}
onRenamed={(name: string): void => {
props.onSheetRenamed(name);
}}
- canDelete={(): boolean => {
- return sheets.length > 1;
- }}
+ canDelete={nonHidenSheets.length > 1}
onDeleted={(): void => {
props.onSheetDeleted();
}}
+ onHideSheet={props.onHideSheet}
workbookState={workbookState}
/>
))}
diff --git a/webapp/src/components/SheetTabBar/types.ts b/webapp/src/components/SheetTabBar/types.ts
index 2a9ec1d..29063e4 100644
--- a/webapp/src/components/SheetTabBar/types.ts
+++ b/webapp/src/components/SheetTabBar/types.ts
@@ -2,4 +2,5 @@ export interface SheetOptions {
name: string;
color: string;
sheetId: number;
+ state: string;
}
diff --git a/webapp/src/components/workbook.tsx b/webapp/src/components/workbook.tsx
index 6f6b3e9..d1a6fff 100644
--- a/webapp/src/components/workbook.tsx
+++ b/webapp/src/components/workbook.tsx
@@ -32,8 +32,8 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
const setRedrawId = useState(0)[1];
const info = model
.getWorksheetsProperties()
- .map(({ name, color, sheet_id }: WorksheetProperties) => {
- return { name, color: color ? color : "#FFF", sheetId: sheet_id };
+ .map(({ name, color, sheet_id, state }: WorksheetProperties) => {
+ return { name, color: color ? color : "#FFF", sheetId: sheet_id, state };
});
const focusWorkbook = useCallback(() => {
if (rootRef.current) {
@@ -586,6 +586,9 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
selectedIndex={model.getSelectedSheet()}
workbookState={workbookState}
onSheetSelected={(sheet: number): void => {
+ if (info[sheet].state !== "visible") {
+ model.unhideSheet(sheet);
+ }
model.setSelectedSheet(sheet);
setRedrawId((value) => value + 1);
}}
@@ -616,6 +619,11 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
model.deleteSheet(selectedSheet);
setRedrawId((value) => value + 1);
}}
+ onHideSheet={(): void => {
+ const selectedSheet = model.getSelectedSheet();
+ model.hideSheet(selectedSheet);
+ setRedrawId((value) => value + 1);
+ }}
/>
);