use std::collections::HashSet;
use logisheets_base::async_func::{AsyncCalcResult, Task};
use logisheets_base::{BlockRange, CellId, NormalRange, Range, SheetId};
use logisheets_workbook::prelude::{read, write};
pub mod display;
mod executor;
pub mod status;
pub mod style;
mod viewer;
use crate::edit_action::{
ActionEffect, CreateSheet, EditAction, PayloadsAction, RecalcCell, StatusCode,
WorkbookUpdateType,
};
use crate::errors::{Error, Result};
use crate::file_loader2::load;
use crate::file_saver::save_file;
use crate::formula_manager::Vertex;
use crate::settings::Settings;
use crate::version_manager::VersionManager;
use executor::Executor;
use status::Status;
use viewer::SheetViewer;
use self::display::{DisplayResponse, DisplaySheetRequest, SheetInfo};
use crate::async_func_manager::AsyncFuncManager;
pub struct Controller {
pub status: Status,
pub async_func_manager: AsyncFuncManager,
pub curr_book_name: String,
pub settings: Settings,
pub version_manager: VersionManager,
}
impl Default for Controller {
fn default() -> Self {
let mut empty = Controller {
status: Status::default(),
curr_book_name: String::from("Book1"),
settings: Settings::default(),
version_manager: VersionManager::default(),
async_func_manager: AsyncFuncManager::default(),
};
let add_sheet = PayloadsAction::new(false).add_payload(CreateSheet {
idx: 0,
new_name: String::from("Sheet1"),
});
empty.handle_action(EditAction::Payloads(add_sheet));
empty
}
}
impl Controller {
pub fn save(&self) -> Result<Vec<u8>> {
let workbook = save_file(self)?;
write(workbook).map_err(|e| Error::Serde(e.into()))
}
pub fn from(status: Status, book_name: String, settings: Settings) -> Self {
Controller {
curr_book_name: book_name,
settings,
status,
version_manager: VersionManager::default(),
async_func_manager: AsyncFuncManager::default(),
}
}
pub fn version(&self) -> u32 {
self.version_manager.version()
}
pub fn from_file(name: String, f: &[u8]) -> Result<Self> {
let res = read(f)?;
Ok(load(res, name))
}
pub fn get_sheet_id_by_idx(&self, idx: usize) -> Option<SheetId> {
self.status.sheet_pos_manager.get_sheet_id(idx)
}
pub fn get_sheet_id_by_name(&self, name: &str) -> Option<SheetId> {
self.status.sheet_id_manager.has(name)
}
pub fn get_all_sheet_info(&self) -> Vec<SheetInfo> {
let id_manager = &self.status.sheet_id_manager;
let pos_manager = &self.status.sheet_pos_manager;
pos_manager
.pos
.iter()
.map(|id| SheetInfo {
name: id_manager.get_string(id).unwrap_or(String::new()),
id: *id,
hidden: pos_manager.is_hidden(id),
tab_color: String::from(""),
})
.collect()
}
pub fn handle_action(&mut self, action: EditAction) -> ActionEffect {
match action {
EditAction::Undo => {
let c = if self.undo() {
WorkbookUpdateType::Undo
} else {
WorkbookUpdateType::UndoNothing
};
ActionEffect::from(0, vec![], c)
}
EditAction::Redo => {
let c = if self.redo() {
WorkbookUpdateType::Redo
} else {
WorkbookUpdateType::RedoNothing
};
ActionEffect::from(0, vec![], c)
}
EditAction::Payloads(payloads_action) => {
let executor = Executor {
status: self.status.clone(),
version_manager: &mut self.version_manager,
async_func_manager: &mut self.async_func_manager,
book_name: &self.curr_book_name,
calc_config: self.settings.calc_config,
async_funcs: &self.settings.async_funcs,
updated_cells: HashSet::new(),
dirty_vertices: HashSet::new(),
sheet_updated: false,
cell_updated: false,
};
let result = executor.execute_and_calc(payloads_action);
match result {
Ok(result) => {
let c = if result.cell_updated && result.sheet_updated {
WorkbookUpdateType::SheetAndCell
} else if result.cell_updated {
WorkbookUpdateType::Cell
} else if result.sheet_updated {
WorkbookUpdateType::Sheet
} else {
WorkbookUpdateType::DoNothing
};
self.status = result.status;
ActionEffect {
version: result.version_manager.version(),
async_tasks: result.async_func_manager.get_calc_tasks(),
status: StatusCode::Ok(c),
}
}
Err(e) => {
println!("{:?}", e.to_string());
ActionEffect::from_err(1) }
}
}
EditAction::Recalc(cells) => {
let dirty_vertices = cells
.into_iter()
.map(|recalc_sheet| {
let sheet_id = recalc_sheet.sheet_id;
let cell_id = recalc_sheet.cell_id;
let range = match cell_id {
CellId::NormalCell(c) => Range::Normal(NormalRange::Single(c)),
CellId::BlockCell(b) => Range::Block(BlockRange::Single(b)),
};
let range_id = self.status.range_manager.get_range_id(&sheet_id, &range);
Vertex::Range(sheet_id, range_id)
})
.collect::<HashSet<_>>();
let executor = Executor {
status: self.status.clone(),
version_manager: &mut self.version_manager,
async_func_manager: &mut self.async_func_manager,
book_name: &self.curr_book_name,
calc_config: self.settings.calc_config,
async_funcs: &self.settings.async_funcs,
updated_cells: HashSet::new(),
dirty_vertices,
sheet_updated: false,
cell_updated: false,
};
if let Ok(result) = executor.calc() {
ActionEffect {
version: result.version_manager.version(),
async_tasks: vec![],
status: StatusCode::Ok(WorkbookUpdateType::Cell),
}
} else {
ActionEffect::from_err(1)
}
}
}
}
pub fn handle_async_calc_results(
&mut self,
tasks: Vec<Task>,
res: Vec<AsyncCalcResult>,
) -> ActionEffect {
let mut pending_cells = vec![];
tasks.into_iter().zip(res.into_iter()).for_each(|(t, r)| {
let cells = self
.async_func_manager
.commit_value(t, r)
.into_iter()
.map(|(s, c)| RecalcCell {
sheet_id: s,
cell_id: c,
});
pending_cells.extend(cells);
});
self.handle_action(EditAction::Recalc(pending_cells))
}
pub fn get_display_sheet_response(&self, req: DisplaySheetRequest) -> Result<DisplayResponse> {
let viewer = SheetViewer::default();
if req.version == 0 {
return Ok(viewer.display_with_idx(self, req.sheet_idx));
}
let sheet_id = self
.get_sheet_id_by_idx(req.sheet_idx)
.ok_or(Error::UnavailableSheetIdx(req.sheet_idx))?;
if let Some(diff) = self
.version_manager
.get_sheet_diffs_from_version(sheet_id, req.version)
{
Ok(viewer.display_with_diff(self, sheet_id, diff))
} else {
Ok(viewer.display_with_idx(self, req.sheet_idx))
}
}
pub fn undo(&mut self) -> bool {
match self.version_manager.undo() {
Some(mut last_status) => {
std::mem::swap(&mut self.status, &mut last_status);
true
}
None => false,
}
}
pub fn redo(&mut self) -> bool {
match self.version_manager.redo() {
Some(mut next_status) => {
std::mem::swap(&mut self.status, &mut next_status);
true
}
None => false,
}
}
}
#[cfg(test)]
mod tests {
use crate::edit_action::{CellInput, EditAction, EditPayload, PayloadsAction};
use super::Controller;
#[test]
fn controller_default_test() {
let wb = Controller::default();
println!("{:?}", wb.status);
}
#[test]
fn controller_input_formula() {
let mut wb = Controller::default();
let payloads_action = PayloadsAction {
payloads: vec![EditPayload::CellInput(CellInput {
sheet_idx: 0,
row: 0,
col: 0,
content: String::from("=ABS(1)"),
})],
undoable: true,
};
wb.handle_action(EditAction::Payloads(payloads_action));
let len = wb.status.formula_manager.formulas.len();
assert_eq!(len, 1);
}
#[test]
fn from_file_test() {
use std::fs;
let buf = fs::read("../../tests/6.xlsx").unwrap();
let controller = Controller::from_file(String::from("6"), &buf);
match controller {
Ok(_) => {}
Err(_) => {
panic!()
}
}
}
}