diff --git a/crates/controller/src/block_schema_manager/ctx.rs b/crates/controller/src/block_schema_manager/ctx.rs new file mode 100644 index 00000000..8b1fb16f --- /dev/null +++ b/crates/controller/src/block_schema_manager/ctx.rs @@ -0,0 +1,3 @@ +use logisheets_base::id_fetcher::{IdFetcherTrait, SheetIdFetcherByIdxTrait}; + +pub trait BlockSchemaCtx: IdFetcherTrait + SheetIdFetcherByIdxTrait {} diff --git a/crates/controller/src/block_schema_manager/executor.rs b/crates/controller/src/block_schema_manager/executor.rs new file mode 100644 index 00000000..b6deed8c --- /dev/null +++ b/crates/controller/src/block_schema_manager/executor.rs @@ -0,0 +1,115 @@ +use logisheets_base::errors::BasicError; + +use crate::{ + block_schema_manager::{ + ctx::BlockSchemaCtx, + manager::SchemaManager, + schema::{ColSchema, RandomSchema, RowSchema, Schema, SchemaTrait}, + }, + edit_action::EditPayload, + Error, +}; + +pub struct BlockSchemaExecutor { + pub manager: SchemaManager, +} + +impl BlockSchemaExecutor { + pub fn new(manager: SchemaManager) -> Self { + Self { manager } + } + + pub fn execute( + self, + ctx: &mut C, + payload: EditPayload, + ) -> Result<(Self, bool), Error> { + match payload { + EditPayload::BindFormSchema(p) => { + let mut manager = self.manager; + let sheet_id = ctx + .fetch_sheet_id_by_index(p.sheet_idx) + .map_err(|l| BasicError::SheetIdxExceed(l))?; + let block_id = p.block_id; + let mut fields = Vec::new(); + for (i, (field, render_id)) in p + .fields + .into_iter() + .zip(p.render_ids.into_iter()) + .enumerate() + { + let idx = i + p.field_from; + let id = if p.row { + ctx.fetch_block_cell_id(&sheet_id, &block_id, idx, 0)?.row + } else { + ctx.fetch_block_cell_id(&sheet_id, &block_id, 0, idx)?.col + }; + fields.push((field, (id, render_id))); + } + let mut keys = Vec::new(); + + for (j, k) in p.keys.into_iter().enumerate() { + let idx = j + p.key_from; + let id = if p.row { + ctx.fetch_block_cell_id(&sheet_id, &block_id, idx, 0)?.row + } else { + ctx.fetch_block_cell_id(&sheet_id, &block_id, 0, idx)?.col + }; + keys.push((k, id)); + } + let schema = if p.row { + Schema::RowSchema(RowSchema { + fields, + keys, + name: p.ref_name.clone(), + }) + } else { + Schema::ColSchema(ColSchema { + fields, + keys, + name: p.ref_name.clone(), + }) + }; + let old_schema = manager.schemas.get(&(sheet_id, block_id)); + if old_schema.is_some() { + manager.refs.remove(&old_schema.unwrap().get_ref_name()); + } + manager.schemas.insert((sheet_id, block_id), schema); + manager.refs.insert(p.ref_name, (sheet_id, block_id)); + Ok((Self { manager }, true)) + } + EditPayload::BindRandomSchema(p) => { + let mut manager = self.manager; + let sheet_id = ctx + .fetch_sheet_id_by_index(p.sheet_idx) + .map_err(|l| BasicError::SheetIdxExceed(l))?; + let block_id = p.block_id; + let mut key_field = Vec::new(); + for unit in p.units { + let r = unit.row; + let c = unit.col; + let cell_id = ctx.fetch_block_cell_id(&sheet_id, &block_id, r, c)?; + key_field.push(( + unit.key, + unit.field, + cell_id.row, + cell_id.col, + unit.render_id, + )); + } + let schema = Schema::RandomSchema(RandomSchema { + key_field, + name: p.ref_name.clone(), + }); + let old_schema = manager.schemas.get(&(sheet_id, block_id)); + if old_schema.is_some() { + manager.refs.remove(&old_schema.unwrap().get_ref_name()); + } + manager.schemas.insert((sheet_id, block_id), schema); + manager.refs.insert(p.ref_name, (sheet_id, block_id)); + Ok((Self { manager }, true)) + } + _ => Ok((self, false)), + } + } +} diff --git a/crates/controller/src/block_schema_manager/manager.rs b/crates/controller/src/block_schema_manager/manager.rs new file mode 100644 index 00000000..c08cda64 --- /dev/null +++ b/crates/controller/src/block_schema_manager/manager.rs @@ -0,0 +1,45 @@ +use imbl::HashMap; +use logisheets_base::{BlockCellId, BlockId, ColId, RowId, SheetId}; + +use crate::block_schema_manager::schema::{Field, Key, RenderId, Schema, SchemaTrait}; + +#[derive(Debug, Clone, Default)] +pub struct SchemaManager { + pub schemas: HashMap<(SheetId, BlockId), Schema>, + pub refs: HashMap, +} + +impl SchemaManager { + pub fn new() -> Self { + SchemaManager { + schemas: HashMap::new(), + refs: HashMap::new(), + } + } + + pub fn resolve_by_ref_name( + &self, + name: &str, + key: &Key, + field: &Field, + ) -> Option<(RowId, ColId)> { + let (sheet_id, block_id) = self.refs.get(name)?; + self.resolve_by_block_id(sheet_id, block_id, key, field) + } + + pub fn resolve_by_block_id( + &self, + sheet_id: &SheetId, + block_id: &BlockId, + key: &Key, + field: &Field, + ) -> Option<(RowId, ColId)> { + let schema = self.schemas.get(&(*sheet_id, *block_id))?; + schema.resolve(key, field) + } + + pub fn get_render_id(&self, sheet_id: &SheetId, cell_id: &BlockCellId) -> Option { + let schema = self.schemas.get(&(*sheet_id, cell_id.block_id))?; + schema.get_render_id(cell_id.row, cell_id.col) + } +} diff --git a/crates/controller/src/block_schema_manager/mod.rs b/crates/controller/src/block_schema_manager/mod.rs new file mode 100644 index 00000000..d03a66b7 --- /dev/null +++ b/crates/controller/src/block_schema_manager/mod.rs @@ -0,0 +1,6 @@ +pub mod ctx; +pub mod executor; +mod manager; +pub mod schema; + +pub use manager::SchemaManager; diff --git a/crates/controller/src/block_schema_manager/schema.rs b/crates/controller/src/block_schema_manager/schema.rs new file mode 100644 index 00000000..d9659737 --- /dev/null +++ b/crates/controller/src/block_schema_manager/schema.rs @@ -0,0 +1,134 @@ +use logisheets_base::{ColId, RowId}; + +pub type RowSchema = FormSchema; +pub type ColSchema = FormSchema; +pub type RenderId = String; + +#[derive(Debug, Clone)] +pub struct FormSchema { + pub fields: Vec<(Field, (F, RenderId))>, + pub keys: Vec<(Key, K)>, + pub name: String, +} + +pub type Field = String; +pub type Key = String; + +#[derive(Debug, Clone)] +pub struct RandomSchema { + pub key_field: Vec<(Key, Field, RowId, ColId, RenderId)>, + pub name: String, +} + +#[derive(Debug, Clone)] +pub enum Schema { + RowSchema(RowSchema), + ColSchema(ColSchema), + RandomSchema(RandomSchema), +} + +impl SchemaTrait for Schema { + fn resolve(&self, key: &Key, field: &Field) -> Option<(RowId, ColId)> { + match self { + Schema::RowSchema(schema) => schema.resolve(key, field), + Schema::ColSchema(schema) => schema.resolve(key, field), + Schema::RandomSchema(schema) => schema.resolve(key, field), + } + } + + fn get_render_id(&self, row: RowId, col: ColId) -> Option { + match self { + Schema::RowSchema(schema) => schema.get_render_id(row, col), + Schema::ColSchema(schema) => schema.get_render_id(row, col), + Schema::RandomSchema(schema) => schema.get_render_id(row, col), + } + } + + fn get_ref_name(&self) -> String { + match self { + Schema::RowSchema(schema) => schema.get_ref_name(), + Schema::ColSchema(schema) => schema.get_ref_name(), + Schema::RandomSchema(schema) => schema.get_ref_name(), + } + } +} + +pub trait SchemaTrait { + fn resolve(&self, key: &Key, field: &Field) -> Option<(RowId, ColId)>; + fn get_render_id(&self, row: RowId, col: ColId) -> Option; + fn get_ref_name(&self) -> String; +} + +impl SchemaTrait for RowSchema { + fn resolve(&self, key: &Key, field: &Field) -> Option<(RowId, ColId)> { + let k = self + .keys + .iter() + .find(|(k, _)| k == key) + .map(|(_, id)| *id)?; + let f = self + .fields + .iter() + .find(|(f, _)| f == field) + .map(|(_, id)| id.0)?; + Some((k, f)) + } + + fn get_render_id(&self, _row: RowId, col: ColId) -> Option { + self.fields + .iter() + .find(|(_, (f, _))| f == &col) + .map(|(_, (_, id))| id.clone()) + } + + fn get_ref_name(&self) -> String { + self.name.clone() + } +} + +impl SchemaTrait for ColSchema { + fn resolve(&self, key: &Key, field: &Field) -> Option<(RowId, ColId)> { + let k = self + .keys + .iter() + .find(|(k, _)| k == key) + .map(|(_, id)| *id)?; + let f = self + .fields + .iter() + .find(|(f, _)| f == field) + .map(|(_, id)| id.0)?; + Some((f, k)) + } + + fn get_render_id(&self, row: RowId, _col: ColId) -> Option { + self.fields + .iter() + .find(|(_, (f, _))| f == &row) + .map(|(_, (_, id))| id.clone()) + } + + fn get_ref_name(&self) -> String { + self.name.clone() + } +} + +impl SchemaTrait for RandomSchema { + fn resolve(&self, key: &Key, field: &Field) -> Option<(RowId, ColId)> { + self.key_field + .iter() + .find(|(k, f, _, _, _)| k == key && f == field) + .map(|(_, _, r, c, _)| (*r, *c)) + } + + fn get_render_id(&self, row: RowId, col: ColId) -> Option { + self.key_field + .iter() + .find(|(_, _, r, c, _)| r == &row && c == &col) + .map(|(_, _, _, _, id)| id.clone()) + } + + fn get_ref_name(&self) -> String { + self.name.clone() + } +} diff --git a/crates/controller/src/connectors/block_schema_connector.rs b/crates/controller/src/connectors/block_schema_connector.rs new file mode 100644 index 00000000..473f92c0 --- /dev/null +++ b/crates/controller/src/connectors/block_schema_connector.rs @@ -0,0 +1,82 @@ +use logisheets_base::{ + errors::BasicError, + id_fetcher::{IdFetcherTrait, SheetIdFetcherByIdxTrait}, + BlockCellId, BlockId, CellId, ColId, ExtBookId, FuncId, NameId, NormalCellId, RowId, SheetId, + TextId, +}; + +use crate::{ + block_schema_manager::ctx::BlockSchemaCtx, id_manager::SheetIdManager, navigator::Navigator, + workbook::sheet_info_manager::SheetInfoManager, +}; + +pub struct BlockSchemaConnector<'a> { + pub id_navigator: &'a Navigator, + pub sheet_info_manager: &'a mut SheetInfoManager, + pub sheet_id_manager: &'a mut SheetIdManager, +} + +type Result = std::result::Result; + +impl<'a> IdFetcherTrait for BlockSchemaConnector<'a> { + fn fetch_row_id(&self, sheet_id: &SheetId, row_idx: usize) -> Result { + self.id_navigator.fetch_row_id(sheet_id, row_idx) + } + + fn fetch_col_id(&self, sheet_id: &SheetId, col_idx: usize) -> Result { + self.id_navigator.fetch_col_id(sheet_id, col_idx) + } + + fn fetch_cell_id(&self, sheet_id: &SheetId, row_idx: usize, col_idx: usize) -> Result { + self.id_navigator.fetch_cell_id(sheet_id, row_idx, col_idx) + } + + fn fetch_sheet_id(&mut self, sheet_name: &str) -> SheetId { + self.sheet_id_manager.get_or_register_id(sheet_name) + } + + fn fetch_name_id(&mut self, _workbook: &Option<&str>, _name: &str) -> NameId { + unreachable!() + } + + fn fetch_ext_book_id(&mut self, _book: &str) -> ExtBookId { + unreachable!() + } + + fn fetch_text_id(&mut self, _text: &str) -> TextId { + unreachable!() + } + + fn fetch_func_id(&mut self, _func_name: &str) -> FuncId { + unreachable!() + } + + fn fetch_norm_cell_id( + &self, + sheet_id: &SheetId, + row_idx: usize, + col_idx: usize, + ) -> Result { + self.id_navigator + .fetch_norm_cell_id(sheet_id, row_idx, col_idx) + } + + fn fetch_block_cell_id( + &self, + sheet_id: &SheetId, + block_id: &BlockId, + row: usize, + col: usize, + ) -> Result { + self.id_navigator + .fetch_block_cell_id(sheet_id, block_id, row, col) + } +} + +impl<'a> SheetIdFetcherByIdxTrait for BlockSchemaConnector<'a> { + fn fetch_sheet_id_by_index(&self, idx: usize) -> std::result::Result { + self.sheet_info_manager.pos.get(idx).copied().ok_or(idx) + } +} + +impl<'a> BlockSchemaCtx for BlockSchemaConnector<'a> {} diff --git a/crates/controller/src/connectors/mod.rs b/crates/controller/src/connectors/mod.rs index 2e3d1acb..fe17e3c5 100644 --- a/crates/controller/src/connectors/mod.rs +++ b/crates/controller/src/connectors/mod.rs @@ -1,3 +1,4 @@ +mod block_schema_connector; mod calc_connector; mod cell_attachments_connector; mod container_connector; @@ -11,6 +12,7 @@ mod navigator_connector; mod range_connector; mod sheet_pos_connector; +pub use block_schema_connector::BlockSchemaConnector; pub use calc_connector::CalcConnector; pub use cell_attachments_connector::CellAttachmentsConnector; pub use container_connector::ContainerConnector; diff --git a/crates/controller/src/controller/executor.rs b/crates/controller/src/controller/executor.rs index a80c3fa5..a6b86900 100644 --- a/crates/controller/src/controller/executor.rs +++ b/crates/controller/src/controller/executor.rs @@ -4,11 +4,12 @@ use logisheets_base::{errors::BasicError, Addr, CellId, CubeId, RangeId, SheetId use crate::{ async_func_manager::AsyncFuncManager, + block_schema_manager::executor::BlockSchemaExecutor, calc_engine::CalcEngine, cell_attachments::executor::CellAttachmentsExecutor, connectors::{ - CalcConnector, CellAttachmentsConnector, ContainerConnector, CubeConnector, - ExclusiveConnector, FormulaConnector, NavigatorConnector, RangeConnector, + BlockSchemaConnector, CalcConnector, CellAttachmentsConnector, ContainerConnector, + CubeConnector, ExclusiveConnector, FormulaConnector, NavigatorConnector, RangeConnector, SheetInfoConnector, }, container::ContainerExecutor, @@ -130,6 +131,10 @@ impl<'a> Executor<'a> { dirty_cubes.insert(e); }); + let (block_schema_executor, _block_schema_updated) = + result.execute_bind_schema(payload.clone())?; + result.status.block_schema_manager = block_schema_executor.manager; + result.status.navigator = nav_executor.nav; result.status.range_manager = range_executor.manager; result.status.cube_manager = cube_executor.manager; @@ -173,6 +178,7 @@ impl<'a> Executor<'a> { cell_attachment_manager: result.status.cell_attachment_manager, dirty_cells_next_round: result.status.dirty_cells_next_round, exclusive_manager: result.status.exclusive_manager, + block_schema_manager: result.status.block_schema_manager, }, version_manager: result.version_manager, async_func_manager: result.async_func_manager, @@ -268,6 +274,19 @@ impl<'a> Executor<'a> { executor.execute(&ctx, payload) } + fn execute_bind_schema( + &mut self, + payload: EditPayload, + ) -> Result<(BlockSchemaExecutor, bool), Error> { + let mut ctx = BlockSchemaConnector { + id_navigator: &self.status.navigator, + sheet_info_manager: &mut self.status.sheet_info_manager, + sheet_id_manager: &mut self.status.sheet_id_manager, + }; + let executor = BlockSchemaExecutor::new(self.status.block_schema_manager.clone()); + executor.execute(&mut ctx, payload) + } + fn execute_cube(&mut self, payload: EditPayload) -> Result { let ctx = CubeConnector { sheet_id_manager: &mut self.status.sheet_id_manager, diff --git a/crates/controller/src/controller/status.rs b/crates/controller/src/controller/status.rs index 0bc481fc..85b41d06 100644 --- a/crates/controller/src/controller/status.rs +++ b/crates/controller/src/controller/status.rs @@ -16,6 +16,7 @@ use crate::id_manager::SheetIdManager; use crate::id_manager::TextIdManager; use crate::navigator::Navigator; +use crate::block_schema_manager::SchemaManager; use crate::range_manager::RangeManager; use crate::style_manager::StyleManager; use crate::workbook::sheet_info_manager::SheetInfoManager; @@ -37,6 +38,7 @@ pub struct Status { pub style_manager: StyleManager, pub cell_attachment_manager: CellAttachmentsManager, pub exclusive_manager: ExclusiveManager, + pub block_schema_manager: SchemaManager, pub dirty_cells_next_round: HashSet<(SheetId, CellId)>, } @@ -62,6 +64,7 @@ impl Default for Status { cell_attachment_manager: CellAttachmentsManager::default(), exclusive_manager: ExclusiveManager::default(), dirty_cells_next_round: HashSet::new(), + block_schema_manager: SchemaManager::default(), } } } diff --git a/crates/controller/src/edit_action/mod.rs b/crates/controller/src/edit_action/mod.rs index e6609932..973e4164 100644 --- a/crates/controller/src/edit_action/mod.rs +++ b/crates/controller/src/edit_action/mod.rs @@ -92,6 +92,8 @@ pub enum EditPayload { CreateBlock(CreateBlock), ResizeBlock(ResizeBlock), ConvertBlock(ConvertBlock), + BindFormSchema(BindFormSchema), + BindRandomSchema(BindRandomSchema), // DiyCell CreateDiyCell(CreateDiyCell), @@ -419,6 +421,44 @@ impl From for EditPayload { } } +#[derive(Debug, Clone, TS)] +#[ts(file_name = "bind_form_schema.ts", builder, rename_all = "camelCase")] +pub struct BindFormSchema { + pub ref_name: String, + pub sheet_idx: usize, + pub block_id: usize, + // Form schema fields start from this index. + pub field_from: usize, + // Form schema keys start from this index. + pub key_from: usize, + pub fields: Vec, + // Generated by frontend app. + // It is used to customize the fields' render behaviors. + // The length of this vector should be the same as `fields`. + pub render_ids: Vec, + pub keys: Vec, + pub row: bool, +} + +#[derive(Debug, Clone, TS)] +#[ts(file_name = "bind_random_schema.ts", builder, rename_all = "camelCase")] +pub struct BindRandomSchema { + pub ref_name: String, + pub sheet_idx: usize, + pub block_id: usize, + pub units: Vec, +} + +#[derive(Debug, Clone, TS)] +#[ts(file_name = "random_schema_unit.ts", builder, rename_all = "camelCase")] +pub struct RandomSchemaUnit { + pub key: String, + pub field: String, + pub render_id: String, + pub row: usize, + pub col: usize, +} + #[derive(Debug, Clone, TS)] #[ts( file_name = "delete_rows_in_block.ts", diff --git a/crates/controller/src/file_loader/mod.rs b/crates/controller/src/file_loader/mod.rs index f65ff3d6..495d960b 100644 --- a/crates/controller/src/file_loader/mod.rs +++ b/crates/controller/src/file_loader/mod.rs @@ -53,6 +53,7 @@ pub fn load_file(wb: Wb, book_name: String) -> Controller { mut ext_ref_manager, exclusive_manager, dirty_cells_next_round: dirty_cells, + block_schema_manager, } = Status::default(); let mut sheet_id_fetcher = SheetIdFetcher { sheet_id_manager: &mut sheet_id_manager, @@ -219,6 +220,7 @@ pub fn load_file(wb: Wb, book_name: String) -> Controller { cube_manager, ext_ref_manager, exclusive_manager, + block_schema_manager, }; if let Some(theme) = xl.theme { settings.theme = ThemeManager::from(theme.1); diff --git a/crates/controller/src/lib.rs b/crates/controller/src/lib.rs index d36a905c..8b2185a9 100644 --- a/crates/controller/src/lib.rs +++ b/crates/controller/src/lib.rs @@ -3,6 +3,7 @@ extern crate lazy_static; pub mod api; mod async_func_manager; +mod block_schema_manager; mod calc_engine; mod cell; mod cell_attachments; diff --git a/crates/controller/src/version_manager/diff.rs b/crates/controller/src/version_manager/diff.rs index 36ed9cc3..33b3928e 100644 --- a/crates/controller/src/version_manager/diff.rs +++ b/crates/controller/src/version_manager/diff.rs @@ -333,5 +333,29 @@ fn convert_diff( sheet_id, ))) } + EditPayload::BindFormSchema(p) => { + let sheet_id = ctx + .fetch_sheet_id_by_index(p.sheet_idx) + .map_err(|l| BasicError::SheetIdxExceed(l))?; + Ok(Some(( + Diff::BlockUpdate { + sheet_id, + id: p.block_id, + }, + sheet_id, + ))) + } + EditPayload::BindRandomSchema(p) => { + let sheet_id = ctx + .fetch_sheet_id_by_index(p.sheet_idx) + .map_err(|l| BasicError::SheetIdxExceed(l))?; + Ok(Some(( + Diff::BlockUpdate { + sheet_id, + id: p.block_id, + }, + sheet_id, + ))) + } } }