UPDATE: Hide/Unhide sheets

This commit is contained in:
Nicolás Hatcher
2024-12-20 00:45:16 +01:00
parent 44f7929f4e
commit 5d8e6255a3
12 changed files with 181 additions and 15 deletions

View File

@@ -682,6 +682,13 @@ impl Model {
Err(format!("Invalid color: {}", color)) 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`) /// 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> { pub fn set_show_grid_lines(&mut self, sheet: u32, show_grid_lines: bool) -> Result<(), String> {
let worksheet = self.workbook.worksheet_mut(sheet)?; let worksheet = self.workbook.worksheet_mut(sheet)?;

View File

@@ -14,6 +14,7 @@ mod test_on_paste_styles;
mod test_paste_csv; mod test_paste_csv;
mod test_rename_sheet; mod test_rename_sheet;
mod test_row_column; mod test_row_column;
mod test_sheet_state;
mod test_styles; mod test_styles;
mod test_to_from_bytes; mod test_to_from_bytes;
mod test_undo_redo; mod test_undo_redo;

View File

@@ -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());
}

View File

@@ -13,8 +13,8 @@ use crate::{
}, },
model::Model, model::Model,
types::{ types::{
Alignment, BorderItem, CellType, Col, HorizontalAlignment, SheetProperties, Style, Alignment, BorderItem, CellType, Col, HorizontalAlignment, SheetProperties, SheetState,
VerticalAlignment, Style, VerticalAlignment,
}, },
utils::is_valid_hex_color, utils::is_valid_hex_color,
}; };
@@ -440,6 +440,48 @@ impl UserModel {
Ok(()) 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 /// Sets sheet color
/// ///
/// Note: an empty string will remove the color /// Note: an empty string will remove the color
@@ -1862,6 +1904,11 @@ impl UserModel {
} => { } => {
self.model.set_show_grid_lines(*sheet, *old_value)?; 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 { if needs_evaluation {
@@ -1989,6 +2036,11 @@ impl UserModel {
} => { } => {
self.model.set_show_grid_lines(*sheet, *new_value)?; 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())?,
} }
} }

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use bitcode::{Decode, Encode}; use bitcode::{Decode, Encode};
use crate::types::{Cell, Col, Row, Style}; use crate::types::{Cell, Col, Row, SheetState, Style};
#[derive(Clone, Encode, Decode)] #[derive(Clone, Encode, Decode)]
pub(crate) struct RowData { pub(crate) struct RowData {
@@ -104,6 +104,11 @@ pub(crate) enum Diff {
old_value: String, old_value: String,
new_value: String, new_value: String,
}, },
SetSheetState {
index: u32,
old_value: SheetState,
new_value: SheetState,
},
SetShowGridLines { SetShowGridLines {
sheet: u32, sheet: u32,
old_value: bool, old_value: bool,

View File

@@ -106,6 +106,16 @@ impl Model {
self.model.delete_sheet(sheet).map_err(to_js_error) 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")] #[wasm_bindgen(js_name = "renameSheet")]
pub fn rename_sheet(&mut self, sheet: u32, name: &str) -> Result<(), JsError> { pub fn rename_sheet(&mut self, sheet: u32, name: &str) -> Result<(), JsError> {
self.model.rename_sheet(sheet, name).map_err(to_js_error) self.model.rename_sheet(sheet, name).map_err(to_js_error)

View File

@@ -113,6 +113,7 @@ export interface WorksheetProperties {
name: string; name: string;
color: string; color: string;
sheet_id: number; sheet_id: number;
state: string;
} }
interface CellStyleFill { interface CellStyleFill {

View File

@@ -59,7 +59,10 @@ const SheetListMenu = (properties: SheetListMenuProps) => {
)} )}
{hasColors && <ItemColor style={{ backgroundColor: tab.color }} />} {hasColors && <ItemColor style={{ backgroundColor: tab.color }} />}
<ItemName <ItemName
style={{ fontWeight: index === selectedIndex ? "bold" : "normal" }} style={{
fontWeight: index === selectedIndex ? "bold" : "normal",
color: tab.state === "visible" ? "#333" : "#888",
}}
> >
{tab.name} {tab.name}
</ItemName> </ItemName>

View File

@@ -15,8 +15,9 @@ interface SheetTabProps {
onSelected: () => void; onSelected: () => void;
onColorChanged: (hex: string) => void; onColorChanged: (hex: string) => void;
onRenamed: (name: string) => void; onRenamed: (name: string) => void;
canDelete: () => boolean; canDelete: boolean;
onDeleted: () => void; onDeleted: () => void;
onHideSheet: () => void;
workbookState: WorkbookState; workbookState: WorkbookState;
} }
@@ -94,15 +95,23 @@ function SheetTab(props: SheetTabProps) {
Change Color Change Color
</StyledMenuItem> </StyledMenuItem>
<StyledMenuItem <StyledMenuItem
disabled={!props.canDelete()} disabled={!props.canDelete}
onClick={() => { onClick={() => {
props.onDeleted(); props.onDeleted();
handleClose(); handleClose();
}} }}
> >
{" "}
Delete Delete
</StyledMenuItem> </StyledMenuItem>
<StyledMenuItem
disabled={!props.canDelete}
onClick={() => {
props.onHideSheet();
handleClose();
}}
>
Hide sheet
</StyledMenuItem>
</StyledMenu> </StyledMenu>
<SheetRenameDialog <SheetRenameDialog
open={renameDialogOpen} open={renameDialogOpen}

View File

@@ -19,6 +19,7 @@ export interface SheetTabBarProps {
onSheetColorChanged: (hex: string) => void; onSheetColorChanged: (hex: string) => void;
onSheetRenamed: (name: string) => void; onSheetRenamed: (name: string) => void;
onSheetDeleted: () => void; onSheetDeleted: () => void;
onHideSheet: () => void;
} }
function SheetTabBar(props: SheetTabBarProps) { function SheetTabBar(props: SheetTabBarProps) {
@@ -33,6 +34,18 @@ function SheetTabBar(props: SheetTabBarProps) {
setAnchorEl(null); 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 ( return (
<Container> <Container>
<LeftButtonsContainer> <LeftButtonsContainer>
@@ -54,25 +67,24 @@ function SheetTabBar(props: SheetTabBarProps) {
<VerticalDivider /> <VerticalDivider />
<Sheets> <Sheets>
<SheetInner> <SheetInner>
{sheets.map((tab, index) => ( {nonHidenSheets.map((tab) => (
<SheetTab <SheetTab
key={tab.sheetId} key={tab.sheetId}
name={tab.name} name={tab.name}
color={tab.color} color={tab.color}
selected={index === selectedIndex} selected={tab.index === selectedIndex}
onSelected={() => onSheetSelected(index)} onSelected={() => onSheetSelected(tab.index)}
onColorChanged={(hex: string): void => { onColorChanged={(hex: string): void => {
props.onSheetColorChanged(hex); props.onSheetColorChanged(hex);
}} }}
onRenamed={(name: string): void => { onRenamed={(name: string): void => {
props.onSheetRenamed(name); props.onSheetRenamed(name);
}} }}
canDelete={(): boolean => { canDelete={nonHidenSheets.length > 1}
return sheets.length > 1;
}}
onDeleted={(): void => { onDeleted={(): void => {
props.onSheetDeleted(); props.onSheetDeleted();
}} }}
onHideSheet={props.onHideSheet}
workbookState={workbookState} workbookState={workbookState}
/> />
))} ))}

View File

@@ -2,4 +2,5 @@ export interface SheetOptions {
name: string; name: string;
color: string; color: string;
sheetId: number; sheetId: number;
state: string;
} }

View File

@@ -32,8 +32,8 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
const setRedrawId = useState(0)[1]; const setRedrawId = useState(0)[1];
const info = model const info = model
.getWorksheetsProperties() .getWorksheetsProperties()
.map(({ name, color, sheet_id }: WorksheetProperties) => { .map(({ name, color, sheet_id, state }: WorksheetProperties) => {
return { name, color: color ? color : "#FFF", sheetId: sheet_id }; return { name, color: color ? color : "#FFF", sheetId: sheet_id, state };
}); });
const focusWorkbook = useCallback(() => { const focusWorkbook = useCallback(() => {
if (rootRef.current) { if (rootRef.current) {
@@ -586,6 +586,9 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
selectedIndex={model.getSelectedSheet()} selectedIndex={model.getSelectedSheet()}
workbookState={workbookState} workbookState={workbookState}
onSheetSelected={(sheet: number): void => { onSheetSelected={(sheet: number): void => {
if (info[sheet].state !== "visible") {
model.unhideSheet(sheet);
}
model.setSelectedSheet(sheet); model.setSelectedSheet(sheet);
setRedrawId((value) => value + 1); setRedrawId((value) => value + 1);
}} }}
@@ -616,6 +619,11 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
model.deleteSheet(selectedSheet); model.deleteSheet(selectedSheet);
setRedrawId((value) => value + 1); setRedrawId((value) => value + 1);
}} }}
onHideSheet={(): void => {
const selectedSheet = model.getSelectedSheet();
model.hideSheet(selectedSheet);
setRedrawId((value) => value + 1);
}}
/> />
</Container> </Container>
); );