Просмотр исходного кода

Merge pull request #882 from AppFlowy-IO/feat/board_configuration

Feat/board configuration
Nathan.fooo 2 лет назад
Родитель
Сommit
5e6b9496fc
61 измененных файлов с 2212 добавлено и 1459 удалено
  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. 0 1
      frontend/app_flowy/lib/plugins/grid/application/grid_service.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. 1 0
      frontend/rust-lib/flowy-error/src/errors.rs
  10. 2 1
      frontend/rust-lib/flowy-grid/src/dart_notification.rs
  11. 21 1
      frontend/rust-lib/flowy-grid/src/entities/field_entities.rs
  12. 17 5
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs
  13. 43 0
      frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs
  14. 0 28
      frontend/rust-lib/flowy-grid/src/entities/group_entities/board_card.rs
  15. 27 0
      frontend/rust-lib/flowy-grid/src/entities/group_entities/configuration.rs
  16. 40 10
      frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs
  17. 64 1
      frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs
  18. 0 2
      frontend/rust-lib/flowy-grid/src/entities/group_entities/mod.rs
  19. 0 2
      frontend/rust-lib/flowy-grid/src/entities/mod.rs
  20. 18 24
      frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs
  21. 0 65
      frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs
  22. 22 12
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  23. 9 1
      frontend/rust-lib/flowy-grid/src/event_map.rs
  24. 0 1
      frontend/rust-lib/flowy-grid/src/manager.rs
  25. 1 2
      frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs
  26. 132 54
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  27. 197 51
      frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs
  28. 29 12
      frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs
  29. 17 0
      frontend/rust-lib/flowy-grid/src/services/group/action.rs
  30. 221 0
      frontend/rust-lib/flowy-grid/src/services/group/configuration.rs
  31. 214 0
      frontend/rust-lib/flowy-grid/src/services/group/controller.rs
  32. 17 15
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs
  33. 5 0
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/mod.rs
  34. 7 0
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/mod.rs
  35. 98 0
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs
  36. 100 0
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs
  37. 105 0
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/util.rs
  38. 73 0
      frontend/rust-lib/flowy-grid/src/services/group/entities.rs
  39. 0 296
      frontend/rust-lib/flowy-grid/src/services/group/group_generator/group_controller.rs
  40. 0 7
      frontend/rust-lib/flowy-grid/src/services/group/group_generator/mod.rs
  41. 0 287
      frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs
  42. 141 91
      frontend/rust-lib/flowy-grid/src/services/group/group_service.rs
  43. 8 2
      frontend/rust-lib/flowy-grid/src/services/group/mod.rs
  44. 2 50
      frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs
  45. 1 2
      frontend/rust-lib/flowy-grid/tests/grid/field_test/script.rs
  46. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs
  47. 6 20
      frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs
  48. 0 3
      frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs
  49. 40 8
      frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs
  50. 140 25
      frontend/rust-lib/flowy-grid/tests/grid/group_test/test.rs
  51. 1 1
      frontend/rust-lib/flowy-sdk/src/lib.rs
  52. 3 0
      shared-lib/flowy-error-code/src/code.rs
  53. 9 0
      shared-lib/flowy-grid-data-model/src/revision/filter_rev.rs
  54. 14 137
      shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs
  55. 57 8
      shared-lib/flowy-grid-data-model/src/revision/grid_view.rs
  56. 181 0
      shared-lib/flowy-grid-data-model/src/revision/group_rev.rs
  57. 4 0
      shared-lib/flowy-grid-data-model/src/revision/mod.rs
  58. 1 57
      shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs
  59. 37 93
      shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs
  60. 0 67
      shared-lib/flowy-sync/src/entities/grid.rs
  61. 0 1
      shared-lib/flowy-sync/src/entities/mod.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);
           }
         },

+ 0 - 1
frontend/app_flowy/lib/plugins/grid/application/grid_service.dart

@@ -3,7 +3,6 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/board_card.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';

+ 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();
   }
 

+ 1 - 0
frontend/rust-lib/flowy-error/src/errors.rs

@@ -66,6 +66,7 @@ impl FlowyError {
     static_flowy_error!(user_not_exist, ErrorCode::UserNotExist);
     static_flowy_error!(text_too_long, ErrorCode::TextTooLong);
     static_flowy_error!(invalid_data, ErrorCode::InvalidData);
+    static_flowy_error!(out_of_bounds, ErrorCode::OutOfBounds);
 }
 
 impl std::convert::From<ErrorCode> for FlowyError {

+ 2 - 1
frontend/rust-lib/flowy-grid/src/dart_notification.rs

@@ -11,7 +11,8 @@ pub enum GridNotification {
     DidUpdateRow = 30,
     DidUpdateCell = 40,
     DidUpdateField = 50,
-    DidUpdateGroup = 60,
+    DidUpdateGroupView = 60,
+    DidUpdateGroup = 61,
 }
 
 impl std::default::Default for GridNotification {

+ 21 - 1
frontend/rust-lib/flowy-grid/src/entities/field_entities.rs

@@ -2,7 +2,6 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::parser::NotEmptyStr;
 use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision};
-use flowy_sync::entities::grid::FieldChangesetParams;
 use serde_repr::*;
 use std::sync::Arc;
 
@@ -491,6 +490,27 @@ impl TryInto<FieldChangesetParams> for FieldChangesetPayloadPB {
     }
 }
 
+#[derive(Debug, Clone, Default)]
+pub struct FieldChangesetParams {
+    pub field_id: String,
+
+    pub grid_id: String,
+
+    pub name: Option<String>,
+
+    pub desc: Option<String>,
+
+    pub field_type: Option<FieldTypeRevision>,
+
+    pub frozen: Option<bool>,
+
+    pub visibility: Option<bool>,
+
+    pub width: Option<i32>,
+
+    pub type_option_data: Option<Vec<u8>>,
+}
+
 #[derive(
     Debug,
     Clone,

+ 17 - 5
frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs

@@ -5,8 +5,7 @@ use crate::entities::{
 use flowy_derive::ProtoBuf;
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::parser::NotEmptyStr;
-use flowy_grid_data_model::revision::{FieldRevision, FilterConfigurationRevision};
-use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams};
+use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision, FilterConfigurationRevision};
 use std::convert::TryInto;
 use std::sync::Arc;
 
@@ -72,6 +71,12 @@ impl TryInto<DeleteFilterParams> for DeleteFilterPayloadPB {
     }
 }
 
+pub struct DeleteFilterParams {
+    pub field_id: String,
+    pub filter_id: String,
+    pub field_type_rev: FieldTypeRevision,
+}
+
 #[derive(ProtoBuf, Debug, Default, Clone)]
 pub struct CreateGridFilterPayloadPB {
     #[pb(index = 1)]
@@ -99,10 +104,10 @@ impl CreateGridFilterPayloadPB {
     }
 }
 
-impl TryInto<CreateGridFilterParams> for CreateGridFilterPayloadPB {
+impl TryInto<CreateFilterParams> for CreateGridFilterPayloadPB {
     type Error = ErrorCode;
 
-    fn try_into(self) -> Result<CreateGridFilterParams, Self::Error> {
+    fn try_into(self) -> Result<CreateFilterParams, Self::Error> {
         let field_id = NotEmptyStr::parse(self.field_id)
             .map_err(|_| ErrorCode::FieldIdIsEmpty)?
             .0;
@@ -125,7 +130,7 @@ impl TryInto<CreateGridFilterParams> for CreateGridFilterPayloadPB {
             }
         }
 
-        Ok(CreateGridFilterParams {
+        Ok(CreateFilterParams {
             field_id,
             field_type_rev: self.field_type.into(),
             condition,
@@ -133,3 +138,10 @@ impl TryInto<CreateGridFilterParams> for CreateGridFilterPayloadPB {
         })
     }
 }
+
+pub struct CreateFilterParams {
+    pub field_id: String,
+    pub field_type_rev: FieldTypeRevision,
+    pub condition: u8,
+    pub content: Option<String>,
+}

+ 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,
+        })
+    }
+}

+ 0 - 28
frontend/rust-lib/flowy-grid/src/entities/group_entities/board_card.rs

@@ -1,28 +0,0 @@
-use crate::entities::{CreateRowParams, GridLayout};
-use flowy_derive::ProtoBuf;
-use flowy_error::ErrorCode;
-use flowy_grid_data_model::parser::NotEmptyStr;
-
-#[derive(ProtoBuf, Debug, Default, Clone)]
-pub struct CreateBoardCardPayloadPB {
-    #[pb(index = 1)]
-    pub grid_id: String,
-
-    #[pb(index = 2)]
-    pub group_id: String,
-}
-
-impl TryInto<CreateRowParams> for CreateBoardCardPayloadPB {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<CreateRowParams, Self::Error> {
-        let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
-        let group_id = NotEmptyStr::parse(self.group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
-        Ok(CreateRowParams {
-            grid_id: grid_id.0,
-            start_row_id: None,
-            group_id: Some(group_id.0),
-            layout: GridLayout::Board,
-        })
-    }
-}

+ 27 - 0
frontend/rust-lib/flowy-grid/src/entities/group_entities/configuration.rs

@@ -1,4 +1,5 @@
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_grid_data_model::revision::{GroupRecordRevision, SelectOptionGroupConfigurationRevision};
 
 #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
 pub struct UrlGroupConfigurationPB {
@@ -18,6 +19,32 @@ pub struct SelectOptionGroupConfigurationPB {
     hide_empty: bool,
 }
 
+impl std::convert::From<SelectOptionGroupConfigurationRevision> for SelectOptionGroupConfigurationPB {
+    fn from(rev: SelectOptionGroupConfigurationRevision) -> Self {
+        Self {
+            hide_empty: rev.hide_empty,
+        }
+    }
+}
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct GroupRecordPB {
+    #[pb(index = 1)]
+    group_id: String,
+
+    #[pb(index = 2)]
+    visible: bool,
+}
+
+impl std::convert::From<GroupRecordRevision> for GroupRecordPB {
+    fn from(rev: GroupRecordRevision) -> Self {
+        Self {
+            group_id: rev.group_id,
+            visible: rev.visible,
+        }
+    }
+}
+
 #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
 pub struct NumberGroupConfigurationPB {
     #[pb(index = 1)]

+ 40 - 10
frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs

@@ -1,12 +1,35 @@
-use crate::entities::{FieldType, RowPB};
+use crate::entities::{CreateRowParams, FieldType, GridLayout, RowPB};
 use flowy_derive::ProtoBuf;
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::parser::NotEmptyStr;
-use flowy_grid_data_model::revision::GroupConfigurationRevision;
-use flowy_sync::entities::grid::{CreateGridGroupParams, DeleteGroupParams};
+use flowy_grid_data_model::revision::{FieldTypeRevision, GroupConfigurationRevision};
 use std::convert::TryInto;
 use std::sync::Arc;
 
+#[derive(ProtoBuf, Debug, Default, Clone)]
+pub struct CreateBoardCardPayloadPB {
+    #[pb(index = 1)]
+    pub grid_id: String,
+
+    #[pb(index = 2)]
+    pub group_id: String,
+}
+
+impl TryInto<CreateRowParams> for CreateBoardCardPayloadPB {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<CreateRowParams, Self::Error> {
+        let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+        let group_id = NotEmptyStr::parse(self.group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
+        Ok(CreateRowParams {
+            grid_id: grid_id.0,
+            start_row_id: None,
+            group_id: Some(group_id.0),
+            layout: GridLayout::Board,
+        })
+    }
+}
+
 #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
 pub struct GridGroupConfigurationPB {
     #[pb(index = 1)]
@@ -86,27 +109,28 @@ pub struct CreateGridGroupPayloadPB {
 
     #[pb(index = 2)]
     pub field_type: FieldType,
-
-    #[pb(index = 3, one_of)]
-    pub content: Option<Vec<u8>>,
 }
 
-impl TryInto<CreateGridGroupParams> for CreateGridGroupPayloadPB {
+impl TryInto<CreatGroupParams> for CreateGridGroupPayloadPB {
     type Error = ErrorCode;
 
-    fn try_into(self) -> Result<CreateGridGroupParams, Self::Error> {
+    fn try_into(self) -> Result<CreatGroupParams, Self::Error> {
         let field_id = NotEmptyStr::parse(self.field_id)
             .map_err(|_| ErrorCode::FieldIdIsEmpty)?
             .0;
 
-        Ok(CreateGridGroupParams {
+        Ok(CreatGroupParams {
             field_id,
             field_type_rev: self.field_type.into(),
-            content: self.content,
         })
     }
 }
 
+pub struct CreatGroupParams {
+    pub field_id: String,
+    pub field_type_rev: FieldTypeRevision,
+}
+
 #[derive(ProtoBuf, Debug, Default, Clone)]
 pub struct DeleteGroupPayloadPB {
     #[pb(index = 1)]
@@ -137,3 +161,9 @@ impl TryInto<DeleteGroupParams> for DeleteGroupPayloadPB {
         })
     }
 }
+
+pub struct DeleteGroupParams {
+    pub field_id: String,
+    pub group_id: String,
+    pub field_type_rev: FieldTypeRevision,
+}

+ 64 - 1
frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs

@@ -1,5 +1,7 @@
-use crate::entities::{InsertedRowPB, RowPB};
+use crate::entities::{GroupPB, InsertedRowPB, RowPB};
 use flowy_derive::ProtoBuf;
+use flowy_error::ErrorCode;
+use flowy_grid_data_model::parser::NotEmptyStr;
 use std::fmt::Formatter;
 
 #[derive(Debug, Default, ProtoBuf)]
@@ -62,3 +64,64 @@ impl GroupRowsChangesetPB {
         }
     }
 }
+#[derive(Debug, Default, ProtoBuf)]
+pub struct MoveGroupPayloadPB {
+    #[pb(index = 1)]
+    pub view_id: String,
+
+    #[pb(index = 2)]
+    pub from_group_id: String,
+
+    #[pb(index = 3)]
+    pub to_group_id: String,
+}
+
+pub struct MoveGroupParams {
+    pub view_id: String,
+    pub from_group_id: String,
+    pub to_group_id: String,
+}
+
+impl TryInto<MoveGroupParams> for MoveGroupPayloadPB {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<MoveGroupParams, Self::Error> {
+        let view_id = NotEmptyStr::parse(self.view_id)
+            .map_err(|_| ErrorCode::GridViewIdIsEmpty)?
+            .0;
+        let from_group_id = NotEmptyStr::parse(self.from_group_id)
+            .map_err(|_| ErrorCode::GroupIdIsEmpty)?
+            .0;
+        let to_group_id = NotEmptyStr::parse(self.to_group_id)
+            .map_err(|_| ErrorCode::GroupIdIsEmpty)?
+            .0;
+        Ok(MoveGroupParams {
+            view_id,
+            from_group_id,
+            to_group_id,
+        })
+    }
+}
+
+#[derive(Debug, Default, ProtoBuf)]
+pub struct GroupViewChangesetPB {
+    #[pb(index = 1)]
+    pub view_id: String,
+
+    #[pb(index = 2)]
+    pub inserted_groups: Vec<InsertedGroupPB>,
+
+    #[pb(index = 3)]
+    pub deleted_groups: Vec<String>,
+}
+
+impl GroupViewChangesetPB {}
+
+#[derive(Debug, Default, ProtoBuf)]
+pub struct InsertedGroupPB {
+    #[pb(index = 1)]
+    pub group: GroupPB,
+
+    #[pb(index = 2)]
+    pub index: i32,
+}

+ 0 - 2
frontend/rust-lib/flowy-grid/src/entities/group_entities/mod.rs

@@ -1,9 +1,7 @@
-mod board_card;
 mod configuration;
 mod group;
 mod group_changeset;
 
-pub use board_card::*;
 pub use configuration::*;
 pub use group::*;
 pub use group_changeset::*;

+ 0 - 2
frontend/rust-lib/flowy-grid/src/entities/mod.rs

@@ -6,7 +6,6 @@ mod grid_entities;
 mod group_entities;
 mod row_entities;
 mod setting_entities;
-mod sort_entities;
 
 pub use block_entities::*;
 pub use cell_entities::*;
@@ -16,4 +15,3 @@ pub use grid_entities::*;
 pub use group_entities::*;
 pub use row_entities::*;
 pub use setting_entities::*;
-pub use sort_entities::*;

+ 18 - 24
frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs

@@ -1,12 +1,12 @@
 use crate::entities::{
-    CreateGridFilterPayloadPB, CreateGridGroupPayloadPB, CreateGridSortPayloadPB, DeleteFilterPayloadPB,
-    DeleteGroupPayloadPB, RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB, RepeatedGridSortPB,
+    CreatGroupParams, CreateFilterParams, CreateGridFilterPayloadPB, CreateGridGroupPayloadPB, DeleteFilterParams,
+    DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, RepeatedGridConfigurationFilterPB,
+    RepeatedGridGroupConfigurationPB,
 };
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::parser::NotEmptyStr;
 use flowy_grid_data_model::revision::LayoutRevision;
-use flowy_sync::entities::grid::GridSettingChangesetParams;
 use std::collections::HashMap;
 use std::convert::TryInto;
 use strum::IntoEnumIterator;
@@ -26,9 +26,6 @@ pub struct GridSettingPB {
 
     #[pb(index = 4)]
     pub group_configuration_by_field_id: HashMap<String, RepeatedGridGroupConfigurationPB>,
-
-    #[pb(index = 5)]
-    pub sorts_by_field_id: HashMap<String, RepeatedGridSortPB>,
 }
 
 #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
@@ -98,12 +95,6 @@ pub struct GridSettingChangesetPayloadPB {
 
     #[pb(index = 6, one_of)]
     pub delete_group: Option<DeleteGroupPayloadPB>,
-
-    #[pb(index = 7, one_of)]
-    pub insert_sort: Option<CreateGridSortPayloadPB>,
-
-    #[pb(index = 8, one_of)]
-    pub delete_sort: Option<String>,
 }
 
 impl TryInto<GridSettingChangesetParams> for GridSettingChangesetPayloadPB {
@@ -134,16 +125,6 @@ impl TryInto<GridSettingChangesetParams> for GridSettingChangesetPayloadPB {
             None => None,
         };
 
-        let insert_sort = match self.insert_sort {
-            None => None,
-            Some(payload) => Some(payload.try_into()?),
-        };
-
-        let delete_sort = match self.delete_sort {
-            None => None,
-            Some(filter_id) => Some(NotEmptyStr::parse(filter_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?.0),
-        };
-
         Ok(GridSettingChangesetParams {
             grid_id: view_id,
             layout_type: self.layout_type.into(),
@@ -151,8 +132,21 @@ impl TryInto<GridSettingChangesetParams> for GridSettingChangesetPayloadPB {
             delete_filter,
             insert_group,
             delete_group,
-            insert_sort,
-            delete_sort,
         })
     }
 }
+
+pub struct GridSettingChangesetParams {
+    pub grid_id: String,
+    pub layout_type: LayoutRevision,
+    pub insert_filter: Option<CreateFilterParams>,
+    pub delete_filter: Option<DeleteFilterParams>,
+    pub insert_group: Option<CreatGroupParams>,
+    pub delete_group: Option<DeleteGroupParams>,
+}
+
+impl GridSettingChangesetParams {
+    pub fn is_filter_changed(&self) -> bool {
+        self.insert_filter.is_some() || self.delete_filter.is_some()
+    }
+}

+ 0 - 65
frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs

@@ -1,65 +0,0 @@
-use flowy_derive::ProtoBuf;
-use flowy_error::ErrorCode;
-use flowy_grid_data_model::parser::NotEmptyStr;
-use flowy_grid_data_model::revision::SortConfigurationRevision;
-use flowy_sync::entities::grid::CreateGridSortParams;
-use std::convert::TryInto;
-use std::sync::Arc;
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridSort {
-    #[pb(index = 1)]
-    pub id: String,
-
-    #[pb(index = 2, one_of)]
-    pub field_id: Option<String>,
-}
-
-impl std::convert::From<&SortConfigurationRevision> for GridSort {
-    fn from(rev: &SortConfigurationRevision) -> Self {
-        GridSort {
-            id: rev.id.clone(),
-
-            field_id: rev.field_id.clone(),
-        }
-    }
-}
-
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct RepeatedGridSortPB {
-    #[pb(index = 1)]
-    pub items: Vec<GridSort>,
-}
-
-impl std::convert::From<Vec<Arc<SortConfigurationRevision>>> for RepeatedGridSortPB {
-    fn from(revs: Vec<Arc<SortConfigurationRevision>>) -> Self {
-        RepeatedGridSortPB {
-            items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(),
-        }
-    }
-}
-
-impl std::convert::From<Vec<GridSort>> for RepeatedGridSortPB {
-    fn from(items: Vec<GridSort>) -> Self {
-        Self { items }
-    }
-}
-
-#[derive(ProtoBuf, Debug, Default, Clone)]
-pub struct CreateGridSortPayloadPB {
-    #[pb(index = 1, one_of)]
-    pub field_id: Option<String>,
-}
-
-impl TryInto<CreateGridSortParams> for CreateGridSortPayloadPB {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<CreateGridSortParams, Self::Error> {
-        let field_id = match self.field_id {
-            None => None,
-            Some(field_id) => Some(NotEmptyStr::parse(field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?.0),
-        };
-
-        Ok(CreateGridSortParams { field_id })
-    }
-}

+ 22 - 12
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -10,7 +10,6 @@ use crate::services::field::{
 use crate::services::row::make_row_from_row_rev;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_grid_data_model::revision::FieldRevision;
-use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams};
 use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
 use std::sync::Arc;
 
@@ -36,17 +35,6 @@ pub(crate) async fn get_grid_setting_handler(
     data_result(grid_setting)
 }
 
-#[tracing::instrument(level = "trace", skip(data, manager), err)]
-pub(crate) async fn update_grid_setting_handler(
-    data: Data<GridSettingChangesetPayloadPB>,
-    manager: AppData<Arc<GridManager>>,
-) -> Result<(), FlowyError> {
-    let params: GridSettingChangesetParams = data.into_inner().try_into()?;
-    let editor = manager.open_grid(&params.grid_id).await?;
-    let _ = editor.update_grid_setting(params).await?;
-    Ok(())
-}
-
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn get_grid_blocks_handler(
     data: Data<QueryBlocksPayloadPB>,
@@ -437,3 +425,25 @@ pub(crate) async fn create_board_card_handler(
     let row = editor.create_row(params).await?;
     data_result(row)
 }
+
+#[tracing::instrument(level = "debug", skip(data, manager), err)]
+pub(crate) async fn move_group_handler(
+    data: Data<MoveGroupPayloadPB>,
+    manager: AppData<Arc<GridManager>>,
+) -> FlowyResult<()> {
+    let params: MoveGroupParams = data.into_inner().try_into()?;
+    let editor = manager.get_grid_editor(params.view_id.as_ref())?;
+    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(())
+}

+ 9 - 1
frontend/rust-lib/flowy-grid/src/event_map.rs

@@ -11,7 +11,7 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
         .event(GridEvent::GetGrid, get_grid_handler)
         .event(GridEvent::GetGridBlocks, get_grid_blocks_handler)
         .event(GridEvent::GetGridSetting, get_grid_setting_handler)
-        .event(GridEvent::UpdateGridSetting, update_grid_setting_handler)
+        // .event(GridEvent::UpdateGridSetting, update_grid_setting_handler)
         // Field
         .event(GridEvent::GetFields, get_fields_handler)
         .event(GridEvent::UpdateField, update_field_handler)
@@ -41,6 +41,8 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
         .event(GridEvent::UpdateDateCell, update_date_cell_handler)
         // 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
@@ -217,4 +219,10 @@ pub enum GridEvent {
 
     #[event(input = "CreateBoardCardPayloadPB", output = "RowPB")]
     CreateBoardCard = 110,
+
+    #[event(input = "MoveGroupPayloadPB")]
+    MoveGroup = 111,
+
+    #[event(input = "MoveGroupRowPayloadPB")]
+    MoveGroupRow = 112,
 }

+ 0 - 1
frontend/rust-lib/flowy-grid/src/manager.rs

@@ -131,7 +131,6 @@ impl GridManager {
     async fn get_or_create_grid_editor(&self, grid_id: &str) -> FlowyResult<Arc<GridRevisionEditor>> {
         match self.grid_editors.get(grid_id) {
             None => {
-                tracing::trace!("Create grid editor with id: {}", grid_id);
                 let db_pool = self.grid_user.db_pool()?;
                 let editor = self.make_grid_rev_editor(grid_id, db_pool).await?;
 

+ 1 - 2
frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs

@@ -4,7 +4,7 @@
 #![allow(unused_imports)]
 #![allow(unused_results)]
 use crate::dart_notification::{send_dart_notification, GridNotification};
-use crate::entities::{FieldType, GridBlockChangesetPB};
+use crate::entities::{FieldType, GridBlockChangesetPB, GridSettingChangesetParams};
 use crate::services::block_manager::GridBlockManager;
 use crate::services::cell::{AnyCellData, CellFilterOperation};
 use crate::services::field::{
@@ -20,7 +20,6 @@ use crate::services::tasks::{FilterTaskContext, Task, TaskContent};
 use flowy_error::FlowyResult;
 use flowy_grid_data_model::revision::{CellRevision, FieldId, FieldRevision, RowRevision};
 use flowy_sync::client_grid::GridRevisionPad;
-use flowy_sync::entities::grid::GridSettingChangesetParams;
 use rayon::prelude::*;
 use std::collections::HashMap;
 use std::sync::Arc;

+ 132 - 54
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -15,7 +15,6 @@ use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_grid_data_model::revision::*;
 use flowy_revision::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
 use flowy_sync::client_grid::{GridRevisionChangeset, GridRevisionPad, JsonDeserializer};
-use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams};
 use flowy_sync::entities::revision::Revision;
 use flowy_sync::errors::CollaborateResult;
 use flowy_sync::util::make_text_delta_from_revisions;
@@ -96,23 +95,19 @@ impl GridRevisionEditor {
         } = params;
         let field_id = field.id.clone();
         if self.contain_field(&field_id).await {
-            let _ = self
-                .modify(|grid| {
-                    let deserializer = TypeOptionJsonDeserializer(field.field_type.clone());
-                    let changeset = FieldChangesetParams {
-                        field_id: field.id,
-                        grid_id,
-                        name: Some(field.name),
-                        desc: Some(field.desc),
-                        field_type: Some(field.field_type.into()),
-                        frozen: Some(field.frozen),
-                        visibility: Some(field.visibility),
-                        width: Some(field.width),
-                        type_option_data: Some(type_option_data),
-                    };
-                    Ok(grid.update_field_rev(changeset, deserializer)?)
-                })
-                .await?;
+            let changeset = FieldChangesetParams {
+                field_id: field.id,
+                grid_id,
+                name: Some(field.name),
+                desc: Some(field.desc),
+                field_type: Some(field.field_type.clone().into()),
+                frozen: Some(field.frozen),
+                visibility: Some(field.visibility),
+                width: Some(field.width),
+                type_option_data: Some(type_option_data),
+            };
+
+            let _ = self.update_field_rev(changeset, field.field_type).await?;
             let _ = self.notify_did_update_grid_field(&field_id).await?;
         } else {
             let _ = self
@@ -140,19 +135,13 @@ impl GridRevisionEditor {
             return Ok(());
         }
         let field_rev = result.unwrap();
-        let _ = self
-            .modify(|grid| {
-                let field_type = field_rev.ty.into();
-                let deserializer = TypeOptionJsonDeserializer(field_type);
-                let changeset = FieldChangesetParams {
-                    field_id: field_id.to_owned(),
-                    grid_id: grid_id.to_owned(),
-                    type_option_data: Some(type_option_data),
-                    ..Default::default()
-                };
-                Ok(grid.update_field_rev(changeset, deserializer)?)
-            })
-            .await?;
+        let changeset = FieldChangesetParams {
+            field_id: field_id.to_owned(),
+            grid_id: grid_id.to_owned(),
+            type_option_data: Some(type_option_data),
+            ..Default::default()
+        };
+        let _ = self.update_field_rev(changeset, field_rev.ty.into()).await?;
         let _ = self.notify_did_update_grid_field(field_id).await?;
         Ok(())
     }
@@ -179,17 +168,21 @@ impl GridRevisionEditor {
 
     pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> {
         let field_id = params.field_id.clone();
-        let json_deserializer = match self.grid_pad.read().await.get_field_rev(params.field_id.as_str()) {
-            None => return Err(ErrorCode::FieldDoesNotExist.into()),
-            Some((_, field_rev)) => TypeOptionJsonDeserializer(field_rev.ty.into()),
-        };
-
-        let _ = self
-            .modify(|grid| Ok(grid.update_field_rev(params, json_deserializer)?))
-            .await?;
-
-        let _ = self.notify_did_update_grid_field(&field_id).await?;
-        Ok(())
+        let field_type: Option<FieldType> = self
+            .grid_pad
+            .read()
+            .await
+            .get_field_rev(params.field_id.as_str())
+            .map(|(_, field_rev)| field_rev.ty.into());
+
+        match field_type {
+            None => Err(ErrorCode::FieldDoesNotExist.into()),
+            Some(field_type) => {
+                let _ = self.update_field_rev(params, field_type).await?;
+                let _ = self.notify_did_update_grid_field(&field_id).await?;
+                Ok(())
+            }
+        }
     }
 
     pub async fn replace_field(&self, field_rev: Arc<FieldRevision>) -> FlowyResult<()> {
@@ -269,6 +262,62 @@ impl GridRevisionEditor {
         Ok(field_revs)
     }
 
+    async fn update_field_rev(&self, params: FieldChangesetParams, field_type: FieldType) -> FlowyResult<()> {
+        self.modify(|grid| {
+            let deserializer = TypeOptionJsonDeserializer(field_type);
+
+            let changeset = grid.modify_field(&params.field_id, |field| {
+                let mut is_changed = None;
+                if let Some(name) = params.name {
+                    field.name = name;
+                    is_changed = Some(())
+                }
+
+                if let Some(desc) = params.desc {
+                    field.desc = desc;
+                    is_changed = Some(())
+                }
+
+                if let Some(field_type) = params.field_type {
+                    field.ty = field_type;
+                    is_changed = Some(())
+                }
+
+                if let Some(frozen) = params.frozen {
+                    field.frozen = frozen;
+                    is_changed = Some(())
+                }
+
+                if let Some(visibility) = params.visibility {
+                    field.visibility = visibility;
+                    is_changed = Some(())
+                }
+
+                if let Some(width) = params.width {
+                    field.width = width;
+                    is_changed = Some(())
+                }
+
+                if let Some(type_option_data) = params.type_option_data {
+                    match deserializer.deserialize(type_option_data) {
+                        Ok(json_str) => {
+                            let field_type = field.ty;
+                            field.insert_type_option_str(&field_type, json_str);
+                            is_changed = Some(())
+                        }
+                        Err(err) => {
+                            tracing::error!("Deserialize data to type option json failed: {}", err);
+                        }
+                    }
+                }
+
+                Ok(is_changed)
+            })?;
+            Ok(changeset)
+        })
+        .await
+    }
+
     pub async fn create_block(&self, block_meta_rev: GridBlockMetaRevision) -> FlowyResult<()> {
         let _ = self
             .modify(|grid_pad| Ok(grid_pad.create_block_meta_rev(block_meta_rev)?))
@@ -294,6 +343,11 @@ impl GridRevisionEditor {
         Ok(row_pb)
     }
 
+    pub async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
+        let _ = self.view_manager.move_group(params).await?;
+        Ok(())
+    }
+
     pub async fn insert_rows(&self, row_revs: Vec<RowRevision>) -> FlowyResult<Vec<RowPB>> {
         let block_id = self.block_id().await?;
         let mut rows_by_block_id: HashMap<String, Vec<RowRevision>> = HashMap::new();
@@ -460,8 +514,13 @@ impl GridRevisionEditor {
         self.view_manager.get_filters().await
     }
 
-    pub async fn update_grid_setting(&self, params: GridSettingChangesetParams) -> FlowyResult<()> {
-        let _ = self.view_manager.update_setting(params).await?;
+    pub async fn update_filter(&self, params: CreateFilterParams) -> FlowyResult<()> {
+        let _ = self.view_manager.update_filter(params).await?;
+        Ok(())
+    }
+
+    pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> {
+        let _ = self.view_manager.delete_filter(params).await?;
         Ok(())
     }
 
@@ -501,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),
@@ -520,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: _,

+ 197 - 51
frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs

@@ -1,18 +1,23 @@
 use crate::dart_notification::{send_dart_notification, GridNotification};
 use crate::entities::{
-    CreateRowParams, GridFilterConfiguration, GridSettingPB, GroupPB, GroupRowsChangesetPB, InsertedRowPB, RowPB,
+    CreateFilterParams, CreateRowParams, DeleteFilterParams, GridFilterConfiguration, GridLayout, GridLayoutPB,
+    GridSettingPB, GroupPB, GroupRowsChangesetPB, GroupViewChangesetPB, InsertedGroupPB, InsertedRowPB,
+    MoveGroupParams, RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB, RowPB,
 };
 use crate::services::grid_editor_task::GridServiceTaskScheduler;
 use crate::services::grid_view_manager::{GridViewFieldDelegate, GridViewRowDelegate};
-use crate::services::group::{default_group_configuration, GroupConfigurationDelegate, GroupService};
-use crate::services::setting::make_grid_setting;
+use crate::services::group::{GroupConfigurationReader, GroupConfigurationWriter, GroupService};
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{FieldRevision, GroupConfigurationRevision, RowChangeset, RowRevision};
+use flowy_grid_data_model::revision::{
+    gen_grid_filter_id, FieldRevision, FieldTypeRevision, FilterConfigurationRevision, GroupConfigurationRevision,
+    RowChangeset, RowRevision,
+};
 use flowy_revision::{RevisionCloudService, RevisionManager, RevisionObjectBuilder};
 use flowy_sync::client_grid::{GridViewRevisionChangeset, GridViewRevisionPad};
-use flowy_sync::entities::grid::GridSettingChangesetParams;
 use flowy_sync::entities::revision::Revision;
 use lib_infra::future::{wrap_future, AFFuture, FutureResult};
+use std::collections::HashMap;
+
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::Arc;
 use tokio::sync::RwLock;
@@ -31,6 +36,7 @@ pub struct GridViewRevisionEditor {
 }
 
 impl GridViewRevisionEditor {
+    #[tracing::instrument(level = "trace", skip_all, err)]
     pub(crate) async fn new(
         user_id: &str,
         token: &str,
@@ -46,7 +52,14 @@ impl GridViewRevisionEditor {
         let view_revision_pad = rev_manager.load::<GridViewRevisionPadBuilder>(Some(cloud)).await?;
         let pad = Arc::new(RwLock::new(view_revision_pad));
         let rev_manager = Arc::new(rev_manager);
-        let group_service = GroupService::new(Box::new(pad.clone())).await;
+
+        let configuration_reader = GroupConfigurationReaderImpl(pad.clone());
+        let configuration_writer = GroupConfigurationWriterImpl {
+            user_id: user_id.to_owned(),
+            rev_manager: rev_manager.clone(),
+            view_pad: pad.clone(),
+        };
+        let group_service = GroupService::new(configuration_reader, configuration_writer).await;
         let user_id = user_id.to_owned();
         let did_load_group = AtomicBool::new(false);
         Ok(Self {
@@ -67,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)
@@ -87,7 +100,7 @@ impl GridViewRevisionEditor {
                     index: None,
                 };
                 let changeset = GroupRowsChangesetPB::insert(group_id.clone(), vec![inserted_row]);
-                self.notify_did_update_group(changeset).await;
+                self.notify_did_update_group_rows(changeset).await;
             }
         }
     }
@@ -102,7 +115,7 @@ impl GridViewRevisionEditor {
             .await
         {
             for changeset in changesets {
-                self.notify_did_update_group(changeset).await;
+                self.notify_did_update_group_rows(changeset).await;
             }
         }
     }
@@ -116,38 +129,40 @@ impl GridViewRevisionEditor {
             .await
         {
             for changeset in changesets {
-                self.notify_did_update_group(changeset).await;
+                self.notify_did_update_group_rows(changeset).await;
             }
         }
     }
 
-    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
             .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(changeset).await;
+                self.notify_did_update_group_rows(changeset).await;
             }
         }
     }
 
+    #[tracing::instrument(level = "trace", skip(self))]
     pub(crate) async fn load_groups(&self) -> FlowyResult<Vec<GroupPB>> {
         let groups = if !self.did_load_group.load(Ordering::SeqCst) {
             self.did_load_group.store(true, Ordering::SeqCst);
             let field_revs = self.field_delegate.get_field_revs().await;
             let row_revs = self.row_delegate.gv_row_revs().await;
+
             match self
                 .group_service
                 .write()
@@ -162,23 +177,47 @@ impl GridViewRevisionEditor {
             self.group_service.read().await.groups().await
         };
 
+        tracing::trace!("Number of groups: {}", groups.len());
         Ok(groups.into_iter().map(GroupPB::from).collect())
     }
 
+    pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
+        let _ = self
+            .group_service
+            .write()
+            .await
+            .move_group(&params.from_group_id, &params.to_group_id)
+            .await?;
+
+        match self.group_service.read().await.get_group(&params.from_group_id).await {
+            None => {}
+            Some((index, group)) => {
+                let inserted_group = InsertedGroupPB {
+                    group: GroupPB::from(group),
+                    index: index as i32,
+                };
+
+                let changeset = GroupViewChangesetPB {
+                    view_id: "".to_string(),
+                    inserted_groups: vec![inserted_group],
+                    deleted_groups: vec![params.from_group_id.clone()],
+                };
+
+                self.notify_did_update_view(changeset).await;
+            }
+        }
+        Ok(())
+    }
+
     pub(crate) async fn get_setting(&self) -> GridSettingPB {
         let field_revs = self.field_delegate.get_field_revs().await;
-        let grid_setting = make_grid_setting(self.pad.read().await.get_setting_rev(), &field_revs);
+        let grid_setting = make_grid_setting(&*self.pad.read().await, &field_revs);
         grid_setting
     }
 
-    pub(crate) async fn update_setting(&self, changeset: GridSettingChangesetParams) -> FlowyResult<()> {
-        let _ = self.modify(|pad| Ok(pad.update_setting(changeset)?)).await;
-        Ok(())
-    }
-
     pub(crate) async fn get_filters(&self) -> Vec<GridFilterConfiguration> {
         let field_revs = self.field_delegate.get_field_revs().await;
-        match self.pad.read().await.get_setting_rev().get_all_filters(&field_revs) {
+        match self.pad.read().await.get_all_filters(&field_revs) {
             None => vec![],
             Some(filters) => filters
                 .into_values()
@@ -188,12 +227,45 @@ impl GridViewRevisionEditor {
         }
     }
 
-    async fn notify_did_update_group(&self, changeset: GroupRowsChangesetPB) {
+    pub(crate) async fn insert_filter(&self, insert_filter: CreateFilterParams) -> FlowyResult<()> {
+        self.modify(|pad| {
+            let filter_rev = FilterConfigurationRevision {
+                id: gen_grid_filter_id(),
+                field_id: insert_filter.field_id.clone(),
+                condition: insert_filter.condition,
+                content: insert_filter.content,
+            };
+            let changeset = pad.insert_filter(&insert_filter.field_id, &insert_filter.field_type_rev, filter_rev)?;
+            Ok(changeset)
+        })
+        .await
+    }
+
+    pub(crate) async fn delete_filter(&self, delete_filter: DeleteFilterParams) -> FlowyResult<()> {
+        self.modify(|pad| {
+            let changeset = pad.delete_filter(
+                &delete_filter.field_id,
+                &delete_filter.field_type_rev,
+                &delete_filter.filter_id,
+            )?;
+            Ok(changeset)
+        })
+        .await
+    }
+
+    async fn notify_did_update_group_rows(&self, changeset: GroupRowsChangesetPB) {
         send_dart_notification(&changeset.group_id, GridNotification::DidUpdateGroup)
             .payload(changeset)
             .send();
     }
 
+    async fn notify_did_update_view(&self, changeset: GroupViewChangesetPB) {
+        send_dart_notification(&self.view_id, GridNotification::DidUpdateGroupView)
+            .payload(changeset)
+            .send();
+    }
+
+    #[allow(dead_code)]
     async fn modify<F>(&self, f: F) -> FlowyResult<()>
     where
         F: for<'a> FnOnce(&'a mut GridViewRevisionPad) -> FlowyResult<Option<GridViewRevisionChangeset>>,
@@ -202,28 +274,24 @@ impl GridViewRevisionEditor {
         match f(&mut *write_guard)? {
             None => {}
             Some(change) => {
-                let _ = self.apply_change(change).await?;
+                let _ = apply_change(&self.user_id, self.rev_manager.clone(), change).await?;
             }
         }
         Ok(())
     }
+}
 
-    async fn apply_change(&self, change: GridViewRevisionChangeset) -> FlowyResult<()> {
-        let GridViewRevisionChangeset { delta, md5 } = change;
-        let user_id = self.user_id.clone();
-        let (base_rev_id, rev_id) = self.rev_manager.next_rev_id_pair();
-        let delta_data = delta.json_bytes();
-        let revision = Revision::new(
-            &self.rev_manager.object_id,
-            base_rev_id,
-            rev_id,
-            delta_data,
-            &user_id,
-            md5,
-        );
-        let _ = self.rev_manager.add_local_revision(&revision).await?;
-        Ok(())
-    }
+async fn apply_change(
+    user_id: &str,
+    rev_manager: Arc<RevisionManager>,
+    change: GridViewRevisionChangeset,
+) -> FlowyResult<()> {
+    let GridViewRevisionChangeset { delta, md5 } = change;
+    let (base_rev_id, rev_id) = rev_manager.next_rev_id_pair();
+    let delta_data = delta.json_bytes();
+    let revision = Revision::new(&rev_manager.object_id, base_rev_id, rev_id, delta_data, user_id, md5);
+    let _ = rev_manager.add_local_revision(&revision).await?;
+    Ok(())
 }
 
 struct GridViewRevisionCloudService {
@@ -248,19 +316,97 @@ impl RevisionObjectBuilder for GridViewRevisionPadBuilder {
     }
 }
 
-impl GroupConfigurationDelegate for Arc<RwLock<GridViewRevisionPad>> {
-    fn get_group_configuration(&self, field_rev: Arc<FieldRevision>) -> AFFuture<GroupConfigurationRevision> {
-        let view_pad = self.clone();
+struct GroupConfigurationReaderImpl(Arc<RwLock<GridViewRevisionPad>>);
+
+impl GroupConfigurationReader for GroupConfigurationReaderImpl {
+    fn get_group_configuration(
+        &self,
+        field_rev: Arc<FieldRevision>,
+    ) -> AFFuture<Option<Arc<GroupConfigurationRevision>>> {
+        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 {
+                debug_assert_eq!(groups.len(), 1);
+                Some(groups.pop().unwrap())
+            }
+        })
+    }
+}
+
+struct GroupConfigurationWriterImpl {
+    user_id: String,
+    rev_manager: Arc<RevisionManager>,
+    view_pad: Arc<RwLock<GridViewRevisionPad>>,
+}
+
+impl GroupConfigurationWriter for GroupConfigurationWriterImpl {
+    fn save_group_configuration(
+        &self,
+        field_id: &str,
+        field_type: FieldTypeRevision,
+        group_configuration: GroupConfigurationRevision,
+    ) -> AFFuture<FlowyResult<()>> {
+        let user_id = self.user_id.clone();
+        let rev_manager = self.rev_manager.clone();
+        let view_pad = self.view_pad.clone();
+        let field_id = field_id.to_owned();
+
         wrap_future(async move {
-            let grid_pad = view_pad.read().await;
-            let configurations = grid_pad.get_groups(&field_rev.id, &field_rev.ty);
-            match configurations {
-                None => default_group_configuration(&field_rev),
-                Some(mut configurations) => {
-                    assert_eq!(configurations.len(), 1);
-                    (&*configurations.pop().unwrap()).clone()
-                }
+            let changeset = view_pad
+                .write()
+                .await
+                .insert_group(&field_id, &field_type, group_configuration)?;
+
+            if let Some(changeset) = changeset {
+                let _ = apply_change(&user_id, rev_manager, changeset).await?;
             }
+            Ok(())
         })
     }
 }
+
+pub fn make_grid_setting(view_pad: &GridViewRevisionPad, field_revs: &[Arc<FieldRevision>]) -> GridSettingPB {
+    let current_layout_type: GridLayout = view_pad.layout.clone().into();
+    let filters_by_field_id = view_pad
+        .get_all_filters(field_revs)
+        .map(|filters_by_field_id| {
+            filters_by_field_id
+                .into_iter()
+                .map(|(k, v)| (k, v.into()))
+                .collect::<HashMap<String, RepeatedGridConfigurationFilterPB>>()
+        })
+        .unwrap_or_default();
+    let groups_by_field_id = view_pad
+        .get_all_groups(field_revs)
+        .map(|groups_by_field_id| {
+            groups_by_field_id
+                .into_iter()
+                .map(|(k, v)| (k, v.into()))
+                .collect::<HashMap<String, RepeatedGridGroupConfigurationPB>>()
+        })
+        .unwrap_or_default();
+
+    GridSettingPB {
+        layouts: GridLayoutPB::all(),
+        current_layout_type,
+        filter_configuration_by_field_id: filters_by_field_id,
+        group_configuration_by_field_id: groups_by_field_id,
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use lib_ot::core::TextDelta;
+
+    #[test]
+    fn test() {
+        let s1 = r#"[{"insert":"{\"view_id\":\"fTURELffPr\",\"grid_id\":\"fTURELffPr\",\"layout\":0,\"filters\":[],\"groups\":[]}"}]"#;
+        let _delta_1 = TextDelta::from_json(s1).unwrap();
+
+        let s2 = r#"[{"retain":195},{"insert":"{\\\"group_id\\\":\\\"wD9i\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"xZtv\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"tFV2\\\",\\\"visible\\\":true}"},{"retain":10}]"#;
+        let _delta_2 = TextDelta::from_json(s2).unwrap();
+    }
+}

+ 29 - 12
frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs

@@ -1,4 +1,7 @@
-use crate::entities::{CreateRowParams, GridFilterConfiguration, GridSettingPB, RepeatedGridGroupPB, RowPB};
+use crate::entities::{
+    CreateFilterParams, CreateRowParams, DeleteFilterParams, GridFilterConfiguration, GridSettingPB, MoveGroupParams,
+    RepeatedGridGroupPB, RowPB,
+};
 use crate::manager::GridUser;
 use crate::services::grid_editor_task::GridServiceTaskScheduler;
 use crate::services::grid_view_editor::GridViewRevisionEditor;
@@ -8,7 +11,6 @@ use flowy_error::FlowyResult;
 use flowy_grid_data_model::revision::{FieldRevision, RowChangeset, RowRevision};
 use flowy_revision::disk::SQLiteGridViewRevisionPersistence;
 use flowy_revision::{RevisionCompactor, RevisionManager, RevisionPersistence, SQLiteRevisionSnapshotPersistence};
-use flowy_sync::entities::grid::GridSettingChangesetParams;
 use flowy_sync::entities::revision::Revision;
 use flowy_sync::util::make_text_delta_from_revisions;
 use lib_infra::future::AFFuture;
@@ -97,15 +99,19 @@ impl GridViewManager {
         Ok(view_editor.get_setting().await)
     }
 
-    pub(crate) async fn update_setting(&self, params: GridSettingChangesetParams) -> FlowyResult<()> {
+    pub(crate) async fn get_filters(&self) -> FlowyResult<Vec<GridFilterConfiguration>> {
         let view_editor = self.get_default_view_editor().await?;
-        let _ = view_editor.update_setting(params).await?;
-        Ok(())
+        Ok(view_editor.get_filters().await)
     }
 
-    pub(crate) async fn get_filters(&self) -> FlowyResult<Vec<GridFilterConfiguration>> {
+    pub(crate) async fn update_filter(&self, insert_filter: CreateFilterParams) -> FlowyResult<()> {
         let view_editor = self.get_default_view_editor().await?;
-        Ok(view_editor.get_filters().await)
+        view_editor.insert_filter(insert_filter).await
+    }
+
+    pub(crate) async fn delete_filter(&self, delete_filter: DeleteFilterParams) -> FlowyResult<()> {
+        let view_editor = self.get_default_view_editor().await?;
+        view_editor.delete_filter(delete_filter).await
     }
 
     pub(crate) async fn load_groups(&self) -> FlowyResult<RepeatedGridGroupPB> {
@@ -114,13 +120,26 @@ impl GridViewManager {
         Ok(RepeatedGridGroupPB { items: groups })
     }
 
+    pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
+        let view_editor = self.get_default_view_editor().await?;
+        let _ = view_editor.move_group(params).await?;
+        Ok(())
+    }
+
     /// 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() {
@@ -163,12 +182,11 @@ async fn make_view_editor(
     row_delegate: Arc<dyn GridViewRowDelegate>,
     scheduler: Arc<dyn GridServiceTaskScheduler>,
 ) -> FlowyResult<GridViewRevisionEditor> {
-    tracing::trace!("Open view:{} editor", view_id);
-
     let rev_manager = make_grid_view_rev_manager(user, view_id).await?;
     let user_id = user.user_id()?;
     let token = user.token()?;
     let view_id = view_id.to_owned();
+
     GridViewRevisionEditor::new(
         &user_id,
         &token,
@@ -182,7 +200,6 @@ async fn make_view_editor(
 }
 
 pub async fn make_grid_view_rev_manager(user: &Arc<dyn GridUser>, view_id: &str) -> FlowyResult<RevisionManager> {
-    tracing::trace!("Open view:{} editor", view_id);
     let user_id = user.user_id()?;
     let pool = user.db_pool()?;
 

+ 17 - 0
frontend/rust-lib/flowy-grid/src/services/group/action.rs

@@ -0,0 +1,17 @@
+use crate::entities::GroupRowsChangesetPB;
+
+use crate::services::group::controller::MoveGroupRowContext;
+use flowy_grid_data_model::revision::RowRevision;
+
+pub trait GroupAction: Send + Sync {
+    type CellDataType;
+    fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool;
+    fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB>;
+    fn remove_row_if_match(
+        &mut self,
+        row_rev: &RowRevision,
+        cell_data: &Self::CellDataType,
+    ) -> Vec<GroupRowsChangesetPB>;
+
+    fn move_row(&mut self, cell_data: &Self::CellDataType, context: MoveGroupRowContext) -> Vec<GroupRowsChangesetPB>;
+}

+ 221 - 0
frontend/rust-lib/flowy-grid/src/services/group/configuration.rs

@@ -0,0 +1,221 @@
+use crate::services::group::{default_group_configuration, Group};
+use flowy_error::{FlowyError, FlowyResult};
+use flowy_grid_data_model::revision::{
+    FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRecordRevision,
+};
+use std::marker::PhantomData;
+
+use indexmap::IndexMap;
+use lib_infra::future::AFFuture;
+use std::sync::Arc;
+
+pub trait GroupConfigurationReader: Send + Sync + 'static {
+    fn get_group_configuration(
+        &self,
+        field_rev: Arc<FieldRevision>,
+    ) -> AFFuture<Option<Arc<GroupConfigurationRevision>>>;
+}
+
+pub trait GroupConfigurationWriter: Send + Sync + 'static {
+    fn save_group_configuration(
+        &self,
+        field_id: &str,
+        field_type: FieldTypeRevision,
+        group_configuration: GroupConfigurationRevision,
+    ) -> AFFuture<FlowyResult<()>>;
+}
+
+pub struct GenericGroupConfiguration<C> {
+    pub configuration: Arc<GroupConfigurationRevision>,
+    configuration_content: PhantomData<C>,
+    field_rev: Arc<FieldRevision>,
+    groups_map: IndexMap<String, Group>,
+    writer: Arc<dyn GroupConfigurationWriter>,
+}
+
+impl<C> GenericGroupConfiguration<C>
+where
+    C: GroupConfigurationContentSerde,
+{
+    #[tracing::instrument(level = "trace", skip_all, err)]
+    pub async fn new(
+        field_rev: Arc<FieldRevision>,
+        reader: Arc<dyn GroupConfigurationReader>,
+        writer: Arc<dyn GroupConfigurationWriter>,
+    ) -> FlowyResult<Self> {
+        let configuration = match reader.get_group_configuration(field_rev.clone()).await {
+            None => {
+                let default_group_configuration = default_group_configuration(&field_rev);
+                writer
+                    .save_group_configuration(&field_rev.id, field_rev.ty, default_group_configuration.clone())
+                    .await?;
+                Arc::new(default_group_configuration)
+            }
+            Some(configuration) => configuration,
+        };
+
+        // let configuration = C::from_configuration_content(&configuration_rev.content)?;
+        Ok(Self {
+            field_rev,
+            groups_map: IndexMap::new(),
+            writer,
+            configuration,
+            configuration_content: PhantomData,
+        })
+    }
+
+    pub(crate) fn groups(&self) -> Vec<&Group> {
+        self.groups_map.values().collect()
+    }
+
+    pub(crate) fn clone_groups(&self) -> Vec<Group> {
+        self.groups_map.values().cloned().collect()
+    }
+
+    pub(crate) async fn merge_groups(&mut self, groups: Vec<Group>) -> FlowyResult<()> {
+        let (group_revs, groups) = merge_groups(&self.configuration.groups, groups);
+        self.mut_configuration(move |configuration| {
+            configuration.groups = group_revs;
+            true
+        })?;
+
+        groups.into_iter().for_each(|group| {
+            self.groups_map.insert(group.id.clone(), group);
+        });
+        Ok(())
+    }
+
+    #[allow(dead_code)]
+    pub(crate) async fn hide_group(&mut self, group_id: &str) -> FlowyResult<()> {
+        self.mut_configuration_group(group_id, |group_rev| {
+            group_rev.visible = false;
+        })?;
+        Ok(())
+    }
+
+    #[allow(dead_code)]
+    pub(crate) async fn show_group(&mut self, group_id: &str) -> FlowyResult<()> {
+        self.mut_configuration_group(group_id, |group_rev| {
+            group_rev.visible = true;
+        })?;
+        Ok(())
+    }
+
+    pub(crate) fn with_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) {
+        self.groups_map.iter_mut().for_each(|(_, group)| {
+            each(group);
+        })
+    }
+
+    pub(crate) fn get_mut_group(&mut self, group_id: &str) -> Option<&mut Group> {
+        self.groups_map.get_mut(group_id)
+    }
+
+    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_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);
+                    }
+                    true
+                })?;
+                Ok(())
+            }
+            _ => Err(FlowyError::out_of_bounds()),
+        }
+    }
+
+    // Returns the index and group specified by the group_id
+    pub(crate) fn get_group(&self, group_id: &str) -> Option<(usize, &Group)> {
+        match (self.groups_map.get_index_of(group_id), self.groups_map.get(group_id)) {
+            (Some(index), Some(group)) => Some((index, group)),
+            _ => None,
+        }
+    }
+
+    pub fn save_configuration(&self) -> FlowyResult<()> {
+        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;
+        tokio::spawn(async move {
+            match writer
+                .save_group_configuration(&field_id, field_type, configuration)
+                .await
+            {
+                Ok(_) => {}
+                Err(e) => {
+                    tracing::error!("Save group configuration failed: {}", e);
+                }
+            }
+        });
+
+        Ok(())
+    }
+
+    fn mut_configuration_group(
+        &mut self,
+        group_id: &str,
+        mut_groups_fn: impl Fn(&mut GroupRecordRevision),
+    ) -> FlowyResult<()> {
+        self.mut_configuration(|configuration| {
+            match configuration.groups.iter_mut().find(|group| group.group_id == group_id) {
+                None => false,
+                Some(group_rev) => {
+                    mut_groups_fn(group_rev);
+                    true
+                }
+            }
+        })
+    }
+
+    fn mut_configuration(
+        &mut self,
+        mut_configuration_fn: impl FnOnce(&mut GroupConfigurationRevision) -> bool,
+    ) -> FlowyResult<()> {
+        let configuration = Arc::make_mut(&mut self.configuration);
+        let is_changed = mut_configuration_fn(configuration);
+        if is_changed {
+            let _ = self.save_configuration()?;
+        }
+        Ok(())
+    }
+}
+
+fn merge_groups(old_group_revs: &[GroupRecordRevision], groups: Vec<Group>) -> (Vec<GroupRecordRevision>, Vec<Group>) {
+    if old_group_revs.is_empty() {
+        let new_groups = groups
+            .iter()
+            .map(|group| GroupRecordRevision::new(group.id.clone()))
+            .collect();
+        return (new_groups, groups);
+    }
+
+    let mut group_map: IndexMap<String, Group> = IndexMap::new();
+    groups.into_iter().for_each(|group| {
+        group_map.insert(group.id.clone(), group);
+    });
+
+    // Inert
+    let mut sorted_groups: Vec<Group> = vec![];
+    for group_rev in old_group_revs {
+        if let Some(group) = group_map.remove(&group_rev.group_id) {
+            sorted_groups.push(group);
+        }
+    }
+    sorted_groups.extend(group_map.into_values().collect::<Vec<Group>>());
+    let new_group_revs = sorted_groups
+        .iter()
+        .map(|group| GroupRecordRevision::new(group.id.clone()))
+        .collect::<Vec<GroupRecordRevision>>();
+
+    tracing::trace!("group revs: {}, groups: {}", new_group_revs.len(), sorted_groups.len());
+    (new_group_revs, sorted_groups)
+}

+ 214 - 0
frontend/rust-lib/flowy-grid/src/services/group/controller.rs

@@ -0,0 +1,214 @@
+use crate::entities::{GroupRowsChangesetPB, RowPB};
+use crate::services::cell::{decode_any_cell_data, CellBytesParser};
+use crate::services::group::action::GroupAction;
+use crate::services::group::configuration::GenericGroupConfiguration;
+use crate::services::group::entities::Group;
+use flowy_error::FlowyResult;
+use flowy_grid_data_model::revision::{
+    FieldRevision, GroupConfigurationContentSerde, RowChangeset, RowRevision, TypeOptionDataDeserializer,
+};
+
+use std::marker::PhantomData;
+use std::sync::Arc;
+
+const DEFAULT_GROUP_ID: &str = "default_group";
+
+// Each kind of group must implement this trait to provide custom group
+// operations. For example, insert cell data to the row_rev when creating
+// a new row.
+pub trait GroupController: GroupControllerSharedOperation + Send + Sync {
+    fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str);
+}
+
+pub trait GroupGenerator {
+    type ConfigurationType;
+    type TypeOptionType;
+
+    fn generate_groups(
+        field_id: &str,
+        configuration: &Self::ConfigurationType,
+        type_option: &Option<Self::TypeOptionType>,
+    ) -> 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
+    fn field_id(&self) -> &str;
+    fn groups(&self) -> Vec<Group>;
+    fn get_group(&self, group_id: &str) -> Option<(usize, Group)>;
+    fn fill_groups(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<Vec<Group>>;
+    fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()>;
+    fn did_update_row(
+        &mut self,
+        row_rev: &RowRevision,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<Vec<GroupRowsChangesetPB>>;
+
+    fn did_delete_row(
+        &mut self,
+        row_rev: &RowRevision,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<Vec<GroupRowsChangesetPB>>;
+
+    fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsChangesetPB>>;
+}
+
+/// C: represents the group configuration that impl [GroupConfigurationSerde]
+/// T: the type option data deserializer that impl [TypeOptionDataDeserializer]
+/// G: the group generator, [GroupGenerator]
+/// P: the parser that impl [CellBytesParser] for the CellBytes
+pub struct GenericGroupController<C, T, G, P> {
+    pub field_id: String,
+    pub type_option: Option<T>,
+    pub configuration: GenericGroupConfiguration<C>,
+    /// default_group is used to store the rows that don't belong to any groups.
+    default_group: Group,
+    group_action_phantom: PhantomData<G>,
+    cell_parser_phantom: PhantomData<P>,
+}
+
+impl<C, T, G, P> GenericGroupController<C, T, G, P>
+where
+    C: GroupConfigurationContentSerde,
+    T: TypeOptionDataDeserializer,
+    G: GroupGenerator<ConfigurationType = GenericGroupConfiguration<C>, TypeOptionType = T>,
+{
+    pub async fn new(
+        field_rev: &Arc<FieldRevision>,
+        mut configuration: GenericGroupConfiguration<C>,
+    ) -> FlowyResult<Self> {
+        let field_type_rev = field_rev.ty;
+        let type_option = field_rev.get_type_option_entry::<T>(field_type_rev);
+        let groups = G::generate_groups(&field_rev.id, &configuration, &type_option);
+        let _ = configuration.merge_groups(groups).await?;
+        let default_group = Group::new(
+            DEFAULT_GROUP_ID.to_owned(),
+            field_rev.id.clone(),
+            format!("No {}", field_rev.name),
+            "".to_string(),
+        );
+
+        Ok(Self {
+            field_id: field_rev.id.clone(),
+            default_group,
+            type_option,
+            configuration,
+            group_action_phantom: PhantomData,
+            cell_parser_phantom: PhantomData,
+        })
+    }
+}
+
+impl<C, T, G, P> GroupControllerSharedOperation for GenericGroupController<C, T, G, P>
+where
+    P: CellBytesParser,
+    C: GroupConfigurationContentSerde,
+    Self: GroupAction<CellDataType = P::Object>,
+{
+    fn field_id(&self) -> &str {
+        &self.field_id
+    }
+
+    fn groups(&self) -> Vec<Group> {
+        self.configuration.clone_groups()
+    }
+
+    fn get_group(&self, group_id: &str) -> Option<(usize, Group)> {
+        let group = self.configuration.get_group(group_id)?;
+        Some((group.0, group.1.clone()))
+    }
+
+    fn fill_groups(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<Vec<Group>> {
+        for row_rev in row_revs {
+            if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
+                let mut group_rows: Vec<GroupRow> = vec![];
+                let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
+                let cell_data = cell_bytes.parser::<P>()?;
+                for group in self.configuration.groups() {
+                    if self.can_group(&group.content, &cell_data) {
+                        group_rows.push(GroupRow {
+                            row: row_rev.into(),
+                            group_id: group.id.clone(),
+                        });
+                    }
+                }
+
+                if group_rows.is_empty() {
+                    self.default_group.add_row(row_rev.into());
+                } else {
+                    for group_row in group_rows {
+                        if let Some(group) = self.configuration.get_mut_group(&group_row.group_id) {
+                            group.add_row(group_row.row);
+                        }
+                    }
+                }
+            } else {
+                self.default_group.add_row(row_rev.into());
+            }
+        }
+
+        let default_group = self.default_group.clone();
+        let mut groups: Vec<Group> = self.configuration.clone_groups();
+        if !default_group.number_of_row() == 0 {
+            groups.push(default_group);
+        }
+
+        Ok(groups)
+    }
+
+    fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()> {
+        self.configuration.move_group(from_group_id, to_group_id)
+    }
+
+    fn did_update_row(
+        &mut self,
+        row_rev: &RowRevision,
+        field_rev: &FieldRevision,
+    ) -> 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);
+            let cell_data = cell_bytes.parser::<P>()?;
+            Ok(self.add_row_if_match(row_rev, &cell_data))
+        } else {
+            Ok(vec![])
+        }
+    }
+
+    fn did_delete_row(
+        &mut self,
+        row_rev: &RowRevision,
+        field_rev: &FieldRevision,
+    ) -> 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);
+            let cell_data = cell_bytes.parser::<P>()?;
+            Ok(self.remove_row_if_match(row_rev, &cell_data))
+        } else {
+            Ok(vec![])
+        }
+    }
+
+    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>()?;
+            Ok(self.move_row(&cell_data, context))
+        } else {
+            Ok(vec![])
+        }
+    }
+}
+
+struct GroupRow {
+    row: RowPB,
+    group_id: String,
+}

+ 17 - 15
frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs → frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs

@@ -1,20 +1,25 @@
-use crate::entities::{CheckboxGroupConfigurationPB, GroupRowsChangesetPB};
-
-use flowy_grid_data_model::revision::{FieldRevision, RowChangeset, RowRevision};
-
+use crate::entities::GroupRowsChangesetPB;
 use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK};
-use crate::services::group::{GenericGroupController, Group, GroupController, GroupGenerator, Groupable};
+use crate::services::group::action::GroupAction;
+use crate::services::group::configuration::GenericGroupConfiguration;
+use crate::services::group::controller::{
+    GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
+};
+use crate::services::group::entities::Group;
+
+use flowy_grid_data_model::revision::{CheckboxGroupConfigurationRevision, FieldRevision, RowRevision};
 
 pub type CheckboxGroupController = GenericGroupController<
-    CheckboxGroupConfigurationPB,
+    CheckboxGroupConfigurationRevision,
     CheckboxTypeOptionPB,
     CheckboxGroupGenerator,
     CheckboxCellDataParser,
 >;
 
-impl Groupable for CheckboxGroupController {
-    type CellDataType = CheckboxCellData;
+pub type CheckboxGroupConfiguration = GenericGroupConfiguration<CheckboxGroupConfigurationRevision>;
 
+impl GroupAction for CheckboxGroupController {
+    type CellDataType = CheckboxCellData;
     fn can_group(&self, _content: &str, _cell_data: &Self::CellDataType) -> bool {
         false
     }
@@ -35,13 +40,10 @@ impl Groupable 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!()
     }
@@ -55,12 +57,12 @@ impl GroupController for CheckboxGroupController {
 
 pub struct CheckboxGroupGenerator();
 impl GroupGenerator for CheckboxGroupGenerator {
-    type ConfigurationType = CheckboxGroupConfigurationPB;
+    type ConfigurationType = CheckboxGroupConfiguration;
     type TypeOptionType = CheckboxTypeOptionPB;
 
     fn generate_groups(
         field_id: &str,
-        _configuration: &Option<Self::ConfigurationType>,
+        _configuration: &Self::ConfigurationType,
         _type_option: &Option<Self::TypeOptionType>,
     ) -> Vec<Group> {
         let check_group = Group::new(

+ 5 - 0
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/mod.rs

@@ -0,0 +1,5 @@
+mod checkbox_controller;
+mod select_option_controller;
+
+pub use checkbox_controller::*;
+pub use select_option_controller::*;

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

@@ -0,0 +1,7 @@
+mod multi_select_controller;
+mod single_select_controller;
+mod util;
+
+pub use multi_select_controller::*;
+pub use single_select_controller::*;
+pub use util::*;

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

@@ -0,0 +1,98 @@
+use crate::entities::GroupRowsChangesetPB;
+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, MoveGroupRowContext,
+};
+use crate::services::group::controller_impls::select_option_controller::util::*;
+use crate::services::group::entities::Group;
+use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision};
+
+// MultiSelect
+pub type MultiSelectGroupController = GenericGroupController<
+    SelectOptionGroupConfigurationRevision,
+    MultiSelectTypeOptionPB,
+    MultiSelectGroupGenerator,
+    SelectOptionCellDataParser,
+>;
+
+impl GroupAction for MultiSelectGroupController {
+    type CellDataType = SelectOptionCellDataPB;
+
+    fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
+        cell_data.select_options.iter().any(|option| option.id == content)
+    }
+
+    fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB> {
+        let mut changesets = vec![];
+        self.configuration.with_mut_groups(|group| {
+            add_row(group, &mut changesets, cell_data, row_rev);
+        });
+        changesets
+    }
+
+    fn remove_row_if_match(
+        &mut self,
+        row_rev: &RowRevision,
+        cell_data: &Self::CellDataType,
+    ) -> Vec<GroupRowsChangesetPB> {
+        let mut changesets = vec![];
+        self.configuration.with_mut_groups(|group| {
+            remove_row(group, &mut changesets, cell_data, row_rev);
+        });
+        changesets
+    }
+
+    fn move_row(
+        &mut self,
+        cell_data: &Self::CellDataType,
+        mut context: MoveGroupRowContext,
+    ) -> Vec<GroupRowsChangesetPB> {
+        let mut group_changeset = vec![];
+        self.configuration.with_mut_groups(|group| {
+            move_select_option_row(group, &mut group_changeset, cell_data, &mut context);
+        });
+        group_changeset
+    }
+}
+
+impl GroupController for MultiSelectGroupController {
+    fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) {
+        match self.configuration.get_group(group_id) {
+            None => tracing::warn!("Can not find the group: {}", group_id),
+            Some((_, group)) => {
+                let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
+                row_rev.cells.insert(field_rev.id.clone(), cell_rev);
+            }
+        }
+    }
+}
+
+pub struct MultiSelectGroupGenerator();
+impl GroupGenerator for MultiSelectGroupGenerator {
+    type ConfigurationType = SelectOptionGroupConfiguration;
+    type TypeOptionType = MultiSelectTypeOptionPB;
+    fn generate_groups(
+        field_id: &str,
+        _configuration: &Self::ConfigurationType,
+        type_option: &Option<Self::TypeOptionType>,
+    ) -> Vec<Group> {
+        match type_option {
+            None => vec![],
+            Some(type_option) => type_option
+                .options
+                .iter()
+                .map(|option| {
+                    Group::new(
+                        option.id.clone(),
+                        field_id.to_owned(),
+                        option.name.clone(),
+                        option.id.clone(),
+                    )
+                })
+                .collect(),
+        }
+    }
+}

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

@@ -0,0 +1,100 @@
+use crate::entities::{GroupRowsChangesetPB, RowPB};
+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, MoveGroupRowContext,
+};
+use crate::services::group::controller_impls::select_option_controller::util::*;
+use crate::services::group::entities::Group;
+
+use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision};
+
+// SingleSelect
+pub type SingleSelectGroupController = GenericGroupController<
+    SelectOptionGroupConfigurationRevision,
+    SingleSelectTypeOptionPB,
+    SingleSelectGroupGenerator,
+    SelectOptionCellDataParser,
+>;
+
+impl GroupAction for SingleSelectGroupController {
+    type CellDataType = SelectOptionCellDataPB;
+    fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
+        cell_data.select_options.iter().any(|option| option.id == content)
+    }
+
+    fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB> {
+        let mut changesets = vec![];
+        self.configuration.with_mut_groups(|group| {
+            add_row(group, &mut changesets, cell_data, row_rev);
+        });
+        changesets
+    }
+
+    fn remove_row_if_match(
+        &mut self,
+        row_rev: &RowRevision,
+        cell_data: &Self::CellDataType,
+    ) -> Vec<GroupRowsChangesetPB> {
+        let mut changesets = vec![];
+        self.configuration.with_mut_groups(|group| {
+            remove_row(group, &mut changesets, cell_data, row_rev);
+        });
+        changesets
+    }
+
+    fn move_row(
+        &mut self,
+        cell_data: &Self::CellDataType,
+        mut context: MoveGroupRowContext,
+    ) -> Vec<GroupRowsChangesetPB> {
+        let mut group_changeset = vec![];
+        self.configuration.with_mut_groups(|group| {
+            move_select_option_row(group, &mut group_changeset, cell_data, &mut context);
+        });
+        group_changeset
+    }
+}
+
+impl GroupController for SingleSelectGroupController {
+    fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) {
+        let group: Option<&mut Group> = self.configuration.get_mut_group(group_id);
+        match group {
+            None => {}
+            Some(group) => {
+                let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
+                row_rev.cells.insert(field_rev.id.clone(), cell_rev);
+                group.add_row(RowPB::from(row_rev));
+            }
+        }
+    }
+}
+
+pub struct SingleSelectGroupGenerator();
+impl GroupGenerator for SingleSelectGroupGenerator {
+    type ConfigurationType = SelectOptionGroupConfiguration;
+    type TypeOptionType = SingleSelectTypeOptionPB;
+    fn generate_groups(
+        field_id: &str,
+        _configuration: &Self::ConfigurationType,
+        type_option: &Option<Self::TypeOptionType>,
+    ) -> Vec<Group> {
+        match type_option {
+            None => vec![],
+            Some(type_option) => type_option
+                .options
+                .iter()
+                .map(|option| {
+                    Group::new(
+                        option.id.clone(),
+                        field_id.to_owned(),
+                        option.name.clone(),
+                        option.id.clone(),
+                    )
+                })
+                .collect(),
+        }
+    }
+}

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

@@ -0,0 +1,105 @@
+use crate::entities::{GroupRowsChangesetPB, InsertedRowPB, RowPB};
+use crate::services::cell::insert_select_option_cell;
+use crate::services::field::SelectOptionCellDataPB;
+use crate::services::group::configuration::GenericGroupConfiguration;
+use crate::services::group::Group;
+
+use crate::services::group::controller::MoveGroupRowContext;
+use flowy_grid_data_model::revision::{RowRevision, SelectOptionGroupConfigurationRevision};
+
+pub type SelectOptionGroupConfiguration = GenericGroupConfiguration<SelectOptionGroupConfigurationRevision>;
+
+pub fn add_row(
+    group: &mut Group,
+    changesets: &mut Vec<GroupRowsChangesetPB>,
+    cell_data: &SelectOptionCellDataPB,
+    row_rev: &RowRevision,
+) {
+    cell_data.select_options.iter().for_each(|option| {
+        if option.id == group.id {
+            if !group.contains_row(&row_rev.id) {
+                let row_pb = RowPB::from(row_rev);
+                changesets.push(GroupRowsChangesetPB::insert(
+                    group.id.clone(),
+                    vec![InsertedRowPB::new(row_pb.clone())],
+                ));
+                group.add_row(row_pb);
+            }
+        } else 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);
+        }
+    });
+}
+
+pub fn remove_row(
+    group: &mut Group,
+    changesets: &mut Vec<GroupRowsChangesetPB>,
+    cell_data: &SelectOptionCellDataPB,
+    row_rev: &RowRevision,
+) {
+    cell_data.select_options.iter().for_each(|option| {
+        if option.id == group.id && group.contains_row(&row_rev.id) {
+            changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()]));
+            group.remove_row(&row_rev.id);
+        }
+    });
+}
+
+pub fn move_select_option_row(
+    group: &mut Group,
+    group_changeset: &mut Vec<GroupRowsChangesetPB>,
+    _cell_data: &SelectOptionCellDataPB,
+    context: &mut MoveGroupRowContext,
+) {
+    let MoveGroupRowContext {
+        row_rev,
+        row_changeset,
+        field_rev,
+        to_group_id,
+        to_row_id,
+    } = context;
+
+    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);
+    }
+
+    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);
+            }
+            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]));
+            }
+        }
+
+        // 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);
+        }
+    }
+}

+ 73 - 0
frontend/rust-lib/flowy-grid/src/services/group/entities.rs

@@ -0,0 +1,73 @@
+use crate::entities::{GroupPB, RowPB};
+
+#[derive(Clone)]
+pub struct Group {
+    pub id: String,
+    pub field_id: String,
+    pub desc: String,
+    rows: Vec<RowPB>,
+
+    /// [content] is used to determine which group the cell belongs to.
+    pub content: String,
+}
+
+impl std::convert::From<Group> for GroupPB {
+    fn from(group: Group) -> Self {
+        Self {
+            field_id: group.field_id,
+            group_id: group.id,
+            desc: group.desc,
+            rows: group.rows,
+        }
+    }
+}
+
+impl Group {
+    pub fn new(id: String, field_id: String, desc: String, content: String) -> Self {
+        Self {
+            id,
+            field_id,
+            desc,
+            rows: vec![],
+            content,
+        }
+    }
+
+    pub fn contains_row(&self, row_id: &str) -> bool {
+        self.rows.iter().any(|row| row.id == row_id)
+    }
+
+    pub fn remove_row(&mut self, row_id: &str) {
+        match self.rows.iter().position(|row| row.id == row_id) {
+            None => {}
+            Some(pos) => {
+                self.rows.remove(pos);
+            }
+        }
+    }
+
+    pub fn add_row(&mut self, row_pb: RowPB) {
+        match self.rows.iter().find(|row| row.id == row_pb.id) {
+            None => {
+                self.rows.push(row_pb);
+            }
+            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<usize> {
+        self.rows.iter().position(|row| row.id == row_id)
+    }
+
+    pub fn number_of_row(&self) -> usize {
+        self.rows.len()
+    }
+}

+ 0 - 296
frontend/rust-lib/flowy-grid/src/services/group/group_generator/group_controller.rs

@@ -1,296 +0,0 @@
-use crate::entities::{GroupPB, GroupRowsChangesetPB, RowPB};
-use crate::services::cell::{decode_any_cell_data, CellBytesParser};
-use bytes::Bytes;
-use flowy_error::FlowyResult;
-use flowy_grid_data_model::revision::{
-    FieldRevision, GroupConfigurationRevision, RowChangeset, RowRevision, TypeOptionDataDeserializer,
-};
-use indexmap::IndexMap;
-use std::marker::PhantomData;
-use std::sync::Arc;
-
-pub trait GroupGenerator {
-    type ConfigurationType;
-    type TypeOptionType;
-
-    fn generate_groups(
-        field_id: &str,
-        configuration: &Option<Self::ConfigurationType>,
-        type_option: &Option<Self::TypeOptionType>,
-    ) -> Vec<Group>;
-}
-
-pub trait Groupable: Send + Sync {
-    type CellDataType;
-    fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool;
-    fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB>;
-    fn remove_row_if_match(
-        &mut self,
-        row_rev: &RowRevision,
-        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>;
-}
-
-pub trait GroupController: GroupControllerSharedAction + Send + Sync {
-    fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str);
-}
-
-pub trait GroupControllerSharedAction: Send + Sync {
-    // The field that is used for grouping the rows
-    fn field_id(&self) -> &str;
-    fn groups(&self) -> Vec<Group>;
-    fn group_rows(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<()>;
-    fn did_update_row(
-        &mut self,
-        row_rev: &RowRevision,
-        field_rev: &FieldRevision,
-    ) -> FlowyResult<Vec<GroupRowsChangesetPB>>;
-
-    fn did_delete_row(
-        &mut self,
-        row_rev: &RowRevision,
-        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>>;
-}
-
-const DEFAULT_GROUP_ID: &str = "default_group";
-
-/// C: represents the group configuration structure
-/// T: the type option data deserializer that impl [TypeOptionDataDeserializer]
-/// G: the group generator, [GroupGenerator]
-/// P: the parser that impl [CellBytesParser] for the CellBytes
-pub struct GenericGroupController<C, T, G, P> {
-    pub field_id: String,
-    pub groups_map: IndexMap<String, Group>,
-    default_group: Group,
-    pub type_option: Option<T>,
-    pub configuration: Option<C>,
-    group_action_phantom: PhantomData<G>,
-    cell_parser_phantom: PhantomData<P>,
-}
-
-#[derive(Clone)]
-pub struct Group {
-    pub id: String,
-    pub field_id: String,
-    pub desc: String,
-    rows: Vec<RowPB>,
-    pub content: String,
-}
-
-impl std::convert::From<Group> for GroupPB {
-    fn from(group: Group) -> Self {
-        Self {
-            field_id: group.field_id,
-            group_id: group.id,
-            desc: group.desc,
-            rows: group.rows,
-        }
-    }
-}
-
-impl Group {
-    pub fn new(id: String, field_id: String, desc: String, content: String) -> Self {
-        Self {
-            id,
-            field_id,
-            desc,
-            rows: vec![],
-            content,
-        }
-    }
-
-    pub fn contains_row(&self, row_id: &str) -> bool {
-        self.rows.iter().any(|row| row.id == row_id)
-    }
-
-    pub fn remove_row(&mut self, row_id: &str) {
-        match self.rows.iter().position(|row| row.id == row_id) {
-            None => {}
-            Some(pos) => {
-                self.rows.remove(pos);
-            }
-        }
-    }
-
-    pub fn add_row(&mut self, row_pb: RowPB) {
-        match self.rows.iter().find(|row| row.id == row_pb.id) {
-            None => {
-                self.rows.push(row_pb);
-            }
-            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<usize> {
-        self.rows.iter().position(|row| row.id == row_id)
-    }
-
-    pub fn number_of_row(&self) -> usize {
-        self.rows.len()
-    }
-}
-
-impl<C, T, G, P> GenericGroupController<C, T, G, P>
-where
-    C: TryFrom<Bytes, Error = protobuf::ProtobufError>,
-    T: TypeOptionDataDeserializer,
-    G: GroupGenerator<ConfigurationType = C, TypeOptionType = T>,
-{
-    pub fn new(field_rev: &Arc<FieldRevision>, configuration: GroupConfigurationRevision) -> FlowyResult<Self> {
-        let configuration = match configuration.content {
-            None => None,
-            Some(content) => Some(C::try_from(Bytes::from(content))?),
-        };
-        let field_type_rev = field_rev.ty;
-        let type_option = field_rev.get_type_option_entry::<T>(field_type_rev);
-        let groups = G::generate_groups(&field_rev.id, &configuration, &type_option);
-
-        let default_group = Group::new(
-            DEFAULT_GROUP_ID.to_owned(),
-            field_rev.id.clone(),
-            format!("No {}", field_rev.name),
-            "".to_string(),
-        );
-
-        Ok(Self {
-            field_id: field_rev.id.clone(),
-            groups_map: groups.into_iter().map(|group| (group.id.clone(), group)).collect(),
-            default_group,
-            type_option,
-            configuration,
-            group_action_phantom: PhantomData,
-            cell_parser_phantom: PhantomData,
-        })
-    }
-}
-
-impl<C, T, G, P> GroupControllerSharedAction for GenericGroupController<C, T, G, P>
-where
-    P: CellBytesParser,
-    Self: Groupable<CellDataType = P::Object>,
-{
-    fn field_id(&self) -> &str {
-        &self.field_id
-    }
-
-    fn groups(&self) -> Vec<Group> {
-        let default_group = self.default_group.clone();
-        let mut groups: Vec<Group> = self.groups_map.values().cloned().collect();
-        if !default_group.rows.is_empty() {
-            groups.push(default_group);
-        }
-        groups
-    }
-
-    fn group_rows(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<()> {
-        if self.configuration.is_none() {
-            return Ok(());
-        }
-
-        for row_rev in row_revs {
-            if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
-                let mut records: Vec<GroupRecord> = vec![];
-                let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
-                let cell_data = cell_bytes.parser::<P>()?;
-                for group in self.groups_map.values() {
-                    if self.can_group(&group.content, &cell_data) {
-                        records.push(GroupRecord {
-                            row: row_rev.into(),
-                            group_id: group.id.clone(),
-                        });
-                    }
-                }
-
-                if records.is_empty() {
-                    self.default_group.rows.push(row_rev.into());
-                } else {
-                    for record in records {
-                        if let Some(group) = self.groups_map.get_mut(&record.group_id) {
-                            group.rows.push(record.row);
-                        }
-                    }
-                }
-            } else {
-                self.default_group.rows.push(row_rev.into());
-            }
-        }
-
-        Ok(())
-    }
-
-    fn did_update_row(
-        &mut self,
-        row_rev: &RowRevision,
-        field_rev: &FieldRevision,
-    ) -> 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);
-            let cell_data = cell_bytes.parser::<P>()?;
-            Ok(self.add_row_if_match(row_rev, &cell_data))
-        } else {
-            Ok(vec![])
-        }
-    }
-
-    fn did_delete_row(
-        &mut self,
-        row_rev: &RowRevision,
-        field_rev: &FieldRevision,
-    ) -> 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);
-            let cell_data = cell_bytes.parser::<P>()?;
-            Ok(self.remove_row_if_match(row_rev, &cell_data))
-        } else {
-            Ok(vec![])
-        }
-    }
-
-    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);
-            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))
-        } else {
-            Ok(vec![])
-        }
-    }
-}
-
-struct GroupRecord {
-    row: RowPB,
-    group_id: String,
-}

+ 0 - 7
frontend/rust-lib/flowy-grid/src/services/group/group_generator/mod.rs

@@ -1,7 +0,0 @@
-mod checkbox_group;
-mod group_controller;
-mod select_option_group;
-
-pub use checkbox_group::*;
-pub use group_controller::*;
-pub use select_option_group::*;

+ 0 - 287
frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs

@@ -1,287 +0,0 @@
-use crate::entities::{GroupRowsChangesetPB, InsertedRowPB, RowPB, SelectOptionGroupConfigurationPB};
-use crate::services::cell::insert_select_option_cell;
-use crate::services::field::{
-    MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB,
-};
-use crate::services::group::{GenericGroupController, Group, GroupController, GroupGenerator, Groupable};
-
-use flowy_grid_data_model::revision::{FieldRevision, RowChangeset, RowRevision};
-
-// SingleSelect
-pub type SingleSelectGroupController = GenericGroupController<
-    SelectOptionGroupConfigurationPB,
-    SingleSelectTypeOptionPB,
-    SingleSelectGroupGenerator,
-    SelectOptionCellDataParser,
->;
-
-impl Groupable for SingleSelectGroupController {
-    type CellDataType = SelectOptionCellDataPB;
-    fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
-        cell_data.select_options.iter().any(|option| option.id == content)
-    }
-
-    fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB> {
-        let mut changesets = vec![];
-        self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| {
-            add_row(group, &mut changesets, cell_data, row_rev);
-        });
-        changesets
-    }
-
-    fn remove_row_if_match(
-        &mut self,
-        row_rev: &RowRevision,
-        cell_data: &Self::CellDataType,
-    ) -> Vec<GroupRowsChangesetPB> {
-        let mut changesets = vec![];
-        self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| {
-            remove_row(group, &mut changesets, cell_data, row_rev);
-        });
-        changesets
-    }
-
-    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> {
-        let mut group_changeset = vec![];
-        self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| {
-            move_row(
-                group,
-                &mut group_changeset,
-                field_rev,
-                row_rev,
-                row_changeset,
-                cell_data,
-                to_row_id,
-            );
-        });
-        group_changeset
-    }
-}
-
-impl GroupController for SingleSelectGroupController {
-    fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) {
-        let group: Option<&mut Group> = self.groups_map.get_mut(group_id);
-        match group {
-            None => {}
-            Some(group) => {
-                let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
-                row_rev.cells.insert(field_rev.id.clone(), cell_rev);
-                group.add_row(RowPB::from(row_rev));
-            }
-        }
-    }
-}
-
-pub struct SingleSelectGroupGenerator();
-impl GroupGenerator for SingleSelectGroupGenerator {
-    type ConfigurationType = SelectOptionGroupConfigurationPB;
-    type TypeOptionType = SingleSelectTypeOptionPB;
-    fn generate_groups(
-        field_id: &str,
-        _configuration: &Option<Self::ConfigurationType>,
-        type_option: &Option<Self::TypeOptionType>,
-    ) -> Vec<Group> {
-        match type_option {
-            None => vec![],
-            Some(type_option) => type_option
-                .options
-                .iter()
-                .map(|option| {
-                    Group::new(
-                        option.id.clone(),
-                        field_id.to_owned(),
-                        option.name.clone(),
-                        option.id.clone(),
-                    )
-                })
-                .collect(),
-        }
-    }
-}
-
-// MultiSelect
-pub type MultiSelectGroupController = GenericGroupController<
-    SelectOptionGroupConfigurationPB,
-    MultiSelectTypeOptionPB,
-    MultiSelectGroupGenerator,
-    SelectOptionCellDataParser,
->;
-
-impl Groupable for MultiSelectGroupController {
-    type CellDataType = SelectOptionCellDataPB;
-
-    fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
-        cell_data.select_options.iter().any(|option| option.id == content)
-    }
-
-    fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB> {
-        let mut changesets = vec![];
-        self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| {
-            add_row(group, &mut changesets, cell_data, row_rev);
-        });
-        changesets
-    }
-
-    fn remove_row_if_match(
-        &mut self,
-        row_rev: &RowRevision,
-        cell_data: &Self::CellDataType,
-    ) -> Vec<GroupRowsChangesetPB> {
-        let mut changesets = vec![];
-        self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| {
-            remove_row(group, &mut changesets, cell_data, row_rev);
-        });
-        changesets
-    }
-
-    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> {
-        let mut group_changeset = vec![];
-        self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| {
-            move_row(
-                group,
-                &mut group_changeset,
-                field_rev,
-                row_rev,
-                row_changeset,
-                cell_data,
-                to_row_id,
-            );
-        });
-        group_changeset
-    }
-}
-
-impl GroupController for MultiSelectGroupController {
-    fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) {
-        let group: Option<&Group> = self.groups_map.get(group_id);
-        match group {
-            None => tracing::warn!("Can not find the group: {}", group_id),
-            Some(group) => {
-                let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
-                row_rev.cells.insert(field_rev.id.clone(), cell_rev);
-            }
-        }
-    }
-}
-
-pub struct MultiSelectGroupGenerator();
-impl GroupGenerator for MultiSelectGroupGenerator {
-    type ConfigurationType = SelectOptionGroupConfigurationPB;
-    type TypeOptionType = MultiSelectTypeOptionPB;
-
-    fn generate_groups(
-        field_id: &str,
-        _configuration: &Option<Self::ConfigurationType>,
-        type_option: &Option<Self::TypeOptionType>,
-    ) -> Vec<Group> {
-        match type_option {
-            None => vec![],
-            Some(type_option) => type_option
-                .options
-                .iter()
-                .map(|option| {
-                    Group::new(
-                        option.id.clone(),
-                        field_id.to_owned(),
-                        option.name.clone(),
-                        option.id.clone(),
-                    )
-                })
-                .collect(),
-        }
-    }
-}
-
-fn add_row(
-    group: &mut Group,
-    changesets: &mut Vec<GroupRowsChangesetPB>,
-    cell_data: &SelectOptionCellDataPB,
-    row_rev: &RowRevision,
-) {
-    cell_data.select_options.iter().for_each(|option| {
-        if option.id == group.id {
-            if !group.contains_row(&row_rev.id) {
-                let row_pb = RowPB::from(row_rev);
-                changesets.push(GroupRowsChangesetPB::insert(
-                    group.id.clone(),
-                    vec![InsertedRowPB::new(row_pb.clone())],
-                ));
-                group.add_row(row_pb);
-            }
-        } else 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);
-        }
-    });
-}
-
-fn remove_row(
-    group: &mut Group,
-    changesets: &mut Vec<GroupRowsChangesetPB>,
-    cell_data: &SelectOptionCellDataPB,
-    row_rev: &RowRevision,
-) {
-    cell_data.select_options.iter().for_each(|option| {
-        if option.id == group.id && group.contains_row(&row_rev.id) {
-            changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()]));
-            group.remove_row(&row_rev.id);
-        }
-    });
-}
-
-fn move_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.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);
-
-        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);
-        }
-
-        // 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 {
-                group.add_row(row_pb);
-            } else {
-                group.insert_row(to_index, row_pb);
-            }
-        }
-
-        // 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.
-            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);
-        }
-    });
-}

+ 141 - 91
frontend/rust-lib/flowy-grid/src/services/group/group_service.rs

@@ -1,43 +1,49 @@
-use crate::entities::{
-    CheckboxGroupConfigurationPB, DateGroupConfigurationPB, FieldType, GroupRowsChangesetPB,
-    NumberGroupConfigurationPB, SelectOptionGroupConfigurationPB, TextGroupConfigurationPB, UrlGroupConfigurationPB,
-};
+use crate::entities::{FieldType, GroupRowsChangesetPB};
+use crate::services::group::configuration::GroupConfigurationReader;
+use crate::services::group::controller::{GroupController, MoveGroupRowContext};
 use crate::services::group::{
-    CheckboxGroupController, Group, GroupController, MultiSelectGroupController, SingleSelectGroupController,
+    CheckboxGroupConfiguration, CheckboxGroupController, Group, GroupConfigurationWriter, MultiSelectGroupController,
+    SelectOptionGroupConfiguration, SingleSelectGroupController,
 };
-use bytes::Bytes;
 use flowy_error::FlowyResult;
 use flowy_grid_data_model::revision::{
-    gen_grid_group_id, FieldRevision, GroupConfigurationRevision, RowChangeset, RowRevision,
+    CheckboxGroupConfigurationRevision, DateGroupConfigurationRevision, FieldRevision, GroupConfigurationRevision,
+    NumberGroupConfigurationRevision, RowChangeset, RowRevision, SelectOptionGroupConfigurationRevision,
+    TextGroupConfigurationRevision, UrlGroupConfigurationRevision,
 };
-use lib_infra::future::AFFuture;
 use std::future::Future;
 use std::sync::Arc;
-use tokio::sync::RwLock;
-
-pub trait GroupConfigurationDelegate: Send + Sync + 'static {
-    fn get_group_configuration(&self, field_rev: Arc<FieldRevision>) -> AFFuture<GroupConfigurationRevision>;
-}
 
 pub(crate) struct GroupService {
-    delegate: Box<dyn GroupConfigurationDelegate>,
-    group_controller: Option<Arc<RwLock<dyn GroupController>>>,
+    configuration_reader: Arc<dyn GroupConfigurationReader>,
+    configuration_writer: Arc<dyn GroupConfigurationWriter>,
+    group_controller: Option<Box<dyn GroupController>>,
 }
 
 impl GroupService {
-    pub(crate) async fn new(delegate: Box<dyn GroupConfigurationDelegate>) -> Self {
+    pub(crate) async fn new<R, W>(configuration_reader: R, configuration_writer: W) -> Self
+    where
+        R: GroupConfigurationReader,
+        W: GroupConfigurationWriter,
+    {
         Self {
-            delegate,
+            configuration_reader: Arc::new(configuration_reader),
+            configuration_writer: Arc::new(configuration_writer),
             group_controller: None,
         }
     }
 
     pub(crate) async fn groups(&self) -> Vec<Group> {
-        if let Some(group_action_handler) = self.group_controller.as_ref() {
-            group_action_handler.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)> {
+        self.group_controller
+            .as_ref()
+            .and_then(|group_controller| group_controller.get_group(group_id))
     }
 
     pub(crate) async fn load_groups(
@@ -47,37 +53,37 @@ impl GroupService {
     ) -> Option<Vec<Group>> {
         let field_rev = find_group_field(field_revs)?;
         let field_type: FieldType = field_rev.ty.into();
-        let configuration = self.delegate.get_group_configuration(field_rev.clone()).await;
-        match self
-            .build_groups(&field_type, &field_rev, row_revs, configuration)
-            .await
-        {
-            Ok(groups) => Some(groups),
-            Err(_) => None,
-        }
+
+        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>>
@@ -85,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);
@@ -98,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);
@@ -128,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>>
@@ -136,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);
@@ -149,14 +159,24 @@ impl GroupService {
         }
     }
 
-    #[tracing::instrument(level = "trace", skip_all, err)]
-    async fn build_groups(
-        &mut self,
+    #[tracing::instrument(level = "trace", skip_all)]
+    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.move_group(from_group_id, to_group_id)?;
+                Ok(())
+            }
+        }
+    }
+
+    #[tracing::instrument(level = "trace", skip(self, field_rev), err)]
+    async fn make_group_controller(
+        &self,
         field_type: &FieldType,
         field_rev: &Arc<FieldRevision>,
-        row_revs: Vec<Arc<RowRevision>>,
-        configuration: GroupConfigurationRevision,
-    ) -> FlowyResult<Vec<Group>> {
+    ) -> 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);
@@ -168,31 +188,40 @@ impl GroupService {
                 // let generator = GroupGenerator::<DateGroupConfigurationPB>::from_configuration(configuration);
             }
             FieldType::SingleSelect => {
-                let controller = SingleSelectGroupController::new(field_rev, configuration)?;
-                self.group_controller = Some(Arc::new(RwLock::new(controller)));
+                let configuration = SelectOptionGroupConfiguration::new(
+                    field_rev.clone(),
+                    self.configuration_reader.clone(),
+                    self.configuration_writer.clone(),
+                )
+                .await?;
+                let controller = SingleSelectGroupController::new(field_rev, configuration).await?;
+                group_controller = Some(Box::new(controller));
             }
             FieldType::MultiSelect => {
-                let controller = MultiSelectGroupController::new(field_rev, configuration)?;
-                self.group_controller = Some(Arc::new(RwLock::new(controller)));
+                let configuration = SelectOptionGroupConfiguration::new(
+                    field_rev.clone(),
+                    self.configuration_reader.clone(),
+                    self.configuration_writer.clone(),
+                )
+                .await?;
+                let controller = MultiSelectGroupController::new(field_rev, configuration).await?;
+                group_controller = Some(Box::new(controller));
             }
             FieldType::Checkbox => {
-                let controller = CheckboxGroupController::new(field_rev, configuration)?;
-                self.group_controller = Some(Arc::new(RwLock::new(controller)));
+                let configuration = CheckboxGroupConfiguration::new(
+                    field_rev.clone(),
+                    self.configuration_reader.clone(),
+                    self.configuration_writer.clone(),
+                )
+                .await?;
+                let controller = CheckboxGroupController::new(field_rev, configuration).await?;
+                group_controller = Some(Box::new(controller));
             }
             FieldType::URL => {
                 // let generator = GroupGenerator::<UrlGroupConfigurationPB>::from_configuration(configuration);
             }
-        };
-
-        let mut groups = vec![];
-        if let Some(group_action_handler) = self.group_controller.as_ref() {
-            let mut write_guard = group_action_handler.write().await;
-            let _ = write_guard.group_rows(&row_revs, field_rev)?;
-            groups = write_guard.groups();
-            drop(write_guard);
         }
-
-        Ok(groups)
+        Ok(group_controller)
     }
 }
 
@@ -208,20 +237,41 @@ fn find_group_field(field_revs: &[Arc<FieldRevision>]) -> Option<Arc<FieldRevisi
 }
 
 pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurationRevision {
+    let field_id = field_rev.id.clone();
+    let field_type_rev = field_rev.ty;
     let field_type: FieldType = field_rev.ty.into();
-    let bytes: Bytes = match field_type {
-        FieldType::RichText => TextGroupConfigurationPB::default().try_into().unwrap(),
-        FieldType::Number => NumberGroupConfigurationPB::default().try_into().unwrap(),
-        FieldType::DateTime => DateGroupConfigurationPB::default().try_into().unwrap(),
-        FieldType::SingleSelect => SelectOptionGroupConfigurationPB::default().try_into().unwrap(),
-        FieldType::MultiSelect => SelectOptionGroupConfigurationPB::default().try_into().unwrap(),
-        FieldType::Checkbox => CheckboxGroupConfigurationPB::default().try_into().unwrap(),
-        FieldType::URL => UrlGroupConfigurationPB::default().try_into().unwrap(),
-    };
-    GroupConfigurationRevision {
-        id: gen_grid_group_id(),
-        field_id: field_rev.id.clone(),
-        field_type_rev: field_rev.ty,
-        content: Some(bytes.to_vec()),
+    match field_type {
+        FieldType::RichText => {
+            GroupConfigurationRevision::new(field_id, field_type_rev, TextGroupConfigurationRevision::default())
+                .unwrap()
+        }
+        FieldType::Number => {
+            GroupConfigurationRevision::new(field_id, field_type_rev, NumberGroupConfigurationRevision::default())
+                .unwrap()
+        }
+        FieldType::DateTime => {
+            GroupConfigurationRevision::new(field_id, field_type_rev, DateGroupConfigurationRevision::default())
+                .unwrap()
+        }
+
+        FieldType::SingleSelect => GroupConfigurationRevision::new(
+            field_id,
+            field_type_rev,
+            SelectOptionGroupConfigurationRevision::default(),
+        )
+        .unwrap(),
+        FieldType::MultiSelect => GroupConfigurationRevision::new(
+            field_id,
+            field_type_rev,
+            SelectOptionGroupConfigurationRevision::default(),
+        )
+        .unwrap(),
+        FieldType::Checkbox => {
+            GroupConfigurationRevision::new(field_id, field_type_rev, CheckboxGroupConfigurationRevision::default())
+                .unwrap()
+        }
+        FieldType::URL => {
+            GroupConfigurationRevision::new(field_id, field_type_rev, UrlGroupConfigurationRevision::default()).unwrap()
+        }
     }
 }

+ 8 - 2
frontend/rust-lib/flowy-grid/src/services/group/mod.rs

@@ -1,5 +1,11 @@
-mod group_generator;
+mod action;
+mod configuration;
+mod controller;
+mod controller_impls;
+mod entities;
 mod group_service;
 
-pub(crate) use group_generator::*;
+pub(crate) use configuration::*;
+pub(crate) use controller_impls::*;
+pub(crate) use entities::*;
 pub(crate) use group_service::*;

+ 2 - 50
frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs

@@ -1,11 +1,4 @@
-use crate::entities::{
-    GridLayout, GridLayoutPB, GridSettingPB, RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB,
-    RepeatedGridSortPB,
-};
-use flowy_grid_data_model::revision::{FieldRevision, SettingRevision};
-use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, GridSettingChangesetParams};
-use std::collections::HashMap;
-use std::sync::Arc;
+use crate::entities::{CreateFilterParams, DeleteFilterParams, GridLayout, GridSettingChangesetParams};
 
 pub struct GridSettingChangesetBuilder {
     params: GridSettingChangesetParams,
@@ -20,13 +13,11 @@ impl GridSettingChangesetBuilder {
             delete_filter: None,
             insert_group: None,
             delete_group: None,
-            insert_sort: None,
-            delete_sort: None,
         };
         Self { params }
     }
 
-    pub fn insert_filter(mut self, params: CreateGridFilterParams) -> Self {
+    pub fn insert_filter(mut self, params: CreateFilterParams) -> Self {
         self.params.insert_filter = Some(params);
         self
     }
@@ -40,42 +31,3 @@ impl GridSettingChangesetBuilder {
         self.params
     }
 }
-
-pub fn make_grid_setting(grid_setting_rev: &SettingRevision, field_revs: &[Arc<FieldRevision>]) -> GridSettingPB {
-    let current_layout_type: GridLayout = grid_setting_rev.layout.clone().into();
-    let filters_by_field_id = grid_setting_rev
-        .get_all_filters(field_revs)
-        .map(|filters_by_field_id| {
-            filters_by_field_id
-                .into_iter()
-                .map(|(k, v)| (k, v.into()))
-                .collect::<HashMap<String, RepeatedGridConfigurationFilterPB>>()
-        })
-        .unwrap_or_default();
-    let groups_by_field_id = grid_setting_rev
-        .get_all_groups(field_revs)
-        .map(|groups_by_field_id| {
-            groups_by_field_id
-                .into_iter()
-                .map(|(k, v)| (k, v.into()))
-                .collect::<HashMap<String, RepeatedGridGroupConfigurationPB>>()
-        })
-        .unwrap_or_default();
-    let sorts_by_field_id = grid_setting_rev
-        .get_all_sort()
-        .map(|sorts_by_field_id| {
-            sorts_by_field_id
-                .into_iter()
-                .map(|(k, v)| (k, v.into()))
-                .collect::<HashMap<String, RepeatedGridSortPB>>()
-        })
-        .unwrap_or_default();
-
-    GridSettingPB {
-        layouts: GridLayoutPB::all(),
-        current_layout_type,
-        filter_configuration_by_field_id: filters_by_field_id,
-        group_configuration_by_field_id: groups_by_field_id,
-        sorts_by_field_id,
-    }
-}

+ 1 - 2
frontend/rust-lib/flowy-grid/tests/grid/field_test/script.rs

@@ -1,7 +1,6 @@
 use crate::grid::grid_editor::GridEditorTest;
-use flowy_grid::entities::InsertFieldParams;
+use flowy_grid::entities::{FieldChangesetParams, InsertFieldParams};
 use flowy_grid_data_model::revision::FieldRevision;
-use flowy_sync::entities::grid::FieldChangesetParams;
 
 pub enum FieldScript {
     CreateField {

+ 1 - 1
frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs

@@ -1,10 +1,10 @@
 use crate::grid::field_test::script::FieldScript::*;
 use crate::grid::field_test::script::GridFieldTest;
 use crate::grid::field_test::util::*;
+use flowy_grid::entities::FieldChangesetParams;
 use flowy_grid::services::field::selection_type_option::SelectOptionPB;
 use flowy_grid::services::field::SingleSelectTypeOptionPB;
 use flowy_grid_data_model::revision::TypeOptionDataEntry;
-use flowy_sync::entities::grid::FieldChangesetParams;
 
 #[tokio::test]
 async fn grid_create_field() {

+ 6 - 20
frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs

@@ -3,17 +3,12 @@
 #![allow(dead_code)]
 #![allow(unused_imports)]
 
-use flowy_grid::entities::{CreateGridFilterPayloadPB, GridLayout, GridSettingPB};
+use flowy_grid::entities::{CreateFilterParams, CreateGridFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB};
 use flowy_grid::services::setting::GridSettingChangesetBuilder;
 use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision};
-use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, GridSettingChangesetParams};
 use crate::grid::grid_editor::GridEditorTest;
 
 pub enum FilterScript {
-    #[allow(dead_code)]
-    UpdateGridSetting {
-        params: GridSettingChangesetParams,
-    },
     InsertGridTableFilter {
         payload: CreateGridFilterPayloadPB,
     },
@@ -50,27 +45,18 @@ impl GridFilterTest {
 
     pub async fn run_script(&mut self, script: FilterScript) {
         match script {
-            FilterScript::UpdateGridSetting { params } => {
-                let _ = self.editor.update_grid_setting(params).await.unwrap();
-            }
+           
             FilterScript::InsertGridTableFilter { payload } => {
-                let params: CreateGridFilterParams = payload.try_into().unwrap();
-                let layout_type = GridLayout::Table;
-                let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type)
-                    .insert_filter(params)
-                    .build();
-                let _ = self.editor.update_grid_setting(params).await.unwrap();
+                let params: CreateFilterParams = payload.try_into().unwrap();
+                let _ = self.editor.update_filter(params).await.unwrap();
             }
             FilterScript::AssertTableFilterCount { count } => {
                 let filters = self.editor.get_grid_filter().await.unwrap();
                 assert_eq!(count as usize, filters.len());
             }
             FilterScript::DeleteGridTableFilter { filter_id, field_rev} => {
-                let layout_type = GridLayout::Table;
-                let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type)
-                    .delete_filter(DeleteFilterParams { field_id: field_rev.id, filter_id, field_type_rev: field_rev.ty })
-                    .build();
-                let _ = self.editor.update_grid_setting(params).await.unwrap();
+                let params = DeleteFilterParams { field_id: field_rev.id, filter_id, field_type_rev: field_rev.ty };
+                let _ = self.editor.delete_filter(params).await.unwrap();
             }
             FilterScript::AssertGridSetting { expected_setting } => {
                 let setting = self.editor.get_grid_setting().await.unwrap();

+ 0 - 3
frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs

@@ -12,9 +12,6 @@ use flowy_grid::services::setting::GridSettingChangesetBuilder;
 use flowy_grid_data_model::revision::*;
 use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
 use flowy_sync::client_grid::GridBuilder;
-use flowy_sync::entities::grid::{
-    CreateGridFilterParams, DeleteFilterParams, FieldChangesetParams, GridSettingChangesetParams,
-};
 use flowy_test::helper::ViewTest;
 use flowy_test::FlowySDKTest;
 use std::collections::HashMap;

+ 40 - 8
frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs

@@ -1,14 +1,20 @@
 use crate::grid::grid_editor::GridEditorTest;
-use flowy_grid::entities::{CreateRowParams, FieldType, GridLayout, GroupPB, 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;
 
 pub enum GroupScript {
-    AssertGroup {
+    AssertGroupRowCount {
         group_index: usize,
         row_count: usize,
     },
     AssertGroupCount(usize),
+    AssertGroup {
+        group_index: usize,
+        expected_group: GroupPB,
+    },
     AssertRow {
         group_index: usize,
         row_index: usize,
@@ -32,6 +38,10 @@ pub enum GroupScript {
         row_index: usize,
         to_group_index: usize,
     },
+    MoveGroup {
+        from_group_index: usize,
+        to_group_index: usize,
+    },
 }
 
 pub struct GridGroupTest {
@@ -52,7 +62,7 @@ impl GridGroupTest {
 
     pub async fn run_script(&mut self, script: GroupScript) {
         match script {
-            GroupScript::AssertGroup { group_index, row_count } => {
+            GroupScript::AssertGroupRowCount { group_index, row_count } => {
                 assert_eq!(row_count, self.group_at_index(group_index).await.rows.len());
             }
             GroupScript::AssertGroupCount(count) => {
@@ -67,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,
@@ -84,7 +96,6 @@ impl GridGroupTest {
                 //
                 let group = self.group_at_index(group_index).await;
                 let compare_row = group.rows.get(row_index).unwrap().clone();
-
                 assert_eq!(row.id, compare_row.id);
             }
             GroupScript::CreateRow { group_index } => {
@@ -125,6 +136,27 @@ impl GridGroupTest {
                 row_changeset.cell_by_field_id.insert(field_id, cell_rev);
                 self.editor.update_row(row_changeset).await.unwrap();
             }
+            GroupScript::MoveGroup {
+                from_group_index,
+                to_group_index,
+            } => {
+                let from_group = self.group_at_index(from_group_index).await;
+                let to_group = self.group_at_index(to_group_index).await;
+                let params = MoveGroupParams {
+                    view_id: self.editor.grid_id.clone(),
+                    from_group_id: from_group.group_id,
+                    to_group_id: to_group.group_id,
+                };
+                self.editor.move_group(params).await.unwrap();
+                //
+            }
+            GroupScript::AssertGroup {
+                group_index,
+                expected_group: group_pb,
+            } => {
+                let group = self.group_at_index(group_index).await;
+                assert_eq!(group.group_id, group_pb.group_id);
+            }
         }
     }
 

+ 140 - 25
frontend/rust-lib/flowy-grid/tests/grid/group_test/test.rs

@@ -2,19 +2,19 @@ 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),
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 0,
             row_count: 2,
         },
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 1,
             row_count: 2,
         },
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 2,
             row_count: 1,
         },
@@ -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![
@@ -34,7 +34,7 @@ async fn board_move_row_test() {
             to_group_index: 0,
             to_row_index: 1,
         },
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 0,
             row_count: 2,
         },
@@ -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![
@@ -58,11 +58,11 @@ async fn board_move_row_to_other_group_test() {
             to_group_index: 1,
             to_row_index: 1,
         },
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 0,
             row_count: 1,
         },
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 1,
             row_count: 3,
         },
@@ -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,33 +86,126 @@ 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 },
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 0,
             row_count: 3,
         },
         CreateRow { group_index: 1 },
         CreateRow { group_index: 1 },
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 1,
             row_count: 4,
         },
@@ -121,14 +214,14 @@ 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 {
             group_index: 0,
             row_index: 0,
         },
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 0,
             row_count: 1,
         },
@@ -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 {
@@ -148,7 +241,7 @@ async fn board_delete_all_row_test() {
             group_index: 0,
             row_index: 0,
         },
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 0,
             row_count: 0,
         },
@@ -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
@@ -166,11 +259,11 @@ async fn board_update_row_test() {
             row_index: 0,
             to_group_index: 1,
         },
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 0,
             row_count: 1,
         },
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 1,
             row_count: 3,
         },
@@ -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
@@ -188,14 +281,36 @@ async fn board_reorder_group_test() {
             row_index: 0,
             to_group_index: 1,
         },
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 0,
             row_count: 1,
         },
-        AssertGroup {
+        AssertGroupRowCount {
             group_index: 1,
             row_count: 3,
         },
     ];
     test.run_scripts(scripts).await;
 }
+
+#[tokio::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;
+    let scripts = vec![
+        MoveGroup {
+            from_group_index: 0,
+            to_group_index: 1,
+        },
+        AssertGroup {
+            group_index: 0,
+            expected_group: group_1,
+        },
+        AssertGroup {
+            group_index: 1,
+            expected_group: group_0,
+        },
+    ];
+    test.run_scripts(scripts).await;
+}

+ 1 - 1
frontend/rust-lib/flowy-sdk/src/lib.rs

@@ -74,7 +74,7 @@ fn crate_log_filter(level: String) -> String {
     filters.push(format!("lib_ot={}", level));
     filters.push(format!("lib_ws={}", level));
     filters.push(format!("lib_infra={}", level));
-    // filters.push(format!("flowy_sync={}", level));
+    filters.push(format!("flowy_sync={}", level));
     // filters.push(format!("flowy_revision={}", level));
     // filters.push(format!("lib_dispatch={}", level));
 

+ 3 - 0
shared-lib/flowy-error-code/src/code.rs

@@ -125,6 +125,9 @@ pub enum ErrorCode {
 
     #[display(fmt = "Invalid data")]
     InvalidData = 1000,
+
+    #[display(fmt = "Out of bounds")]
+    OutOfBounds = 10001,
 }
 
 impl ErrorCode {

+ 9 - 0
shared-lib/flowy-grid-data-model/src/revision/filter_rev.rs

@@ -0,0 +1,9 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
+pub struct FilterConfigurationRevision {
+    pub id: String,
+    pub field_id: String,
+    pub condition: u8,
+    pub content: Option<String>,
+}

+ 14 - 137
shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs

@@ -1,8 +1,7 @@
-use crate::revision::{FieldRevision, FieldTypeRevision};
+use crate::revision::{FieldRevision, FieldTypeRevision, FilterConfigurationRevision, GroupConfigurationRevision};
 use indexmap::IndexMap;
 use nanoid::nanoid;
 use serde::{Deserialize, Serialize};
-use serde_repr::*;
 use std::collections::HashMap;
 use std::fmt::Debug;
 use std::sync::Arc;
@@ -15,6 +14,7 @@ pub fn gen_grid_group_id() -> String {
     nanoid!(6)
 }
 
+#[allow(dead_code)]
 pub fn gen_grid_sort_id() -> String {
     nanoid!(6)
 }
@@ -24,120 +24,12 @@ pub type FilterConfigurationsByFieldId = HashMap<String, Vec<Arc<FilterConfigura
 //
 pub type GroupConfiguration = Configuration<GroupConfigurationRevision>;
 pub type GroupConfigurationsByFieldId = HashMap<String, Vec<Arc<GroupConfigurationRevision>>>;
-//
-pub type SortConfiguration = Configuration<SortConfigurationRevision>;
-pub type SortConfigurationsByFieldId = HashMap<String, Vec<Arc<SortConfigurationRevision>>>;
-
-#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)]
-pub struct SettingRevision {
-    pub layout: LayoutRevision,
-
-    pub filters: FilterConfiguration,
-
-    #[serde(default)]
-    pub groups: GroupConfiguration,
-
-    #[serde(skip)]
-    pub sorts: SortConfiguration,
-}
-
-#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize_repr, Deserialize_repr)]
-#[repr(u8)]
-pub enum LayoutRevision {
-    Table = 0,
-    Board = 1,
-}
-
-impl ToString for LayoutRevision {
-    fn to_string(&self) -> String {
-        let layout_rev = self.clone() as u8;
-        layout_rev.to_string()
-    }
-}
-
-impl std::default::Default for LayoutRevision {
-    fn default() -> Self {
-        LayoutRevision::Table
-    }
-}
-
-impl SettingRevision {
-    pub fn get_all_groups(&self, field_revs: &[Arc<FieldRevision>]) -> Option<GroupConfigurationsByFieldId> {
-        self.groups.get_all_objects(field_revs)
-    }
-
-    pub fn get_groups(
-        &self,
-        field_id: &str,
-        field_type_rev: &FieldTypeRevision,
-    ) -> Option<Vec<Arc<GroupConfigurationRevision>>> {
-        self.groups.get_objects(field_id, field_type_rev)
-    }
-
-    pub fn get_mut_groups(
-        &mut self,
-        field_id: &str,
-        field_type: &FieldTypeRevision,
-    ) -> Option<&mut Vec<Arc<GroupConfigurationRevision>>> {
-        self.groups.get_mut_objects(field_id, field_type)
-    }
 
-    pub fn insert_group(
-        &mut self,
-        field_id: &str,
-        field_type: &FieldTypeRevision,
-        group_rev: GroupConfigurationRevision,
-    ) {
-        // only one group can be set
-        self.groups.remove_all();
-        self.groups.insert_object(field_id, field_type, group_rev);
-    }
-
-    pub fn get_all_filters(&self, field_revs: &[Arc<FieldRevision>]) -> Option<FilterConfigurationsByFieldId> {
-        self.filters.get_all_objects(field_revs)
-    }
-
-    pub fn get_filters(
-        &self,
-        field_id: &str,
-        field_type_rev: &FieldTypeRevision,
-    ) -> Option<Vec<Arc<FilterConfigurationRevision>>> {
-        self.filters.get_objects(field_id, field_type_rev)
-    }
-
-    pub fn get_mut_filters(
-        &mut self,
-        field_id: &str,
-        field_type: &FieldTypeRevision,
-    ) -> Option<&mut Vec<Arc<FilterConfigurationRevision>>> {
-        self.filters.get_mut_objects(field_id, field_type)
-    }
-
-    pub fn insert_filter(
-        &mut self,
-        field_id: &str,
-        field_type: &FieldTypeRevision,
-        filter_rev: FilterConfigurationRevision,
-    ) {
-        self.filters.insert_object(field_id, field_type, filter_rev);
-    }
-
-    pub fn get_all_sort(&self) -> Option<SortConfigurationsByFieldId> {
-        None
-    }
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
-pub struct SortConfigurationRevision {
-    pub id: String,
-    pub field_id: Option<String>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)]
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
 #[serde(transparent)]
 pub struct Configuration<T>
 where
-    T: Debug + Clone + Default + Eq + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
+    T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static,
 {
     /// Key:    field_id
     /// Value:  this value contains key/value.
@@ -149,7 +41,7 @@ where
 
 impl<T> Configuration<T>
 where
-    T: Debug + Clone + Default + Eq + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
+    T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static,
 {
     pub fn get_mut_objects(&mut self, field_id: &str, field_type: &FieldTypeRevision) -> Option<&mut Vec<Arc<T>>> {
         let value = self
@@ -157,7 +49,7 @@ where
             .get_mut(field_id)
             .and_then(|object_rev_map| object_rev_map.get_mut(field_type));
         if value.is_none() {
-            tracing::warn!("Can't find the {:?} with", std::any::type_name::<T>());
+            tracing::warn!("[Configuration] Can't find the {:?} with", std::any::type_name::<T>());
         }
         value
     }
@@ -184,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())
@@ -196,16 +89,16 @@ where
             .push(Arc::new(object))
     }
 
-    pub fn remove_all(&mut self) {
+    pub fn clear(&mut self) {
         self.inner.clear()
     }
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)]
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
 #[serde(transparent)]
 pub struct ObjectIndexMap<T>
 where
-    T: Debug + Clone + Default + Eq + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
+    T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static,
 {
     #[serde(with = "indexmap::serde_seq")]
     pub object_by_field_type: IndexMap<FieldTypeRevision, Vec<Arc<T>>>,
@@ -213,7 +106,7 @@ where
 
 impl<T> ObjectIndexMap<T>
 where
-    T: Debug + Clone + Default + Eq + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
+    T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static,
 {
     pub fn new() -> Self {
         ObjectIndexMap::default()
@@ -222,7 +115,7 @@ where
 
 impl<T> std::ops::Deref for ObjectIndexMap<T>
 where
-    T: Debug + Clone + Default + Eq + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
+    T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static,
 {
     type Target = IndexMap<FieldTypeRevision, Vec<Arc<T>>>;
 
@@ -233,25 +126,9 @@ where
 
 impl<T> std::ops::DerefMut for ObjectIndexMap<T>
 where
-    T: Debug + Clone + Default + Eq + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
+    T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static,
 {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.object_by_field_type
     }
 }
-
-#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
-pub struct GroupConfigurationRevision {
-    pub id: String,
-    pub field_id: String,
-    pub field_type_rev: FieldTypeRevision,
-    pub content: Option<Vec<u8>>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
-pub struct FilterConfigurationRevision {
-    pub id: String,
-    pub field_id: String,
-    pub condition: u8,
-    pub content: Option<String>,
-}

+ 57 - 8
shared-lib/flowy-grid-data-model/src/revision/grid_view.rs

@@ -1,24 +1,50 @@
-use crate::revision::SettingRevision;
+use crate::revision::{FilterConfiguration, GroupConfiguration};
 use nanoid::nanoid;
 use serde::{Deserialize, Serialize};
+use serde_repr::*;
 
 #[allow(dead_code)]
 pub fn gen_grid_view_id() -> String {
     nanoid!(6)
 }
 
+#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize_repr, Deserialize_repr)]
+#[repr(u8)]
+pub enum LayoutRevision {
+    Table = 0,
+    Board = 1,
+}
+
+impl ToString for LayoutRevision {
+    fn to_string(&self) -> String {
+        let layout_rev = self.clone() as u8;
+        layout_rev.to_string()
+    }
+}
+
+impl std::default::Default for LayoutRevision {
+    fn default() -> Self {
+        LayoutRevision::Table
+    }
+}
+
 #[derive(Debug, Clone, Default, Serialize, Deserialize)]
 pub struct GridViewRevision {
     pub view_id: String,
 
     pub grid_id: String,
 
-    pub setting: SettingRevision,
+    pub layout: LayoutRevision,
+
+    #[serde(default)]
+    pub filters: FilterConfiguration,
 
-    // For the moment, we just use the order returned from the GridRevision
-    #[allow(dead_code)]
-    #[serde(skip, rename = "row")]
-    pub row_orders: Vec<RowOrderRevision>,
+    #[serde(default)]
+    pub groups: GroupConfiguration,
+    // // For the moment, we just use the order returned from the GridRevision
+    // #[allow(dead_code)]
+    // #[serde(skip, rename = "rows")]
+    // pub row_orders: Vec<RowOrderRevision>,
 }
 
 impl GridViewRevision {
@@ -26,8 +52,10 @@ impl GridViewRevision {
         GridViewRevision {
             view_id,
             grid_id,
-            setting: Default::default(),
-            row_orders: vec![],
+            layout: Default::default(),
+            filters: Default::default(),
+            groups: Default::default(),
+            // row_orders: vec![],
         }
     }
 }
@@ -36,3 +64,24 @@ impl GridViewRevision {
 pub struct RowOrderRevision {
     pub row_id: String,
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::revision::GridViewRevision;
+
+    #[test]
+    fn grid_view_revision_serde_test() {
+        let grid_view_revision = GridViewRevision {
+            view_id: "1".to_string(),
+            grid_id: "1".to_string(),
+            layout: Default::default(),
+            filters: Default::default(),
+            groups: Default::default(),
+        };
+        let s = serde_json::to_string(&grid_view_revision).unwrap();
+        assert_eq!(
+            s,
+            r#"{"view_id":"1","grid_id":"1","layout":0,"filters":[],"groups":[]}"#
+        );
+    }
+}

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

@@ -0,0 +1,181 @@
+use crate::revision::{gen_grid_group_id, FieldTypeRevision};
+use serde::{Deserialize, Serialize};
+use serde_json::Error;
+use serde_repr::*;
+
+pub trait GroupConfigurationContentSerde: Sized + Send + Sync {
+    fn from_configuration_content(s: &str) -> Result<Self, serde_json::Error>;
+
+    fn to_configuration_content(&self) -> Result<String, serde_json::Error>;
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct GroupConfigurationRevision {
+    pub id: String,
+    pub field_id: String,
+    pub field_type_rev: FieldTypeRevision,
+    pub groups: Vec<GroupRecordRevision>,
+    pub content: String,
+}
+
+impl GroupConfigurationRevision {
+    pub fn new<T>(field_id: String, field_type: FieldTypeRevision, content: T) -> Result<Self, serde_json::Error>
+    where
+        T: GroupConfigurationContentSerde,
+    {
+        let content = content.to_configuration_content()?;
+        Ok(Self {
+            id: gen_grid_group_id(),
+            field_id,
+            field_type_rev: field_type,
+            groups: vec![],
+            content,
+        })
+    }
+}
+
+#[derive(Default, Serialize, Deserialize)]
+pub struct TextGroupConfigurationRevision {
+    pub hide_empty: bool,
+}
+
+impl GroupConfigurationContentSerde for TextGroupConfigurationRevision {
+    fn from_configuration_content(s: &str) -> Result<Self, Error> {
+        serde_json::from_str(s)
+    }
+    fn to_configuration_content(&self) -> Result<String, Error> {
+        serde_json::to_string(self)
+    }
+}
+
+#[derive(Default, Serialize, Deserialize)]
+pub struct NumberGroupConfigurationRevision {
+    pub hide_empty: bool,
+}
+
+impl GroupConfigurationContentSerde for NumberGroupConfigurationRevision {
+    fn from_configuration_content(s: &str) -> Result<Self, Error> {
+        serde_json::from_str(s)
+    }
+    fn to_configuration_content(&self) -> Result<String, Error> {
+        serde_json::to_string(self)
+    }
+}
+
+#[derive(Default, Serialize, Deserialize)]
+pub struct UrlGroupConfigurationRevision {
+    pub hide_empty: bool,
+}
+
+impl GroupConfigurationContentSerde for UrlGroupConfigurationRevision {
+    fn from_configuration_content(s: &str) -> Result<Self, Error> {
+        serde_json::from_str(s)
+    }
+    fn to_configuration_content(&self) -> Result<String, Error> {
+        serde_json::to_string(self)
+    }
+}
+
+#[derive(Default, Serialize, Deserialize)]
+pub struct CheckboxGroupConfigurationRevision {
+    pub hide_empty: bool,
+}
+
+impl GroupConfigurationContentSerde for CheckboxGroupConfigurationRevision {
+    fn from_configuration_content(s: &str) -> Result<Self, Error> {
+        serde_json::from_str(s)
+    }
+
+    fn to_configuration_content(&self) -> Result<String, Error> {
+        serde_json::to_string(self)
+    }
+}
+
+#[derive(Default, Serialize, Deserialize)]
+pub struct SelectOptionGroupConfigurationRevision {
+    pub hide_empty: bool,
+}
+
+impl GroupConfigurationContentSerde for SelectOptionGroupConfigurationRevision {
+    fn from_configuration_content(s: &str) -> Result<Self, Error> {
+        serde_json::from_str(s)
+    }
+
+    fn to_configuration_content(&self) -> Result<String, Error> {
+        serde_json::to_string(self)
+    }
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize)]
+pub struct GroupRecordRevision {
+    pub group_id: String,
+
+    #[serde(default = "DEFAULT_GROUP_RECORD_VISIBILITY")]
+    pub visible: bool,
+}
+
+const DEFAULT_GROUP_RECORD_VISIBILITY: fn() -> bool = || true;
+
+impl GroupRecordRevision {
+    pub fn new(group_id: String) -> Self {
+        Self {
+            group_id,
+            visible: true,
+        }
+    }
+}
+
+#[derive(Default, Serialize, Deserialize)]
+pub struct DateGroupConfigurationRevision {
+    pub hide_empty: bool,
+    pub condition: DateCondition,
+}
+
+impl GroupConfigurationContentSerde for DateGroupConfigurationRevision {
+    fn from_configuration_content(s: &str) -> Result<Self, Error> {
+        serde_json::from_str(s)
+    }
+    fn to_configuration_content(&self) -> Result<String, Error> {
+        serde_json::to_string(self)
+    }
+}
+
+#[derive(Serialize_repr, Deserialize_repr)]
+#[repr(u8)]
+pub enum DateCondition {
+    Relative = 0,
+    Day = 1,
+    Week = 2,
+    Month = 3,
+    Year = 4,
+}
+
+impl std::default::Default for DateCondition {
+    fn default() -> Self {
+        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 - 0
shared-lib/flowy-grid-data-model/src/revision/mod.rs

@@ -1,9 +1,13 @@
+mod filter_rev;
 mod grid_block;
 mod grid_rev;
 mod grid_setting_rev;
 mod grid_view;
+mod group_rev;
 
+pub use filter_rev::*;
 pub use grid_block::*;
 pub use grid_rev::*;
 pub use grid_setting_rev::*;
 pub use grid_view::*;
+pub use group_rev::*;

+ 1 - 57
shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs

@@ -1,4 +1,3 @@
-use crate::entities::grid::FieldChangesetParams;
 use crate::entities::revision::{md5, RepeatedRevision, Revision};
 use crate::errors::{internal_error, CollaborateError, CollaborateResult};
 use crate::util::{cal_diff, make_text_delta_from_revisions};
@@ -162,61 +161,6 @@ impl GridRevisionPad {
         })
     }
 
-    pub fn update_field_rev<T: JsonDeserializer>(
-        &mut self,
-        changeset: FieldChangesetParams,
-        deserializer: T,
-    ) -> CollaborateResult<Option<GridRevisionChangeset>> {
-        let field_id = changeset.field_id.clone();
-        self.modify_field(&field_id, |field| {
-            let mut is_changed = None;
-            if let Some(name) = changeset.name {
-                field.name = name;
-                is_changed = Some(())
-            }
-
-            if let Some(desc) = changeset.desc {
-                field.desc = desc;
-                is_changed = Some(())
-            }
-
-            if let Some(field_type) = changeset.field_type {
-                field.ty = field_type;
-                is_changed = Some(())
-            }
-
-            if let Some(frozen) = changeset.frozen {
-                field.frozen = frozen;
-                is_changed = Some(())
-            }
-
-            if let Some(visibility) = changeset.visibility {
-                field.visibility = visibility;
-                is_changed = Some(())
-            }
-
-            if let Some(width) = changeset.width {
-                field.width = width;
-                is_changed = Some(())
-            }
-
-            if let Some(type_option_data) = changeset.type_option_data {
-                match deserializer.deserialize(type_option_data) {
-                    Ok(json_str) => {
-                        let field_type = field.ty;
-                        field.insert_type_option_str(&field_type, json_str);
-                        is_changed = Some(())
-                    }
-                    Err(err) => {
-                        tracing::error!("Deserialize data to type option json failed: {}", err);
-                    }
-                }
-            }
-
-            Ok(is_changed)
-        })
-    }
-
     pub fn get_field_rev(&self, field_id: &str) -> Option<(usize, &Arc<FieldRevision>)> {
         self.grid_rev
             .fields
@@ -399,7 +343,7 @@ impl GridRevisionPad {
         )
     }
 
-    fn modify_field<F>(&mut self, field_id: &str, f: F) -> CollaborateResult<Option<GridRevisionChangeset>>
+    pub fn modify_field<F>(&mut self, field_id: &str, f: F) -> CollaborateResult<Option<GridRevisionChangeset>>
     where
         F: FnOnce(&mut FieldRevision) -> CollaborateResult<Option<()>>,
     {

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

@@ -1,11 +1,9 @@
-use crate::entities::grid::{CreateGridFilterParams, CreateGridGroupParams, GridSettingChangesetParams};
 use crate::entities::revision::{md5, Revision};
 use crate::errors::{internal_error, CollaborateError, CollaborateResult};
 use crate::util::{cal_diff, make_text_delta_from_revisions};
 use flowy_grid_data_model::revision::{
-    gen_grid_filter_id, gen_grid_group_id, FieldRevision, FieldTypeRevision, FilterConfigurationRevision,
-    FilterConfigurationsByFieldId, GridViewRevision, GroupConfigurationRevision, GroupConfigurationsByFieldId,
-    SettingRevision, SortConfigurationsByFieldId,
+    FieldRevision, FieldTypeRevision, FilterConfigurationRevision, FilterConfigurationsByFieldId, GridViewRevision,
+    GroupConfigurationRevision, GroupConfigurationsByFieldId,
 };
 use lib_ot::core::{OperationTransform, PhantomAttributes, TextDelta, TextDeltaBuilder};
 use std::sync::Arc;
@@ -50,82 +48,49 @@ impl GridViewRevisionPad {
         Self::from_delta(delta)
     }
 
-    pub fn get_setting_rev(&self) -> &SettingRevision {
-        &self.view.setting
+    pub fn get_all_groups(&self, field_revs: &[Arc<FieldRevision>]) -> Option<GroupConfigurationsByFieldId> {
+        self.groups.get_all_objects(field_revs)
     }
 
-    pub fn update_setting(
+    #[tracing::instrument(level = "trace", skip_all, err)]
+    pub fn insert_group(
         &mut self,
-        changeset: GridSettingChangesetParams,
+        field_id: &str,
+        field_type: &FieldTypeRevision,
+        group_rev: GroupConfigurationRevision,
     ) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
         self.modify(|view| {
-            let mut is_changed = None;
-            if let Some(params) = changeset.insert_filter {
-                view.setting.filters.insert_object(
-                    &params.field_id,
-                    &params.field_type_rev,
-                    make_filter_revision(&params),
-                );
-                is_changed = Some(())
-            }
-            if let Some(params) = changeset.delete_filter {
-                if let Some(filters) = view
-                    .setting
-                    .filters
-                    .get_mut_objects(&params.field_id, &params.field_type_rev)
-                {
-                    filters.retain(|filter| filter.id != params.filter_id);
-                    is_changed = Some(())
-                }
-            }
-            if let Some(params) = changeset.insert_group {
-                view.setting.groups.remove_all();
-                view.setting.groups.insert_object(
-                    &params.field_id,
-                    &params.field_type_rev,
-                    make_group_revision(&params),
-                );
-
-                is_changed = Some(());
-            }
-            if let Some(params) = changeset.delete_group {
-                if let Some(groups) = view
-                    .setting
-                    .groups
-                    .get_mut_objects(&params.field_id, &params.field_type_rev)
-                {
-                    groups.retain(|group| group.id != params.group_id);
-                    is_changed = Some(());
-                }
-            }
-
-            Ok(is_changed)
+            // Only save one group
+            view.groups.clear();
+            view.groups.add_object(field_id, field_type, group_rev);
+            Ok(Some(()))
         })
     }
 
-    pub fn get_all_groups(&self, field_revs: &[Arc<FieldRevision>]) -> Option<GroupConfigurationsByFieldId> {
-        self.setting.groups.get_all_objects(field_revs)
-    }
-
-    pub fn get_groups(
-        &self,
-        field_id: &str,
-        field_type_rev: &FieldTypeRevision,
-    ) -> Option<Vec<Arc<GroupConfigurationRevision>>> {
-        self.setting.groups.get_objects(field_id, field_type_rev)
+    #[tracing::instrument(level = "trace", skip_all)]
+    pub fn contains_group(&self, field_id: &str, field_type: &FieldTypeRevision) -> bool {
+        self.view.groups.get_objects(field_id, field_type).is_some()
     }
 
-    pub fn insert_group(
+    #[tracing::instrument(level = "trace", skip_all, err)]
+    pub fn with_mut_group<F: FnOnce(&mut GroupConfigurationRevision)>(
         &mut self,
         field_id: &str,
         field_type: &FieldTypeRevision,
-        group_rev: GroupConfigurationRevision,
+        configuration_id: &str,
+        mut_configuration_fn: F,
     ) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
-        self.modify(|view| {
-            // only one group can be set
-            view.setting.groups.remove_all();
-            view.setting.groups.insert_object(field_id, field_type, group_rev);
-            Ok(Some(()))
+        self.modify(|view| match view.groups.get_mut_objects(field_id, field_type) {
+            None => Ok(None),
+            Some(configurations_revs) => {
+                for configuration_rev in configurations_revs {
+                    if configuration_rev.id == configuration_id {
+                        mut_configuration_fn(Arc::make_mut(configuration_rev));
+                        return Ok(Some(()));
+                    }
+                }
+                Ok(None)
+            }
         })
     }
 
@@ -136,7 +101,7 @@ impl GridViewRevisionPad {
         group_id: &str,
     ) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
         self.modify(|view| {
-            if let Some(groups) = view.setting.groups.get_mut_objects(field_id, field_type) {
+            if let Some(groups) = view.groups.get_mut_objects(field_id, field_type) {
                 groups.retain(|group| group.id != group_id);
                 Ok(Some(()))
             } else {
@@ -146,7 +111,7 @@ impl GridViewRevisionPad {
     }
 
     pub fn get_all_filters(&self, field_revs: &[Arc<FieldRevision>]) -> Option<FilterConfigurationsByFieldId> {
-        self.setting.filters.get_all_objects(field_revs)
+        self.filters.get_all_objects(field_revs)
     }
 
     pub fn get_filters(
@@ -154,7 +119,7 @@ impl GridViewRevisionPad {
         field_id: &str,
         field_type_rev: &FieldTypeRevision,
     ) -> Option<Vec<Arc<FilterConfigurationRevision>>> {
-        self.setting.filters.get_objects(field_id, field_type_rev)
+        self.filters.get_objects(field_id, field_type_rev)
     }
 
     pub fn insert_filter(
@@ -164,7 +129,7 @@ impl GridViewRevisionPad {
         filter_rev: FilterConfigurationRevision,
     ) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
         self.modify(|view| {
-            view.setting.filters.insert_object(field_id, field_type, filter_rev);
+            view.filters.add_object(field_id, field_type, filter_rev);
             Ok(Some(()))
         })
     }
@@ -176,7 +141,7 @@ impl GridViewRevisionPad {
         filter_id: &str,
     ) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
         self.modify(|view| {
-            if let Some(filters) = view.setting.filters.get_mut_objects(field_id, field_type) {
+            if let Some(filters) = view.filters.get_mut_objects(field_id, field_type) {
                 filters.retain(|filter| filter.id != filter_id);
                 Ok(Some(()))
             } else {
@@ -185,10 +150,6 @@ impl GridViewRevisionPad {
         })
     }
 
-    pub fn get_all_sort(&self) -> Option<SortConfigurationsByFieldId> {
-        None
-    }
-
     pub fn json_str(&self) -> CollaborateResult<String> {
         make_grid_view_rev_json_str(&self.view)
     }
@@ -216,24 +177,7 @@ impl GridViewRevisionPad {
     }
 }
 
-fn make_filter_revision(params: &CreateGridFilterParams) -> FilterConfigurationRevision {
-    FilterConfigurationRevision {
-        id: gen_grid_filter_id(),
-        field_id: params.field_id.clone(),
-        condition: params.condition,
-        content: params.content.clone(),
-    }
-}
-
-fn make_group_revision(params: &CreateGridGroupParams) -> GroupConfigurationRevision {
-    GroupConfigurationRevision {
-        id: gen_grid_group_id(),
-        field_id: params.field_id.clone(),
-        field_type_rev: params.field_type_rev,
-        content: params.content.clone(),
-    }
-}
-
+#[derive(Debug)]
 pub struct GridViewRevisionChangeset {
     pub delta: TextDelta,
     pub md5: String,

+ 0 - 67
shared-lib/flowy-sync/src/entities/grid.rs

@@ -1,67 +0,0 @@
-use flowy_grid_data_model::revision::{FieldTypeRevision, LayoutRevision};
-
-pub struct GridSettingChangesetParams {
-    pub grid_id: String,
-    pub layout_type: LayoutRevision,
-    pub insert_filter: Option<CreateGridFilterParams>,
-    pub delete_filter: Option<DeleteFilterParams>,
-    pub insert_group: Option<CreateGridGroupParams>,
-    pub delete_group: Option<DeleteGroupParams>,
-    pub insert_sort: Option<CreateGridSortParams>,
-    pub delete_sort: Option<String>,
-}
-
-impl GridSettingChangesetParams {
-    pub fn is_filter_changed(&self) -> bool {
-        self.insert_filter.is_some() || self.delete_filter.is_some()
-    }
-}
-pub struct CreateGridFilterParams {
-    pub field_id: String,
-    pub field_type_rev: FieldTypeRevision,
-    pub condition: u8,
-    pub content: Option<String>,
-}
-
-pub struct DeleteFilterParams {
-    pub field_id: String,
-    pub filter_id: String,
-    pub field_type_rev: FieldTypeRevision,
-}
-
-pub struct CreateGridGroupParams {
-    pub field_id: String,
-    pub field_type_rev: FieldTypeRevision,
-    pub content: Option<Vec<u8>>,
-}
-
-pub struct DeleteGroupParams {
-    pub field_id: String,
-    pub group_id: String,
-    pub field_type_rev: FieldTypeRevision,
-}
-
-pub struct CreateGridSortParams {
-    pub field_id: Option<String>,
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct FieldChangesetParams {
-    pub field_id: String,
-
-    pub grid_id: String,
-
-    pub name: Option<String>,
-
-    pub desc: Option<String>,
-
-    pub field_type: Option<FieldTypeRevision>,
-
-    pub frozen: Option<bool>,
-
-    pub visibility: Option<bool>,
-
-    pub width: Option<i32>,
-
-    pub type_option_data: Option<Vec<u8>>,
-}

+ 0 - 1
shared-lib/flowy-sync/src/entities/mod.rs

@@ -1,5 +1,4 @@
 pub mod folder;
-pub mod grid;
 pub mod parser;
 pub mod revision;
 pub mod text_block;