diff --git a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart index c837e4346f..e7668ffcc0 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart @@ -21,7 +21,7 @@ part 'board_bloc.freezed.dart'; class BoardBloc extends Bloc { final BoardDataController _dataController; late final AFBoardDataController afBoardDataController; - List groupControllers = []; + Map groupControllers = {}; GridFieldCache get fieldCache => _dataController.fieldCache; String get gridId => _dataController.gridId; @@ -38,13 +38,17 @@ class BoardBloc extends Bloc { columnId, fromIndex, toIndex, - ) {}, + ) { + groupControllers[columnId]?.moveRow(fromIndex, toIndex); + }, onMoveColumnItemToColumn: ( fromColumnId, fromIndex, toColumnId, toIndex, - ) {}, + ) { + // + }, ); on( @@ -84,7 +88,7 @@ class BoardBloc extends Bloc { @override Future close() async { await _dataController.dispose(); - for (final controller in groupControllers) { + for (final controller in groupControllers.values) { controller.dispose(); } return super.close(); @@ -94,11 +98,12 @@ class BoardBloc extends Bloc { for (final group in groups) { final delegate = GroupControllerDelegateImpl(afBoardDataController); final controller = GroupController( + gridId: state.gridId, group: group, delegate: delegate, ); controller.startListening(); - groupControllers.add(controller); + groupControllers[controller.group.groupId] = (controller); } } diff --git a/frontend/app_flowy/lib/plugins/board/application/group_controller.dart b/frontend/app_flowy/lib/plugins/board/application/group_controller.dart index a0cd5c3ced..62d2130194 100644 --- a/frontend/app_flowy/lib/plugins/board/application/group_controller.dart +++ b/frontend/app_flowy/lib/plugins/board/application/group_controller.dart @@ -1,8 +1,12 @@ +import 'package:app_flowy/plugins/grid/application/row/row_service.dart'; import 'package:flowy_sdk/log.dart'; +import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart'; import 'group_listener.dart'; +typedef OnGroupError = void Function(FlowyError); + abstract class GroupControllerDelegate { void removeRow(String groupId, String rowId); void insertRow(String groupId, RowPB row, int? index); @@ -12,12 +16,36 @@ abstract class GroupControllerDelegate { class GroupController { final GroupPB group; final GroupListener _listener; + final MoveRowFFIService _rowService; final GroupControllerDelegate delegate; + OnGroupError? _onError; - GroupController({required this.group, required this.delegate}) - : _listener = GroupListener(group); + GroupController({ + required String gridId, + required this.group, + required this.delegate, + }) : _rowService = MoveRowFFIService(gridId: gridId), + _listener = GroupListener(group); - void startListening() { + Future moveRow(int fromIndex, int toIndex) async { + if (fromIndex < group.rows.length && toIndex < group.rows.length) { + final fromRow = group.rows[fromIndex]; + final toRow = group.rows[toIndex]; + + final result = await _rowService.moveRow( + rowId: fromRow.id, + fromIndex: fromIndex, + toIndex: toIndex, + upperRowId: toRow.id, + layout: GridLayout.Board, + ); + + result.fold((l) => null, (r) => _onError?.call(r)); + } + } + + void startListening({OnGroupError? onError}) { + _onError = onError; _listener.start(onGroupChanged: (result) { result.fold( (GroupRowsChangesetPB changeset) { diff --git a/frontend/app_flowy/lib/plugins/grid/application/row/row_service.dart b/frontend/app_flowy/lib/plugins/grid/application/row/row_service.dart index 52af18b296..594bd230f9 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/row/row_service.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/row/row_service.dart @@ -71,3 +71,32 @@ class RowFFIService { return GridEventDuplicateRow(payload).send(); } } + +class MoveRowFFIService { + final String gridId; + + MoveRowFFIService({ + required this.gridId, + }); + + Future> moveRow({ + required String rowId, + required int fromIndex, + required int toIndex, + required GridLayout layout, + String? upperRowId, + }) { + var payload = MoveRowPayloadPB.create() + ..viewId = gridId + ..rowId = rowId + ..layout = layout + ..fromIndex = fromIndex + ..toIndex = toIndex; + + if (upperRowId != null) { + payload.upperRowId = upperRowId; + } + + return GridEventMoveRow(payload).send(); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs index f4200d81cf..b185f6702b 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs @@ -96,28 +96,27 @@ pub struct MoveRowPayloadPB { pub view_id: String, #[pb(index = 2)] - pub row_id: String, - - #[pb(index = 3)] - pub from_index: i32, - - #[pb(index = 4)] - pub to_index: i32, + pub from_row_id: String, + // #[pb(index = 3)] + // pub from_index: i32, + // + // #[pb(index = 4)] + // pub to_index: i32, #[pb(index = 5)] pub layout: GridLayout, - #[pb(index = 6, one_of)] - pub upper_row_id: Option, + #[pb(index = 6)] + pub to_row_id: String, } pub struct MoveRowParams { pub view_id: String, - pub row_id: String, - pub from_index: i32, - pub to_index: i32, + pub from_row_id: String, + // pub from_index: i32, + // pub to_index: i32, pub layout: GridLayout, - pub upper_row_id: Option, + pub to_row_id: String, } impl TryInto for MoveRowPayloadPB { @@ -125,18 +124,19 @@ impl TryInto for MoveRowPayloadPB { fn try_into(self) -> Result { let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::GridViewIdIsEmpty)?; - let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; - let upper_row_id = match self.upper_row_id { + let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; + let to_row_id = NotEmptyStr::parse(self.to_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; + let upper_row_id = match self.to_row_id { None => None, Some(upper_row_id) => Some(NotEmptyStr::parse(upper_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?.0), }; Ok(MoveRowParams { view_id: view_id.0, - row_id: row_id.0, - from_index: self.from_index, - to_index: self.to_index, + from_row_id: from_row_id.0, + // from_index: self.from_index, + // to_index: self.to_index, layout: self.layout, - upper_row_id, + to_row_id: upper_row_id, }) } } diff --git a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs index f4602b9a50..96bb832323 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs @@ -1,5 +1,6 @@ use crate::entities::{InsertedRowPB, RowPB}; use flowy_derive::ProtoBuf; +use std::fmt::Formatter; #[derive(Debug, Default, ProtoBuf)] pub struct GroupRowsChangesetPB { @@ -16,6 +17,14 @@ pub struct GroupRowsChangesetPB { pub updated_rows: Vec, } +impl std::fmt::Display for GroupRowsChangesetPB { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let _ = f.write_fmt(format_args!("Group:{}", self.group_id))?; + let _ = f.write_fmt(format_args!("Insert:{:?}", self.inserted_rows))?; + f.write_fmt(format_args!("Delete:{:?}", self.deleted_rows)) + } +} + impl GroupRowsChangesetPB { pub fn is_empty(&self) -> bool { self.inserted_rows.is_empty() && self.deleted_rows.is_empty() && self.updated_rows.is_empty() diff --git a/frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs b/frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs index 2696ef0b2c..1bb42310ff 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs @@ -1,5 +1,5 @@ use crate::services::block_manager::GridBlockManager; -use crate::services::grid_view_manager::{GridViewRowDelegate, GridViewRowOperation}; +use crate::services::grid_view_manager::GridViewRowDelegate; use flowy_error::FlowyResult; use flowy_grid_data_model::revision::RowRevision; use lib_infra::future::{wrap_future, AFFuture}; @@ -36,10 +36,3 @@ impl GridViewRowDelegate for Arc { }) } } - -impl GridViewRowOperation for Arc { - fn gv_move_row(&self, row_rev: Arc, from: usize, to: usize) -> AFFuture> { - let block_manager = self.clone(); - wrap_future(async move { block_manager.move_row(row_rev, from, to).await }) - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index 2b0e83bada..1f16f95475 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -72,7 +72,6 @@ impl GridRevisionEditor { user.clone(), Arc::new(grid_pad.clone()), Arc::new(block_manager.clone()), - Arc::new(block_manager.clone()), Arc::new(task_scheduler.clone()), ) .await?, @@ -494,7 +493,42 @@ impl GridRevisionEditor { } pub async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> { - self.view_manager.move_row(params).await + let MoveRowParams { + view_id: _, + from_row_id: row_id, + from_index, + to_index, + layout: _, + to_row_id: upper_row_id, + } = params; + + let from_index = from_index as usize; + let to_index = to_index as usize; + + match self.block_manager.get_row_rev(&row_id).await? { + None => tracing::warn!("Move row failed, can not find the row:{}", row_id), + Some(row_rev) => match upper_row_id { + None => { + tracing::trace!("Move row from {} to {}", from_index, to_index); + let _ = self + .block_manager + .move_row(row_rev.clone(), from_index, to_index) + .await?; + } + Some(to_row_id) => match self.block_manager.index_of_row(&to_row_id).await { + None => tracing::error!("Can not find the row: {} when moving the row", to_row_id), + Some(to_row_index) => { + tracing::trace!("Move row from {} to {}", from_index, to_row_index); + let _ = self + .block_manager + .move_row(row_rev.clone(), from_index, to_row_index) + .await?; + self.view_manager.move_row(row_rev, to_row_id).await; + } + }, + }, + } + Ok(()) } pub async fn move_field(&self, params: MoveFieldParams) -> FlowyResult<()> { 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 c396e79239..c8f2babf9f 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 @@ -118,17 +118,22 @@ impl GridViewRevisionEditor { } } - // async fn get_mut_group(&self, group_id: &str, f: F) -> FlowyResult<()> - // where - // F: Fn(&mut Group) -> FlowyResult<()>, - // { - // for group in self.groups.write().await.iter_mut() { - // if group.id == group_id { - // let _ = f(group)?; - // } - // } - // Ok(()) - // } + pub(crate) async fn did_move_row(&self, row_rev: &RowRevision, upper_row_id: &str) { + if let Some(changesets) = self + .group_service + .write() + .await + .did_move_row(row_rev, upper_row_id, |field_id| { + self.field_delegate.get_field_rev(&field_id) + }) + .await + { + for changeset in changesets { + tracing::trace!("Group changeset: {}", changeset); + self.notify_did_update_group(changeset).await; + } + } + } pub(crate) async fn load_groups(&self) -> FlowyResult> { let field_revs = self.field_delegate.get_field_revs().await; diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs index ceb02672b8..4c320d527a 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs @@ -31,17 +31,11 @@ pub trait GridViewRowDelegate: Send + Sync + 'static { fn gv_row_revs(&self) -> AFFuture>>; } -pub trait GridViewRowOperation: Send + Sync + 'static { - // Will be removed in the future. - fn gv_move_row(&self, row_rev: Arc, from: usize, to: usize) -> AFFuture>; -} - pub(crate) struct GridViewManager { grid_id: String, user: Arc, field_delegate: Arc, row_delegate: Arc, - row_operation: Arc, view_editors: DashMap>, scheduler: Arc, } @@ -52,7 +46,6 @@ impl GridViewManager { user: Arc, field_delegate: Arc, row_delegate: Arc, - row_operation: Arc, scheduler: Arc, ) -> FlowyResult { Ok(Self { @@ -61,7 +54,6 @@ impl GridViewManager { scheduler, field_delegate, row_delegate, - row_operation, view_editors: DashMap::default(), }) } @@ -119,37 +111,10 @@ impl GridViewManager { Ok(RepeatedGridGroupPB { items: groups }) } - pub(crate) async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> { - let MoveRowParams { - view_id: _, - row_id, - from_index, - to_index, - layout, - upper_row_id, - } = params; - - let from_index = from_index as usize; - - match self.row_delegate.gv_get_row_rev(&row_id).await { - None => tracing::warn!("Move row failed, can not find the row:{}", row_id), - Some(row_rev) => match layout { - GridLayout::Table => { - tracing::trace!("Move row from {} to {}", from_index, to_index); - let to_index = to_index as usize; - let _ = self.row_operation.gv_move_row(row_rev, from_index, to_index).await?; - } - GridLayout::Board => { - if let Some(upper_row_id) = upper_row_id { - if let Some(to_index) = self.row_delegate.gv_index_of_row(&upper_row_id).await { - tracing::trace!("Move row from {} to {}", from_index, to_index); - let _ = self.row_operation.gv_move_row(row_rev, from_index, to_index).await?; - } - } - } - }, + pub(crate) async fn move_row(&self, row_rev: Arc, to_row_id: String) { + for view_editor in self.view_editors.iter() { + view_editor.did_move_row(&row_rev, &to_row_id).await; } - Ok(()) } pub(crate) async fn get_view_editor(&self, view_id: &str) -> FlowyResult> { diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs index 03fe4fe1c8..ca6fd3a4bd 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs @@ -39,6 +39,7 @@ impl Groupable for CheckboxGroupController { &mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType, + to_row_id: &str, ) -> Vec { todo!() } diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/group_controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/group_controller.rs index 851714a52a..6135aabb7e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/group_controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/group_controller.rs @@ -29,8 +29,12 @@ pub trait Groupable: Send + Sync { cell_data: &Self::CellDataType, ) -> Vec; - fn move_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) - -> Vec; + fn move_row_if_match( + &mut self, + row_rev: &RowRevision, + cell_data: &Self::CellDataType, + to_row_id: &str, + ) -> Vec; } pub trait GroupController: GroupControllerSharedAction + Send + Sync { @@ -53,6 +57,13 @@ pub trait GroupControllerSharedAction: Send + Sync { row_rev: &RowRevision, field_rev: &FieldRevision, ) -> FlowyResult>; + + fn did_move_row( + &mut self, + row_rev: &RowRevision, + field_rev: &FieldRevision, + to_row_id: &str, + ) -> FlowyResult>; } const DEFAULT_GROUP_ID: &str = "default_group"; @@ -120,6 +131,18 @@ impl Group { Some(_) => {} } } + + pub fn insert_row(&mut self, index: usize, row_pb: RowPB) { + if index < self.rows.len() { + self.rows.insert(index, row_pb); + } else { + tracing::error!("Insert row index:{} beyond the bounds:{},", index, self.rows.len()); + } + } + + pub fn index_of_row(&self, row_id: &str) -> Option { + self.rows.iter().position(|row| row.id == row_id) + } } impl GenericGroupController @@ -236,6 +259,21 @@ where Ok(vec![]) } } + + fn did_move_row( + &mut self, + row_rev: &RowRevision, + field_rev: &FieldRevision, + to_row_id: &str, + ) -> FlowyResult> { + if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { + let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev); + let cell_data = cell_bytes.parser::

()?; + Ok(self.move_row_if_match(row_rev, &cell_data, to_row_id)) + } else { + Ok(vec![]) + } + } } // impl GroupController diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs index 3083980986..efc2d4ff63 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs @@ -45,8 +45,13 @@ impl Groupable for SingleSelectGroupController { &mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType, + to_row_id: &str, ) -> Vec { - todo!() + let mut changesets = vec![]; + self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| { + move_row(group, &mut changesets, cell_data, row_rev, to_row_id); + }); + changesets } } @@ -100,7 +105,6 @@ impl Groupable for MultiSelectGroupController { fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { let mut changesets = vec![]; - self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| { add_row(group, &mut changesets, cell_data, row_rev); }); @@ -123,8 +127,13 @@ impl Groupable for MultiSelectGroupController { &mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType, + to_row_id: &str, ) -> Vec { - todo!() + let mut changesets = vec![]; + self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| { + move_row(group, &mut changesets, cell_data, row_rev, to_row_id); + }); + changesets } } @@ -178,11 +187,6 @@ fn add_row( group.add_row(row_pb); } } - - // else if group.contains_row(&row_rev.id) { - // group.remove_row(&row_rev.id); - // changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()])); - // } }); } @@ -201,3 +205,30 @@ fn remove_row( } }); } + +fn move_row( + group: &mut Group, + changesets: &mut Vec, + cell_data: &SelectOptionCellDataPB, + row_rev: &RowRevision, + upper_row_id: &str, +) { + cell_data.select_options.iter().for_each(|option| { + if option.id == group.id { + if group.contains_row(&row_rev.id) { + changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()])); + group.remove_row(&row_rev.id); + } + } + + if let Some(index) = group.index_of_row(upper_row_id) { + let row_pb = RowPB::from(row_rev); + let inserted_row = InsertedRowPB { + row: row_pb.clone(), + index: Some(index as i32), + }; + changesets.push(GroupRowsChangesetPB::insert(group.id.clone(), vec![inserted_row])); + group.insert_row(index, row_pb); + } + }); +} diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs index 0bca6d8730..e4bf92e22a 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs @@ -87,7 +87,34 @@ impl GroupService { match group_controller.write().await.did_delete_row(row_rev, &field_rev) { Ok(changesets) => Some(changesets), Err(e) => { - tracing::error!("Update group data failed, {:?}", e); + tracing::error!("Delete group data failed, {:?}", e); + None + } + } + } + + pub(crate) async fn did_move_row( + &self, + row_rev: &RowRevision, + upper_row_id: &str, + get_field_fn: F, + ) -> Option> + where + F: FnOnce(String) -> O, + O: Future>> + Send + Sync + 'static, + { + let group_controller = self.group_controller.as_ref()?; + let field_id = group_controller.read().await.field_id().to_owned(); + let field_rev = get_field_fn(field_id).await?; + + match group_controller + .write() + .await + .did_move_row(row_rev, &field_rev, upper_row_id) + { + Ok(changesets) => Some(changesets), + Err(e) => { + tracing::error!("Move group data failed, {:?}", e); None } } diff --git a/shared-lib/flowy-sync/src/client_grid/block_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/block_revision_pad.rs index 142463b8fc..5902fda35c 100644 --- a/shared-lib/flowy-sync/src/client_grid/block_revision_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/block_revision_pad.rs @@ -178,8 +178,12 @@ impl GridBlockRevisionPad { if let Some(position) = row_revs.iter().position(|row_rev| row_rev.id == row_id) { debug_assert_eq!(from, position); let row_rev = row_revs.remove(position); - row_revs.insert(to, row_rev); - Ok(Some(())) + if to > row_revs.len() { + return Err(CollaborateError::out_of_bound()); + } else { + row_revs.insert(to, row_rev); + Ok(Some(())) + } } else { Ok(None) }