Преглед на файлове

chore: reload after delete or insert row

appflowy преди 3 години
родител
ревизия
f01d3250ae
променени са 17 файла, в които са добавени 533 реда и са изтрити 117 реда
  1. 45 22
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  2. 3 12
      frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
  3. 4 4
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
  4. 19 4
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_action_sheet.dart
  5. 79 3
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
  6. 16 2
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
  7. 19 3
      frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs
  8. 26 10
      frontend/rust-lib/flowy-grid/src/services/block_meta_manager.rs
  9. 1 1
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs
  10. 4 4
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs
  11. 5 4
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  12. 1 1
      frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs
  13. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs
  14. 24 2
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  15. 269 35
      shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
  16. 5 1
      shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
  17. 12 8
      shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs

+ 45 - 22
frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart

@@ -34,8 +34,6 @@ class GridBloc extends Bloc<GridEvent, GridState> {
           createRow: (_CreateRow value) {
             _gridService.createRow(gridId: view.id);
           },
-          delete: (_Delete value) {},
-          rename: (_Rename value) {},
           updateDesc: (_Desc value) {},
           didReceiveRowUpdate: (_DidReceiveRowUpdate value) {
             emit(state.copyWith(rows: value.rows));
@@ -71,14 +69,18 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     _gridListener.rowsUpdateNotifier.addPublishListener((result) {
       result.fold((gridBlockChangeset) {
         for (final changeset in gridBlockChangeset) {
-          if (changeset.insertedRows.isNotEmpty) {}
+          if (changeset.insertedRows.isNotEmpty) {
+            _insertRows(changeset.insertedRows);
+          }
 
-          if (changeset.deletedRows.isNotEmpty) {}
+          if (changeset.deletedRows.isNotEmpty) {
+            _deleteRows(changeset.deletedRows);
+          }
 
-          if (changeset.updatedRows.isNotEmpty) {}
+          if (changeset.updatedRows.isNotEmpty) {
+            _updateRows(changeset.updatedRows);
+          }
         }
-
-        // add(GridEvent.didReceiveRowUpdate(_buildRows(blockOrders)));
       }, (err) => Log.error(err));
     });
     _gridListener.start();
@@ -111,29 +113,50 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     );
   }
 
-  List<GridBlockRow> _buildRows(List<GridBlockOrder> blockOrders) {
-    List<GridBlockRow> rows = [];
-    for (final blockOrder in blockOrders) {
-      rows.addAll(blockOrder.rowOrders.map(
-        (rowOrder) => GridBlockRow(
-          gridId: view.id,
-          rowId: rowOrder.rowId,
-          height: rowOrder.height.toDouble(),
-        ),
-      ));
+  void _deleteRows(List<RowOrder> deletedRows) {
+    final List<RowOrder> rows = List.from(state.rows);
+    rows.retainWhere(
+      (row) => deletedRows.where((deletedRow) => deletedRow.rowId == row.rowId).isEmpty,
+    );
+
+    add(GridEvent.didReceiveRowUpdate(rows));
+  }
+
+  void _insertRows(List<IndexRowOrder> createdRows) {
+    final List<RowOrder> rows = List.from(state.rows);
+    for (final newRow in createdRows) {
+      if (newRow.hasIndex()) {
+        rows.insert(newRow.index, newRow.rowOrder);
+      } else {
+        rows.add(newRow.rowOrder);
+      }
     }
-    return rows;
+    add(GridEvent.didReceiveRowUpdate(rows));
+  }
+
+  void _updateRows(List<RowOrder> updatedRows) {
+    final List<RowOrder> rows = List.from(state.rows);
+    for (final updatedRow in updatedRows) {
+      final index = rows.indexWhere((row) => row.rowId == updatedRow.rowId);
+      if (index != -1) {
+        rows.removeAt(index);
+        rows.insert(index, updatedRow);
+      }
+    }
+    add(GridEvent.didReceiveRowUpdate(rows));
+  }
+
+  List<RowOrder> _buildRows(List<GridBlockOrder> blockOrders) {
+    return blockOrders.expand((blockOrder) => blockOrder.rowOrders).toList();
   }
 }
 
 @freezed
 class GridEvent with _$GridEvent {
   const factory GridEvent.initial() = InitialGrid;
-  const factory GridEvent.rename(String gridId, String name) = _Rename;
   const factory GridEvent.updateDesc(String gridId, String desc) = _Desc;
-  const factory GridEvent.delete(String gridId) = _Delete;
   const factory GridEvent.createRow() = _CreateRow;
-  const factory GridEvent.didReceiveRowUpdate(List<GridBlockRow> rows) = _DidReceiveRowUpdate;
+  const factory GridEvent.didReceiveRowUpdate(List<RowOrder> rows) = _DidReceiveRowUpdate;
   const factory GridEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate;
 }
 
@@ -142,7 +165,7 @@ class GridState with _$GridState {
   const factory GridState({
     required GridLoadingState loadingState,
     required List<Field> fields,
-    required List<GridBlockRow> rows,
+    required List<RowOrder> rows,
     required Option<Grid> grid,
   }) = _GridState;
 

+ 3 - 12
frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart

@@ -65,21 +65,12 @@ class RowData with _$RowData {
     required double height,
   }) = _RowData;
 
-  factory RowData.fromBlockRow(GridBlockRow row, List<Field> fields) {
+  factory RowData.fromBlockRow(String gridId, RowOrder row, List<Field> fields) {
     return RowData(
-      gridId: row.gridId,
+      gridId: gridId,
       rowId: row.rowId,
       fields: fields,
-      height: row.height,
+      height: row.height.toDouble(),
     );
   }
 }
-
-@freezed
-class GridBlockRow with _$GridBlockRow {
-  const factory GridBlockRow({
-    required String gridId,
-    required String rowId,
-    required double height,
-  }) = _GridBlockRow;
-}

+ 4 - 4
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart

@@ -73,7 +73,7 @@ class FlowyGrid extends StatefulWidget {
 
 class _FlowyGridState extends State<FlowyGrid> {
   final _scrollController = GridScrollController();
-  final _key = GlobalKey<SliverAnimatedListState>();
+  // final _key = GlobalKey<SliverAnimatedListState>();
 
   @override
   void dispose() {
@@ -105,7 +105,7 @@ class _FlowyGridState extends State<FlowyGrid> {
                   slivers: [
                     _renderToolbar(gridId),
                     GridHeader(gridId: gridId, fields: List.from(state.fields)),
-                    _renderRows(context),
+                    _renderRows(gridId: gridId, context: context),
                     const GridFooter(),
                   ],
                 ),
@@ -147,7 +147,7 @@ class _FlowyGridState extends State<FlowyGrid> {
     );
   }
 
-  Widget _renderRows(BuildContext context) {
+  Widget _renderRows({required String gridId, required BuildContext context}) {
     return BlocBuilder<GridBloc, GridState>(
       buildWhen: (previous, current) {
         final rowChanged = previous.rows.length != current.rows.length;
@@ -160,7 +160,7 @@ class _FlowyGridState extends State<FlowyGrid> {
           (context, index) {
             final blockRow = context.read<GridBloc>().state.rows[index];
             final fields = context.read<GridBloc>().state.fields;
-            final rowData = RowData.fromBlockRow(blockRow, fields);
+            final rowData = RowData.fromBlockRow(gridId, blockRow, fields);
             return GridRowWidget(data: rowData, key: ValueKey(rowData.rowId));
           },
           childCount: context.read<GridBloc>().state.rows.length,

+ 19 - 4
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_action_sheet.dart

@@ -84,10 +84,16 @@ class _RowActionCell extends StatelessWidget {
     return SizedBox(
       height: GridSize.typeOptionItemHeight,
       child: FlowyButton(
-        text: FlowyText.medium(action.title(), fontSize: 12),
+        text: FlowyText.medium(
+          action.title(),
+          fontSize: 12,
+          color: action.enable() ? theme.textColor : theme.shader3,
+        ),
         hoverColor: theme.hover,
         onTap: () {
-          action.performAction(context);
+          if (action.enable()) {
+            action.performAction(context);
+          }
           onDismissed();
         },
         leftIcon: svgWidget(action.iconName(), color: theme.iconColor),
@@ -120,13 +126,22 @@ extension _RowActionExtension on _RowAction {
     }
   }
 
+  bool enable() {
+    switch (this) {
+      case _RowAction.duplicate:
+        return false;
+      case _RowAction.delete:
+        return true;
+    }
+  }
+
   void performAction(BuildContext context) {
     switch (this) {
       case _RowAction.duplicate:
-        // context.read<RowActionSheetBloc>().add(const RowActionSheetEvent.duplicateRow());
+        context.read<RowActionSheetBloc>().add(const RowActionSheetEvent.duplicateRow());
         break;
       case _RowAction.delete:
-        // context.read<RowActionSheetBloc>().add(const RowActionSheetEvent.deleteRow());
+        context.read<RowActionSheetBloc>().add(const RowActionSheetEvent.deleteRow());
         break;
     }
   }

+ 79 - 3
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart

@@ -858,7 +858,7 @@ class GridBlockOrder extends $pb.GeneratedMessage {
 class GridBlockOrderChangeset extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridBlockOrderChangeset', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blockId')
-    ..pc<RowOrder>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'insertedRows', $pb.PbFieldType.PM, subBuilder: RowOrder.create)
+    ..pc<IndexRowOrder>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'insertedRows', $pb.PbFieldType.PM, subBuilder: IndexRowOrder.create)
     ..pc<RowOrder>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deletedRows', $pb.PbFieldType.PM, subBuilder: RowOrder.create)
     ..pc<RowOrder>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'updatedRows', $pb.PbFieldType.PM, subBuilder: RowOrder.create)
     ..hasRequiredFields = false
@@ -867,7 +867,7 @@ class GridBlockOrderChangeset extends $pb.GeneratedMessage {
   GridBlockOrderChangeset._() : super();
   factory GridBlockOrderChangeset({
     $core.String? blockId,
-    $core.Iterable<RowOrder>? insertedRows,
+    $core.Iterable<IndexRowOrder>? insertedRows,
     $core.Iterable<RowOrder>? deletedRows,
     $core.Iterable<RowOrder>? updatedRows,
   }) {
@@ -917,7 +917,7 @@ class GridBlockOrderChangeset extends $pb.GeneratedMessage {
   void clearBlockId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.List<RowOrder> get insertedRows => $_getList(1);
+  $core.List<IndexRowOrder> get insertedRows => $_getList(1);
 
   @$pb.TagNumber(3)
   $core.List<RowOrder> get deletedRows => $_getList(2);
@@ -926,6 +926,82 @@ class GridBlockOrderChangeset extends $pb.GeneratedMessage {
   $core.List<RowOrder> get updatedRows => $_getList(3);
 }
 
+enum IndexRowOrder_OneOfIndex {
+  index_, 
+  notSet
+}
+
+class IndexRowOrder extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, IndexRowOrder_OneOfIndex> _IndexRowOrder_OneOfIndexByTag = {
+    2 : IndexRowOrder_OneOfIndex.index_,
+    0 : IndexRowOrder_OneOfIndex.notSet
+  };
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'IndexRowOrder', createEmptyInstance: create)
+    ..oo(0, [2])
+    ..aOM<RowOrder>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowOrder', subBuilder: RowOrder.create)
+    ..a<$core.int>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'index', $pb.PbFieldType.O3)
+    ..hasRequiredFields = false
+  ;
+
+  IndexRowOrder._() : super();
+  factory IndexRowOrder({
+    RowOrder? rowOrder,
+    $core.int? index,
+  }) {
+    final _result = create();
+    if (rowOrder != null) {
+      _result.rowOrder = rowOrder;
+    }
+    if (index != null) {
+      _result.index = index;
+    }
+    return _result;
+  }
+  factory IndexRowOrder.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory IndexRowOrder.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  IndexRowOrder clone() => IndexRowOrder()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  IndexRowOrder copyWith(void Function(IndexRowOrder) updates) => super.copyWith((message) => updates(message as IndexRowOrder)) as IndexRowOrder; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static IndexRowOrder create() => IndexRowOrder._();
+  IndexRowOrder createEmptyInstance() => create();
+  static $pb.PbList<IndexRowOrder> createRepeated() => $pb.PbList<IndexRowOrder>();
+  @$core.pragma('dart2js:noInline')
+  static IndexRowOrder getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<IndexRowOrder>(create);
+  static IndexRowOrder? _defaultInstance;
+
+  IndexRowOrder_OneOfIndex whichOneOfIndex() => _IndexRowOrder_OneOfIndexByTag[$_whichOneof(0)]!;
+  void clearOneOfIndex() => clearField($_whichOneof(0));
+
+  @$pb.TagNumber(1)
+  RowOrder get rowOrder => $_getN(0);
+  @$pb.TagNumber(1)
+  set rowOrder(RowOrder v) { setField(1, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasRowOrder() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearRowOrder() => clearField(1);
+  @$pb.TagNumber(1)
+  RowOrder ensureRowOrder() => $_ensure(0);
+
+  @$pb.TagNumber(2)
+  $core.int get index => $_getIZ(1);
+  @$pb.TagNumber(2)
+  set index($core.int v) { $_setSignedInt32(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasIndex() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearIndex() => clearField(2);
+}
+
 class GridBlock extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridBlock', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')

+ 16 - 2
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart

@@ -176,14 +176,28 @@ const GridBlockOrderChangeset$json = const {
   '1': 'GridBlockOrderChangeset',
   '2': const [
     const {'1': 'block_id', '3': 1, '4': 1, '5': 9, '10': 'blockId'},
-    const {'1': 'inserted_rows', '3': 2, '4': 3, '5': 11, '6': '.RowOrder', '10': 'insertedRows'},
+    const {'1': 'inserted_rows', '3': 2, '4': 3, '5': 11, '6': '.IndexRowOrder', '10': 'insertedRows'},
     const {'1': 'deleted_rows', '3': 3, '4': 3, '5': 11, '6': '.RowOrder', '10': 'deletedRows'},
     const {'1': 'updated_rows', '3': 4, '4': 3, '5': 11, '6': '.RowOrder', '10': 'updatedRows'},
   ],
 };
 
 /// Descriptor for `GridBlockOrderChangeset`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List gridBlockOrderChangesetDescriptor = $convert.base64Decode('ChdHcmlkQmxvY2tPcmRlckNoYW5nZXNldBIZCghibG9ja19pZBgBIAEoCVIHYmxvY2tJZBIuCg1pbnNlcnRlZF9yb3dzGAIgAygLMgkuUm93T3JkZXJSDGluc2VydGVkUm93cxIsCgxkZWxldGVkX3Jvd3MYAyADKAsyCS5Sb3dPcmRlclILZGVsZXRlZFJvd3MSLAoMdXBkYXRlZF9yb3dzGAQgAygLMgkuUm93T3JkZXJSC3VwZGF0ZWRSb3dz');
+final $typed_data.Uint8List gridBlockOrderChangesetDescriptor = $convert.base64Decode('ChdHcmlkQmxvY2tPcmRlckNoYW5nZXNldBIZCghibG9ja19pZBgBIAEoCVIHYmxvY2tJZBIzCg1pbnNlcnRlZF9yb3dzGAIgAygLMg4uSW5kZXhSb3dPcmRlclIMaW5zZXJ0ZWRSb3dzEiwKDGRlbGV0ZWRfcm93cxgDIAMoCzIJLlJvd09yZGVyUgtkZWxldGVkUm93cxIsCgx1cGRhdGVkX3Jvd3MYBCADKAsyCS5Sb3dPcmRlclILdXBkYXRlZFJvd3M=');
+@$core.Deprecated('Use indexRowOrderDescriptor instead')
+const IndexRowOrder$json = const {
+  '1': 'IndexRowOrder',
+  '2': const [
+    const {'1': 'row_order', '3': 1, '4': 1, '5': 11, '6': '.RowOrder', '10': 'rowOrder'},
+    const {'1': 'index', '3': 2, '4': 1, '5': 5, '9': 0, '10': 'index'},
+  ],
+  '8': const [
+    const {'1': 'one_of_index'},
+  ],
+};
+
+/// Descriptor for `IndexRowOrder`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List indexRowOrderDescriptor = $convert.base64Decode('Cg1JbmRleFJvd09yZGVyEiYKCXJvd19vcmRlchgBIAEoCzIJLlJvd09yZGVyUghyb3dPcmRlchIWCgVpbmRleBgCIAEoBUgAUgVpbmRleEIOCgxvbmVfb2ZfaW5kZXg=');
 @$core.Deprecated('Use gridBlockDescriptor instead')
 const GridBlock$json = const {
   '1': 'GridBlock',

+ 19 - 3
frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs

@@ -42,17 +42,30 @@ impl ClientGridBlockMetaEditor {
         })
     }
 
-    pub(crate) async fn create_row(&self, row: RowMeta, start_row_id: Option<String>) -> FlowyResult<i32> {
+    /// return current number of rows and the inserted index. The inserted index will be None if the start_row_id is None
+    pub(crate) async fn create_row(
+        &self,
+        row: RowMeta,
+        start_row_id: Option<String>,
+    ) -> FlowyResult<(i32, Option<i32>)> {
         let mut row_count = 0;
+        let mut row_index = None;
         let _ = self
             .modify(|pad| {
+                if let Some(start_row_id) = start_row_id.as_ref() {
+                    match pad.index_of_row(start_row_id) {
+                        None => {}
+                        Some(index) => row_index = Some(index + 1),
+                    }
+                }
+
                 let change = pad.add_row_meta(row, start_row_id)?;
                 row_count = pad.number_of_rows();
                 Ok(change)
             })
             .await?;
 
-        Ok(row_count)
+        Ok((row_count, row_index))
     }
 
     pub async fn delete_rows(&self, ids: Vec<Cow<'_, String>>) -> FlowyResult<i32> {
@@ -89,7 +102,10 @@ impl ClientGridBlockMetaEditor {
         Ok(cell_metas)
     }
 
-    pub async fn get_row_orders(&self, row_ids: Option<Vec<Cow<'_, String>>>) -> FlowyResult<Vec<RowOrder>> {
+    pub async fn get_row_orders<T>(&self, row_ids: Option<Vec<Cow<'_, T>>>) -> FlowyResult<Vec<RowOrder>>
+    where
+        T: AsRef<str> + ToOwned + ?Sized,
+    {
         let row_orders = self
             .pad
             .read()

+ 26 - 10
frontend/rust-lib/flowy-grid/src/services/block_meta_manager.rs

@@ -9,7 +9,7 @@ use dashmap::DashMap;
 use flowy_error::FlowyResult;
 use flowy_grid_data_model::entities::{
     CellMeta, CellMetaChangeset, CellNotificationData, FieldMeta, GridBlockMeta, GridBlockMetaChangeset,
-    GridBlockOrder, GridBlockOrderChangeset, RowMeta, RowMetaChangeset, RowOrder,
+    GridBlockOrderChangeset, IndexRowOrder, RowMeta, RowMetaChangeset, RowOrder,
 };
 use flowy_revision::disk::SQLiteGridBlockMetaRevisionPersistence;
 use flowy_revision::{RevisionManager, RevisionPersistence};
@@ -70,11 +70,13 @@ impl GridBlockMetaEditorManager {
     ) -> FlowyResult<i32> {
         let _ = self.persistence.insert_or_update(&row_meta.block_id, &row_meta.id)?;
         let editor = self.get_editor(&row_meta.block_id).await?;
-        let row_order = RowOrder::from(&row_meta);
-        let row_count = editor.create_row(row_meta, start_row_id).await?;
+
+        let mut index_row_order = IndexRowOrder::from(&row_meta);
+        let (row_count, row_index) = editor.create_row(row_meta, start_row_id).await?;
+        index_row_order.index = row_index;
 
         let _ = self
-            .notify_block_did_update_row(GridBlockOrderChangeset::from_update(block_id, vec![row_order]))
+            .notify_did_update_grid_rows(GridBlockOrderChangeset::from_insert(block_id, vec![index_row_order]))
             .await?;
         Ok(row_count)
     }
@@ -90,12 +92,13 @@ impl GridBlockMetaEditorManager {
             let mut row_count = 0;
             for row in row_metas {
                 let _ = self.persistence.insert_or_update(&row.block_id, &row.id)?;
-                inserted_row_orders.push(RowOrder::from(&row));
-                row_count = editor.create_row(row, None).await?;
+                inserted_row_orders.push(IndexRowOrder::from(&row));
+                row_count = editor.create_row(row, None).await?.0;
             }
             changesets.push(GridBlockMetaChangeset::from_row_count(&block_id, row_count));
+
             let _ = self
-                .notify_block_did_update_row(GridBlockOrderChangeset::from_insert(&block_id, inserted_row_orders))
+                .notify_did_update_grid_rows(GridBlockOrderChangeset::from_insert(&block_id, inserted_row_orders))
                 .await?;
         }
 
@@ -114,13 +117,26 @@ impl GridBlockMetaEditorManager {
             None => {}
             Some(row_order) => {
                 let block_order_changeset = GridBlockOrderChangeset::from_update(&editor.block_id, vec![row_order]);
-                let _ = self.notify_block_did_update_row(block_order_changeset).await?;
+                let _ = self.notify_did_update_grid_rows(block_order_changeset).await?;
             }
         }
 
         Ok(())
     }
 
+    pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> {
+        let row_id = row_id.to_owned();
+        let block_id = self.persistence.get_block_id(&row_id)?;
+        let editor = self.get_editor(&block_id).await?;
+        let row_orders = editor.get_row_orders(Some(vec![Cow::Borrowed(&row_id)])).await?;
+        let _ = editor.delete_rows(vec![Cow::Borrowed(&row_id)]).await?;
+        let _ = self
+            .notify_did_update_grid_rows(GridBlockOrderChangeset::from_delete(&block_id, row_orders))
+            .await?;
+
+        Ok(())
+    }
+
     pub(crate) async fn delete_rows(&self, row_orders: Vec<RowOrder>) -> FlowyResult<Vec<GridBlockMetaChangeset>> {
         let mut changesets = vec![];
         for block_order in group_row_orders(row_orders) {
@@ -167,7 +183,7 @@ impl GridBlockMetaEditorManager {
 
     pub async fn get_row_orders(&self, block_id: &str) -> FlowyResult<Vec<RowOrder>> {
         let editor = self.get_editor(block_id).await?;
-        editor.get_row_orders(None).await
+        editor.get_row_orders::<&str>(None).await
     }
 
     pub(crate) async fn make_block_snapshots(&self, block_ids: Vec<String>) -> FlowyResult<Vec<GridBlockSnapshot>> {
@@ -197,7 +213,7 @@ impl GridBlockMetaEditorManager {
         Ok(block_cell_metas)
     }
 
-    async fn notify_block_did_update_row(&self, changeset: GridBlockOrderChangeset) -> FlowyResult<()> {
+    async fn notify_did_update_grid_rows(&self, changeset: GridBlockOrderChangeset) -> FlowyResult<()> {
         send_dart_notification(&self.grid_id, GridNotification::DidUpdateGridBlock)
             .payload(changeset)
             .send();

+ 1 - 1
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs

@@ -70,7 +70,7 @@ impl CellDataOperation for DateTypeOption {
     ) -> Result<String, FlowyError> {
         let changeset = changeset.into();
         if changeset.parse::<f64>().is_err() || changeset.parse::<i64>().is_err() {
-            return Err(FlowyError::internal().context(format!("Parse {} failed", changeset.to_string())));
+            return Err(FlowyError::internal().context(format!("Parse {} failed", changeset)));
         };
 
         Ok(TypeOptionCellData::new(changeset, self.field_type()).json())

+ 4 - 4
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs

@@ -469,7 +469,7 @@ mod tests {
         let single_select = SingleSelectTypeOptionBuilder::default()
             .option(google_option.clone())
             .option(facebook_option.clone())
-            .option(twitter_option.clone());
+            .option(twitter_option);
 
         let field_meta = FieldBuilder::new(single_select)
             .name("Platform")
@@ -478,7 +478,7 @@ mod tests {
 
         let type_option = SingleSelectTypeOption::from(&field_meta);
 
-        let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
+        let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR);
         let data = SelectOptionCellChangeset::from_insert(&option_ids).cell_data();
         let cell_data = type_option.apply_changeset(data, None).unwrap();
         assert_eq!(type_option.decode_cell_data(cell_data, &field_meta), google_option.name,);
@@ -511,7 +511,7 @@ mod tests {
         let multi_select = MultiSelectTypeOptionBuilder::default()
             .option(google_option.clone())
             .option(facebook_option.clone())
-            .option(twitter_option.clone());
+            .option(twitter_option);
 
         let field_meta = FieldBuilder::new(multi_select)
             .name("Platform")
@@ -525,7 +525,7 @@ mod tests {
         let cell_data = type_option.apply_changeset(data, None).unwrap();
         assert_eq!(
             type_option.decode_cell_data(cell_data, &field_meta),
-            vec![google_option.name.clone(), facebook_option.name.clone()].join(SELECTION_IDS_SEPARATOR),
+            vec![google_option.name.clone(), facebook_option.name].join(SELECTION_IDS_SEPARATOR),
         );
 
         let data = SelectOptionCellChangeset::from_insert(&google_option.id).cell_data();

+ 5 - 4
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -268,11 +268,12 @@ impl ClientGridEditor {
         }
     }
     pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> {
-        todo!()
+        let _ = self.block_meta_manager.delete_row(row_id).await?;
+        Ok(())
     }
 
-    pub async fn duplicate_row(&self, row_id: &str) -> FlowyResult<()> {
-        todo!()
+    pub async fn duplicate_row(&self, _row_id: &str) -> FlowyResult<()> {
+        Ok(())
     }
 
     pub async fn get_cell(&self, params: &CellIdentifier) -> Option<Cell> {
@@ -429,7 +430,7 @@ impl ClientGridEditor {
         debug_assert!(field_metas.len() == 1);
 
         if let Some(field_meta) = field_metas.pop() {
-            send_dart_notification(&field_id, GridNotification::DidUpdateField)
+            send_dart_notification(field_id, GridNotification::DidUpdateField)
                 .payload(field_meta)
                 .send();
         }

+ 1 - 1
frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs

@@ -4,7 +4,7 @@ use flowy_grid_data_model::entities::{
     Cell, CellMeta, FieldMeta, GridBlock, GridBlockOrder, RepeatedGridBlock, Row, RowMeta, RowOrder,
 };
 use rayon::iter::{IntoParallelIterator, ParallelIterator};
-use std::borrow::Cow;
+
 use std::collections::HashMap;
 
 use std::sync::Arc;

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

@@ -4,7 +4,7 @@ use chrono::NaiveDateTime;
 use flowy_grid::services::field::{
     MultiSelectTypeOption, SelectOption, SelectOptionCellChangeset, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR,
 };
-use flowy_grid::services::row::{apply_cell_data_changeset, decode_cell_data, CellDataOperation, CreateRowMetaBuilder};
+use flowy_grid::services::row::{decode_cell_data, CreateRowMetaBuilder};
 use flowy_grid_data_model::entities::{
     CellMetaChangeset, FieldChangesetParams, FieldType, GridBlockMeta, GridBlockMetaChangeset, RowMetaChangeset,
     TypeOptionDataEntry,

+ 24 - 2
shared-lib/flowy-grid-data-model/src/entities/grid.rs

@@ -280,7 +280,7 @@ pub struct GridBlockOrderChangeset {
     pub block_id: String,
 
     #[pb(index = 2)]
-    pub inserted_rows: Vec<RowOrder>,
+    pub inserted_rows: Vec<IndexRowOrder>,
 
     #[pb(index = 3)]
     pub deleted_rows: Vec<RowOrder>,
@@ -289,8 +289,30 @@ pub struct GridBlockOrderChangeset {
     pub updated_rows: Vec<RowOrder>,
 }
 
+#[derive(Debug, Clone, Default, ProtoBuf)]
+pub struct IndexRowOrder {
+    #[pb(index = 1)]
+    pub row_order: RowOrder,
+
+    #[pb(index = 2, one_of)]
+    pub index: Option<i32>,
+}
+
+impl std::convert::From<RowOrder> for IndexRowOrder {
+    fn from(row_order: RowOrder) -> Self {
+        Self { row_order, index: None }
+    }
+}
+
+impl std::convert::From<&RowMeta> for IndexRowOrder {
+    fn from(row: &RowMeta) -> Self {
+        let row_order = RowOrder::from(row);
+        Self::from(row_order)
+    }
+}
+
 impl GridBlockOrderChangeset {
-    pub fn from_insert(block_id: &str, inserted_rows: Vec<RowOrder>) -> Self {
+    pub fn from_insert(block_id: &str, inserted_rows: Vec<IndexRowOrder>) -> Self {
         Self {
             block_id: block_id.to_owned(),
             inserted_rows,

+ 269 - 35
shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs

@@ -2923,7 +2923,7 @@ impl ::protobuf::reflect::ProtobufValue for GridBlockOrder {
 pub struct GridBlockOrderChangeset {
     // message fields
     pub block_id: ::std::string::String,
-    pub inserted_rows: ::protobuf::RepeatedField<RowOrder>,
+    pub inserted_rows: ::protobuf::RepeatedField<IndexRowOrder>,
     pub deleted_rows: ::protobuf::RepeatedField<RowOrder>,
     pub updated_rows: ::protobuf::RepeatedField<RowOrder>,
     // special fields
@@ -2968,10 +2968,10 @@ impl GridBlockOrderChangeset {
         ::std::mem::replace(&mut self.block_id, ::std::string::String::new())
     }
 
-    // repeated .RowOrder inserted_rows = 2;
+    // repeated .IndexRowOrder inserted_rows = 2;
 
 
-    pub fn get_inserted_rows(&self) -> &[RowOrder] {
+    pub fn get_inserted_rows(&self) -> &[IndexRowOrder] {
         &self.inserted_rows
     }
     pub fn clear_inserted_rows(&mut self) {
@@ -2979,17 +2979,17 @@ impl GridBlockOrderChangeset {
     }
 
     // Param is passed by value, moved
-    pub fn set_inserted_rows(&mut self, v: ::protobuf::RepeatedField<RowOrder>) {
+    pub fn set_inserted_rows(&mut self, v: ::protobuf::RepeatedField<IndexRowOrder>) {
         self.inserted_rows = v;
     }
 
     // Mutable pointer to the field.
-    pub fn mut_inserted_rows(&mut self) -> &mut ::protobuf::RepeatedField<RowOrder> {
+    pub fn mut_inserted_rows(&mut self) -> &mut ::protobuf::RepeatedField<IndexRowOrder> {
         &mut self.inserted_rows
     }
 
     // Take field
-    pub fn take_inserted_rows(&mut self) -> ::protobuf::RepeatedField<RowOrder> {
+    pub fn take_inserted_rows(&mut self) -> ::protobuf::RepeatedField<IndexRowOrder> {
         ::std::mem::replace(&mut self.inserted_rows, ::protobuf::RepeatedField::new())
     }
 
@@ -3174,7 +3174,7 @@ impl ::protobuf::Message for GridBlockOrderChangeset {
                 |m: &GridBlockOrderChangeset| { &m.block_id },
                 |m: &mut GridBlockOrderChangeset| { &mut m.block_id },
             ));
-            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<RowOrder>>(
+            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<IndexRowOrder>>(
                 "inserted_rows",
                 |m: &GridBlockOrderChangeset| { &m.inserted_rows },
                 |m: &mut GridBlockOrderChangeset| { &mut m.inserted_rows },
@@ -3225,6 +3225,238 @@ impl ::protobuf::reflect::ProtobufValue for GridBlockOrderChangeset {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct IndexRowOrder {
+    // message fields
+    pub row_order: ::protobuf::SingularPtrField<RowOrder>,
+    // message oneof groups
+    pub one_of_index: ::std::option::Option<IndexRowOrder_oneof_one_of_index>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a IndexRowOrder {
+    fn default() -> &'a IndexRowOrder {
+        <IndexRowOrder as ::protobuf::Message>::default_instance()
+    }
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum IndexRowOrder_oneof_one_of_index {
+    index(i32),
+}
+
+impl IndexRowOrder {
+    pub fn new() -> IndexRowOrder {
+        ::std::default::Default::default()
+    }
+
+    // .RowOrder row_order = 1;
+
+
+    pub fn get_row_order(&self) -> &RowOrder {
+        self.row_order.as_ref().unwrap_or_else(|| <RowOrder as ::protobuf::Message>::default_instance())
+    }
+    pub fn clear_row_order(&mut self) {
+        self.row_order.clear();
+    }
+
+    pub fn has_row_order(&self) -> bool {
+        self.row_order.is_some()
+    }
+
+    // Param is passed by value, moved
+    pub fn set_row_order(&mut self, v: RowOrder) {
+        self.row_order = ::protobuf::SingularPtrField::some(v);
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_row_order(&mut self) -> &mut RowOrder {
+        if self.row_order.is_none() {
+            self.row_order.set_default();
+        }
+        self.row_order.as_mut().unwrap()
+    }
+
+    // Take field
+    pub fn take_row_order(&mut self) -> RowOrder {
+        self.row_order.take().unwrap_or_else(|| RowOrder::new())
+    }
+
+    // int32 index = 2;
+
+
+    pub fn get_index(&self) -> i32 {
+        match self.one_of_index {
+            ::std::option::Option::Some(IndexRowOrder_oneof_one_of_index::index(v)) => v,
+            _ => 0,
+        }
+    }
+    pub fn clear_index(&mut self) {
+        self.one_of_index = ::std::option::Option::None;
+    }
+
+    pub fn has_index(&self) -> bool {
+        match self.one_of_index {
+            ::std::option::Option::Some(IndexRowOrder_oneof_one_of_index::index(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_index(&mut self, v: i32) {
+        self.one_of_index = ::std::option::Option::Some(IndexRowOrder_oneof_one_of_index::index(v))
+    }
+}
+
+impl ::protobuf::Message for IndexRowOrder {
+    fn is_initialized(&self) -> bool {
+        for v in &self.row_order {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.row_order)?;
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_index = ::std::option::Option::Some(IndexRowOrder_oneof_one_of_index::index(is.read_int32()?));
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if let Some(ref v) = self.row_order.as_ref() {
+            let len = v.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_index {
+            match v {
+                &IndexRowOrder_oneof_one_of_index::index(v) => {
+                    my_size += ::protobuf::rt::value_size(2, v, ::protobuf::wire_format::WireTypeVarint);
+                },
+            };
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if let Some(ref v) = self.row_order.as_ref() {
+            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_index {
+            match v {
+                &IndexRowOrder_oneof_one_of_index::index(v) => {
+                    os.write_int32(2, v)?;
+                },
+            };
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> IndexRowOrder {
+        IndexRowOrder::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<RowOrder>>(
+                "row_order",
+                |m: &IndexRowOrder| { &m.row_order },
+                |m: &mut IndexRowOrder| { &mut m.row_order },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_i32_accessor::<_>(
+                "index",
+                IndexRowOrder::has_index,
+                IndexRowOrder::get_index,
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<IndexRowOrder>(
+                "IndexRowOrder",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static IndexRowOrder {
+        static instance: ::protobuf::rt::LazyV2<IndexRowOrder> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(IndexRowOrder::new)
+    }
+}
+
+impl ::protobuf::Clear for IndexRowOrder {
+    fn clear(&mut self) {
+        self.row_order.clear();
+        self.one_of_index = ::std::option::Option::None;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for IndexRowOrder {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for IndexRowOrder {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(PartialEq,Clone,Default)]
 pub struct GridBlock {
     // message fields
@@ -5640,34 +5872,36 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x20\x03(\x0b2\x04.RowR\x05items\"5\n\x11RepeatedGridBlock\x12\x20\n\x05\
     items\x18\x01\x20\x03(\x0b2\n.GridBlockR\x05items\"U\n\x0eGridBlockOrder\
     \x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x12(\n\nrow_orders\
-    \x18\x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\"\xc0\x01\n\x17GridBlockOr\
-    derChangeset\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x12.\n\
-    \rinserted_rows\x18\x02\x20\x03(\x0b2\t.RowOrderR\x0cinsertedRows\x12,\n\
-    \x0cdeleted_rows\x18\x03\x20\x03(\x0b2\t.RowOrderR\x0bdeletedRows\x12,\n\
-    \x0cupdated_rows\x18\x04\x20\x03(\x0b2\t.RowOrderR\x0bupdatedRows\"E\n\t\
-    GridBlock\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12(\n\nrow_orders\
-    \x18\x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\";\n\x04Cell\x12\x19\n\x08\
-    field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\x07content\x18\x02\x20\
-    \x01(\tR\x07content\"\x8f\x01\n\x14CellNotificationData\x12\x17\n\x07gri\
-    d_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\x20\x01\
-    (\tR\x07fieldId\x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\x05rowId\x12\x1a\
-    \n\x07content\x18\x04\x20\x01(\tH\0R\x07contentB\x10\n\x0eone_of_content\
-    \"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\x01\x20\x03(\x0b2\x05.CellR\
-    \x05items\"'\n\x11CreateGridPayload\x12\x12\n\x04name\x18\x01\x20\x01(\t\
-    R\x04name\"\x1e\n\x06GridId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05va\
-    lue\"#\n\x0bGridBlockId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\
-    \"f\n\x10CreateRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gr\
-    idId\x12\"\n\x0cstart_row_id\x18\x02\x20\x01(\tH\0R\nstartRowIdB\x15\n\
-    \x13one_of_start_row_id\"\xb6\x01\n\x12CreateFieldPayload\x12\x17\n\x07g\
-    rid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x1c\n\x05field\x18\x02\x20\x01(\
-    \x0b2\x06.FieldR\x05field\x12(\n\x10type_option_data\x18\x03\x20\x01(\
-    \x0cR\x0etypeOptionData\x12&\n\x0estart_field_id\x18\x04\x20\x01(\tH\0R\
-    \x0cstartFieldIdB\x17\n\x15one_of_start_field_id\"d\n\x11QueryFieldPaylo\
-    ad\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_or\
-    ders\x18\x02\x20\x01(\x0b2\x13.RepeatedFieldOrderR\x0bfieldOrders\"e\n\
-    \x16QueryGridBlocksPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06g\
-    ridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\
-    \x0bblockOrdersb\x06proto3\
+    \x18\x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\"\xc5\x01\n\x17GridBlockOr\
+    derChangeset\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x123\n\
+    \rinserted_rows\x18\x02\x20\x03(\x0b2\x0e.IndexRowOrderR\x0cinsertedRows\
+    \x12,\n\x0cdeleted_rows\x18\x03\x20\x03(\x0b2\t.RowOrderR\x0bdeletedRows\
+    \x12,\n\x0cupdated_rows\x18\x04\x20\x03(\x0b2\t.RowOrderR\x0bupdatedRows\
+    \"_\n\rIndexRowOrder\x12&\n\trow_order\x18\x01\x20\x01(\x0b2\t.RowOrderR\
+    \x08rowOrder\x12\x16\n\x05index\x18\x02\x20\x01(\x05H\0R\x05indexB\x0e\n\
+    \x0cone_of_index\"E\n\tGridBlock\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02\
+    id\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\";\n\
+    \x04Cell\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\
+    \x07content\x18\x02\x20\x01(\tR\x07content\"\x8f\x01\n\x14CellNotificati\
+    onData\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08f\
+    ield_id\x18\x02\x20\x01(\tR\x07fieldId\x12\x15\n\x06row_id\x18\x03\x20\
+    \x01(\tR\x05rowId\x12\x1a\n\x07content\x18\x04\x20\x01(\tH\0R\x07content\
+    B\x10\n\x0eone_of_content\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\
+    \x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\x12\
+    \n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\x05va\
+    lue\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\x05value\
+    \x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\x07grid\
+    _id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\x02\x20\
+    \x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\"\xb6\x01\n\x12Cre\
+    ateFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\
+    \x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\x12(\n\x10type\
+    _option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\x12&\n\x0estart_fie\
+    ld_id\x18\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\x15one_of_start_fiel\
+    d_id\"d\n\x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
+    \x06gridId\x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFiel\
+    dOrderR\x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\x12\x17\n\x07grid_\
+    id\x18\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\
+    \x0b2\x0f.GridBlockOrderR\x0bblockOrdersb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 5 - 1
shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto

@@ -61,10 +61,14 @@ message GridBlockOrder {
 }
 message GridBlockOrderChangeset {
     string block_id = 1;
-    repeated RowOrder inserted_rows = 2;
+    repeated IndexRowOrder inserted_rows = 2;
     repeated RowOrder deleted_rows = 3;
     repeated RowOrder updated_rows = 4;
 }
+message IndexRowOrder {
+    RowOrder row_order = 1;
+    oneof one_of_index { int32 index = 2; };
+}
 message GridBlock {
     string id = 1;
     repeated RowOrder row_orders = 2;

+ 12 - 8
shared-lib/flowy-sync/src/client_grid/grid_block_meta_pad.rs

@@ -48,14 +48,11 @@ impl GridBlockMetaPad {
     ) -> CollaborateResult<Option<GridBlockMetaChange>> {
         self.modify(|rows| {
             if let Some(start_row_id) = start_row_id {
-                if start_row_id.is_empty() {
-                    rows.insert(0, Arc::new(row));
-                    return Ok(Some(()));
-                }
-
-                if let Some(index) = rows.iter().position(|row| row.id == start_row_id) {
-                    rows.insert(index + 1, Arc::new(row));
-                    return Ok(Some(()));
+                if !start_row_id.is_empty() {
+                    if let Some(index) = rows.iter().position(|row| row.id == start_row_id) {
+                        rows.insert(index + 1, Arc::new(row));
+                        return Ok(Some(()));
+                    }
                 }
             }
 
@@ -121,6 +118,13 @@ impl GridBlockMetaPad {
         self.rows.len() as i32
     }
 
+    pub fn index_of_row(&self, row_id: &str) -> Option<i32> {
+        self.rows
+            .iter()
+            .position(|row| row.id == row_id)
+            .map(|index| index as i32)
+    }
+
     pub fn update_row(&mut self, changeset: RowMetaChangeset) -> CollaborateResult<Option<GridBlockMetaChange>> {
         let row_id = changeset.row_id.clone();
         self.modify_row(&row_id, |row| {