Merge pull request #207 from ironcalc/feature/nicolas-hidden
UPDATE: Hide/Unhide sheets
This commit is contained in:
@@ -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)?;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
57
base/src/test/user_model/test_sheet_state.rs
Normal file
57
base/src/test/user_model/test_sheet_state.rs
Normal 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());
|
||||||
|
}
|
||||||
@@ -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())?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ export interface SheetOptions {
|
|||||||
name: string;
|
name: string;
|
||||||
color: string;
|
color: string;
|
||||||
sheetId: number;
|
sheetId: number;
|
||||||
|
state: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user