diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart index 29fb521976..80bd4dcb13 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart @@ -283,7 +283,12 @@ class IGridCellController extends Equatable { _loadDataOperation?.cancel(); _loadDataOperation = Timer(const Duration(milliseconds: 10), () { _cellDataLoader.loadData().then((data) { - _cellsCache.insert(_cacheKey, GridCell(object: data)); + if (data != null) { + _cellsCache.insert(_cacheKey, GridCell(object: data)); + } else { + _cellsCache.remove(_cacheKey); + } + _cellDataNotifier?.value = data; }); }); diff --git a/frontend/rust-lib/flowy-folder/src/manager.rs b/frontend/rust-lib/flowy-folder/src/manager.rs index 25e3b1f15b..a8ed3d56c1 100644 --- a/frontend/rust-lib/flowy-folder/src/manager.rs +++ b/frontend/rust-lib/flowy-folder/src/manager.rs @@ -221,8 +221,9 @@ impl DefaultFolderBuilder { initial_quill_delta_string() }; let _ = view_controller.set_latest_view(&view.id); + let layout_type = ViewLayoutTypePB::from(view.layout.clone()); let _ = view_controller - .create_view(&view.id, ViewDataTypePB::Text, Bytes::from(view_data)) + .create_view(&view.id, ViewDataTypePB::Text, layout_type, Bytes::from(view_data)) .await?; } } @@ -249,7 +250,13 @@ impl FolderManager { pub trait ViewDataProcessor { fn initialize(&self) -> FutureResult<(), FlowyError>; - fn create_container(&self, user_id: &str, view_id: &str, delta_data: Bytes) -> FutureResult<(), FlowyError>; + fn create_container( + &self, + user_id: &str, + view_id: &str, + layout: ViewLayoutTypePB, + delta_data: Bytes, + ) -> FutureResult<(), FlowyError>; fn close_container(&self, view_id: &str) -> FutureResult<(), FlowyError>; @@ -267,6 +274,7 @@ pub trait ViewDataProcessor { user_id: &str, view_id: &str, data: Vec, + layout: ViewLayoutTypePB, ) -> FutureResult; fn data_type(&self) -> ViewDataTypePB; diff --git a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs index 944a1b68db..1f0b61ebb1 100644 --- a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs @@ -1,5 +1,5 @@ pub use crate::entities::view::ViewDataTypePB; -use crate::entities::ViewInfoPB; +use crate::entities::{ViewInfoPB, ViewLayoutTypePB}; use crate::manager::{ViewDataProcessor, ViewDataProcessorMap}; use crate::{ dart_notification::{send_dart_notification, FolderNotification}, @@ -67,10 +67,20 @@ impl ViewController { params.view_content_data = view_data.to_vec(); } else { let delta_data = processor - .create_view_from_delta_data(&user_id, ¶ms.view_id, params.view_content_data.clone()) + .create_view_from_delta_data( + &user_id, + ¶ms.view_id, + params.view_content_data.clone(), + params.layout.clone(), + ) .await?; let _ = self - .create_view(¶ms.view_id, params.data_type.clone(), delta_data) + .create_view( + ¶ms.view_id, + params.data_type.clone(), + params.layout.clone(), + delta_data, + ) .await?; }; @@ -84,6 +94,7 @@ impl ViewController { &self, view_id: &str, data_type: ViewDataTypePB, + layout_type: ViewLayoutTypePB, delta_data: Bytes, ) -> Result<(), FlowyError> { if delta_data.is_empty() { @@ -91,7 +102,9 @@ impl ViewController { } let user_id = self.user.user_id()?; let processor = self.get_data_processor(data_type)?; - let _ = processor.create_container(&user_id, view_id, delta_data).await?; + let _ = processor + .create_container(&user_id, view_id, layout_type, delta_data) + .await?; Ok(()) } diff --git a/frontend/rust-lib/flowy-grid/src/manager.rs b/frontend/rust-lib/flowy-grid/src/manager.rs index bf094b2f9d..39f8641c7e 100644 --- a/frontend/rust-lib/flowy-grid/src/manager.rs +++ b/frontend/rust-lib/flowy-grid/src/manager.rs @@ -1,3 +1,4 @@ +use crate::entities::GridLayout; use crate::services::block_editor::GridBlockRevisionCompactor; use crate::services::grid_editor::{GridRevisionCompactor, GridRevisionEditor}; use crate::services::grid_view_manager::make_grid_view_rev_manager; @@ -178,6 +179,7 @@ impl GridManager { pub async fn make_grid_view_data( user_id: &str, view_id: &str, + layout: GridLayout, grid_manager: Arc, build_context: BuildGridContext, ) -> FlowyResult { @@ -208,7 +210,7 @@ pub async fn make_grid_view_data( let _ = grid_manager.create_grid(&grid_id, repeated_revision).await?; // Create grid view - let grid_view = GridViewRevision::new(grid_id, view_id.to_owned()); + let grid_view = GridViewRevision::new(grid_id, view_id.to_owned(), layout.into()); let grid_view_delta = make_grid_view_delta(&grid_view); let grid_view_delta_bytes = grid_view_delta.json_bytes(); let repeated_revision: RepeatedRevision = diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs index c7fb7f0a7f..c926743f9f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs @@ -277,6 +277,7 @@ impl GridViewRevisionEditor { Ok(()) } + #[tracing::instrument(level = "debug", skip_all, err)] pub(crate) async fn group_by_field(&self, field_id: &str) -> FlowyResult<()> { if let Some(field_rev) = self.field_delegate.get_field_rev(field_id).await { let new_group_controller = new_group_controller_with_field_rev( @@ -374,13 +375,14 @@ impl GridViewRevisionEditor { async fn new_group_controller( user_id: String, view_id: String, - pad: Arc>, + view_rev_pad: Arc>, rev_manager: Arc, field_delegate: Arc, row_delegate: Arc, ) -> FlowyResult> { - let configuration_reader = GroupConfigurationReaderImpl(pad.clone()); + let configuration_reader = GroupConfigurationReaderImpl(view_rev_pad.clone()); let field_revs = field_delegate.get_field_revs().await; + let layout = view_rev_pad.read().await.layout(); // Read the group field or find a new group field let field_rev = configuration_reader .get_configuration() @@ -391,24 +393,24 @@ async fn new_group_controller( .find(|field_rev| field_rev.id == configuration.field_id) .cloned() }) - .unwrap_or_else(|| find_group_field(&field_revs).unwrap()); + .unwrap_or_else(|| find_group_field(&field_revs, &layout).unwrap()); - new_group_controller_with_field_rev(user_id, view_id, pad, rev_manager, field_rev, row_delegate).await + new_group_controller_with_field_rev(user_id, view_id, view_rev_pad, rev_manager, field_rev, row_delegate).await } async fn new_group_controller_with_field_rev( user_id: String, view_id: String, - pad: Arc>, + view_rev_pad: Arc>, rev_manager: Arc, field_rev: Arc, row_delegate: Arc, ) -> FlowyResult> { - let configuration_reader = GroupConfigurationReaderImpl(pad.clone()); + let configuration_reader = GroupConfigurationReaderImpl(view_rev_pad.clone()); let configuration_writer = GroupConfigurationWriterImpl { user_id, rev_manager, - view_pad: pad, + view_pad: view_rev_pad, }; let row_revs = row_delegate.gv_row_revs().await; make_group_controller(view_id, field_rev, row_revs, configuration_reader, configuration_writer).await diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs index 50d93396d7..c15717e2a7 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs @@ -8,7 +8,7 @@ use crate::services::group::{ use flowy_error::FlowyResult; use flowy_grid_data_model::revision::{ CheckboxGroupConfigurationRevision, DateGroupConfigurationRevision, FieldRevision, GroupConfigurationRevision, - NumberGroupConfigurationRevision, RowRevision, SelectOptionGroupConfigurationRevision, + LayoutRevision, NumberGroupConfigurationRevision, RowRevision, SelectOptionGroupConfigurationRevision, TextGroupConfigurationRevision, UrlGroupConfigurationRevision, }; use std::sync::Arc; @@ -62,15 +62,17 @@ where Ok(group_controller) } -pub fn find_group_field(field_revs: &[Arc]) -> Option> { - let field_rev = field_revs - .iter() - .find(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - field_type.can_be_group() - }) - .cloned(); - field_rev +pub fn find_group_field(field_revs: &[Arc], layout: &LayoutRevision) -> Option> { + match layout { + LayoutRevision::Table => field_revs.iter().find(|field_rev| field_rev.is_primary).cloned(), + LayoutRevision::Board => field_revs + .iter() + .find(|field_rev| { + let field_type: FieldType = field_rev.ty.into(); + field_type.can_be_group() + }) + .cloned(), + } } pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurationRevision { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs index 2bbf5874aa..eca0a17e16 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs @@ -127,6 +127,7 @@ fn make_test_grid() -> BuildGridContext { let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) .name("Name") .visibility(true) + .primary(true) .build(); grid_builder.add_field(text_field); } diff --git a/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs b/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs index d373c6dc2d..91cbfc2355 100644 --- a/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs +++ b/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs @@ -7,6 +7,7 @@ use flowy_folder::{ event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser}, manager::FolderManager, }; +use flowy_grid::entities::GridLayout; use flowy_grid::manager::{make_grid_view_data, GridManager}; use flowy_grid::util::{make_default_board, make_default_grid}; use flowy_grid_data_model::revision::BuildGridContext; @@ -142,7 +143,15 @@ impl ViewDataProcessor for TextBlockViewDataProcessor { FutureResult::new(async move { manager.init() }) } - fn create_container(&self, user_id: &str, view_id: &str, delta_data: Bytes) -> FutureResult<(), FlowyError> { + fn create_container( + &self, + user_id: &str, + view_id: &str, + layout: ViewLayoutTypePB, + delta_data: Bytes, + ) -> FutureResult<(), FlowyError> { + // Only accept Document type + debug_assert_eq!(layout, ViewLayoutTypePB::Document); let repeated_revision: RepeatedRevision = Revision::initial_revision(user_id, view_id, delta_data).into(); let view_id = view_id.to_string(); let manager = self.0.clone(); @@ -196,7 +205,9 @@ impl ViewDataProcessor for TextBlockViewDataProcessor { _user_id: &str, _view_id: &str, data: Vec, + layout: ViewLayoutTypePB, ) -> FutureResult { + debug_assert_eq!(layout, ViewLayoutTypePB::Document); FutureResult::new(async move { Ok(Bytes::from(data)) }) } @@ -211,7 +222,13 @@ impl ViewDataProcessor for GridViewDataProcessor { FutureResult::new(async { Ok(()) }) } - fn create_container(&self, user_id: &str, view_id: &str, delta_data: Bytes) -> FutureResult<(), FlowyError> { + fn create_container( + &self, + user_id: &str, + view_id: &str, + _layout: ViewLayoutTypePB, + delta_data: Bytes, + ) -> FutureResult<(), FlowyError> { let repeated_revision: RepeatedRevision = Revision::initial_revision(user_id, view_id, delta_data).into(); let view_id = view_id.to_string(); let grid_manager = self.0.clone(); @@ -246,19 +263,22 @@ impl ViewDataProcessor for GridViewDataProcessor { view_id: &str, layout: ViewLayoutTypePB, ) -> FutureResult { - let build_context = match layout { - ViewLayoutTypePB::Grid => make_default_grid(), - ViewLayoutTypePB::Board => make_default_board(), + let (build_context, layout) = match layout { + ViewLayoutTypePB::Grid => (make_default_grid(), GridLayout::Table), + ViewLayoutTypePB::Board => (make_default_board(), GridLayout::Board), ViewLayoutTypePB::Document => { return FutureResult::new(async move { Err(FlowyError::internal().context(format!("Can't handle {:?} layout type", layout))) }); } }; + let user_id = user_id.to_string(); let view_id = view_id.to_string(); let grid_manager = self.0.clone(); - FutureResult::new(async move { make_grid_view_data(&user_id, &view_id, grid_manager, build_context).await }) + FutureResult::new( + async move { make_grid_view_data(&user_id, &view_id, layout, grid_manager, build_context).await }, + ) } fn create_view_from_delta_data( @@ -266,15 +286,26 @@ impl ViewDataProcessor for GridViewDataProcessor { user_id: &str, view_id: &str, data: Vec, + layout: ViewLayoutTypePB, ) -> FutureResult { let user_id = user_id.to_string(); let view_id = view_id.to_string(); let grid_manager = self.0.clone(); + let layout = match layout { + ViewLayoutTypePB::Grid => GridLayout::Table, + ViewLayoutTypePB::Board => GridLayout::Board, + ViewLayoutTypePB::Document => { + return FutureResult::new(async move { + Err(FlowyError::internal().context(format!("Can't handle {:?} layout type", layout))) + }); + } + }; + FutureResult::new(async move { let bytes = Bytes::from(data); let build_context = BuildGridContext::try_from(bytes)?; - make_grid_view_data(&user_id, &view_id, grid_manager, build_context).await + make_grid_view_data(&user_id, &view_id, layout, grid_manager, build_context).await }) } diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_view.rs b/shared-lib/flowy-grid-data-model/src/revision/grid_view.rs index 74c3c72511..24b991be6a 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_view.rs +++ b/shared-lib/flowy-grid-data-model/src/revision/grid_view.rs @@ -48,11 +48,11 @@ pub struct GridViewRevision { } impl GridViewRevision { - pub fn new(grid_id: String, view_id: String) -> Self { + pub fn new(grid_id: String, view_id: String, layout: LayoutRevision) -> Self { GridViewRevision { view_id, grid_id, - layout: Default::default(), + layout, filters: Default::default(), groups: Default::default(), // row_orders: vec![], diff --git a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs index 5be920ec5e..64197716bb 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs @@ -103,8 +103,12 @@ impl GridRevisionPad { |grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) { None => Ok(None), Some(index) => { - grid_meta.fields.remove(index); - Ok(Some(())) + if grid_meta.fields[index].is_primary { + Err(CollaborateError::can_not_delete_primary_field()) + } else { + grid_meta.fields.remove(index); + Ok(Some(())) + } } }, ) diff --git a/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs index 652c6d6867..8613418c40 100644 --- a/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs @@ -3,7 +3,7 @@ use crate::errors::{internal_error, CollaborateError, CollaborateResult}; use crate::util::{cal_diff, make_text_delta_from_revisions}; use flowy_grid_data_model::revision::{ FieldRevision, FieldTypeRevision, FilterConfigurationRevision, FilterConfigurationsByFieldId, GridViewRevision, - GroupConfigurationRevision, GroupConfigurationsByFieldId, + GroupConfigurationRevision, GroupConfigurationsByFieldId, LayoutRevision, }; use lib_ot::core::{Delta, DeltaBuilder, EmptyAttributes, OperationTransform}; use std::sync::Arc; @@ -25,8 +25,8 @@ impl std::ops::Deref for GridViewRevisionPad { impl GridViewRevisionPad { // For the moment, the view_id is equal to grid_id. The grid_id represents the database id. // A database can be referenced by multiple views. - pub fn new(grid_id: String, view_id: String) -> Self { - let view = Arc::new(GridViewRevision::new(grid_id, view_id)); + pub fn new(grid_id: String, view_id: String, layout: LayoutRevision) -> Self { + let view = Arc::new(GridViewRevision::new(grid_id, view_id, layout)); let json = serde_json::to_string(&view).unwrap(); let delta = DeltaBuilder::new().insert(&json).build(); Self { view, delta } @@ -34,7 +34,11 @@ impl GridViewRevisionPad { pub fn from_delta(view_id: &str, delta: Delta) -> CollaborateResult { if delta.is_empty() { - return Ok(GridViewRevisionPad::new(view_id.to_owned(), view_id.to_owned())); + return Ok(GridViewRevisionPad::new( + view_id.to_owned(), + view_id.to_owned(), + LayoutRevision::Table, + )); } let s = delta.content()?; let view: GridViewRevision = serde_json::from_str(&s).map_err(|e| { @@ -163,6 +167,10 @@ impl GridViewRevisionPad { make_grid_view_rev_json_str(&self.view) } + pub fn layout(&self) -> LayoutRevision { + self.layout.clone() + } + fn modify(&mut self, f: F) -> CollaborateResult> where F: FnOnce(&mut GridViewRevision) -> CollaborateResult>, diff --git a/shared-lib/flowy-sync/src/errors.rs b/shared-lib/flowy-sync/src/errors.rs index bc498e4d70..62f9519e63 100644 --- a/shared-lib/flowy-sync/src/errors.rs +++ b/shared-lib/flowy-sync/src/errors.rs @@ -1,7 +1,7 @@ use std::{fmt, fmt::Debug}; use strum_macros::Display; -macro_rules! static_doc_error { +macro_rules! static_error { ($name:ident, $status:expr) => { #[allow(non_snake_case, missing_docs)] pub fn $name() -> CollaborateError { @@ -34,12 +34,13 @@ impl CollaborateError { self } - static_doc_error!(internal, ErrorCode::InternalError); - static_doc_error!(undo, ErrorCode::UndoFail); - static_doc_error!(redo, ErrorCode::RedoFail); - static_doc_error!(out_of_bound, ErrorCode::OutOfBound); - static_doc_error!(record_not_found, ErrorCode::RecordNotFound); - static_doc_error!(revision_conflict, ErrorCode::RevisionConflict); + static_error!(internal, ErrorCode::InternalError); + static_error!(undo, ErrorCode::UndoFail); + static_error!(redo, ErrorCode::RedoFail); + static_error!(out_of_bound, ErrorCode::OutOfBound); + static_error!(record_not_found, ErrorCode::RecordNotFound); + static_error!(revision_conflict, ErrorCode::RevisionConflict); + static_error!(can_not_delete_primary_field, ErrorCode::CannotDeleteThePrimaryField); } impl fmt::Display for CollaborateError { @@ -57,6 +58,7 @@ pub enum ErrorCode { OutOfBound = 202, RevisionConflict = 203, RecordNotFound = 300, + CannotDeleteThePrimaryField = 301, InternalError = 1000, }