Bläddra i källkod

fix: fix some bugs

appflowy 2 år sedan
förälder
incheckning
074c497d57
27 ändrade filer med 482 tillägg och 248 borttagningar
  1. 1 1
      frontend/.vscode/launch.json
  2. 24 8
      frontend/app_flowy/lib/plugins/board/application/board_bloc.dart
  3. 17 0
      frontend/app_flowy/lib/plugins/board/application/group_controller.dart
  4. 1 1
      frontend/app_flowy/lib/plugins/board/board.dart
  5. 30 0
      frontend/app_flowy/lib/plugins/grid/application/row/row_service.dart
  6. 1 1
      frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart
  7. 2 1
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart
  8. 11 4
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart
  9. 43 0
      frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs
  10. 11 0
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  11. 4 0
      frontend/rust-lib/flowy-grid/src/event_map.rs
  12. 29 10
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  13. 9 10
      frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs
  14. 9 2
      frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs
  15. 3 9
      frontend/rust-lib/flowy-grid/src/services/group/action.rs
  16. 9 16
      frontend/rust-lib/flowy-grid/src/services/group/configuration.rs
  17. 13 18
      frontend/rust-lib/flowy-grid/src/services/group/controller.rs
  18. 6 7
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs
  19. 7 18
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs
  20. 7 18
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs
  21. 46 32
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/util.rs
  22. 54 69
      frontend/rust-lib/flowy-grid/src/services/group/group_service.rs
  23. 9 5
      frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs
  24. 105 12
      frontend/rust-lib/flowy-grid/tests/grid/group_test/test.rs
  25. 3 2
      shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs
  26. 24 0
      shared-lib/flowy-grid-data-model/src/revision/group_rev.rs
  27. 4 4
      shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs

+ 1 - 1
frontend/.vscode/launch.json

@@ -29,7 +29,7 @@
             "program": "./lib/main.dart",
             "type": "dart",
             "env": {
-                "RUST_LOG": "trace"
+                "RUST_LOG": "debug"
             },
             "cwd": "${workspaceRoot}/app_flowy"
         },

+ 24 - 8
frontend/app_flowy/lib/plugins/board/application/board_bloc.dart

@@ -23,7 +23,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
   final BoardDataController _dataController;
   late final AFBoardDataController afBoardDataController;
   final MoveRowFFIService _rowService;
-  Map<String, GroupController> groupControllers = {};
+  LinkedHashMap<String, GroupController> groupControllers = LinkedHashMap.new();
 
   GridFieldCache get fieldCache => _dataController.fieldCache;
   String get gridId => _dataController.gridId;
@@ -34,9 +34,13 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
         super(BoardState.initial(view.id)) {
     afBoardDataController = AFBoardDataController(
       onMoveColumn: (
+        fromColumnId,
         fromIndex,
+        toColumnId,
         toIndex,
-      ) {},
+      ) {
+        _moveGroup(fromColumnId, toColumnId);
+      },
       onMoveColumnItem: (
         columnId,
         fromIndex,
@@ -44,7 +48,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
       ) {
         final fromRow = groupControllers[columnId]?.rowAtIndex(fromIndex);
         final toRow = groupControllers[columnId]?.rowAtIndex(toIndex);
-        _moveRow(fromRow, toRow);
+        _moveRow(fromRow, columnId, toRow);
       },
       onMoveColumnItemToColumn: (
         fromColumnId,
@@ -54,7 +58,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
       ) {
         final fromRow = groupControllers[fromColumnId]?.rowAtIndex(fromIndex);
         final toRow = groupControllers[toColumnId]?.rowAtIndex(toIndex);
-        _moveRow(fromRow, toRow);
+        _moveRow(fromRow, toColumnId, toRow);
       },
     );
 
@@ -95,12 +99,13 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
     );
   }
 
-  void _moveRow(RowPB? fromRow, RowPB? toRow) {
-    if (fromRow != null && toRow != null) {
+  void _moveRow(RowPB? fromRow, String columnId, RowPB? toRow) {
+    if (fromRow != null) {
       _rowService
-          .moveRow(
+          .moveGroupRow(
         fromRowId: fromRow.id,
-        toRowId: toRow.id,
+        toGroupId: columnId,
+        toRowId: toRow?.id,
       )
           .then((result) {
         result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r)));
@@ -108,6 +113,17 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
     }
   }
 
+  void _moveGroup(String fromColumnId, String toColumnId) {
+    _rowService
+        .moveGroup(
+      fromGroupId: fromColumnId,
+      toGroupId: toColumnId,
+    )
+        .then((result) {
+      result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r)));
+    });
+  }
+
   @override
   Future<void> close() async {
     await _dataController.dispose();

+ 17 - 0
frontend/app_flowy/lib/plugins/board/application/group_controller.dart

@@ -37,6 +37,14 @@ class GroupController {
         (GroupRowsChangesetPB changeset) {
           for (final insertedRow in changeset.insertedRows) {
             final index = insertedRow.hasIndex() ? insertedRow.index : null;
+
+            if (insertedRow.hasIndex() &&
+                group.rows.length > insertedRow.index) {
+              group.rows.insert(insertedRow.index, insertedRow.row);
+            } else {
+              group.rows.add(insertedRow.row);
+            }
+
             delegate.insertRow(
               group.groupId,
               insertedRow.row,
@@ -45,10 +53,19 @@ class GroupController {
           }
 
           for (final deletedRow in changeset.deletedRows) {
+            group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
             delegate.removeRow(group.groupId, deletedRow);
           }
 
           for (final updatedRow in changeset.updatedRows) {
+            final index = group.rows.indexWhere(
+              (rowPB) => rowPB.id == updatedRow.id,
+            );
+
+            if (index != -1) {
+              group.rows[index] = updatedRow;
+            }
+
             delegate.updateRow(group.groupId, updatedRow);
           }
         },

+ 1 - 1
frontend/app_flowy/lib/plugins/board/board.dart

@@ -31,7 +31,7 @@ class BoardPluginBuilder implements PluginBuilder {
 
 class BoardPluginConfig implements PluginConfig {
   @override
-  bool get creatable => true;
+  bool get creatable => false;
 }
 
 class BoardPlugin extends Plugin {

+ 30 - 0
frontend/app_flowy/lib/plugins/grid/application/row/row_service.dart

@@ -3,6 +3,7 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
 
 class RowFFIService {
@@ -68,4 +69,33 @@ class MoveRowFFIService {
 
     return GridEventMoveRow(payload).send();
   }
+
+  Future<Either<Unit, FlowyError>> moveGroupRow({
+    required String fromRowId,
+    required String toGroupId,
+    required String? toRowId,
+  }) {
+    var payload = MoveGroupRowPayloadPB.create()
+      ..viewId = gridId
+      ..fromRowId = fromRowId
+      ..toGroupId = toGroupId;
+
+    if (toRowId != null) {
+      payload.toRowId = toRowId;
+    }
+
+    return GridEventMoveGroupRow(payload).send();
+  }
+
+  Future<Either<Unit, FlowyError>> moveGroup({
+    required String fromGroupId,
+    required String toGroupId,
+  }) {
+    final payload = MoveGroupPayloadPB.create()
+      ..viewId = gridId
+      ..fromGroupId = fromGroupId
+      ..toGroupId = toGroupId;
+
+    return GridEventMoveGroup(payload).send();
+  }
 }

+ 1 - 1
frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart

@@ -10,7 +10,7 @@ class MultiBoardListExample extends StatefulWidget {
 
 class _MultiBoardListExampleState extends State<MultiBoardListExample> {
   final AFBoardDataController boardDataController = AFBoardDataController(
-    onMoveColumn: (fromIndex, toIndex) {
+    onMoveColumn: (fromColumnId, fromIndex, toColumnId, toIndex) {
       debugPrint('Move column from $fromIndex to $toIndex');
     },
     onMoveColumnItem: (columnId, fromIndex, toIndex) {

+ 2 - 1
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart

@@ -145,7 +145,8 @@ class AFBoardColumnData<CustomData> extends ReoderFlexItem with EquatableMixin {
   }) : _items = items;
 
   /// Returns the readonly List<ColumnItem>
-  UnmodifiableListView<AFColumnItem> get items => UnmodifiableListView(_items);
+  UnmodifiableListView<AFColumnItem> get items =>
+      UnmodifiableListView([..._items]);
 
   @override
   List<Object?> get props => [id, ..._items];

+ 11 - 4
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart

@@ -8,7 +8,12 @@ import 'reorder_flex/reorder_flex.dart';
 import 'package:flutter/material.dart';
 import 'reorder_phantom/phantom_controller.dart';
 
-typedef OnMoveColumn = void Function(int fromIndex, int toIndex);
+typedef OnMoveColumn = void Function(
+  String fromColumnId,
+  int fromIndex,
+  String toColumnId,
+  int toIndex,
+);
 
 typedef OnMoveColumnItem = void Function(
   String columnId,
@@ -98,9 +103,11 @@ class AFBoardDataController extends ChangeNotifier
   }
 
   void moveColumn(int fromIndex, int toIndex, {bool notify = true}) {
-    final columnData = _columnDatas.removeAt(fromIndex);
-    _columnDatas.insert(toIndex, columnData);
-    onMoveColumn?.call(fromIndex, toIndex);
+    final toColumnData = _columnDatas[toIndex];
+    final fromColumnData = _columnDatas.removeAt(fromIndex);
+
+    _columnDatas.insert(toIndex, fromColumnData);
+    onMoveColumn?.call(fromColumnData.id, fromIndex, toColumnData.id, toIndex);
     if (notify) notifyListeners();
   }
 

+ 43 - 0
frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs

@@ -123,3 +123,46 @@ impl TryInto<MoveRowParams> for MoveRowPayloadPB {
         })
     }
 }
+#[derive(Debug, Clone, Default, ProtoBuf)]
+pub struct MoveGroupRowPayloadPB {
+    #[pb(index = 1)]
+    pub view_id: String,
+
+    #[pb(index = 2)]
+    pub from_row_id: String,
+
+    #[pb(index = 3)]
+    pub to_group_id: String,
+
+    #[pb(index = 4, one_of)]
+    pub to_row_id: Option<String>,
+}
+
+pub struct MoveGroupRowParams {
+    pub view_id: String,
+    pub from_row_id: String,
+    pub to_group_id: String,
+    pub to_row_id: Option<String>,
+}
+
+impl TryInto<MoveGroupRowParams> for MoveGroupRowPayloadPB {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<MoveGroupRowParams, Self::Error> {
+        let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::GridViewIdIsEmpty)?;
+        let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
+        let to_group_id = NotEmptyStr::parse(self.to_group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
+
+        let to_row_id = match self.to_row_id {
+            None => None,
+            Some(to_row_id) => Some(NotEmptyStr::parse(to_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?.0),
+        };
+
+        Ok(MoveGroupRowParams {
+            view_id: view_id.0,
+            from_row_id: from_row_id.0,
+            to_group_id: to_group_id.0,
+            to_row_id,
+        })
+    }
+}

+ 11 - 0
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -436,3 +436,14 @@ pub(crate) async fn move_group_handler(
     let _ = editor.move_group(params).await?;
     Ok(())
 }
+
+#[tracing::instrument(level = "debug", skip(data, manager), err)]
+pub(crate) async fn move_group_row_handler(
+    data: Data<MoveGroupRowPayloadPB>,
+    manager: AppData<Arc<GridManager>>,
+) -> FlowyResult<()> {
+    let params: MoveGroupRowParams = data.into_inner().try_into()?;
+    let editor = manager.get_grid_editor(params.view_id.as_ref())?;
+    let _ = editor.move_group_row(params).await?;
+    Ok(())
+}

+ 4 - 0
frontend/rust-lib/flowy-grid/src/event_map.rs

@@ -42,6 +42,7 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
         // Group
         .event(GridEvent::CreateBoardCard, create_board_card_handler)
         .event(GridEvent::MoveGroup, move_group_handler)
+        .event(GridEvent::MoveGroupRow, move_group_row_handler)
         .event(GridEvent::GetGroup, get_groups_handler);
 
     module
@@ -221,4 +222,7 @@ pub enum GridEvent {
 
     #[event(input = "MoveGroupPayloadPB")]
     MoveGroup = 111,
+
+    #[event(input = "MoveGroupRowPayloadPB")]
+    MoveGroupRow = 112,
 }

+ 29 - 10
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -560,16 +560,6 @@ impl GridRevisionEditor {
                             .block_manager
                             .move_row(row_rev.clone(), from_index, to_index)
                             .await?;
-
-                        if let Some(row_changeset) = self.view_manager.move_row(row_rev, to_row_id.clone()).await {
-                            tracing::trace!("Receive row changeset after moving the row");
-                            match self.block_manager.update_row(row_changeset).await {
-                                Ok(_) => {}
-                                Err(e) => {
-                                    tracing::error!("Apply row changeset error:{:?}", e);
-                                }
-                            }
-                        }
                     }
                     (_, None) => tracing::warn!("Can not find the from row id: {}", from_row_id),
                     (None, _) => tracing::warn!("Can not find the to row id: {}", to_row_id),
@@ -579,6 +569,35 @@ impl GridRevisionEditor {
         Ok(())
     }
 
+    pub async fn move_group_row(&self, params: MoveGroupRowParams) -> FlowyResult<()> {
+        let MoveGroupRowParams {
+            view_id: _,
+            from_row_id,
+            to_group_id,
+            to_row_id,
+        } = params;
+
+        match self.block_manager.get_row_rev(&from_row_id).await? {
+            None => tracing::warn!("Move row failed, can not find the row:{}", from_row_id),
+            Some(row_rev) => {
+                if let Some(row_changeset) = self
+                    .view_manager
+                    .move_group_row(row_rev, to_group_id, to_row_id.clone())
+                    .await
+                {
+                    match self.block_manager.update_row(row_changeset).await {
+                        Ok(_) => {}
+                        Err(e) => {
+                            tracing::error!("Apply row changeset error:{:?}", e);
+                        }
+                    }
+                }
+            }
+        }
+
+        Ok(())
+    }
+
     pub async fn move_field(&self, params: MoveFieldParams) -> FlowyResult<()> {
         let MoveFieldParams {
             grid_id: _,

+ 9 - 10
frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs

@@ -80,7 +80,7 @@ impl GridViewRevisionEditor {
             None => {}
             Some(group_id) => {
                 self.group_service
-                    .read()
+                    .write()
                     .await
                     .will_create_row(row_rev, group_id, |field_id| {
                         self.field_delegate.get_field_rev(&field_id)
@@ -109,7 +109,7 @@ impl GridViewRevisionEditor {
         // Send the group notification if the current view has groups;
         if let Some(changesets) = self
             .group_service
-            .read()
+            .write()
             .await
             .did_delete_row(row_rev, |field_id| self.field_delegate.get_field_rev(&field_id))
             .await
@@ -123,7 +123,7 @@ impl GridViewRevisionEditor {
     pub(crate) async fn did_update_row(&self, row_rev: &RowRevision) {
         if let Some(changesets) = self
             .group_service
-            .read()
+            .write()
             .await
             .did_update_row(row_rev, |field_id| self.field_delegate.get_field_rev(&field_id))
             .await
@@ -134,23 +134,23 @@ impl GridViewRevisionEditor {
         }
     }
 
-    pub(crate) async fn did_move_row(
+    pub(crate) async fn move_group_row(
         &self,
         row_rev: &RowRevision,
         row_changeset: &mut RowChangeset,
-        upper_row_id: &str,
+        to_group_id: &str,
+        to_row_id: Option<String>,
     ) {
         if let Some(changesets) = self
             .group_service
-            .read()
+            .write()
             .await
-            .did_move_row(row_rev, row_changeset, upper_row_id, |field_id| {
+            .move_group_row(row_rev, row_changeset, to_group_id, to_row_id, |field_id| {
                 self.field_delegate.get_field_rev(&field_id)
             })
             .await
         {
             for changeset in changesets {
-                tracing::trace!("Group: {} changeset: {}", changeset.group_id, changeset);
                 self.notify_did_update_group_rows(changeset).await;
             }
         }
@@ -184,7 +184,7 @@ impl GridViewRevisionEditor {
     pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
         let _ = self
             .group_service
-            .read()
+            .write()
             .await
             .move_group(&params.from_group_id, &params.to_group_id)
             .await?;
@@ -326,7 +326,6 @@ impl GroupConfigurationReader for GroupConfigurationReaderImpl {
         let view_pad = self.0.clone();
         wrap_future(async move {
             let mut groups = view_pad.read().await.groups.get_objects(&field_rev.id, &field_rev.ty)?;
-
             if groups.is_empty() {
                 None
             } else {

+ 9 - 2
frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs

@@ -129,10 +129,17 @@ impl GridViewManager {
     /// It may generate a RowChangeset when the Row was moved from one group to another.
     /// The return value, [RowChangeset], contains the changes made by the groups.
     ///
-    pub(crate) async fn move_row(&self, row_rev: Arc<RowRevision>, to_row_id: String) -> Option<RowChangeset> {
+    pub(crate) async fn move_group_row(
+        &self,
+        row_rev: Arc<RowRevision>,
+        to_group_id: String,
+        to_row_id: Option<String>,
+    ) -> Option<RowChangeset> {
         let mut row_changeset = RowChangeset::new(row_rev.id.clone());
         for view_editor in self.view_editors.iter() {
-            view_editor.did_move_row(&row_rev, &mut row_changeset, &to_row_id).await;
+            view_editor
+                .move_group_row(&row_rev, &mut row_changeset, &to_group_id, to_row_id.clone())
+                .await;
         }
 
         if row_changeset.has_changed() {

+ 3 - 9
frontend/rust-lib/flowy-grid/src/services/group/action.rs

@@ -1,6 +1,7 @@
 use crate::entities::GroupRowsChangesetPB;
 
-use flowy_grid_data_model::revision::{FieldRevision, RowChangeset, RowRevision};
+use crate::services::group::controller::MoveGroupRowContext;
+use flowy_grid_data_model::revision::RowRevision;
 
 pub trait GroupAction: Send + Sync {
     type CellDataType;
@@ -12,12 +13,5 @@ pub trait GroupAction: Send + Sync {
         cell_data: &Self::CellDataType,
     ) -> Vec<GroupRowsChangesetPB>;
 
-    fn move_row_if_match(
-        &mut self,
-        field_rev: &FieldRevision,
-        row_rev: &RowRevision,
-        row_changeset: &mut RowChangeset,
-        cell_data: &Self::CellDataType,
-        to_row_id: &str,
-    ) -> Vec<GroupRowsChangesetPB>;
+    fn move_row(&mut self, cell_data: &Self::CellDataType, context: MoveGroupRowContext) -> Vec<GroupRowsChangesetPB>;
 }

+ 9 - 16
frontend/rust-lib/flowy-grid/src/services/group/configuration.rs

@@ -101,9 +101,9 @@ where
         Ok(())
     }
 
-    pub(crate) fn with_mut_groups(&mut self, mut mut_groups_fn: impl FnMut(&mut Group)) {
+    pub(crate) fn with_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) {
         self.groups_map.iter_mut().for_each(|(_, group)| {
-            mut_groups_fn(group);
+            each(group);
         })
     }
 
@@ -111,22 +111,16 @@ where
         self.groups_map.get_mut(group_id)
     }
 
-    pub(crate) fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()> {
-        let from_group_index = self.groups_map.get_index_of(from_group_id);
-        let to_group_index = self.groups_map.get_index_of(to_group_id);
-        match (from_group_index, to_group_index) {
+    pub(crate) fn move_group(&mut self, from_id: &str, to_id: &str) -> FlowyResult<()> {
+        let from_index = self.groups_map.get_index_of(from_id);
+        let to_index = self.groups_map.get_index_of(to_id);
+        match (from_index, to_index) {
             (Some(from_index), Some(to_index)) => {
                 self.groups_map.swap_indices(from_index, to_index);
 
                 self.mut_configuration(|configuration| {
-                    let from_index = configuration
-                        .groups
-                        .iter()
-                        .position(|group| group.group_id == from_group_id);
-                    let to_index = configuration
-                        .groups
-                        .iter()
-                        .position(|group| group.group_id == to_group_id);
+                    let from_index = configuration.groups.iter().position(|group| group.group_id == from_id);
+                    let to_index = configuration.groups.iter().position(|group| group.group_id == to_id);
                     if let (Some(from), Some(to)) = (from_index, to_index) {
                         configuration.groups.swap(from, to);
                     }
@@ -150,7 +144,7 @@ where
         let configuration = (&*self.configuration).clone();
         let writer = self.writer.clone();
         let field_id = self.field_rev.id.clone();
-        let field_type = self.field_rev.ty.clone();
+        let field_type = self.field_rev.ty;
         tokio::spawn(async move {
             match writer
                 .save_group_configuration(&field_id, field_type, configuration)
@@ -196,7 +190,6 @@ where
 }
 
 fn merge_groups(old_group_revs: &[GroupRecordRevision], groups: Vec<Group>) -> (Vec<GroupRecordRevision>, Vec<Group>) {
-    tracing::trace!("Merge group: old: {}, new: {}", old_group_revs.len(), groups.len());
     if old_group_revs.is_empty() {
         let new_groups = groups
             .iter()

+ 13 - 18
frontend/rust-lib/flowy-grid/src/services/group/controller.rs

@@ -31,6 +31,14 @@ pub trait GroupGenerator {
     ) -> Vec<Group>;
 }
 
+pub struct MoveGroupRowContext<'a> {
+    pub row_rev: &'a RowRevision,
+    pub row_changeset: &'a mut RowChangeset,
+    pub field_rev: &'a FieldRevision,
+    pub to_group_id: &'a str,
+    pub to_row_id: Option<String>,
+}
+
 // Defines the shared actions each group controller can perform.
 pub trait GroupControllerSharedOperation: Send + Sync {
     // The field that is used for grouping the rows
@@ -51,13 +59,7 @@ pub trait GroupControllerSharedOperation: Send + Sync {
         field_rev: &FieldRevision,
     ) -> FlowyResult<Vec<GroupRowsChangesetPB>>;
 
-    fn did_move_row(
-        &mut self,
-        row_rev: &RowRevision,
-        row_changeset: &mut RowChangeset,
-        field_rev: &FieldRevision,
-        to_row_id: &str,
-    ) -> FlowyResult<Vec<GroupRowsChangesetPB>>;
+    fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsChangesetPB>>;
 }
 
 /// C: represents the group configuration that impl [GroupConfigurationSerde]
@@ -195,18 +197,11 @@ where
         }
     }
 
-    fn did_move_row(
-        &mut self,
-        row_rev: &RowRevision,
-        row_changeset: &mut RowChangeset,
-        field_rev: &FieldRevision,
-        to_row_id: &str,
-    ) -> FlowyResult<Vec<GroupRowsChangesetPB>> {
-        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);
+    fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsChangesetPB>> {
+        if let Some(cell_rev) = context.row_rev.cells.get(&self.field_id) {
+            let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), context.field_rev);
             let cell_data = cell_bytes.parser::<P>()?;
-            tracing::trace!("Move row:{} to row:{}", row_rev.id, to_row_id);
-            Ok(self.move_row_if_match(field_rev, row_rev, row_changeset, &cell_data, to_row_id))
+            Ok(self.move_row(&cell_data, context))
         } else {
             Ok(vec![])
         }

+ 6 - 7
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs

@@ -2,10 +2,12 @@ use crate::entities::GroupRowsChangesetPB;
 use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK};
 use crate::services::group::action::GroupAction;
 use crate::services::group::configuration::GenericGroupConfiguration;
-use crate::services::group::controller::{GenericGroupController, GroupController, GroupGenerator};
+use crate::services::group::controller::{
+    GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
+};
 use crate::services::group::entities::Group;
 
-use flowy_grid_data_model::revision::{CheckboxGroupConfigurationRevision, FieldRevision, RowChangeset, RowRevision};
+use flowy_grid_data_model::revision::{CheckboxGroupConfigurationRevision, FieldRevision, RowRevision};
 
 pub type CheckboxGroupController = GenericGroupController<
     CheckboxGroupConfigurationRevision,
@@ -38,13 +40,10 @@ impl GroupAction for CheckboxGroupController {
         todo!()
     }
 
-    fn move_row_if_match(
+    fn move_row(
         &mut self,
-        _field_rev: &FieldRevision,
-        _row_rev: &RowRevision,
-        _row_changeset: &mut RowChangeset,
         _cell_data: &Self::CellDataType,
-        _to_row_id: &str,
+        _context: MoveGroupRowContext,
     ) -> Vec<GroupRowsChangesetPB> {
         todo!()
     }

+ 7 - 18
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs

@@ -3,12 +3,12 @@ use crate::services::cell::insert_select_option_cell;
 use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser};
 use crate::services::group::action::GroupAction;
 
-use crate::services::group::controller::{GenericGroupController, GroupController, GroupGenerator};
+use crate::services::group::controller::{
+    GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
+};
 use crate::services::group::controller_impls::select_option_controller::util::*;
 use crate::services::group::entities::Group;
-use flowy_grid_data_model::revision::{
-    FieldRevision, RowChangeset, RowRevision, SelectOptionGroupConfigurationRevision,
-};
+use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision};
 
 // MultiSelect
 pub type MultiSelectGroupController = GenericGroupController<
@@ -45,25 +45,14 @@ impl GroupAction for MultiSelectGroupController {
         changesets
     }
 
-    fn move_row_if_match(
+    fn move_row(
         &mut self,
-        field_rev: &FieldRevision,
-        row_rev: &RowRevision,
-        row_changeset: &mut RowChangeset,
         cell_data: &Self::CellDataType,
-        to_row_id: &str,
+        mut context: MoveGroupRowContext,
     ) -> Vec<GroupRowsChangesetPB> {
         let mut group_changeset = vec![];
         self.configuration.with_mut_groups(|group| {
-            move_row(
-                group,
-                &mut group_changeset,
-                field_rev,
-                row_rev,
-                row_changeset,
-                cell_data,
-                to_row_id,
-            );
+            move_select_option_row(group, &mut group_changeset, cell_data, &mut context);
         });
         group_changeset
     }

+ 7 - 18
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs

@@ -3,13 +3,13 @@ use crate::services::cell::insert_select_option_cell;
 use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB};
 use crate::services::group::action::GroupAction;
 
-use crate::services::group::controller::{GenericGroupController, GroupController, GroupGenerator};
+use crate::services::group::controller::{
+    GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
+};
 use crate::services::group::controller_impls::select_option_controller::util::*;
 use crate::services::group::entities::Group;
 
-use flowy_grid_data_model::revision::{
-    FieldRevision, RowChangeset, RowRevision, SelectOptionGroupConfigurationRevision,
-};
+use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision};
 
 // SingleSelect
 pub type SingleSelectGroupController = GenericGroupController<
@@ -45,25 +45,14 @@ impl GroupAction for SingleSelectGroupController {
         changesets
     }
 
-    fn move_row_if_match(
+    fn move_row(
         &mut self,
-        field_rev: &FieldRevision,
-        row_rev: &RowRevision,
-        row_changeset: &mut RowChangeset,
         cell_data: &Self::CellDataType,
-        to_row_id: &str,
+        mut context: MoveGroupRowContext,
     ) -> Vec<GroupRowsChangesetPB> {
         let mut group_changeset = vec![];
         self.configuration.with_mut_groups(|group| {
-            move_row(
-                group,
-                &mut group_changeset,
-                field_rev,
-                row_rev,
-                row_changeset,
-                cell_data,
-                to_row_id,
-            );
+            move_select_option_row(group, &mut group_changeset, cell_data, &mut context);
         });
         group_changeset
     }

+ 46 - 32
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/util.rs

@@ -4,9 +4,8 @@ use crate::services::field::SelectOptionCellDataPB;
 use crate::services::group::configuration::GenericGroupConfiguration;
 use crate::services::group::Group;
 
-use flowy_grid_data_model::revision::{
-    FieldRevision, RowChangeset, RowRevision, SelectOptionGroupConfigurationRevision,
-};
+use crate::services::group::controller::MoveGroupRowContext;
+use flowy_grid_data_model::revision::{RowRevision, SelectOptionGroupConfigurationRevision};
 
 pub type SelectOptionGroupConfiguration = GenericGroupConfiguration<SelectOptionGroupConfigurationRevision>;
 
@@ -47,45 +46,60 @@ pub fn remove_row(
     });
 }
 
-pub fn move_row(
+pub fn move_select_option_row(
     group: &mut Group,
     group_changeset: &mut Vec<GroupRowsChangesetPB>,
-    field_rev: &FieldRevision,
-    row_rev: &RowRevision,
-    row_changeset: &mut RowChangeset,
-    cell_data: &SelectOptionCellDataPB,
-    to_row_id: &str,
+    _cell_data: &SelectOptionCellDataPB,
+    context: &mut MoveGroupRowContext,
 ) {
-    cell_data.select_options.iter().for_each(|option| {
-        // Remove the row in which group contains the row
-        let is_group_contains = group.contains_row(&row_rev.id);
-        let to_index = group.index_of_row(to_row_id);
+    let MoveGroupRowContext {
+        row_rev,
+        row_changeset,
+        field_rev,
+        to_group_id,
+        to_row_id,
+    } = context;
 
-        if option.id == group.id && is_group_contains {
-            group_changeset.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()]));
-            group.remove_row(&row_rev.id);
-        }
+    let from_index = group.index_of_row(&row_rev.id);
+    let to_index = match to_row_id {
+        None => None,
+        Some(to_row_id) => group.index_of_row(to_row_id),
+    };
+
+    // Remove the row in which group contains it
+    if from_index.is_some() {
+        group_changeset.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()]));
+        tracing::debug!("Group:{} remove row:{}", group.id, row_rev.id);
+        group.remove_row(&row_rev.id);
+    }
 
-        // Find the inserted group
-        if let Some(to_index) = to_index {
-            let row_pb = RowPB::from(row_rev);
-            let inserted_row = InsertedRowPB {
-                row: row_pb.clone(),
-                index: Some(to_index as i32),
-            };
-            group_changeset.push(GroupRowsChangesetPB::insert(group.id.clone(), vec![inserted_row]));
-            if group.number_of_row() == to_index {
+    if group.id == *to_group_id {
+        let row_pb = RowPB::from(*row_rev);
+        let mut inserted_row = InsertedRowPB::new(row_pb.clone());
+        match to_index {
+            None => {
+                group_changeset.push(GroupRowsChangesetPB::insert(group.id.clone(), vec![inserted_row]));
+                tracing::debug!("Group:{} append row:{}", group.id, row_rev.id);
                 group.add_row(row_pb);
-            } else {
-                group.insert_row(to_index, row_pb);
+            }
+            Some(to_index) => {
+                if to_index < group.number_of_row() {
+                    tracing::debug!("Group:{} insert row:{} at {} ", group.id, row_rev.id, to_index);
+                    inserted_row.index = Some(to_index as i32);
+                    group.insert_row(to_index, row_pb);
+                } else {
+                    tracing::debug!("Group:{} append row:{}", group.id, row_rev.id);
+                    group.add_row(row_pb);
+                }
+                group_changeset.push(GroupRowsChangesetPB::insert(group.id.clone(), vec![inserted_row]));
             }
         }
 
-        // If the inserted row comes from other group, it needs to update the corresponding cell content.
-        if to_index.is_some() && option.id != group.id {
-            // Update the corresponding row's cell content.
+        // Update the corresponding row's cell content.
+        if from_index.is_none() {
+            tracing::debug!("Mark row:{} belong to group:{}", row_rev.id, group.id);
             let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
             row_changeset.cell_by_field_id.insert(field_rev.id.clone(), cell_rev);
         }
-    });
+    }
 }

+ 54 - 69
frontend/rust-lib/flowy-grid/src/services/group/group_service.rs

@@ -1,5 +1,6 @@
+use crate::entities::{FieldType, GroupRowsChangesetPB};
 use crate::services::group::configuration::GroupConfigurationReader;
-use crate::services::group::controller::GroupController;
+use crate::services::group::controller::{GroupController, MoveGroupRowContext};
 use crate::services::group::{
     CheckboxGroupConfiguration, CheckboxGroupController, Group, GroupConfigurationWriter, MultiSelectGroupController,
     SelectOptionGroupConfiguration, SingleSelectGroupController,
@@ -10,16 +11,13 @@ use flowy_grid_data_model::revision::{
     NumberGroupConfigurationRevision, RowChangeset, RowRevision, SelectOptionGroupConfigurationRevision,
     TextGroupConfigurationRevision, UrlGroupConfigurationRevision,
 };
-
-use crate::entities::{FieldType, GroupRowsChangesetPB};
 use std::future::Future;
 use std::sync::Arc;
-use tokio::sync::RwLock;
 
 pub(crate) struct GroupService {
     configuration_reader: Arc<dyn GroupConfigurationReader>,
     configuration_writer: Arc<dyn GroupConfigurationWriter>,
-    group_controller: Option<Arc<RwLock<dyn GroupController>>>,
+    group_controller: Option<Box<dyn GroupController>>,
 }
 
 impl GroupService {
@@ -36,19 +34,16 @@ impl GroupService {
     }
 
     pub(crate) async fn groups(&self) -> Vec<Group> {
-        if let Some(group_controller) = self.group_controller.as_ref() {
-            group_controller.read().await.groups()
-        } else {
-            vec![]
-        }
+        self.group_controller
+            .as_ref()
+            .and_then(|group_controller| Some(group_controller.groups()))
+            .unwrap_or(vec![])
     }
 
     pub(crate) async fn get_group(&self, group_id: &str) -> Option<(usize, Group)> {
-        if let Some(group_controller) = self.group_controller.as_ref() {
-            group_controller.read().await.get_group(group_id)
-        } else {
-            None
-        }
+        self.group_controller
+            .as_ref()
+            .and_then(|group_controller| group_controller.get_group(group_id))
     }
 
     pub(crate) async fn load_groups(
@@ -58,51 +53,37 @@ impl GroupService {
     ) -> Option<Vec<Group>> {
         let field_rev = find_group_field(field_revs)?;
         let field_type: FieldType = field_rev.ty.into();
-        match self.make_group_controller(&field_type, &field_rev).await {
-            Ok(group_controller) => {
-                self.group_controller = group_controller;
-                let mut groups = vec![];
-                if let Some(group_action_handler) = self.group_controller.as_ref() {
-                    let mut write_guard = group_action_handler.write().await;
-                    groups = match write_guard.fill_groups(&row_revs, &field_rev) {
-                        Ok(groups) => groups,
-                        Err(e) => {
-                            tracing::error!("Fill groups failed:{:?}", e);
-                            vec![]
-                        }
-                    };
-                    drop(write_guard);
-                }
-                Some(groups)
-            }
-            Err(err) => {
-                tracing::error!("Load group failed: {}", err);
-                Some(vec![])
+
+        let mut group_controller = self.make_group_controller(&field_type, &field_rev).await.ok()??;
+        let groups = match group_controller.fill_groups(&row_revs, &field_rev) {
+            Ok(groups) => groups,
+            Err(e) => {
+                tracing::error!("Fill groups failed:{:?}", e);
+                vec![]
             }
-        }
+        };
+        self.group_controller = Some(group_controller);
+        Some(groups)
     }
 
-    pub(crate) async fn will_create_row<F, O>(&self, row_rev: &mut RowRevision, group_id: &str, get_field_fn: F)
+    pub(crate) async fn will_create_row<F, O>(&mut self, row_rev: &mut RowRevision, group_id: &str, get_field_fn: F)
     where
         F: FnOnce(String) -> O,
         O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static,
     {
-        if let Some(group_controller) = self.group_controller.as_ref() {
-            let field_id = group_controller.read().await.field_id().to_owned();
+        if let Some(group_controller) = self.group_controller.as_mut() {
+            let field_id = group_controller.field_id().to_owned();
             match get_field_fn(field_id).await {
                 None => {}
                 Some(field_rev) => {
-                    group_controller
-                        .write()
-                        .await
-                        .will_create_row(row_rev, &field_rev, group_id);
+                    group_controller.will_create_row(row_rev, &field_rev, group_id);
                 }
             }
         }
     }
 
     pub(crate) async fn did_delete_row<F, O>(
-        &self,
+        &mut self,
         row_rev: &RowRevision,
         get_field_fn: F,
     ) -> Option<Vec<GroupRowsChangesetPB>>
@@ -110,11 +91,11 @@ impl GroupService {
         F: FnOnce(String) -> O,
         O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static,
     {
-        let group_controller = self.group_controller.as_ref()?;
-        let field_id = group_controller.read().await.field_id().to_owned();
+        let group_controller = self.group_controller.as_mut()?;
+        let field_id = group_controller.field_id().to_owned();
         let field_rev = get_field_fn(field_id).await?;
 
-        match group_controller.write().await.did_delete_row(row_rev, &field_rev) {
+        match group_controller.did_delete_row(row_rev, &field_rev) {
             Ok(changesets) => Some(changesets),
             Err(e) => {
                 tracing::error!("Delete group data failed, {:?}", e);
@@ -123,26 +104,30 @@ impl GroupService {
         }
     }
 
-    pub(crate) async fn did_move_row<F, O>(
-        &self,
+    pub(crate) async fn move_group_row<F, O>(
+        &mut self,
         row_rev: &RowRevision,
         row_changeset: &mut RowChangeset,
-        upper_row_id: &str,
+        to_group_id: &str,
+        to_row_id: Option<String>,
         get_field_fn: F,
     ) -> Option<Vec<GroupRowsChangesetPB>>
     where
         F: FnOnce(String) -> O,
         O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static,
     {
-        let group_controller = self.group_controller.as_ref()?;
-        let field_id = group_controller.read().await.field_id().to_owned();
+        let group_controller = self.group_controller.as_mut()?;
+        let field_id = group_controller.field_id().to_owned();
         let field_rev = get_field_fn(field_id).await?;
+        let move_row_context = MoveGroupRowContext {
+            row_rev,
+            row_changeset,
+            field_rev: field_rev.as_ref(),
+            to_group_id,
+            to_row_id,
+        };
 
-        match group_controller
-            .write()
-            .await
-            .did_move_row(row_rev, row_changeset, &field_rev, upper_row_id)
-        {
+        match group_controller.move_group_row(move_row_context) {
             Ok(changesets) => Some(changesets),
             Err(e) => {
                 tracing::error!("Move group data failed, {:?}", e);
@@ -153,7 +138,7 @@ impl GroupService {
 
     #[tracing::instrument(level = "trace", skip_all)]
     pub(crate) async fn did_update_row<F, O>(
-        &self,
+        &mut self,
         row_rev: &RowRevision,
         get_field_fn: F,
     ) -> Option<Vec<GroupRowsChangesetPB>>
@@ -161,11 +146,11 @@ impl GroupService {
         F: FnOnce(String) -> O,
         O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static,
     {
-        let group_controller = self.group_controller.as_ref()?;
-        let field_id = group_controller.read().await.field_id().to_owned();
+        let group_controller = self.group_controller.as_mut()?;
+        let field_id = group_controller.field_id().to_owned();
         let field_rev = get_field_fn(field_id).await?;
 
-        match group_controller.write().await.did_update_row(row_rev, &field_rev) {
+        match group_controller.did_update_row(row_rev, &field_rev) {
             Ok(changeset) => Some(changeset),
             Err(e) => {
                 tracing::error!("Update group data failed, {:?}", e);
@@ -175,11 +160,11 @@ impl GroupService {
     }
 
     #[tracing::instrument(level = "trace", skip_all)]
-    pub(crate) async fn move_group(&self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()> {
-        match self.group_controller.as_ref() {
+    pub(crate) async fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()> {
+        match self.group_controller.as_mut() {
             None => Ok(()),
             Some(group_controller) => {
-                let _ = group_controller.write().await.move_group(from_group_id, to_group_id)?;
+                let _ = group_controller.move_group(from_group_id, to_group_id)?;
                 Ok(())
             }
         }
@@ -190,8 +175,8 @@ impl GroupService {
         &self,
         field_type: &FieldType,
         field_rev: &Arc<FieldRevision>,
-    ) -> FlowyResult<Option<Arc<RwLock<dyn GroupController>>>> {
-        let mut group_controller: Option<Arc<RwLock<dyn GroupController>>> = None;
+    ) -> FlowyResult<Option<Box<dyn GroupController>>> {
+        let mut group_controller: Option<Box<dyn GroupController>> = None;
         match field_type {
             FieldType::RichText => {
                 // let generator = GroupGenerator::<TextGroupConfigurationPB>::from_configuration(configuration);
@@ -210,7 +195,7 @@ impl GroupService {
                 )
                 .await?;
                 let controller = SingleSelectGroupController::new(field_rev, configuration).await?;
-                group_controller = Some(Arc::new(RwLock::new(controller)));
+                group_controller = Some(Box::new(controller));
             }
             FieldType::MultiSelect => {
                 let configuration = SelectOptionGroupConfiguration::new(
@@ -220,7 +205,7 @@ impl GroupService {
                 )
                 .await?;
                 let controller = MultiSelectGroupController::new(field_rev, configuration).await?;
-                group_controller = Some(Arc::new(RwLock::new(controller)));
+                group_controller = Some(Box::new(controller));
             }
             FieldType::Checkbox => {
                 let configuration = CheckboxGroupConfiguration::new(
@@ -230,7 +215,7 @@ impl GroupService {
                 )
                 .await?;
                 let controller = CheckboxGroupController::new(field_rev, configuration).await?;
-                group_controller = Some(Arc::new(RwLock::new(controller)))
+                group_controller = Some(Box::new(controller));
             }
             FieldType::URL => {
                 // let generator = GroupGenerator::<UrlGroupConfigurationPB>::from_configuration(configuration);

+ 9 - 5
frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs

@@ -1,5 +1,7 @@
 use crate::grid::grid_editor::GridEditorTest;
-use flowy_grid::entities::{CreateRowParams, FieldType, GridLayout, GroupPB, MoveGroupParams, MoveRowParams, RowPB};
+use flowy_grid::entities::{
+    CreateRowParams, FieldType, GridLayout, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB,
+};
 use flowy_grid::services::cell::insert_select_option_cell;
 use flowy_grid_data_model::revision::RowChangeset;
 
@@ -75,14 +77,16 @@ impl GridGroupTest {
             } => {
                 let groups: Vec<GroupPB> = self.editor.load_groups().await.unwrap().items;
                 let from_row = groups.get(from_group_index).unwrap().rows.get(from_row_index).unwrap();
-                let to_row = groups.get(to_group_index).unwrap().rows.get(to_row_index).unwrap();
-                let params = MoveRowParams {
+                let to_group = groups.get(to_group_index).unwrap();
+                let to_row = to_group.rows.get(to_row_index).unwrap();
+                let params = MoveGroupRowParams {
                     view_id: self.inner.grid_id.clone(),
                     from_row_id: from_row.id.clone(),
-                    to_row_id: to_row.id.clone(),
+                    to_group_id: to_group.group_id.clone(),
+                    to_row_id: Some(to_row.id.clone()),
                 };
 
-                self.editor.move_row(params).await.unwrap();
+                self.editor.move_group_row(params).await.unwrap();
             }
             GroupScript::AssertRow {
                 group_index,

+ 105 - 12
frontend/rust-lib/flowy-grid/tests/grid/group_test/test.rs

@@ -2,7 +2,7 @@ use crate::grid::group_test::script::GridGroupTest;
 use crate::grid::group_test::script::GroupScript::*;
 
 #[tokio::test]
-async fn board_init_test() {
+async fn group_init_test() {
     let mut test = GridGroupTest::new().await;
     let scripts = vec![
         AssertGroupCount(3),
@@ -23,7 +23,7 @@ async fn board_init_test() {
 }
 
 #[tokio::test]
-async fn board_move_row_test() {
+async fn group_move_row_test() {
     let mut test = GridGroupTest::new().await;
     let group = test.group_at_index(0).await;
     let scripts = vec![
@@ -48,7 +48,7 @@ async fn board_move_row_test() {
 }
 
 #[tokio::test]
-async fn board_move_row_to_other_group_test() {
+async fn group_move_row_to_other_group_test() {
     let mut test = GridGroupTest::new().await;
     let group = test.group_at_index(0).await;
     let scripts = vec![
@@ -76,7 +76,7 @@ async fn board_move_row_to_other_group_test() {
 }
 
 #[tokio::test]
-async fn board_move_row_to_other_group_and_reorder_test() {
+async fn group_move_two_row_to_other_group_test() {
     let mut test = GridGroupTest::new().await;
     let group = test.group_at_index(0).await;
     let scripts = vec![
@@ -86,23 +86,116 @@ async fn board_move_row_to_other_group_and_reorder_test() {
             to_group_index: 1,
             to_row_index: 1,
         },
+        AssertGroupRowCount {
+            group_index: 0,
+            row_count: 1,
+        },
+        AssertGroupRowCount {
+            group_index: 1,
+            row_count: 3,
+        },
+        AssertRow {
+            group_index: 1,
+            row_index: 1,
+            row: group.rows.get(0).unwrap().clone(),
+        },
+    ];
+    test.run_scripts(scripts).await;
+
+    let group = test.group_at_index(0).await;
+    let scripts = vec![
+        MoveRow {
+            from_group_index: 0,
+            from_row_index: 0,
+            to_group_index: 1,
+            to_row_index: 1,
+        },
+        AssertGroupRowCount {
+            group_index: 0,
+            row_count: 0,
+        },
+        AssertGroupRowCount {
+            group_index: 1,
+            row_count: 4,
+        },
+        AssertRow {
+            group_index: 1,
+            row_index: 1,
+            row: group.rows.get(0).unwrap().clone(),
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn group_move_row_to_other_group_and_reorder_from_up_to_down_test() {
+    let mut test = GridGroupTest::new().await;
+    let group_0 = test.group_at_index(0).await;
+    let group_1 = test.group_at_index(1).await;
+    let scripts = vec![
+        MoveRow {
+            from_group_index: 0,
+            from_row_index: 0,
+            to_group_index: 1,
+            to_row_index: 1,
+        },
+        AssertRow {
+            group_index: 1,
+            row_index: 1,
+            row: group_0.rows.get(0).unwrap().clone(),
+        },
+    ];
+    test.run_scripts(scripts).await;
+
+    let scripts = vec![
         MoveRow {
             from_group_index: 1,
-            from_row_index: 1,
+            from_row_index: 0,
             to_group_index: 1,
             to_row_index: 2,
         },
         AssertRow {
             group_index: 1,
             row_index: 2,
-            row: group.rows.get(0).unwrap().clone(),
+            row: group_1.rows.get(0).unwrap().clone(),
         },
     ];
     test.run_scripts(scripts).await;
 }
 
 #[tokio::test]
-async fn board_create_row_test() {
+async fn group_move_row_to_other_group_and_reorder_from_bottom_to_up_test() {
+    let mut test = GridGroupTest::new().await;
+    let scripts = vec![MoveRow {
+        from_group_index: 0,
+        from_row_index: 0,
+        to_group_index: 1,
+        to_row_index: 1,
+    }];
+    test.run_scripts(scripts).await;
+
+    let group = test.group_at_index(1).await;
+    let scripts = vec![
+        AssertGroupRowCount {
+            group_index: 1,
+            row_count: 3,
+        },
+        MoveRow {
+            from_group_index: 1,
+            from_row_index: 2,
+            to_group_index: 1,
+            to_row_index: 0,
+        },
+        AssertRow {
+            group_index: 1,
+            row_index: 0,
+            row: group.rows.get(2).unwrap().clone(),
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+#[tokio::test]
+async fn group_create_row_test() {
     let mut test = GridGroupTest::new().await;
     let scripts = vec![
         CreateRow { group_index: 0 },
@@ -121,7 +214,7 @@ async fn board_create_row_test() {
 }
 
 #[tokio::test]
-async fn board_delete_row_test() {
+async fn group_delete_row_test() {
     let mut test = GridGroupTest::new().await;
     let scripts = vec![
         DeleteRow {
@@ -137,7 +230,7 @@ async fn board_delete_row_test() {
 }
 
 #[tokio::test]
-async fn board_delete_all_row_test() {
+async fn group_delete_all_row_test() {
     let mut test = GridGroupTest::new().await;
     let scripts = vec![
         DeleteRow {
@@ -157,7 +250,7 @@ async fn board_delete_all_row_test() {
 }
 
 #[tokio::test]
-async fn board_update_row_test() {
+async fn group_update_row_test() {
     let mut test = GridGroupTest::new().await;
     let scripts = vec![
         // Update the row at 0 in group0 by setting the row's group field data
@@ -179,7 +272,7 @@ async fn board_update_row_test() {
 }
 
 #[tokio::test]
-async fn board_reorder_group_test() {
+async fn group_reorder_group_test() {
     let mut test = GridGroupTest::new().await;
     let scripts = vec![
         // Update the row at 0 in group0 by setting the row's group field data
@@ -201,7 +294,7 @@ async fn board_reorder_group_test() {
 }
 
 #[tokio::test]
-async fn board_move_group_test() {
+async fn group_move_group_test() {
     let mut test = GridGroupTest::new().await;
     let group_0 = test.group_at_index(0).await;
     let group_1 = test.group_at_index(1).await;

+ 3 - 2
shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs

@@ -76,7 +76,8 @@ where
         Some(objects_by_field_id)
     }
 
-    pub fn insert_object(&mut self, field_id: &str, field_type: &FieldTypeRevision, object: T) {
+    /// add object to the end of the list
+    pub fn add_object(&mut self, field_id: &str, field_type: &FieldTypeRevision, object: T) {
         let object_rev_map = self
             .inner
             .entry(field_id.to_string())
@@ -88,7 +89,7 @@ where
             .push(Arc::new(object))
     }
 
-    pub fn remove_all(&mut self) {
+    pub fn clear(&mut self) {
         self.inner.clear()
     }
 }

+ 24 - 0
shared-lib/flowy-grid-data-model/src/revision/group_rev.rs

@@ -155,3 +155,27 @@ impl std::default::Default for DateCondition {
         DateCondition::Relative
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::revision::{GroupConfigurationRevision, SelectOptionGroupConfigurationRevision};
+
+    #[test]
+    fn group_configuration_serde_test() {
+        let content = SelectOptionGroupConfigurationRevision { hide_empty: false };
+        let rev = GroupConfigurationRevision::new("1".to_owned(), 2, content).unwrap();
+        let json = serde_json::to_string(&rev).unwrap();
+
+        let rev: GroupConfigurationRevision = serde_json::from_str(&json).unwrap();
+        let _content: SelectOptionGroupConfigurationRevision = serde_json::from_str(&rev.content).unwrap();
+    }
+
+    #[test]
+    fn group_configuration_serde_test2() {
+        let content = SelectOptionGroupConfigurationRevision { hide_empty: false };
+        let content_json = serde_json::to_string(&content).unwrap();
+        let rev = GroupConfigurationRevision::new("1".to_owned(), 2, content).unwrap();
+
+        assert_eq!(rev.content, content_json);
+    }
+}

+ 4 - 4
shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs

@@ -60,7 +60,9 @@ impl GridViewRevisionPad {
         group_rev: GroupConfigurationRevision,
     ) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
         self.modify(|view| {
-            view.groups.insert_object(field_id, field_type, group_rev);
+            // Only save one group
+            view.groups.clear();
+            view.groups.add_object(field_id, field_type, group_rev);
             Ok(Some(()))
         })
     }
@@ -127,7 +129,7 @@ impl GridViewRevisionPad {
         filter_rev: FilterConfigurationRevision,
     ) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
         self.modify(|view| {
-            view.filters.insert_object(field_id, field_type, filter_rev);
+            view.filters.add_object(field_id, field_type, filter_rev);
             Ok(Some(()))
         })
     }
@@ -166,8 +168,6 @@ impl GridViewRevisionPad {
                     None => Ok(None),
                     Some(delta) => {
                         self.delta = self.delta.compose(&delta)?;
-                        tracing::info!("GridView: {:?}", delta);
-
                         let md5 = md5(&self.delta.json_bytes());
                         Ok(Some(GridViewRevisionChangeset { delta, md5 }))
                     }