From 5d8e6255a318ed09739321d3dfee70a951bc9920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Fri, 20 Dec 2024 00:45:16 +0100 Subject: [PATCH] UPDATE: Hide/Unhide sheets --- base/src/model.rs | 7 +++ base/src/test/user_model/mod.rs | 1 + base/src/test/user_model/test_sheet_state.rs | 57 +++++++++++++++++++ base/src/user_model/common.rs | 56 +++++++++++++++++- base/src/user_model/history.rs | 7 ++- bindings/wasm/src/lib.rs | 10 ++++ bindings/wasm/types.ts | 1 + .../components/SheetTabBar/SheetListMenu.tsx | 5 +- .../src/components/SheetTabBar/SheetTab.tsx | 15 ++++- .../components/SheetTabBar/SheetTabBar.tsx | 24 ++++++-- webapp/src/components/SheetTabBar/types.ts | 1 + webapp/src/components/workbook.tsx | 12 +++- 12 files changed, 181 insertions(+), 15 deletions(-) create mode 100644 base/src/test/user_model/test_sheet_state.rs 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); + }} /> );