Browse Source

chore: refactor grid field listener

appflowy 3 years ago
parent
commit
fd9e13bf4b
26 changed files with 607 additions and 378 deletions
  1. 9 2
      frontend/app_flowy/lib/startup/deps_resolver.dart
  2. 5 5
      frontend/app_flowy/lib/startup/tasks/app_widget.dart
  3. 2 2
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart
  4. 2 2
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/number_cell_bloc.dart
  5. 2 2
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart
  6. 2 2
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart
  7. 2 2
      frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart
  8. 2 2
      frontend/app_flowy/lib/workspace/application/grid/field/field_listener.dart
  9. 2 2
      frontend/app_flowy/lib/workspace/application/grid/field/grid_listenr.dart
  10. 34 86
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  11. 76 0
      frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart
  12. 176 0
      frontend/app_flowy/lib/workspace/application/grid/grid_service.dart
  13. 1 0
      frontend/app_flowy/lib/workspace/application/grid/prelude.dart
  14. 9 12
      frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart
  15. 1 1
      frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart
  16. 14 10
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
  17. 61 19
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart
  18. 3 2
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart
  19. 0 13
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
  20. 2 5
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
  21. 14 14
      frontend/rust-lib/flowy-folder/src/services/folder_editor.rs
  22. 57 55
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  23. 11 2
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  24. 90 113
      shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
  25. 1 1
      shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
  26. 29 24
      shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs

+ 9 - 2
frontend/app_flowy/lib/startup/deps_resolver.dart

@@ -150,9 +150,16 @@ void _resolveGridDeps(GetIt getIt) {
     (view, _) => GridBloc(view: view),
   );
 
-  getIt.registerFactoryParam<RowBloc, RowData, void>(
-    (data, _) => RowBloc(
+  getIt.registerFactoryParam<RowBloc, RowData, GridFieldCache>(
+    (data, fieldCache) => RowBloc(
       rowData: data,
+      fieldCache: fieldCache,
+    ),
+  );
+
+  getIt.registerFactoryParam<GridHeaderBloc, String, List<Field>>(
+    (gridId, fields) => GridHeaderBloc(
+      data: GridHeaderData(gridId: gridId, fields: fields),
     ),
   );
 

+ 5 - 5
frontend/app_flowy/lib/startup/tasks/app_widget.dart

@@ -123,9 +123,9 @@ class ApplicationBlocObserver extends BlocObserver {
     super.onError(bloc, error, stackTrace);
   }
 
-  @override
-  void onEvent(Bloc bloc, Object? event) {
-    Log.debug("$event");
-    super.onEvent(bloc, event);
-  }
+  // @override
+  // void onEvent(Bloc bloc, Object? event) {
+  //   Log.debug("$event");
+  //   super.onEvent(bloc, event);
+  // }
 }

+ 2 - 2
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart

@@ -13,12 +13,12 @@ part 'date_cell_bloc.freezed.dart';
 class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
   final CellService _service;
   final CellListener _cellListener;
-  final FieldListener _fieldListener;
+  final SingleFieldListener _fieldListener;
 
   DateCellBloc({required CellData cellData})
       : _service = CellService(),
         _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
-        _fieldListener = FieldListener(fieldId: cellData.field.id),
+        _fieldListener = SingleFieldListener(fieldId: cellData.field.id),
         super(DateCellState.initial(cellData)) {
     on<DateCellEvent>(
       (event, emit) async {

+ 2 - 2
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/number_cell_bloc.dart

@@ -13,13 +13,13 @@ part 'number_cell_bloc.freezed.dart';
 class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
   final CellService _service;
   final CellListener _cellListener;
-  final FieldListener _fieldListener;
+  final SingleFieldListener _fieldListener;
 
   NumberCellBloc({
     required CellData cellData,
   })  : _service = CellService(),
         _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
-        _fieldListener = FieldListener(fieldId: cellData.field.id),
+        _fieldListener = SingleFieldListener(fieldId: cellData.field.id),
         super(NumberCellState.initial(cellData)) {
     on<NumberCellEvent>(
       (event, emit) async {

+ 2 - 2
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart

@@ -13,13 +13,13 @@ part 'selection_cell_bloc.freezed.dart';
 class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
   final SelectOptionService _service;
   final CellListener _cellListener;
-  final FieldListener _fieldListener;
+  final SingleFieldListener _fieldListener;
 
   SelectionCellBloc({
     required CellData cellData,
   })  : _service = SelectOptionService(),
         _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
-        _fieldListener = FieldListener(fieldId: cellData.field.id),
+        _fieldListener = SingleFieldListener(fieldId: cellData.field.id),
         super(SelectionCellState.initial(cellData)) {
     on<SelectionCellEvent>(
       (event, emit) async {

+ 2 - 2
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart

@@ -13,7 +13,7 @@ part 'selection_editor_bloc.freezed.dart';
 
 class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
   final SelectOptionService _selectOptionService;
-  final FieldListener _fieldListener;
+  final SingleFieldListener _fieldListener;
   final CellListener _cellListener;
   Timer? _delayOperation;
 
@@ -22,7 +22,7 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
     required List<SelectOption> options,
     required List<SelectOption> selectedOptions,
   })  : _selectOptionService = SelectOptionService(),
-        _fieldListener = FieldListener(fieldId: cellData.field.id),
+        _fieldListener = SingleFieldListener(fieldId: cellData.field.id),
         _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
         super(SelectOptionEditorState.initial(cellData, options, selectedOptions)) {
     on<SelectOptionEditorEvent>(

+ 2 - 2
frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart

@@ -9,12 +9,12 @@ import 'dart:async';
 part 'field_cell_bloc.freezed.dart';
 
 class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
-  final FieldListener _fieldListener;
+  final SingleFieldListener _fieldListener;
   final FieldService _fieldService;
 
   FieldCellBloc({
     required GridFieldCellContext cellContext,
-  })  : _fieldListener = FieldListener(fieldId: cellContext.field.id),
+  })  : _fieldListener = SingleFieldListener(fieldId: cellContext.field.id),
         _fieldService = FieldService(gridId: cellContext.gridId),
         super(FieldCellState.initial(cellContext)) {
     on<FieldCellEvent>(

+ 2 - 2
frontend/app_flowy/lib/workspace/application/grid/field/field_listener.dart

@@ -9,12 +9,12 @@ import 'package:app_flowy/core/notification_helper.dart';
 
 typedef UpdateFieldNotifiedValue = Either<Field, FlowyError>;
 
-class FieldListener {
+class SingleFieldListener {
   final String fieldId;
   PublishNotifier<UpdateFieldNotifiedValue> updateFieldNotifier = PublishNotifier();
   GridNotificationListener? _listener;
 
-  FieldListener({required this.fieldId});
+  SingleFieldListener({required this.fieldId});
 
   void start() {
     _listener = GridNotificationListener(

+ 2 - 2
frontend/app_flowy/lib/workspace/application/grid/field/grid_listenr.dart

@@ -7,7 +7,7 @@ import 'dart:async';
 import 'dart:typed_data';
 import 'package:app_flowy/core/notification_helper.dart';
 
-typedef UpdateFieldNotifiedValue = Either<List<Field>, FlowyError>;
+typedef UpdateFieldNotifiedValue = Either<GridFieldChangeset, FlowyError>;
 
 class GridFieldsListener {
   final String gridId;
@@ -26,7 +26,7 @@ class GridFieldsListener {
     switch (ty) {
       case GridNotification.DidUpdateGrid:
         result.fold(
-          (payload) => updateFieldsNotifier.value = left(RepeatedField.fromBuffer(payload).items),
+          (payload) => updateFieldsNotifier.value = left(GridFieldChangeset.fromBuffer(payload)),
           (error) => updateFieldsNotifier.value = right(error),
         );
         break;

+ 34 - 86
frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart

@@ -17,18 +17,22 @@ class GridBloc extends Bloc<GridEvent, GridState> {
   final GridService _gridService;
   final GridListener _gridListener;
   final GridFieldsListener _fieldListener;
+  final GridFieldCache fieldCache;
+  final GridRowCache _rowCache;
 
   GridBloc({required View view})
       : _fieldListener = GridFieldsListener(gridId: view.id),
         _gridService = GridService(gridId: view.id),
         _gridListener = GridListener(gridId: view.id),
+        fieldCache = GridFieldCache(),
+        _rowCache = GridRowCache(gridId: view.id),
         super(GridState.initial(view.id)) {
     on<GridEvent>(
       (event, emit) async {
         await event.map(
           initial: (InitialGrid value) async {
-            await _initGrid(emit);
             _startListening();
+            await _loadGrid(emit);
           },
           createRow: (_CreateRow value) {
             _gridService.createRow();
@@ -37,11 +41,9 @@ class GridBloc extends Bloc<GridEvent, GridState> {
             emit(state.copyWith(rows: value.rows, listState: value.listState));
           },
           didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
-            final rows = state.rows.map((row) => row.copyWith(fields: value.fields)).toList();
             emit(state.copyWith(
-              rows: rows,
+              rows: _rowCache.rows,
               fields: value.fields,
-              listState: const GridListState.reload(),
             ));
           },
         );
@@ -57,35 +59,39 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     return super.close();
   }
 
-  Future<void> _initGrid(Emitter<GridState> emit) async {
+  void _startListening() {
+    fieldCache.addListener((fields) {
+      _rowCache.updateFields(fields);
+    });
+
     _fieldListener.updateFieldsNotifier.addPublishListener((result) {
       result.fold(
-        (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
+        (changeset) {
+          fieldCache.applyChangeset(changeset);
+          add(GridEvent.didReceiveFieldUpdate(List.from(fieldCache.fields)));
+        },
         (err) => Log.error(err),
       );
     });
     _fieldListener.start();
 
-    await _loadGrid(emit);
-  }
-
-  void _startListening() {
     _gridListener.rowsUpdateNotifier.addPublishListener((result) {
-      result.fold((gridBlockChangeset) {
-        for (final changeset in gridBlockChangeset) {
-          if (changeset.insertedRows.isNotEmpty) {
-            _insertRows(changeset.insertedRows);
-          }
+      result.fold(
+        (changesets) {
+          for (final changeset in changesets) {
+            _rowCache
+                .deleteRows(changeset.deletedRows)
+                .foldRight(null, (listState, _) => add(GridEvent.didReceiveRowUpdate(_rowCache.rows, listState)));
 
-          if (changeset.deletedRows.isNotEmpty) {
-            _deleteRows(changeset.deletedRows);
-          }
+            _rowCache
+                .insertRows(changeset.insertedRows)
+                .foldRight(null, (listState, _) => add(GridEvent.didReceiveRowUpdate(_rowCache.rows, listState)));
 
-          if (changeset.updatedRows.isNotEmpty) {
-            _updateRows(changeset.updatedRows);
+            _rowCache.updateRows(changeset.updatedRows);
           }
-        }
-      }, (err) => Log.error(err));
+        },
+        (err) => Log.error(err),
+      );
     });
     _gridListener.start();
   }
@@ -105,10 +111,13 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     return Future(
       () => result.fold(
         (fields) {
+          fieldCache.fields = fields.items;
+          _rowCache.updateWithBlock(grid.blockOrders);
+
           emit(state.copyWith(
             grid: Some(grid),
-            fields: fields.items,
-            rows: _buildRows(grid.blockOrders, fields.items),
+            fields: fieldCache.fields,
+            rows: _rowCache.rows,
             loadingState: GridLoadingState.finish(left(unit)),
           ));
         },
@@ -116,60 +125,6 @@ class GridBloc extends Bloc<GridEvent, GridState> {
       ),
     );
   }
-
-  void _deleteRows(List<RowOrder> deletedRows) {
-    final List<RowData> rows = [];
-    final List<Tuple2<int, RowData>> deletedIndex = [];
-    final Map<String, RowOrder> deletedRowMap = {for (var rowOrder in deletedRows) rowOrder.rowId: rowOrder};
-    state.rows.asMap().forEach((index, value) {
-      if (deletedRowMap[value.rowId] == null) {
-        rows.add(value);
-      } else {
-        deletedIndex.add(Tuple2(index, value));
-      }
-    });
-
-    add(GridEvent.didReceiveRowUpdate(rows, GridListState.delete(deletedIndex)));
-  }
-
-  void _insertRows(List<IndexRowOrder> createdRows) {
-    final List<RowData> rows = List.from(state.rows);
-    List<int> insertIndexs = [];
-    for (final newRow in createdRows) {
-      if (newRow.hasIndex()) {
-        insertIndexs.add(newRow.index);
-        rows.insert(newRow.index, _toRowData(newRow.rowOrder));
-      } else {
-        insertIndexs.add(rows.length);
-        rows.add(_toRowData(newRow.rowOrder));
-      }
-    }
-    add(GridEvent.didReceiveRowUpdate(rows, GridListState.insert(insertIndexs)));
-  }
-
-  void _updateRows(List<RowOrder> updatedRows) {
-    final List<RowData> rows = List.from(state.rows);
-    final List<int> updatedIndexs = [];
-    for (final updatedRow in updatedRows) {
-      final index = rows.indexWhere((row) => row.rowId == updatedRow.rowId);
-      if (index != -1) {
-        rows.removeAt(index);
-        rows.insert(index, _toRowData(updatedRow));
-        updatedIndexs.add(index);
-      }
-    }
-    add(GridEvent.didReceiveRowUpdate(rows, const GridListState.reload()));
-  }
-
-  List<RowData> _buildRows(List<GridBlockOrder> blockOrders, List<Field> fields) {
-    return blockOrders.expand((blockOrder) => blockOrder.rowOrders).map((rowOrder) {
-      return RowData.fromBlockRow(state.gridId, rowOrder, fields);
-    }).toList();
-  }
-
-  RowData _toRowData(RowOrder rowOrder) {
-    return RowData.fromBlockRow(state.gridId, rowOrder, state.fields);
-  }
 }
 
 @freezed
@@ -197,7 +152,7 @@ class GridState with _$GridState {
         grid: none(),
         gridId: gridId,
         loadingState: const _Loading(),
-        listState: const _Reload(),
+        listState: const InitialListState(),
       );
 }
 
@@ -206,10 +161,3 @@ class GridLoadingState with _$GridLoadingState {
   const factory GridLoadingState.loading() = _Loading;
   const factory GridLoadingState.finish(Either<Unit, FlowyError> successOrFail) = _Finish;
 }
-
-@freezed
-class GridListState with _$GridListState {
-  const factory GridListState.insert(List<int> indexs) = _Insert;
-  const factory GridListState.delete(List<Tuple2<int, RowData>> indexs) = _Delete;
-  const factory GridListState.reload() = _Reload;
-}

+ 76 - 0
frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart

@@ -0,0 +1,76 @@
+import 'package:app_flowy/workspace/application/grid/data.dart';
+import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'dart:async';
+import 'field/field_service.dart';
+import 'grid_service.dart';
+
+part 'grid_header_bloc.freezed.dart';
+
+class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
+  final FieldService _fieldService;
+  final GridFieldCache _fieldCache;
+  final GridFieldsListener _fieldListener;
+
+  GridHeaderBloc({
+    required GridHeaderData data,
+  })  : _fieldListener = GridFieldsListener(gridId: data.gridId),
+        _fieldService = FieldService(gridId: data.gridId),
+        _fieldCache = GridFieldCache(),
+        super(GridHeaderState.initial(data.fields)) {
+    _fieldCache.fields = data.fields;
+
+    on<GridHeaderEvent>(
+      (event, emit) async {
+        await event.map(
+          initial: (_InitialHeader value) async {
+            _startListening();
+          },
+          didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
+            value.fields.retainWhere((field) => field.visibility);
+            emit(state.copyWith(fields: value.fields));
+          },
+        );
+      },
+    );
+  }
+
+  Future<void> _startListening() async {
+    _fieldListener.updateFieldsNotifier.addPublishListener((result) {
+      result.fold(
+        (changeset) {
+          _fieldCache.applyChangeset(changeset);
+          add(GridHeaderEvent.didReceiveFieldUpdate(List.from(_fieldCache.fields)));
+        },
+        (err) => Log.error(err),
+      );
+    });
+
+    _fieldListener.start();
+  }
+
+  @override
+  Future<void> close() async {
+    await _fieldListener.stop();
+    return super.close();
+  }
+}
+
+@freezed
+class GridHeaderEvent with _$GridHeaderEvent {
+  const factory GridHeaderEvent.initial() = _InitialHeader;
+  const factory GridHeaderEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate;
+}
+
+@freezed
+class GridHeaderState with _$GridHeaderState {
+  const factory GridHeaderState({required List<Field> fields}) = _GridHeaderState;
+
+  factory GridHeaderState.initial(List<Field> fields) {
+    fields.retainWhere((field) => field.visibility);
+    return GridHeaderState(fields: fields);
+  }
+}

+ 176 - 0
frontend/app_flowy/lib/workspace/application/grid/grid_service.dart

@@ -3,6 +3,11 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:flutter/foundation.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
+
+part 'grid_service.freezed.dart';
 
 class GridService {
   final String gridId;
@@ -35,3 +40,174 @@ class GridService {
     return FolderEventCloseView(request).send();
   }
 }
+
+class FieldsNotifier extends ChangeNotifier {
+  List<Field> _fields = [];
+
+  set fields(List<Field> fields) {
+    _fields = fields;
+    notifyListeners();
+  }
+
+  List<Field> get fields => _fields;
+}
+
+class GridFieldCache {
+  final FieldsNotifier _fieldNotifier = FieldsNotifier();
+  GridFieldCache();
+
+  void applyChangeset(GridFieldChangeset changeset) {
+    _removeFields(changeset.deletedFields);
+    _insertFields(changeset.insertedFields);
+    _updateFields(changeset.updatedFields);
+  }
+
+  List<Field> get fields => _fieldNotifier.fields;
+
+  set fields(List<Field> fields) {
+    _fieldNotifier.fields = fields;
+  }
+
+  set onFieldChanged(void Function(List<Field>) onChanged) {
+    _fieldNotifier.addListener(() => onChanged(fields));
+  }
+
+  void addListener(void Function(List<Field>) onFieldChanged) {
+    _fieldNotifier.addListener(() => onFieldChanged(fields));
+  }
+
+  void _removeFields(List<FieldOrder> deletedFields) {
+    if (deletedFields.isEmpty) {
+      return;
+    }
+    final List<Field> fields = List.from(_fieldNotifier.fields);
+    final Map<String, FieldOrder> deletedFieldMap = {
+      for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
+    };
+
+    fields.retainWhere((field) => (deletedFieldMap[field.id] == null));
+    _fieldNotifier.fields = fields;
+  }
+
+  void _insertFields(List<IndexField> insertedFields) {
+    if (insertedFields.isEmpty) {
+      return;
+    }
+    final List<Field> fields = List.from(_fieldNotifier.fields);
+    for (final indexField in insertedFields) {
+      if (fields.length > indexField.index) {
+        fields.removeAt(indexField.index);
+        fields.insert(indexField.index, indexField.field_1);
+      } else {
+        fields.add(indexField.field_1);
+      }
+    }
+    _fieldNotifier.fields = fields;
+  }
+
+  void _updateFields(List<Field> updatedFields) {
+    if (updatedFields.isEmpty) {
+      return;
+    }
+    final List<Field> fields = List.from(_fieldNotifier.fields);
+    for (final updatedField in updatedFields) {
+      final index = fields.indexWhere((field) => field.id == updatedField.id);
+      if (index != -1) {
+        fields.removeAt(index);
+        fields.insert(index, updatedField);
+      }
+    }
+    _fieldNotifier.fields = fields;
+  }
+}
+
+class GridRowCache {
+  final String gridId;
+  List<Field> _fields = [];
+  List<RowData> _rows = [];
+
+  GridRowCache({required this.gridId});
+
+  List<RowData> get rows => _rows;
+
+  void updateWithBlock(List<GridBlockOrder> blocks) {
+    _rows = blocks.expand((block) => block.rowOrders).map((rowOrder) {
+      return RowData.fromBlockRow(gridId, rowOrder, _fields);
+    }).toList();
+  }
+
+  void updateFields(List<Field> fields) {
+    if (fields.isEmpty) {
+      return;
+    }
+
+    _fields = fields;
+    _rows = _rows.map((row) => row.copyWith(fields: fields)).toList();
+  }
+
+  Option<GridListState> deleteRows(List<RowOrder> deletedRows) {
+    if (deletedRows.isEmpty) {
+      return none();
+    }
+
+    final List<RowData> newRows = [];
+    final List<Tuple2<int, RowData>> deletedIndex = [];
+    final Map<String, RowOrder> deletedRowMap = {for (var rowOrder in deletedRows) rowOrder.rowId: rowOrder};
+    _rows.asMap().forEach((index, value) {
+      if (deletedRowMap[value.rowId] == null) {
+        newRows.add(value);
+      } else {
+        deletedIndex.add(Tuple2(index, value));
+      }
+    });
+    _rows = newRows;
+
+    return Some(GridListState.delete(deletedIndex));
+  }
+
+  Option<GridListState> insertRows(List<IndexRowOrder> createdRows) {
+    if (createdRows.isEmpty) {
+      return none();
+    }
+
+    List<int> insertIndexs = [];
+    for (final newRow in createdRows) {
+      if (newRow.hasIndex()) {
+        insertIndexs.add(newRow.index);
+        _rows.insert(newRow.index, _toRowData(newRow.rowOrder));
+      } else {
+        insertIndexs.add(_rows.length);
+        _rows.add(_toRowData(newRow.rowOrder));
+      }
+    }
+
+    return Some(GridListState.insert(insertIndexs));
+  }
+
+  void updateRows(List<RowOrder> updatedRows) {
+    if (updatedRows.isEmpty) {
+      return;
+    }
+
+    final List<int> updatedIndexs = [];
+    for (final updatedRow in updatedRows) {
+      final index = _rows.indexWhere((row) => row.rowId == updatedRow.rowId);
+      if (index != -1) {
+        _rows.removeAt(index);
+        _rows.insert(index, _toRowData(updatedRow));
+        updatedIndexs.add(index);
+      }
+    }
+  }
+
+  RowData _toRowData(RowOrder rowOrder) {
+    return RowData.fromBlockRow(gridId, rowOrder, _fields);
+  }
+}
+
+@freezed
+class GridListState with _$GridListState {
+  const factory GridListState.insert(List<int> indexs) = _Insert;
+  const factory GridListState.delete(List<Tuple2<int, RowData>> indexs) = _Delete;
+  const factory GridListState.initial() = InitialListState;
+}

+ 1 - 0
frontend/app_flowy/lib/workspace/application/grid/prelude.dart

@@ -3,6 +3,7 @@ export 'row/row_bloc.dart';
 export 'row/row_service.dart';
 export 'grid_service.dart';
 export 'data.dart';
+export 'grid_header_bloc.dart';
 
 // Field
 export 'field/field_service.dart';

+ 9 - 12
frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart

@@ -1,6 +1,6 @@
 import 'dart:collection';
 
-import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
+import 'package:app_flowy/workspace/application/grid/grid_service.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -17,18 +17,18 @@ typedef CellDataMap = LinkedHashMap<String, CellData>;
 class RowBloc extends Bloc<RowEvent, RowState> {
   final RowService _rowService;
   final RowListener _rowlistener;
-  final GridFieldsListener _fieldListener;
+  final GridFieldCache _fieldCache;
 
-  RowBloc({required RowData rowData})
+  RowBloc({required RowData rowData, required GridFieldCache fieldCache})
       : _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
-        _fieldListener = GridFieldsListener(gridId: rowData.gridId),
+        _fieldCache = fieldCache,
         _rowlistener = RowListener(rowId: rowData.rowId),
         super(RowState.initial(rowData)) {
     on<RowEvent>(
       (event, emit) async {
         await event.map(
           initial: (_InitialRow value) async {
-            _startListening();
+            await _startListening();
             await _loadRow(emit);
           },
           createRow: (_CreateRow value) {
@@ -69,7 +69,6 @@ class RowBloc extends Bloc<RowEvent, RowState> {
   @override
   Future<void> close() async {
     await _rowlistener.stop();
-    await _fieldListener.stop();
     return super.close();
   }
 
@@ -81,15 +80,13 @@ class RowBloc extends Bloc<RowEvent, RowState> {
       );
     });
 
-    _fieldListener.updateFieldsNotifier.addPublishListener((result) {
-      result.fold(
-        (fields) => add(RowEvent.didReceiveFieldUpdate(fields)),
-        (err) => Log.error(err),
-      );
+    _fieldCache.addListener((fields) {
+      if (!isClosed) {
+        add(RowEvent.didReceiveFieldUpdate(fields));
+      }
     });
 
     _rowlistener.start();
-    _fieldListener.start();
   }
 
   Future<void> _loadRow(Emitter<RowState> emit) async {

+ 1 - 1
frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart

@@ -50,7 +50,7 @@ class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
     _fieldListener.updateFieldsNotifier.addPublishListener((result) {
       result.fold(
         (fields) {
-          add(GridPropertyEvent.didReceiveFieldUpdate(fields));
+          // add(GridPropertyEvent.didReceiveFieldUpdate(fields));
         },
         (err) => Log.error(err),
       );

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

@@ -150,11 +150,7 @@ class _GridHeader extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return SliverPersistentHeader(
-      delegate: GridHeaderSliverAdaptor(gridId: gridId, fields: List.from(fields)),
-      floating: true,
-      pinned: true,
-    );
+    return GridHeaderSliverAdaptor(gridId: gridId, fields: fields);
   }
 }
 
@@ -174,10 +170,13 @@ class _GridRows extends StatelessWidget {
           },
           delete: (value) {
             for (final index in value.indexs) {
-              _key.currentState?.removeItem(index.value1, (context, animation) => _renderRow(index.value2, animation));
+              _key.currentState?.removeItem(
+                index.value1,
+                (context, animation) => _renderRow(context, index.value2, animation),
+              );
             }
           },
-          reload: (updatedIndexs) {},
+          initial: (updatedIndexs) {},
         );
       },
       buildWhen: (previous, current) => false,
@@ -187,17 +186,22 @@ class _GridRows extends StatelessWidget {
           initialItemCount: context.read<GridBloc>().state.rows.length,
           itemBuilder: (BuildContext context, int index, Animation<double> animation) {
             final rowData = context.read<GridBloc>().state.rows[index];
-            return _renderRow(rowData, animation);
+            return _renderRow(context, rowData, animation);
           },
         );
       },
     );
   }
 
-  Widget _renderRow(RowData rowData, Animation<double> animation) {
+  Widget _renderRow(BuildContext context, RowData rowData, Animation<double> animation) {
+    final fieldCache = context.read<GridBloc>().fieldCache;
     return SizeTransition(
       sizeFactor: animation,
-      child: GridRowWidget(data: rowData, key: ValueKey(rowData.rowId)),
+      child: GridRowWidget(
+        data: rowData,
+        fieldCache: fieldCache,
+        key: ValueKey(rowData.rowId),
+      ),
     );
   }
 }

+ 61 - 19
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart

@@ -12,32 +12,38 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'field_editor.dart';
 import 'field_cell.dart';
 
-class GridHeaderSliverAdaptor extends SliverPersistentHeaderDelegate {
+class GridHeaderSliverAdaptor extends StatelessWidget {
   final String gridId;
   final List<Field> fields;
 
-  GridHeaderSliverAdaptor({required this.gridId, required this.fields});
+  const GridHeaderSliverAdaptor({required this.gridId, required this.fields, Key? key}) : super(key: key);
 
   @override
-  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
-    final cells = fields.map(
-      (field) => GridFieldCell(
-        GridFieldCellContext(gridId: gridId, field: field),
+  Widget build(BuildContext context) {
+    return BlocProvider(
+      create: (context) => getIt<GridHeaderBloc>(param1: gridId, param2: fields)..add(const GridHeaderEvent.initial()),
+      child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
+        builder: (context, state) {
+          return SliverPersistentHeader(
+            delegate: SliverHeaderDelegateImplementation(gridId: gridId, fields: fields),
+            floating: true,
+            pinned: true,
+          );
+        },
       ),
     );
+  }
+}
 
-    return Container(
-      color: Colors.white,
-      child: Row(
-        crossAxisAlignment: CrossAxisAlignment.stretch,
-        children: [
-          const _CellLeading(),
-          ...cells,
-          _CellTrailing(gridId: gridId),
-        ],
-        key: ObjectKey(fields),
-      ),
-    );
+class SliverHeaderDelegateImplementation extends SliverPersistentHeaderDelegate {
+  final String gridId;
+  final List<Field> fields;
+
+  SliverHeaderDelegateImplementation({required this.gridId, required this.fields});
+
+  @override
+  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
+    return _GridHeader(gridId: gridId, fields: fields, key: ObjectKey(fields));
   }
 
   @override
@@ -48,13 +54,49 @@ class GridHeaderSliverAdaptor extends SliverPersistentHeaderDelegate {
 
   @override
   bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
-    if (oldDelegate is GridHeaderSliverAdaptor) {
+    if (oldDelegate is SliverHeaderDelegateImplementation) {
       return fields.length != oldDelegate.fields.length;
     }
     return true;
   }
 }
 
+class _GridHeader extends StatelessWidget {
+  final String gridId;
+  final List<Field> fields;
+
+  const _GridHeader({
+    Key? key,
+    required this.gridId,
+    required this.fields,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return BlocBuilder<GridHeaderBloc, GridHeaderState>(
+      builder: (context, state) {
+        final cells = state.fields
+            .map((field) => GridFieldCellContext(gridId: gridId, field: field))
+            .map((ctx) => GridFieldCell(ctx, key: ValueKey(ctx.field.id)))
+            .toList();
+
+        return Container(
+          color: theme.surface,
+          child: Row(
+            crossAxisAlignment: CrossAxisAlignment.stretch,
+            children: [
+              const _CellLeading(),
+              ...cells,
+              _CellTrailing(gridId: gridId),
+            ],
+          ),
+        );
+      },
+    );
+  }
+}
+
 class _CellLeading extends StatelessWidget {
   const _CellLeading({Key? key}) : super(key: key);
 

+ 3 - 2
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart

@@ -13,7 +13,8 @@ import 'row_action_sheet.dart';
 
 class GridRowWidget extends StatefulWidget {
   final RowData data;
-  const GridRowWidget({required this.data, Key? key}) : super(key: key);
+  final GridFieldCache fieldCache;
+  const GridRowWidget({required this.data, required this.fieldCache, Key? key}) : super(key: key);
 
   @override
   State<GridRowWidget> createState() => _GridRowWidgetState();
@@ -25,7 +26,7 @@ class _GridRowWidgetState extends State<GridRowWidget> {
 
   @override
   void initState() {
-    _rowBloc = getIt<RowBloc>(param1: widget.data)..add(const RowEvent.initial());
+    _rowBloc = getIt<RowBloc>(param1: widget.data, param2: widget.fieldCache)..add(const RowEvent.initial());
     _rowStateNotifier = _RegionStateNotifier();
     super.initState();
   }

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

@@ -325,18 +325,8 @@ class GridFieldChangeset extends $pb.GeneratedMessage {
   $core.List<Field> get updatedFields => $_getList(3);
 }
 
-enum IndexField_OneOfIndex {
-  index_, 
-  notSet
-}
-
 class IndexField extends $pb.GeneratedMessage {
-  static const $core.Map<$core.int, IndexField_OneOfIndex> _IndexField_OneOfIndexByTag = {
-    2 : IndexField_OneOfIndex.index_,
-    0 : IndexField_OneOfIndex.notSet
-  };
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'IndexField', createEmptyInstance: create)
-    ..oo(0, [2])
     ..aOM<Field>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'field', subBuilder: Field.create)
     ..a<$core.int>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'index', $pb.PbFieldType.O3)
     ..hasRequiredFields = false
@@ -377,9 +367,6 @@ class IndexField extends $pb.GeneratedMessage {
   static IndexField getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<IndexField>(create);
   static IndexField? _defaultInstance;
 
-  IndexField_OneOfIndex whichOneOfIndex() => _IndexField_OneOfIndexByTag[$_whichOneof(0)]!;
-  void clearOneOfIndex() => clearField($_whichOneof(0));
-
   @$pb.TagNumber(1)
   Field get field_1 => $_getN(0);
   @$pb.TagNumber(1)

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

@@ -90,15 +90,12 @@ const IndexField$json = const {
   '1': 'IndexField',
   '2': const [
     const {'1': 'field', '3': 1, '4': 1, '5': 11, '6': '.Field', '10': 'field'},
-    const {'1': 'index', '3': 2, '4': 1, '5': 5, '9': 0, '10': 'index'},
-  ],
-  '8': const [
-    const {'1': 'one_of_index'},
+    const {'1': 'index', '3': 2, '4': 1, '5': 5, '10': 'index'},
   ],
 };
 
 /// Descriptor for `IndexField`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List indexFieldDescriptor = $convert.base64Decode('CgpJbmRleEZpZWxkEhwKBWZpZWxkGAEgASgLMgYuRmllbGRSBWZpZWxkEhYKBWluZGV4GAIgASgFSABSBWluZGV4Qg4KDG9uZV9vZl9pbmRleA==');
+final $typed_data.Uint8List indexFieldDescriptor = $convert.base64Decode('CgpJbmRleEZpZWxkEhwKBWZpZWxkGAEgASgLMgYuRmllbGRSBWZpZWxkEhQKBWluZGV4GAIgASgFUgVpbmRleA==');
 @$core.Deprecated('Use getEditFieldContextPayloadDescriptor instead')
 const GetEditFieldContextPayload$json = const {
   '1': 'GetEditFieldContextPayload',

+ 14 - 14
frontend/rust-lib/flowy-folder/src/services/folder_editor.rs

@@ -24,7 +24,7 @@ pub struct ClientFolderEditor {
     pub(crate) folder_id: FolderId,
     pub(crate) folder: Arc<RwLock<FolderPad>>,
     rev_manager: Arc<RevisionManager>,
-    ws_manager: Arc<RevisionWebSocketManager>,
+    // ws_manager: Arc<RevisionWebSocketManager>,
 }
 
 impl ClientFolderEditor {
@@ -40,14 +40,14 @@ impl ClientFolderEditor {
         });
         let folder = Arc::new(RwLock::new(rev_manager.load::<FolderPadBuilder>(Some(cloud)).await?));
         let rev_manager = Arc::new(rev_manager);
-        let ws_manager = make_folder_ws_manager(
-            user_id,
-            folder_id.as_ref(),
-            rev_manager.clone(),
-            web_socket,
-            folder.clone(),
-        )
-        .await;
+        // let ws_manager = make_folder_ws_manager(
+        //     user_id,
+        //     folder_id.as_ref(),
+        //     rev_manager.clone(),
+        //     web_socket,
+        //     folder.clone(),
+        // )
+        // .await;
 
         let user_id = user_id.to_owned();
         let folder_id = folder_id.to_owned();
@@ -56,15 +56,15 @@ impl ClientFolderEditor {
             folder_id,
             folder,
             rev_manager,
-            ws_manager,
+            // ws_manager,
         })
     }
 
     pub async fn receive_ws_data(&self, data: ServerRevisionWSData) -> FlowyResult<()> {
-        let _ = self.ws_manager.ws_passthrough_tx.send(data).await.map_err(|e| {
-            let err_msg = format!("{} passthrough error: {}", self.folder_id, e);
-            FlowyError::internal().context(err_msg)
-        })?;
+        // let _ = self.ws_manager.ws_passthrough_tx.send(data).await.map_err(|e| {
+        //     let err_msg = format!("{} passthrough error: {}", self.folder_id, e);
+        //     FlowyError::internal().context(err_msg)
+        // })?;
         Ok(())
     }
 

+ 57 - 55
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -59,9 +59,9 @@ impl ClientGridEditor {
             grid_id,
         } = params;
         let field_id = field.id.clone();
-        let _ = self
-            .modify(|grid| {
-                if grid.contain_field(&field.id) {
+        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,
@@ -74,17 +74,22 @@ impl ClientGridEditor {
                         width: Some(field.width),
                         type_option_data: Some(type_option_data),
                     };
-                    Ok(grid.update_field(changeset, deserializer)?)
-                } else {
-                    // let type_option_json = type_option_json_str_from_bytes(type_option_data, &field.field_type);
+                    Ok(grid.update_field_meta(changeset, deserializer)?)
+                })
+                .await?;
+            let _ = self.notify_grid_did_update_field(&field_id).await?;
+        } else {
+            let _ = self
+                .modify(|grid| {
                     let builder = type_option_builder_from_bytes(type_option_data, &field.field_type);
                     let field_meta = FieldBuilder::from_field(field, builder).build();
-                    Ok(grid.create_field(field_meta, start_field_id)?)
-                }
-            })
-            .await?;
-        let _ = self.notify_did_update_grid().await?;
-        let _ = self.notify_did_update_field(&field_id).await?;
+
+                    Ok(grid.create_field_meta(field_meta, start_field_id)?)
+                })
+                .await?;
+            let _ = self.notify_grid_did_insert_field(&field_id).await?;
+        }
+
         Ok(())
     }
 
@@ -100,29 +105,31 @@ impl ClientGridEditor {
 
     pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> {
         let field_id = params.field_id.clone();
-        let json_deserializer = match self.pad.read().await.get_field(params.field_id.as_str()) {
+        let json_deserializer = match self.pad.read().await.get_field_meta(params.field_id.as_str()) {
             None => return Err(ErrorCode::FieldDoesNotExist.into()),
-            Some(field_meta) => TypeOptionJsonDeserializer(field_meta.field_type.clone()),
+            Some((_, field_meta)) => TypeOptionJsonDeserializer(field_meta.field_type.clone()),
         };
 
         let _ = self
-            .modify(|grid| Ok(grid.update_field(params, json_deserializer)?))
+            .modify(|grid| Ok(grid.update_field_meta(params, json_deserializer)?))
             .await?;
-        let _ = self.notify_did_update_grid().await?;
-        let _ = self.notify_did_update_field(&field_id).await?;
+
+        let _ = self.notify_grid_did_update_field(&field_id).await?;
         Ok(())
     }
 
     pub async fn replace_field(&self, field_meta: FieldMeta) -> FlowyResult<()> {
         let field_id = field_meta.id.clone();
-        let _ = self.modify(|pad| Ok(pad.replace_field(field_meta)?)).await?;
-        let _ = self.notify_did_update_field(&field_id).await?;
+        let _ = self.modify(|pad| Ok(pad.replace_field_meta(field_meta)?)).await?;
+        let _ = self.notify_grid_did_update_field(&field_id).await?;
         Ok(())
     }
 
     pub async fn delete_field(&self, field_id: &str) -> FlowyResult<()> {
-        let _ = self.modify(|grid| Ok(grid.delete_field(field_id)?)).await?;
-        let _ = self.notify_did_update_grid().await?;
+        let _ = self.modify(|grid| Ok(grid.delete_field_meta(field_id)?)).await?;
+        let field_order = FieldOrder::from(field_id);
+        let notified_changeset = GridFieldChangeset::delete(&self.grid_id, vec![field_order]);
+        let _ = self.notify_did_update_grid(notified_changeset).await?;
         Ok(())
     }
 
@@ -145,27 +152,24 @@ impl ClientGridEditor {
         let _ = self
             .modify(|grid| Ok(grid.switch_to_field(field_id, field_type.clone(), type_option_json_builder)?))
             .await?;
-        let _ = self.notify_did_update_grid().await?;
-        let _ = self.notify_did_update_field(field_id).await?;
+
+        let _ = self.notify_grid_did_update_field(&field_id).await?;
+
         Ok(())
     }
 
     pub async fn duplicate_field(&self, field_id: &str) -> FlowyResult<()> {
-        let mut duplicated_field_meta = None;
+        let duplicated_field_id = gen_field_id();
         let _ = self
-            .modify(|grid| {
-                let (changeset, field_meta) = grid.duplicate_field(field_id)?;
-                duplicated_field_meta = field_meta;
-                Ok(changeset)
-            })
+            .modify(|grid| Ok(grid.duplicate_field_meta(field_id, &duplicated_field_id)?))
             .await?;
 
-        let _ = self.notify_did_update_grid().await?;
+        let _ = self.notify_grid_did_insert_field(field_id).await?;
         Ok(())
     }
 
     pub async fn get_field_meta(&self, field_id: &str) -> Option<FieldMeta> {
-        let field_meta = self.pad.read().await.get_field(field_id)?.clone();
+        let field_meta = self.pad.read().await.get_field_meta(field_id)?.1.clone();
         Some(field_meta)
     }
 
@@ -310,12 +314,12 @@ impl ClientGridEditor {
         let cell_data_changeset = changeset.data.unwrap();
         let cell_meta = self.get_cell_meta(&changeset.row_id, &changeset.field_id).await?;
         tracing::trace!("{}: {:?}", &changeset.field_id, cell_meta);
-        match self.pad.read().await.get_field(&changeset.field_id) {
+        match self.pad.read().await.get_field_meta(&changeset.field_id) {
             None => {
                 let msg = format!("Field not found with id: {}", &changeset.field_id);
                 Err(FlowyError::internal().context(msg))
             }
-            Some(field_meta) => {
+            Some((_, field_meta)) => {
                 // Update the changeset.data property with the return value.
                 changeset.data = Some(apply_cell_data_changeset(cell_data_changeset, cell_meta, field_meta)?);
                 let _ = self.block_meta_manager.update_cell(changeset).await?;
@@ -334,11 +338,6 @@ impl ClientGridEditor {
         Ok(grid_blocks)
     }
 
-    // pub async fn get_field_metas<T>(&self, field_ids: Option<Vec<T>>) -> FlowyResult<Vec<FieldMeta>>
-    //     where
-    //         T: Into<FieldOrder>,
-    // {
-
     pub async fn delete_rows(&self, row_orders: Vec<RowOrder>) -> FlowyResult<()> {
         let changesets = self.block_meta_manager.delete_rows(row_orders).await?;
         for changeset in changesets {
@@ -445,37 +444,40 @@ impl ClientGridEditor {
         }
     }
 
-    async fn notify_did_update_grid(&self) -> FlowyResult<()> {
-        // GridFieldChangeset
-
-        let field_metas = self.get_field_metas::<FieldOrder>(None).await?;
-        let repeated_field: RepeatedField = field_metas.into_iter().map(Field::from).collect::<Vec<_>>().into();
-        send_dart_notification(&self.grid_id, GridNotification::DidUpdateGrid)
-            .payload(repeated_field)
-            .send();
-        Ok(())
-    }
-
-    async fn notify_did_update_grid2(&self, changeset: GridFieldChangeset) -> FlowyResult<()> {
-        send_dart_notification(&self.grid_id, GridNotification::DidUpdateGrid)
-            .payload(changeset)
-            .send();
+    #[tracing::instrument(level = "trace", skip_all, err)]
+    async fn notify_grid_did_insert_field(&self, field_id: &str) -> FlowyResult<()> {
+        if let Some((index, field_meta)) = self.pad.read().await.get_field_meta(field_id) {
+            let index_field = IndexField::from_field_meta(field_meta, index);
+            let notified_changeset = GridFieldChangeset::insert(&self.grid_id, vec![index_field]);
+            let _ = self.notify_did_update_grid(notified_changeset).await?;
+        }
         Ok(())
     }
 
     #[tracing::instrument(level = "trace", skip_all, err)]
-    async fn notify_did_update_field(&self, field_id: &str) -> FlowyResult<()> {
+    async fn notify_grid_did_update_field(&self, field_id: &str) -> FlowyResult<()> {
         let mut field_metas = self.get_field_metas(Some(vec![field_id])).await?;
         debug_assert!(field_metas.len() == 1);
 
         if let Some(field_meta) = field_metas.pop() {
+            let updated_field = Field::from(field_meta);
+            let notified_changeset = GridFieldChangeset::update(&self.grid_id, vec![updated_field.clone()]);
+            let _ = self.notify_did_update_grid(notified_changeset).await?;
+
             send_dart_notification(field_id, GridNotification::DidUpdateField)
-                .payload(Field::from(field_meta))
+                .payload(updated_field)
                 .send();
         }
 
         Ok(())
     }
+
+    async fn notify_did_update_grid(&self, changeset: GridFieldChangeset) -> FlowyResult<()> {
+        send_dart_notification(&self.grid_id, GridNotification::DidUpdateGrid)
+            .payload(changeset)
+            .send();
+        Ok(())
+    }
 }
 
 #[cfg(feature = "flowy_unit_test")]

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

@@ -133,8 +133,17 @@ pub struct IndexField {
     #[pb(index = 1)]
     pub field: Field,
 
-    #[pb(index = 2, one_of)]
-    pub index: Option<i32>,
+    #[pb(index = 2)]
+    pub index: i32,
+}
+
+impl IndexField {
+    pub fn from_field_meta(field_meta: &FieldMeta, index: usize) -> Self {
+        Self {
+            field: Field::from(field_meta.clone()),
+            index: index as i32,
+        }
+    }
 }
 
 #[derive(Debug, Default, ProtoBuf)]

+ 90 - 113
shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs

@@ -1128,8 +1128,7 @@ impl ::protobuf::reflect::ProtobufValue for GridFieldChangeset {
 pub struct IndexField {
     // message fields
     pub field: ::protobuf::SingularPtrField<Field>,
-    // message oneof groups
-    pub one_of_index: ::std::option::Option<IndexField_oneof_one_of_index>,
+    pub index: i32,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -1141,11 +1140,6 @@ impl<'a> ::std::default::Default for &'a IndexField {
     }
 }
 
-#[derive(Clone,PartialEq,Debug)]
-pub enum IndexField_oneof_one_of_index {
-    index(i32),
-}
-
 impl IndexField {
     pub fn new() -> IndexField {
         ::std::default::Default::default()
@@ -1188,25 +1182,15 @@ impl IndexField {
 
 
     pub fn get_index(&self) -> i32 {
-        match self.one_of_index {
-            ::std::option::Option::Some(IndexField_oneof_one_of_index::index(v)) => v,
-            _ => 0,
-        }
+        self.index
     }
     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(IndexField_oneof_one_of_index::index(..)) => true,
-            _ => false,
-        }
+        self.index = 0;
     }
 
     // Param is passed by value, moved
     pub fn set_index(&mut self, v: i32) {
-        self.one_of_index = ::std::option::Option::Some(IndexField_oneof_one_of_index::index(v))
+        self.index = v;
     }
 }
 
@@ -1231,7 +1215,8 @@ impl ::protobuf::Message for IndexField {
                     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(IndexField_oneof_one_of_index::index(is.read_int32()?));
+                    let tmp = is.read_int32()?;
+                    self.index = tmp;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -1249,12 +1234,8 @@ impl ::protobuf::Message for IndexField {
             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 {
-                &IndexField_oneof_one_of_index::index(v) => {
-                    my_size += ::protobuf::rt::value_size(2, v, ::protobuf::wire_format::WireTypeVarint);
-                },
-            };
+        if self.index != 0 {
+            my_size += ::protobuf::rt::value_size(2, self.index, ::protobuf::wire_format::WireTypeVarint);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -1267,12 +1248,8 @@ impl ::protobuf::Message for IndexField {
             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 {
-                &IndexField_oneof_one_of_index::index(v) => {
-                    os.write_int32(2, v)?;
-                },
-            };
+        if self.index != 0 {
+            os.write_int32(2, self.index)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -1317,10 +1294,10 @@ impl ::protobuf::Message for IndexField {
                 |m: &IndexField| { &m.field },
                 |m: &mut IndexField| { &mut m.field },
             ));
-            fields.push(::protobuf::reflect::accessor::make_singular_i32_accessor::<_>(
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt32>(
                 "index",
-                IndexField::has_index,
-                IndexField::get_index,
+                |m: &IndexField| { &m.index },
+                |m: &mut IndexField| { &mut m.index },
             ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<IndexField>(
                 "IndexField",
@@ -1339,7 +1316,7 @@ impl ::protobuf::Message for IndexField {
 impl ::protobuf::Clear for IndexField {
     fn clear(&mut self) {
         self.field.clear();
-        self.one_of_index = ::std::option::Option::None;
+        self.index = 0;
         self.unknown_fields.clear();
     }
 }
@@ -7803,83 +7780,83 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x124\n\x0finserted_field\
     s\x18\x02\x20\x03(\x0b2\x0b.IndexFieldR\x0einsertedFields\x122\n\x0edele\
     ted_fields\x18\x03\x20\x03(\x0b2\x0b.FieldOrderR\rdeletedFields\x12-\n\
-    \x0eupdated_fields\x18\x04\x20\x03(\x0b2\x06.FieldR\rupdatedFields\"R\n\
+    \x0eupdated_fields\x18\x04\x20\x03(\x0b2\x06.FieldR\rupdatedFields\"@\n\
     \nIndexField\x12\x1c\n\x05field\x18\x01\x20\x01(\x0b2\x06.FieldR\x05fiel\
-    d\x12\x16\n\x05index\x18\x02\x20\x01(\x05H\0R\x05indexB\x0e\n\x0cone_of_\
-    index\"\x90\x01\n\x1aGetEditFieldContextPayload\x12\x17\n\x07grid_id\x18\
-    \x01\x20\x01(\tR\x06gridId\x12\x1b\n\x08field_id\x18\x02\x20\x01(\tH\0R\
-    \x07fieldId\x12)\n\nfield_type\x18\x03\x20\x01(\x0e2\n.FieldTypeR\tfield\
-    TypeB\x11\n\x0fone_of_field_id\"q\n\x10EditFieldPayload\x12\x17\n\x07gri\
-    d_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\x20\x01\
-    (\tR\x07fieldId\x12)\n\nfield_type\x18\x03\x20\x01(\x0e2\n.FieldTypeR\tf\
-    ieldType\"|\n\x10EditFieldContext\x12\x17\n\x07grid_id\x18\x01\x20\x01(\
-    \tR\x06gridId\x12%\n\ngrid_field\x18\x02\x20\x01(\x0b2\x06.FieldR\tgridF\
-    ield\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\
-    \"-\n\rRepeatedField\x12\x1c\n\x05items\x18\x01\x20\x03(\x0b2\x06.FieldR\
-    \x05items\"7\n\x12RepeatedFieldOrder\x12!\n\x05items\x18\x01\x20\x03(\
-    \x0b2\x0b.FieldOrderR\x05items\"T\n\x08RowOrder\x12\x15\n\x06row_id\x18\
-    \x01\x20\x01(\tR\x05rowId\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07b\
-    lockId\x12\x16\n\x06height\x18\x03\x20\x01(\x05R\x06height\"\xb8\x01\n\
-    \x03Row\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12@\n\x10cell_by_fiel\
-    d_id\x18\x02\x20\x03(\x0b2\x17.Row.CellByFieldIdEntryR\rcellByFieldId\
-    \x12\x16\n\x06height\x18\x03\x20\x01(\x05R\x06height\x1aG\n\x12CellByFie\
-    ldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x1b\n\x05value\
-    \x18\x02\x20\x01(\x0b2\x05.CellR\x05value:\x028\x01\")\n\x0bRepeatedRow\
-    \x12\x1a\n\x05items\x18\x01\x20\x03(\x0b2\x04.RowR\x05items\"5\n\x11Repe\
-    atedGridBlock\x12\x20\n\x05items\x18\x01\x20\x03(\x0b2\n.GridBlockR\x05i\
-    tems\"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\trowOrd\
-    ers\"_\n\rIndexRowOrder\x12&\n\trow_order\x18\x01\x20\x01(\x0b2\t.RowOrd\
-    erR\x08rowOrder\x12\x16\n\x05index\x18\x02\x20\x01(\x05H\0R\x05indexB\
-    \x0e\n\x0cone_of_index\"\xbf\x01\n\x11GridRowsChangeset\x12\x19\n\x08blo\
-    ck_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\"E\n\tGridBlock\x12\x0e\n\
-    \x02id\x18\x01\x20\x01(\tR\x02id\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b\
-    2\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\x14CellNotificationData\x12\x17\n\x07grid_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\x0cRepe\
-    atedCell\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\x05value\x18\x01\x20\x01(\tR\x05value\"#\n\
-    \x0bGridBlockId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"f\n\x10\
-    CreateRowPayload\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\x12InsertFieldPayload\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\x0etype\
-    OptionData\x12&\n\x0estart_field_id\x18\x04\x20\x01(\tH\0R\x0cstartField\
-    IdB\x17\n\x15one_of_start_field_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.RepeatedFieldOrderR\x0bfieldOrders\"e\n\x16QueryGridB\
-    locksPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\
-    \x0cblock_orders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrder\
-    s\"\xa8\x03\n\x15FieldChangesetPayload\x12\x19\n\x08field_id\x18\x01\x20\
-    \x01(\tR\x07fieldId\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\
-    \x12\x14\n\x04name\x18\x03\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\
-    \x04\x20\x01(\tH\x01R\x04desc\x12+\n\nfield_type\x18\x05\x20\x01(\x0e2\n\
-    .FieldTypeH\x02R\tfieldType\x12\x18\n\x06frozen\x18\x06\x20\x01(\x08H\
-    \x03R\x06frozen\x12\x20\n\nvisibility\x18\x07\x20\x01(\x08H\x04R\nvisibi\
-    lity\x12\x16\n\x05width\x18\x08\x20\x01(\x05H\x05R\x05width\x12*\n\x10ty\
-    pe_option_data\x18\t\x20\x01(\x0cH\x06R\x0etypeOptionDataB\r\n\x0bone_of\
-    _nameB\r\n\x0bone_of_descB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_fro\
-    zenB\x13\n\x11one_of_visibilityB\x0e\n\x0cone_of_widthB\x19\n\x17one_of_\
-    type_option_data\"\x9c\x01\n\x0fMoveItemPayload\x12\x17\n\x07grid_id\x18\
-    \x01\x20\x01(\tR\x06gridId\x12\x17\n\x07item_id\x18\x02\x20\x01(\tR\x06i\
-    temId\x12\x1d\n\nfrom_index\x18\x03\x20\x01(\x05R\tfromIndex\x12\x19\n\
-    \x08to_index\x18\x04\x20\x01(\x05R\x07toIndex\x12\x1d\n\x02ty\x18\x05\
-    \x20\x01(\x0e2\r.MoveItemTypeR\x02ty\"\x7f\n\rCellChangeset\x12\x17\n\
-    \x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\
-    \x20\x01(\tR\x05rowId\x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\x07field\
-    Id\x12\x14\n\x04data\x18\x04\x20\x01(\tH\0R\x04dataB\r\n\x0bone_of_data*\
-    *\n\x0cMoveItemType\x12\r\n\tMoveField\x10\0\x12\x0b\n\x07MoveRow\x10\
-    \x01*d\n\tFieldType\x12\x0c\n\x08RichText\x10\0\x12\n\n\x06Number\x10\
-    \x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0cSingleSelect\x10\x03\x12\
-    \x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Checkbox\x10\x05b\x06proto3\
+    d\x12\x14\n\x05index\x18\x02\x20\x01(\x05R\x05index\"\x90\x01\n\x1aGetEd\
+    itFieldContextPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\
+    \x12\x1b\n\x08field_id\x18\x02\x20\x01(\tH\0R\x07fieldId\x12)\n\nfield_t\
+    ype\x18\x03\x20\x01(\x0e2\n.FieldTypeR\tfieldTypeB\x11\n\x0fone_of_field\
+    _id\"q\n\x10EditFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
+    \x06gridId\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07fieldId\x12)\n\n\
+    field_type\x18\x03\x20\x01(\x0e2\n.FieldTypeR\tfieldType\"|\n\x10EditFie\
+    ldContext\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12%\n\ngri\
+    d_field\x18\x02\x20\x01(\x0b2\x06.FieldR\tgridField\x12(\n\x10type_optio\
+    n_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\"-\n\rRepeatedField\x12\
+    \x1c\n\x05items\x18\x01\x20\x03(\x0b2\x06.FieldR\x05items\"7\n\x12Repeat\
+    edFieldOrder\x12!\n\x05items\x18\x01\x20\x03(\x0b2\x0b.FieldOrderR\x05it\
+    ems\"T\n\x08RowOrder\x12\x15\n\x06row_id\x18\x01\x20\x01(\tR\x05rowId\
+    \x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07blockId\x12\x16\n\x06heigh\
+    t\x18\x03\x20\x01(\x05R\x06height\"\xb8\x01\n\x03Row\x12\x0e\n\x02id\x18\
+    \x01\x20\x01(\tR\x02id\x12@\n\x10cell_by_field_id\x18\x02\x20\x03(\x0b2\
+    \x17.Row.CellByFieldIdEntryR\rcellByFieldId\x12\x16\n\x06height\x18\x03\
+    \x20\x01(\x05R\x06height\x1aG\n\x12CellByFieldIdEntry\x12\x10\n\x03key\
+    \x18\x01\x20\x01(\tR\x03key\x12\x1b\n\x05value\x18\x02\x20\x01(\x0b2\x05\
+    .CellR\x05value:\x028\x01\")\n\x0bRepeatedRow\x12\x1a\n\x05items\x18\x01\
+    \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\"_\n\rIndexRowOrder\x12&\n\
+    \trow_order\x18\x01\x20\x01(\x0b2\t.RowOrderR\x08rowOrder\x12\x16\n\x05i\
+    ndex\x18\x02\x20\x01(\x05H\0R\x05indexB\x0e\n\x0cone_of_index\"\xbf\x01\
+    \n\x11GridRowsChangeset\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blo\
+    ckId\x123\n\rinserted_rows\x18\x02\x20\x03(\x0b2\x0e.IndexRowOrderR\x0ci\
+    nsertedRows\x12,\n\x0cdeleted_rows\x18\x03\x20\x03(\x0b2\t.RowOrderR\x0b\
+    deletedRows\x12,\n\x0cupdated_rows\x18\x04\x20\x03(\x0b2\t.RowOrderR\x0b\
+    updatedRows\"E\n\tGridBlock\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\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\x12Ins\
+    ertFieldPayload\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\x0bblockOrders\"\xa8\x03\n\x15FieldChangesetPa\
+    yload\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x17\n\x07\
+    grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04name\x18\x03\x20\x01(\
+    \tH\0R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\x01R\x04desc\x12+\n\
+    \nfield_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\tfieldType\x12\x18\
+    \n\x06frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\x20\n\nvisibility\
+    \x18\x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05width\x18\x08\x20\
+    \x01(\x05H\x05R\x05width\x12*\n\x10type_option_data\x18\t\x20\x01(\x0cH\
+    \x06R\x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_of_descB\x13\n\
+    \x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_of_visibilityB\
+    \x0e\n\x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\x9c\x01\n\x0f\
+    MoveItemPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\
+    \x17\n\x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\nfrom_index\
+    \x18\x03\x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\x04\x20\x01(\
+    \x05R\x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.MoveItemTypeR\
+    \x02ty\"\x7f\n\rCellChangeset\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
+    \x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\
+    \x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x12\x14\n\x04data\x18\x04\
+    \x20\x01(\tH\0R\x04dataB\r\n\x0bone_of_data**\n\x0cMoveItemType\x12\r\n\
+    \tMoveField\x10\0\x12\x0b\n\x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\
+    \x08RichText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\
+    \x02\x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\
+    \x12\x0c\n\x08Checkbox\x10\x05b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -25,7 +25,7 @@ message GridFieldChangeset {
 }
 message IndexField {
     Field field = 1;
-    oneof one_of_index { int32 index = 2; };
+    int32 index = 2;
 }
 message GetEditFieldContextPayload {
     string grid_id = 1;

+ 29 - 24
shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs

@@ -4,11 +4,12 @@ use crate::util::{cal_diff, make_delta_from_revisions};
 use bytes::Bytes;
 use flowy_grid_data_model::entities::{
     gen_field_id, gen_grid_id, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, GridBlockMeta,
-    GridBlockMetaChangeset, GridMeta,
+    GridBlockMetaChangeset, GridMeta, IndexField,
 };
 use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
 use std::collections::HashMap;
 
+use futures::StreamExt;
 use std::sync::Arc;
 
 pub type GridMetaDelta = PlainTextDelta;
@@ -41,7 +42,7 @@ impl GridMetaPad {
     }
 
     #[tracing::instrument(level = "debug", skip_all, err)]
-    pub fn create_field(
+    pub fn create_field_meta(
         &mut self,
         new_field_meta: FieldMeta,
         start_field_id: Option<String>,
@@ -70,7 +71,7 @@ impl GridMetaPad {
         })
     }
 
-    pub fn delete_field(&mut self, field_id: &str) -> CollaborateResult<Option<GridChangeset>> {
+    pub fn delete_field_meta(&mut self, field_id: &str) -> CollaborateResult<Option<GridChangeset>> {
         self.modify_grid(
             |grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) {
                 None => Ok(None),
@@ -82,23 +83,23 @@ impl GridMetaPad {
         )
     }
 
-    pub fn duplicate_field(&mut self, field_id: &str) -> CollaborateResult<(Option<GridChangeset>, Option<FieldMeta>)> {
-        let mut field_meta = None;
-        let changeset =
-            self.modify_grid(
-                |grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) {
-                    None => Ok(None),
-                    Some(index) => {
-                        let mut duplicate_field_meta = grid_meta.fields[index].clone();
-                        duplicate_field_meta.id = gen_field_id();
-                        duplicate_field_meta.name = format!("{} (copy)", duplicate_field_meta.name);
-                        field_meta = Some(duplicate_field_meta.clone());
-                        grid_meta.fields.insert(index + 1, duplicate_field_meta);
-                        Ok(Some(()))
-                    }
-                },
-            )?;
-        Ok((changeset, field_meta))
+    pub fn duplicate_field_meta(
+        &mut self,
+        field_id: &str,
+        duplicated_field_id: &str,
+    ) -> CollaborateResult<Option<GridChangeset>> {
+        self.modify_grid(
+            |grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) {
+                None => Ok(None),
+                Some(index) => {
+                    let mut duplicate_field_meta = grid_meta.fields[index].clone();
+                    duplicate_field_meta.id = duplicated_field_id.to_string();
+                    duplicate_field_meta.name = format!("{} (copy)", duplicate_field_meta.name);
+                    grid_meta.fields.insert(index + 1, duplicate_field_meta);
+                    Ok(Some(()))
+                }
+            },
+        )
     }
 
     pub fn switch_to_field<B>(
@@ -130,7 +131,7 @@ impl GridMetaPad {
         })
     }
 
-    pub fn update_field<T: JsonDeserializer>(
+    pub fn update_field_meta<T: JsonDeserializer>(
         &mut self,
         changeset: FieldChangesetParams,
         deserializer: T,
@@ -185,11 +186,15 @@ impl GridMetaPad {
         })
     }
 
-    pub fn get_field(&self, field_id: &str) -> Option<&FieldMeta> {
-        self.grid_meta.fields.iter().find(|field| field.id == field_id)
+    pub fn get_field_meta(&self, field_id: &str) -> Option<(usize, &FieldMeta)> {
+        self.grid_meta
+            .fields
+            .iter()
+            .enumerate()
+            .find(|(_, field)| field.id == field_id)
     }
 
-    pub fn replace_field(&mut self, field_meta: FieldMeta) -> CollaborateResult<Option<GridChangeset>> {
+    pub fn replace_field_meta(&mut self, field_meta: FieldMeta) -> CollaborateResult<Option<GridChangeset>> {
         self.modify_grid(
             |grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_meta.id) {
                 None => Ok(None),