ソースを参照

chore: update cell data

appflowy 3 年 前
コミット
99d827f07d
41 ファイル変更1235 行追加172 行削除
  1. 28 11
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart
  2. 34 0
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
  3. 2 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbenum.dart
  4. 2 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbjson.dart
  5. 4 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart
  6. 3 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart
  7. 164 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pb.dart
  8. 28 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pbjson.dart
  9. 3 0
      frontend/rust-lib/flowy-database/migrations/2022-04-05-015536_flowy-grid-block-index/down.sql
  10. 7 0
      frontend/rust-lib/flowy-database/migrations/2022-04-05-015536_flowy-grid-block-index/up.sql
  11. 8 0
      frontend/rust-lib/flowy-database/src/schema.rs
  12. 17 2
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  13. 8 1
      frontend/rust-lib/flowy-grid/src/event_map.rs
  14. 15 22
      frontend/rust-lib/flowy-grid/src/manager.rs
  15. 10 3
      frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs
  16. 591 5
      frontend/rust-lib/flowy-grid/src/protobuf/model/selection_type_option.rs
  17. 2 0
      frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto
  18. 10 0
      frontend/rust-lib/flowy-grid/src/protobuf/proto/selection_type_option.proto
  19. 37 57
      frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs
  20. 3 3
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs
  21. 3 3
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs
  22. 3 3
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs
  23. 77 5
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs
  24. 3 3
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs
  25. 33 16
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  26. 1 1
      frontend/rust-lib/flowy-grid/src/services/mod.rs
  27. 49 0
      frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs
  28. 5 4
      frontend/rust-lib/flowy-grid/src/services/persistence/kv.rs
  29. 16 0
      frontend/rust-lib/flowy-grid/src/services/persistence/mod.rs
  30. 3 2
      frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs
  31. 2 2
      frontend/rust-lib/flowy-grid/src/services/row/mod.rs
  32. 1 0
      frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs
  33. 22 10
      frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs
  34. 1 1
      frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs
  35. 14 2
      frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs
  36. 2 0
      shared-lib/flowy-error-code/src/code.rs
  37. 9 5
      shared-lib/flowy-error-code/src/protobuf/model/code.rs
  38. 1 0
      shared-lib/flowy-error-code/src/protobuf/proto/code.proto
  39. 6 0
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  40. 6 8
      shared-lib/flowy-grid-data-model/src/entities/meta.rs
  41. 2 1
      shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs

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

@@ -1,5 +1,7 @@
 import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
+import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
+import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
@@ -12,16 +14,18 @@ import 'cell_service.dart';
 part 'selection_editor_bloc.freezed.dart';
 
 class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
-  final CellService service = CellService();
+  final TypeOptionService _typeOptionService;
+  final CellService _cellService;
   final FieldListener _listener;
 
   SelectOptionEditorBloc({
-    required String gridId,
-    required Field field,
+    required CellData cellData,
     required List<SelectOption> options,
     required List<SelectOption> selectedOptions,
-  })  : _listener = FieldListener(fieldId: field.id),
-        super(SelectOptionEditorState.initial(gridId, field, options, selectedOptions)) {
+  })  : _cellService = CellService(),
+        _typeOptionService = TypeOptionService(fieldId: cellData.field.id),
+        _listener = FieldListener(fieldId: cellData.field.id),
+        super(SelectOptionEditorState.initial(cellData, options, selectedOptions)) {
     on<SelectOptionEditorEvent>(
       (event, emit) async {
         await event.map(
@@ -36,7 +40,18 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
           didReceiveOptions: (_DidReceiveOptions value) {
             emit(state.copyWith(options: value.options));
           },
-          newOption: (_newOption value) {},
+          newOption: (_NewOption value) async {
+            final result = await _typeOptionService.createOption(value.optionName);
+            result.fold((l) => null, (err) => Log.error(err));
+          },
+          selectOption: (_SelectOption value) {
+            _cellService.updateCell(
+              gridId: state.gridId,
+              fieldId: state.fieldId,
+              rowId: state.rowId,
+              data: data,
+            );
+          },
         );
       },
     );
@@ -85,7 +100,8 @@ class SelectOptionEditorEvent with _$SelectOptionEditorEvent {
   const factory SelectOptionEditorEvent.initial() = _Initial;
   const factory SelectOptionEditorEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
   const factory SelectOptionEditorEvent.didReceiveOptions(List<SelectOption> options) = _DidReceiveOptions;
-  const factory SelectOptionEditorEvent.newOption(String optionName) = _newOption;
+  const factory SelectOptionEditorEvent.newOption(String optionName) = _NewOption;
+  const factory SelectOptionEditorEvent.selectOption(String optionId) = _SelectOption;
 }
 
 @freezed
@@ -93,19 +109,20 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
   const factory SelectOptionEditorState({
     required String gridId,
     required Field field,
+    required String rowId,
     required List<SelectOption> options,
     required List<SelectOption> selectedOptions,
   }) = _SelectOptionEditorState;
 
   factory SelectOptionEditorState.initial(
-    String gridId,
-    Field field,
+    CellData cellData,
     List<SelectOption> options,
     List<SelectOption> selectedOptions,
   ) {
     return SelectOptionEditorState(
-      gridId: gridId,
-      field: field,
+      gridId: cellData.gridId,
+      field: cellData.field,
+      rowId: cellData.rowId,
       options: options,
       selectedOptions: selectedOptions,
     );

+ 34 - 0
frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart

@@ -171,6 +171,23 @@ class GridEventCreateSelectOption {
     }
 }
 
+class GridEventGetSelectOptions {
+     FieldIdentifierPayload request;
+     GridEventGetSelectOptions(this.request);
+
+    Future<Either<SelectOptionContext, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.GetSelectOptions.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(SelectOptionContext.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
 class GridEventCreateRow {
      CreateRowPayload request;
      GridEventCreateRow(this.request);
@@ -222,3 +239,20 @@ class GridEventUpdateCell {
     }
 }
 
+class GridEventInsertSelectOption {
+     InsertSelectOptionPayload request;
+     GridEventInsertSelectOption(this.request);
+
+    Future<Either<Unit, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.InsertSelectOption.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+

+ 2 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbenum.dart

@@ -45,6 +45,7 @@ class ErrorCode extends $pb.ProtobufEnum {
   static const ErrorCode GridIdIsEmpty = ErrorCode._(410, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridIdIsEmpty');
   static const ErrorCode BlockIdIsEmpty = ErrorCode._(420, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'BlockIdIsEmpty');
   static const ErrorCode RowIdIsEmpty = ErrorCode._(430, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RowIdIsEmpty');
+  static const ErrorCode OptionIdIsEmpty = ErrorCode._(431, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'OptionIdIsEmpty');
   static const ErrorCode FieldIdIsEmpty = ErrorCode._(440, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'FieldIdIsEmpty');
   static const ErrorCode FieldDoesNotExist = ErrorCode._(441, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'FieldDoesNotExist');
   static const ErrorCode SelectOptionNameIsEmpty = ErrorCode._(442, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SelectOptionNameIsEmpty');
@@ -87,6 +88,7 @@ class ErrorCode extends $pb.ProtobufEnum {
     GridIdIsEmpty,
     BlockIdIsEmpty,
     RowIdIsEmpty,
+    OptionIdIsEmpty,
     FieldIdIsEmpty,
     FieldDoesNotExist,
     SelectOptionNameIsEmpty,

+ 2 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbjson.dart

@@ -47,6 +47,7 @@ const ErrorCode$json = const {
     const {'1': 'GridIdIsEmpty', '2': 410},
     const {'1': 'BlockIdIsEmpty', '2': 420},
     const {'1': 'RowIdIsEmpty', '2': 430},
+    const {'1': 'OptionIdIsEmpty', '2': 431},
     const {'1': 'FieldIdIsEmpty', '2': 440},
     const {'1': 'FieldDoesNotExist', '2': 441},
     const {'1': 'SelectOptionNameIsEmpty', '2': 442},
@@ -56,4 +57,4 @@ const ErrorCode$json = const {
 };
 
 /// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSDAoISW50ZXJuYWwQABIUChBVc2VyVW5hdXRob3JpemVkEAISEgoOUmVjb3JkTm90Rm91bmQQAxIRCg1Vc2VySWRJc0VtcHR5EAQSGAoUV29ya3NwYWNlTmFtZUludmFsaWQQZBIWChJXb3Jrc3BhY2VJZEludmFsaWQQZRIYChRBcHBDb2xvclN0eWxlSW52YWxpZBBmEhgKFFdvcmtzcGFjZURlc2NUb29Mb25nEGcSGAoUV29ya3NwYWNlTmFtZVRvb0xvbmcQaBIQCgxBcHBJZEludmFsaWQQbhISCg5BcHBOYW1lSW52YWxpZBBvEhMKD1ZpZXdOYW1lSW52YWxpZBB4EhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEHkSEQoNVmlld0lkSW52YWxpZBB6EhMKD1ZpZXdEZXNjVG9vTG9uZxB7EhMKD1ZpZXdEYXRhSW52YWxpZBB8EhMKD1ZpZXdOYW1lVG9vTG9uZxB9EhEKDENvbm5lY3RFcnJvchDIARIRCgxFbWFpbElzRW1wdHkQrAISFwoSRW1haWxGb3JtYXRJbnZhbGlkEK0CEhcKEkVtYWlsQWxyZWFkeUV4aXN0cxCuAhIUCg9QYXNzd29yZElzRW1wdHkQrwISFAoPUGFzc3dvcmRUb29Mb25nELACEiUKIFBhc3N3b3JkQ29udGFpbnNGb3JiaWRDaGFyYWN0ZXJzELECEhoKFVBhc3N3b3JkRm9ybWF0SW52YWxpZBCyAhIVChBQYXNzd29yZE5vdE1hdGNoELMCEhQKD1VzZXJOYW1lVG9vTG9uZxC0AhInCiJVc2VyTmFtZUNvbnRhaW5Gb3JiaWRkZW5DaGFyYWN0ZXJzELUCEhQKD1VzZXJOYW1lSXNFbXB0eRC2AhISCg1Vc2VySWRJbnZhbGlkELcCEhEKDFVzZXJOb3RFeGlzdBC4AhIQCgtUZXh0VG9vTG9uZxCQAxISCg1HcmlkSWRJc0VtcHR5EJoDEhMKDkJsb2NrSWRJc0VtcHR5EKQDEhEKDFJvd0lkSXNFbXB0eRCuAxITCg5GaWVsZElkSXNFbXB0eRC4AxIWChFGaWVsZERvZXNOb3RFeGlzdBC5AxIcChdTZWxlY3RPcHRpb25OYW1lSXNFbXB0eRC6AxIaChVUeXBlT3B0aW9uRGF0YUlzRW1wdHkQwgMSEAoLSW52YWxpZERhdGEQ9AM=');
+final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSDAoISW50ZXJuYWwQABIUChBVc2VyVW5hdXRob3JpemVkEAISEgoOUmVjb3JkTm90Rm91bmQQAxIRCg1Vc2VySWRJc0VtcHR5EAQSGAoUV29ya3NwYWNlTmFtZUludmFsaWQQZBIWChJXb3Jrc3BhY2VJZEludmFsaWQQZRIYChRBcHBDb2xvclN0eWxlSW52YWxpZBBmEhgKFFdvcmtzcGFjZURlc2NUb29Mb25nEGcSGAoUV29ya3NwYWNlTmFtZVRvb0xvbmcQaBIQCgxBcHBJZEludmFsaWQQbhISCg5BcHBOYW1lSW52YWxpZBBvEhMKD1ZpZXdOYW1lSW52YWxpZBB4EhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEHkSEQoNVmlld0lkSW52YWxpZBB6EhMKD1ZpZXdEZXNjVG9vTG9uZxB7EhMKD1ZpZXdEYXRhSW52YWxpZBB8EhMKD1ZpZXdOYW1lVG9vTG9uZxB9EhEKDENvbm5lY3RFcnJvchDIARIRCgxFbWFpbElzRW1wdHkQrAISFwoSRW1haWxGb3JtYXRJbnZhbGlkEK0CEhcKEkVtYWlsQWxyZWFkeUV4aXN0cxCuAhIUCg9QYXNzd29yZElzRW1wdHkQrwISFAoPUGFzc3dvcmRUb29Mb25nELACEiUKIFBhc3N3b3JkQ29udGFpbnNGb3JiaWRDaGFyYWN0ZXJzELECEhoKFVBhc3N3b3JkRm9ybWF0SW52YWxpZBCyAhIVChBQYXNzd29yZE5vdE1hdGNoELMCEhQKD1VzZXJOYW1lVG9vTG9uZxC0AhInCiJVc2VyTmFtZUNvbnRhaW5Gb3JiaWRkZW5DaGFyYWN0ZXJzELUCEhQKD1VzZXJOYW1lSXNFbXB0eRC2AhISCg1Vc2VySWRJbnZhbGlkELcCEhEKDFVzZXJOb3RFeGlzdBC4AhIQCgtUZXh0VG9vTG9uZxCQAxISCg1HcmlkSWRJc0VtcHR5EJoDEhMKDkJsb2NrSWRJc0VtcHR5EKQDEhEKDFJvd0lkSXNFbXB0eRCuAxIUCg9PcHRpb25JZElzRW1wdHkQrwMSEwoORmllbGRJZElzRW1wdHkQuAMSFgoRRmllbGREb2VzTm90RXhpc3QQuQMSHAoXU2VsZWN0T3B0aW9uTmFtZUlzRW1wdHkQugMSGgoVVHlwZU9wdGlvbkRhdGFJc0VtcHR5EMIDEhAKC0ludmFsaWREYXRhEPQD');

+ 4 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart

@@ -20,9 +20,11 @@ class GridEvent extends $pb.ProtobufEnum {
   static const GridEvent DuplicateField = GridEvent._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateField');
   static const GridEvent GetEditFieldContext = GridEvent._(16, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetEditFieldContext');
   static const GridEvent CreateSelectOption = GridEvent._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateSelectOption');
+  static const GridEvent GetSelectOptions = GridEvent._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetSelectOptions');
   static const GridEvent CreateRow = GridEvent._(50, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateRow');
   static const GridEvent GetRow = GridEvent._(51, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetRow');
   static const GridEvent UpdateCell = GridEvent._(70, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateCell');
+  static const GridEvent InsertSelectOption = GridEvent._(71, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InsertSelectOption');
 
   static const $core.List<GridEvent> values = <GridEvent> [
     GetGridData,
@@ -35,9 +37,11 @@ class GridEvent extends $pb.ProtobufEnum {
     DuplicateField,
     GetEditFieldContext,
     CreateSelectOption,
+    GetSelectOptions,
     CreateRow,
     GetRow,
     UpdateCell,
+    InsertSelectOption,
   ];
 
   static final $core.Map<$core.int, GridEvent> _byValue = $pb.ProtobufEnum.initByValue(values);

+ 3 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart

@@ -22,11 +22,13 @@ const GridEvent$json = const {
     const {'1': 'DuplicateField', '2': 15},
     const {'1': 'GetEditFieldContext', '2': 16},
     const {'1': 'CreateSelectOption', '2': 30},
+    const {'1': 'GetSelectOptions', '2': 31},
     const {'1': 'CreateRow', '2': 50},
     const {'1': 'GetRow', '2': 51},
     const {'1': 'UpdateCell', '2': 70},
+    const {'1': 'InsertSelectOption', '2': 71},
   ],
 };
 
 /// Descriptor for `GridEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIPCgtDcmVhdGVGaWVsZBAMEg8KC0RlbGV0ZUZpZWxkEA0SEQoNU3dpdGNoVG9GaWVsZBAOEhIKDkR1cGxpY2F0ZUZpZWxkEA8SFwoTR2V0RWRpdEZpZWxkQ29udGV4dBAQEhYKEkNyZWF0ZVNlbGVjdE9wdGlvbhAeEg0KCUNyZWF0ZVJvdxAyEgoKBkdldFJvdxAzEg4KClVwZGF0ZUNlbGwQRg==');
+final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIPCgtDcmVhdGVGaWVsZBAMEg8KC0RlbGV0ZUZpZWxkEA0SEQoNU3dpdGNoVG9GaWVsZBAOEhIKDkR1cGxpY2F0ZUZpZWxkEA8SFwoTR2V0RWRpdEZpZWxkQ29udGV4dBAQEhYKEkNyZWF0ZVNlbGVjdE9wdGlvbhAeEhQKEEdldFNlbGVjdE9wdGlvbnMQHxINCglDcmVhdGVSb3cQMhIKCgZHZXRSb3cQMxIOCgpVcGRhdGVDZWxsEEYSFgoSSW5zZXJ0U2VsZWN0T3B0aW9uEEc=');

+ 164 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pb.dart

@@ -198,3 +198,167 @@ class SelectOption extends $pb.GeneratedMessage {
   void clearColor() => clearField(3);
 }
 
+enum SelectOptionChangesetPayload_OneOfInsertOptionId {
+  insertOptionId, 
+  notSet
+}
+
+enum SelectOptionChangesetPayload_OneOfDeleteOptionId {
+  deleteOptionId, 
+  notSet
+}
+
+class SelectOptionChangesetPayload extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, SelectOptionChangesetPayload_OneOfInsertOptionId> _SelectOptionChangesetPayload_OneOfInsertOptionIdByTag = {
+    3 : SelectOptionChangesetPayload_OneOfInsertOptionId.insertOptionId,
+    0 : SelectOptionChangesetPayload_OneOfInsertOptionId.notSet
+  };
+  static const $core.Map<$core.int, SelectOptionChangesetPayload_OneOfDeleteOptionId> _SelectOptionChangesetPayload_OneOfDeleteOptionIdByTag = {
+    4 : SelectOptionChangesetPayload_OneOfDeleteOptionId.deleteOptionId,
+    0 : SelectOptionChangesetPayload_OneOfDeleteOptionId.notSet
+  };
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SelectOptionChangesetPayload', createEmptyInstance: create)
+    ..oo(0, [3])
+    ..oo(1, [4])
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowId')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'insertOptionId')
+    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deleteOptionId')
+    ..hasRequiredFields = false
+  ;
+
+  SelectOptionChangesetPayload._() : super();
+  factory SelectOptionChangesetPayload({
+    $core.String? gridId,
+    $core.String? rowId,
+    $core.String? insertOptionId,
+    $core.String? deleteOptionId,
+  }) {
+    final _result = create();
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
+    if (rowId != null) {
+      _result.rowId = rowId;
+    }
+    if (insertOptionId != null) {
+      _result.insertOptionId = insertOptionId;
+    }
+    if (deleteOptionId != null) {
+      _result.deleteOptionId = deleteOptionId;
+    }
+    return _result;
+  }
+  factory SelectOptionChangesetPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory SelectOptionChangesetPayload.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')
+  SelectOptionChangesetPayload clone() => SelectOptionChangesetPayload()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  SelectOptionChangesetPayload copyWith(void Function(SelectOptionChangesetPayload) updates) => super.copyWith((message) => updates(message as SelectOptionChangesetPayload)) as SelectOptionChangesetPayload; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static SelectOptionChangesetPayload create() => SelectOptionChangesetPayload._();
+  SelectOptionChangesetPayload createEmptyInstance() => create();
+  static $pb.PbList<SelectOptionChangesetPayload> createRepeated() => $pb.PbList<SelectOptionChangesetPayload>();
+  @$core.pragma('dart2js:noInline')
+  static SelectOptionChangesetPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SelectOptionChangesetPayload>(create);
+  static SelectOptionChangesetPayload? _defaultInstance;
+
+  SelectOptionChangesetPayload_OneOfInsertOptionId whichOneOfInsertOptionId() => _SelectOptionChangesetPayload_OneOfInsertOptionIdByTag[$_whichOneof(0)]!;
+  void clearOneOfInsertOptionId() => clearField($_whichOneof(0));
+
+  SelectOptionChangesetPayload_OneOfDeleteOptionId whichOneOfDeleteOptionId() => _SelectOptionChangesetPayload_OneOfDeleteOptionIdByTag[$_whichOneof(1)]!;
+  void clearOneOfDeleteOptionId() => clearField($_whichOneof(1));
+
+  @$pb.TagNumber(1)
+  $core.String get gridId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set gridId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasGridId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearGridId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get rowId => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set rowId($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasRowId() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearRowId() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get insertOptionId => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set insertOptionId($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasInsertOptionId() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearInsertOptionId() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.String get deleteOptionId => $_getSZ(3);
+  @$pb.TagNumber(4)
+  set deleteOptionId($core.String v) { $_setString(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasDeleteOptionId() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearDeleteOptionId() => clearField(4);
+}
+
+class SelectOptionContext extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SelectOptionContext', createEmptyInstance: create)
+    ..pc<SelectOption>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'options', $pb.PbFieldType.PM, subBuilder: SelectOption.create)
+    ..pc<SelectOption>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'selectOptions', $pb.PbFieldType.PM, subBuilder: SelectOption.create)
+    ..hasRequiredFields = false
+  ;
+
+  SelectOptionContext._() : super();
+  factory SelectOptionContext({
+    $core.Iterable<SelectOption>? options,
+    $core.Iterable<SelectOption>? selectOptions,
+  }) {
+    final _result = create();
+    if (options != null) {
+      _result.options.addAll(options);
+    }
+    if (selectOptions != null) {
+      _result.selectOptions.addAll(selectOptions);
+    }
+    return _result;
+  }
+  factory SelectOptionContext.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory SelectOptionContext.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')
+  SelectOptionContext clone() => SelectOptionContext()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  SelectOptionContext copyWith(void Function(SelectOptionContext) updates) => super.copyWith((message) => updates(message as SelectOptionContext)) as SelectOptionContext; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static SelectOptionContext create() => SelectOptionContext._();
+  SelectOptionContext createEmptyInstance() => create();
+  static $pb.PbList<SelectOptionContext> createRepeated() => $pb.PbList<SelectOptionContext>();
+  @$core.pragma('dart2js:noInline')
+  static SelectOptionContext getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SelectOptionContext>(create);
+  static SelectOptionContext? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<SelectOption> get options => $_getList(0);
+
+  @$pb.TagNumber(2)
+  $core.List<SelectOption> get selectOptions => $_getList(1);
+}
+

+ 28 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pbjson.dart

@@ -60,3 +60,31 @@ const SelectOption$json = const {
 
 /// Descriptor for `SelectOption`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List selectOptionDescriptor = $convert.base64Decode('CgxTZWxlY3RPcHRpb24SDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSKAoFY29sb3IYAyABKA4yEi5TZWxlY3RPcHRpb25Db2xvclIFY29sb3I=');
+@$core.Deprecated('Use selectOptionChangesetPayloadDescriptor instead')
+const SelectOptionChangesetPayload$json = const {
+  '1': 'SelectOptionChangesetPayload',
+  '2': const [
+    const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
+    const {'1': 'row_id', '3': 2, '4': 1, '5': 9, '10': 'rowId'},
+    const {'1': 'insert_option_id', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'insertOptionId'},
+    const {'1': 'delete_option_id', '3': 4, '4': 1, '5': 9, '9': 1, '10': 'deleteOptionId'},
+  ],
+  '8': const [
+    const {'1': 'one_of_insert_option_id'},
+    const {'1': 'one_of_delete_option_id'},
+  ],
+};
+
+/// Descriptor for `SelectOptionChangesetPayload`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List selectOptionChangesetPayloadDescriptor = $convert.base64Decode('ChxTZWxlY3RPcHRpb25DaGFuZ2VzZXRQYXlsb2FkEhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIVCgZyb3dfaWQYAiABKAlSBXJvd0lkEioKEGluc2VydF9vcHRpb25faWQYAyABKAlIAFIOaW5zZXJ0T3B0aW9uSWQSKgoQZGVsZXRlX29wdGlvbl9pZBgEIAEoCUgBUg5kZWxldGVPcHRpb25JZEIZChdvbmVfb2ZfaW5zZXJ0X29wdGlvbl9pZEIZChdvbmVfb2ZfZGVsZXRlX29wdGlvbl9pZA==');
+@$core.Deprecated('Use selectOptionContextDescriptor instead')
+const SelectOptionContext$json = const {
+  '1': 'SelectOptionContext',
+  '2': const [
+    const {'1': 'options', '3': 1, '4': 3, '5': 11, '6': '.SelectOption', '10': 'options'},
+    const {'1': 'select_options', '3': 2, '4': 3, '5': 11, '6': '.SelectOption', '10': 'selectOptions'},
+  ],
+};
+
+/// Descriptor for `SelectOptionContext`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List selectOptionContextDescriptor = $convert.base64Decode('ChNTZWxlY3RPcHRpb25Db250ZXh0EicKB29wdGlvbnMYASADKAsyDS5TZWxlY3RPcHRpb25SB29wdGlvbnMSNAoOc2VsZWN0X29wdGlvbnMYAiADKAsyDS5TZWxlY3RPcHRpb25SDXNlbGVjdE9wdGlvbnM=');

+ 3 - 0
frontend/rust-lib/flowy-database/migrations/2022-04-05-015536_flowy-grid-block-index/down.sql

@@ -0,0 +1,3 @@
+-- This file should undo anything in `up.sql`
+DROP TABLE grid_block_index_table;
+-- DROP TABLE grid_block_fts_table;

+ 7 - 0
frontend/rust-lib/flowy-database/migrations/2022-04-05-015536_flowy-grid-block-index/up.sql

@@ -0,0 +1,7 @@
+-- Your SQL goes here
+CREATE TABLE grid_block_index_table (
+     row_id TEXT NOT NULL PRIMARY KEY,
+     block_id TEXT NOT NULL
+);
+
+-- CREATE VIRTUAL TABLE grid_block_fts_table USING FTS5(content, grid_id, block_id, row_id);

+ 8 - 0
frontend/rust-lib/flowy-database/src/schema.rs

@@ -21,6 +21,13 @@ table! {
     }
 }
 
+table! {
+    grid_block_index_table (row_id) {
+        row_id -> Text,
+        block_id -> Text,
+    }
+}
+
 table! {
     grid_meta_rev_table (id) {
         id -> Integer,
@@ -113,6 +120,7 @@ table! {
 allow_tables_to_appear_in_same_query!(
     app_table,
     doc_table,
+    grid_block_index_table,
     grid_meta_rev_table,
     grid_rev_table,
     kv_table,

+ 17 - 2
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -1,5 +1,8 @@
 use crate::manager::GridManager;
-use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_json_str, SelectOption};
+use crate::services::field::{
+    default_type_option_builder_from_type, type_option_builder_from_json_str, SelectOption,
+    SelectOptionChangesetParams, SelectOptionChangesetPayload,
+};
 use crate::services::grid_editor::ClientGridEditor;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::*;
@@ -40,7 +43,8 @@ pub(crate) async fn get_fields_handler(
 ) -> DataResult<RepeatedField, FlowyError> {
     let params: QueryFieldParams = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
-    let field_metas = editor.get_field_metas(Some(params.field_orders)).await?;
+    let field_orders = params.field_orders.items;
+    let field_metas = editor.get_field_metas(Some(field_orders)).await?;
     let repeated_field: RepeatedField = field_metas.into_iter().map(Field::from).collect::<Vec<_>>().into();
     data_result(repeated_field)
 }
@@ -203,3 +207,14 @@ pub(crate) async fn update_cell_handler(
     let _ = editor.update_cell(changeset).await?;
     Ok(())
 }
+
+#[tracing::instrument(level = "debug", skip_all, err)]
+pub(crate) async fn insert_select_option_handler(
+    data: Data<SelectOptionChangesetPayload>,
+    manager: AppData<Arc<GridManager>>,
+) -> Result<(), FlowyError> {
+    let params: SelectOptionChangesetParams = data.into_inner().try_into()?;
+    let editor = manager.get_grid_editor(&params.grid_id)?;
+    let _ = editor.insert_select_option(params).await?;
+    Ok(())
+}

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

@@ -20,7 +20,8 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
         .event(GridEvent::CreateSelectOption, create_select_option_handler)
         .event(GridEvent::CreateRow, create_row_handler)
         .event(GridEvent::GetRow, get_row_handler)
-        .event(GridEvent::UpdateCell, update_cell_handler);
+        .event(GridEvent::UpdateCell, update_cell_handler)
+        .event(GridEvent::InsertSelectOption, insert_select_option_handler);
 
     module
 }
@@ -58,6 +59,9 @@ pub enum GridEvent {
     #[event(input = "CreateSelectOptionPayload", output = "SelectOption")]
     CreateSelectOption = 30,
 
+    #[event(input = "FieldIdentifierPayload", output = "SelectOptionContext")]
+    GetSelectOptions = 31,
+
     #[event(input = "CreateRowPayload", output = "Row")]
     CreateRow = 50,
 
@@ -66,4 +70,7 @@ pub enum GridEvent {
 
     #[event(input = "CellMetaChangeset")]
     UpdateCell = 70,
+
+    #[event(input = "InsertSelectOptionPayload")]
+    InsertSelectOption = 71,
 }

+ 15 - 22
frontend/rust-lib/flowy-grid/src/manager.rs

@@ -1,5 +1,7 @@
 use crate::services::grid_editor::ClientGridEditor;
-use crate::services::kv_persistence::GridKVPersistence;
+use crate::services::persistence::block_index::BlockIndexPersistence;
+use crate::services::persistence::kv::GridKVPersistence;
+use crate::services::persistence::GridDatabase;
 use bytes::Bytes;
 use dashmap::DashMap;
 use flowy_database::ConnectionPool;
@@ -21,20 +23,24 @@ pub trait GridUser: Send + Sync {
 pub struct GridManager {
     editor_map: Arc<GridEditorMap>,
     grid_user: Arc<dyn GridUser>,
-    kv_persistence: Arc<RwLock<Option<Arc<GridKVPersistence>>>>,
+    block_index_persistence: Arc<BlockIndexPersistence>,
+    kv_persistence: Arc<GridKVPersistence>,
 }
 
 impl GridManager {
-    pub fn new(grid_user: Arc<dyn GridUser>, _rev_web_socket: Arc<dyn RevisionWebSocket>) -> Self {
+    pub fn new(
+        grid_user: Arc<dyn GridUser>,
+        _rev_web_socket: Arc<dyn RevisionWebSocket>,
+        database: Arc<dyn GridDatabase>,
+    ) -> Self {
         let grid_editors = Arc::new(GridEditorMap::new());
-
-        // kv_persistence will be initialized after first access.
-        // See get_kv_persistence function below
-        let kv_persistence = Arc::new(RwLock::new(None));
+        let kv_persistence = Arc::new(GridKVPersistence::new(database.clone()));
+        let block_index_persistence = Arc::new(BlockIndexPersistence::new(database));
         Self {
             editor_map: grid_editors,
             grid_user,
             kv_persistence,
+            block_index_persistence,
         }
     }
 
@@ -111,7 +117,8 @@ impl GridManager {
     ) -> Result<Arc<ClientGridEditor>, FlowyError> {
         let user = self.grid_user.clone();
         let rev_manager = self.make_grid_rev_manager(grid_id, pool.clone())?;
-        let grid_editor = ClientGridEditor::new(grid_id, user, rev_manager).await?;
+        let grid_editor =
+            ClientGridEditor::new(grid_id, user, rev_manager, self.block_index_persistence.clone()).await?;
         Ok(grid_editor)
     }
 
@@ -135,20 +142,6 @@ impl GridManager {
         let rev_manager = RevisionManager::new(&user_id, block_d, rev_persistence);
         Ok(rev_manager)
     }
-
-    #[allow(dead_code)]
-    async fn get_kv_persistence(&self) -> FlowyResult<Arc<GridKVPersistence>> {
-        let read_guard = self.kv_persistence.read().await;
-        if read_guard.is_some() {
-            return Ok(read_guard.clone().unwrap());
-        }
-        drop(read_guard);
-
-        let pool = self.grid_user.db_pool()?;
-        let kv_persistence = Arc::new(GridKVPersistence::new(pool));
-        *self.kv_persistence.write().await = Some(kv_persistence.clone());
-        Ok(kv_persistence)
-    }
 }
 
 pub struct GridEditorMap {

+ 10 - 3
frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs

@@ -35,9 +35,11 @@ pub enum GridEvent {
     DuplicateField = 15,
     GetEditFieldContext = 16,
     CreateSelectOption = 30,
+    GetSelectOptions = 31,
     CreateRow = 50,
     GetRow = 51,
     UpdateCell = 70,
+    InsertSelectOption = 71,
 }
 
 impl ::protobuf::ProtobufEnum for GridEvent {
@@ -57,9 +59,11 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             15 => ::std::option::Option::Some(GridEvent::DuplicateField),
             16 => ::std::option::Option::Some(GridEvent::GetEditFieldContext),
             30 => ::std::option::Option::Some(GridEvent::CreateSelectOption),
+            31 => ::std::option::Option::Some(GridEvent::GetSelectOptions),
             50 => ::std::option::Option::Some(GridEvent::CreateRow),
             51 => ::std::option::Option::Some(GridEvent::GetRow),
             70 => ::std::option::Option::Some(GridEvent::UpdateCell),
+            71 => ::std::option::Option::Some(GridEvent::InsertSelectOption),
             _ => ::std::option::Option::None
         }
     }
@@ -76,9 +80,11 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             GridEvent::DuplicateField,
             GridEvent::GetEditFieldContext,
             GridEvent::CreateSelectOption,
+            GridEvent::GetSelectOptions,
             GridEvent::CreateRow,
             GridEvent::GetRow,
             GridEvent::UpdateCell,
+            GridEvent::InsertSelectOption,
         ];
         values
     }
@@ -107,13 +113,14 @@ impl ::protobuf::reflect::ProtobufValue for GridEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0fevent_map.proto*\xf4\x01\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
+    \n\x0fevent_map.proto*\xa2\x02\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
     \0\x12\x11\n\rGetGridBlocks\x10\x01\x12\r\n\tGetFields\x10\n\x12\x0f\n\
     \x0bUpdateField\x10\x0b\x12\x0f\n\x0bCreateField\x10\x0c\x12\x0f\n\x0bDe\
     leteField\x10\r\x12\x11\n\rSwitchToField\x10\x0e\x12\x12\n\x0eDuplicateF\
     ield\x10\x0f\x12\x17\n\x13GetEditFieldContext\x10\x10\x12\x16\n\x12Creat\
-    eSelectOption\x10\x1e\x12\r\n\tCreateRow\x102\x12\n\n\x06GetRow\x103\x12\
-    \x0e\n\nUpdateCell\x10Fb\x06proto3\
+    eSelectOption\x10\x1e\x12\x14\n\x10GetSelectOptions\x10\x1f\x12\r\n\tCre\
+    ateRow\x102\x12\n\n\x06GetRow\x103\x12\x0e\n\nUpdateCell\x10F\x12\x16\n\
+    \x12InsertSelectOption\x10Gb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 591 - 5
frontend/rust-lib/flowy-grid/src/protobuf/model/selection_type_option.rs

@@ -657,6 +657,585 @@ impl ::protobuf::reflect::ProtobufValue for SelectOption {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct SelectOptionChangesetPayload {
+    // message fields
+    pub grid_id: ::std::string::String,
+    pub row_id: ::std::string::String,
+    // message oneof groups
+    pub one_of_insert_option_id: ::std::option::Option<SelectOptionChangesetPayload_oneof_one_of_insert_option_id>,
+    pub one_of_delete_option_id: ::std::option::Option<SelectOptionChangesetPayload_oneof_one_of_delete_option_id>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a SelectOptionChangesetPayload {
+    fn default() -> &'a SelectOptionChangesetPayload {
+        <SelectOptionChangesetPayload as ::protobuf::Message>::default_instance()
+    }
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum SelectOptionChangesetPayload_oneof_one_of_insert_option_id {
+    insert_option_id(::std::string::String),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum SelectOptionChangesetPayload_oneof_one_of_delete_option_id {
+    delete_option_id(::std::string::String),
+}
+
+impl SelectOptionChangesetPayload {
+    pub fn new() -> SelectOptionChangesetPayload {
+        ::std::default::Default::default()
+    }
+
+    // string grid_id = 1;
+
+
+    pub fn get_grid_id(&self) -> &str {
+        &self.grid_id
+    }
+    pub fn clear_grid_id(&mut self) {
+        self.grid_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_grid_id(&mut self, v: ::std::string::String) {
+        self.grid_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_grid_id(&mut self) -> &mut ::std::string::String {
+        &mut self.grid_id
+    }
+
+    // Take field
+    pub fn take_grid_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.grid_id, ::std::string::String::new())
+    }
+
+    // string row_id = 2;
+
+
+    pub fn get_row_id(&self) -> &str {
+        &self.row_id
+    }
+    pub fn clear_row_id(&mut self) {
+        self.row_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_row_id(&mut self, v: ::std::string::String) {
+        self.row_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_row_id(&mut self) -> &mut ::std::string::String {
+        &mut self.row_id
+    }
+
+    // Take field
+    pub fn take_row_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.row_id, ::std::string::String::new())
+    }
+
+    // string insert_option_id = 3;
+
+
+    pub fn get_insert_option_id(&self) -> &str {
+        match self.one_of_insert_option_id {
+            ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_insert_option_id::insert_option_id(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_insert_option_id(&mut self) {
+        self.one_of_insert_option_id = ::std::option::Option::None;
+    }
+
+    pub fn has_insert_option_id(&self) -> bool {
+        match self.one_of_insert_option_id {
+            ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_insert_option_id::insert_option_id(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_insert_option_id(&mut self, v: ::std::string::String) {
+        self.one_of_insert_option_id = ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_insert_option_id::insert_option_id(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_insert_option_id(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_insert_option_id::insert_option_id(_)) = self.one_of_insert_option_id {
+        } else {
+            self.one_of_insert_option_id = ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_insert_option_id::insert_option_id(::std::string::String::new()));
+        }
+        match self.one_of_insert_option_id {
+            ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_insert_option_id::insert_option_id(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_insert_option_id(&mut self) -> ::std::string::String {
+        if self.has_insert_option_id() {
+            match self.one_of_insert_option_id.take() {
+                ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_insert_option_id::insert_option_id(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+
+    // string delete_option_id = 4;
+
+
+    pub fn get_delete_option_id(&self) -> &str {
+        match self.one_of_delete_option_id {
+            ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_delete_option_id::delete_option_id(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_delete_option_id(&mut self) {
+        self.one_of_delete_option_id = ::std::option::Option::None;
+    }
+
+    pub fn has_delete_option_id(&self) -> bool {
+        match self.one_of_delete_option_id {
+            ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_delete_option_id::delete_option_id(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_delete_option_id(&mut self, v: ::std::string::String) {
+        self.one_of_delete_option_id = ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_delete_option_id::delete_option_id(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_delete_option_id(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_delete_option_id::delete_option_id(_)) = self.one_of_delete_option_id {
+        } else {
+            self.one_of_delete_option_id = ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_delete_option_id::delete_option_id(::std::string::String::new()));
+        }
+        match self.one_of_delete_option_id {
+            ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_delete_option_id::delete_option_id(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_delete_option_id(&mut self) -> ::std::string::String {
+        if self.has_delete_option_id() {
+            match self.one_of_delete_option_id.take() {
+                ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_delete_option_id::delete_option_id(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+}
+
+impl ::protobuf::Message for SelectOptionChangesetPayload {
+    fn is_initialized(&self) -> bool {
+        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_proto3_string_into(wire_type, is, &mut self.grid_id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.row_id)?;
+                },
+                3 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_insert_option_id = ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_insert_option_id::insert_option_id(is.read_string()?));
+                },
+                4 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_delete_option_id = ::std::option::Option::Some(SelectOptionChangesetPayload_oneof_one_of_delete_option_id::delete_option_id(is.read_string()?));
+                },
+                _ => {
+                    ::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 !self.grid_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.grid_id);
+        }
+        if !self.row_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.row_id);
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_insert_option_id {
+            match v {
+                &SelectOptionChangesetPayload_oneof_one_of_insert_option_id::insert_option_id(ref v) => {
+                    my_size += ::protobuf::rt::string_size(3, &v);
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_delete_option_id {
+            match v {
+                &SelectOptionChangesetPayload_oneof_one_of_delete_option_id::delete_option_id(ref v) => {
+                    my_size += ::protobuf::rt::string_size(4, &v);
+                },
+            };
+        }
+        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 !self.grid_id.is_empty() {
+            os.write_string(1, &self.grid_id)?;
+        }
+        if !self.row_id.is_empty() {
+            os.write_string(2, &self.row_id)?;
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_insert_option_id {
+            match v {
+                &SelectOptionChangesetPayload_oneof_one_of_insert_option_id::insert_option_id(ref v) => {
+                    os.write_string(3, v)?;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_delete_option_id {
+            match v {
+                &SelectOptionChangesetPayload_oneof_one_of_delete_option_id::delete_option_id(ref v) => {
+                    os.write_string(4, 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() -> SelectOptionChangesetPayload {
+        SelectOptionChangesetPayload::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_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "grid_id",
+                |m: &SelectOptionChangesetPayload| { &m.grid_id },
+                |m: &mut SelectOptionChangesetPayload| { &mut m.grid_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "row_id",
+                |m: &SelectOptionChangesetPayload| { &m.row_id },
+                |m: &mut SelectOptionChangesetPayload| { &mut m.row_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "insert_option_id",
+                SelectOptionChangesetPayload::has_insert_option_id,
+                SelectOptionChangesetPayload::get_insert_option_id,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "delete_option_id",
+                SelectOptionChangesetPayload::has_delete_option_id,
+                SelectOptionChangesetPayload::get_delete_option_id,
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SelectOptionChangesetPayload>(
+                "SelectOptionChangesetPayload",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static SelectOptionChangesetPayload {
+        static instance: ::protobuf::rt::LazyV2<SelectOptionChangesetPayload> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(SelectOptionChangesetPayload::new)
+    }
+}
+
+impl ::protobuf::Clear for SelectOptionChangesetPayload {
+    fn clear(&mut self) {
+        self.grid_id.clear();
+        self.row_id.clear();
+        self.one_of_insert_option_id = ::std::option::Option::None;
+        self.one_of_delete_option_id = ::std::option::Option::None;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for SelectOptionChangesetPayload {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for SelectOptionChangesetPayload {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct SelectOptionContext {
+    // message fields
+    pub options: ::protobuf::RepeatedField<SelectOption>,
+    pub select_options: ::protobuf::RepeatedField<SelectOption>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a SelectOptionContext {
+    fn default() -> &'a SelectOptionContext {
+        <SelectOptionContext as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl SelectOptionContext {
+    pub fn new() -> SelectOptionContext {
+        ::std::default::Default::default()
+    }
+
+    // repeated .SelectOption options = 1;
+
+
+    pub fn get_options(&self) -> &[SelectOption] {
+        &self.options
+    }
+    pub fn clear_options(&mut self) {
+        self.options.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_options(&mut self, v: ::protobuf::RepeatedField<SelectOption>) {
+        self.options = v;
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_options(&mut self) -> &mut ::protobuf::RepeatedField<SelectOption> {
+        &mut self.options
+    }
+
+    // Take field
+    pub fn take_options(&mut self) -> ::protobuf::RepeatedField<SelectOption> {
+        ::std::mem::replace(&mut self.options, ::protobuf::RepeatedField::new())
+    }
+
+    // repeated .SelectOption select_options = 2;
+
+
+    pub fn get_select_options(&self) -> &[SelectOption] {
+        &self.select_options
+    }
+    pub fn clear_select_options(&mut self) {
+        self.select_options.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_select_options(&mut self, v: ::protobuf::RepeatedField<SelectOption>) {
+        self.select_options = v;
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_select_options(&mut self) -> &mut ::protobuf::RepeatedField<SelectOption> {
+        &mut self.select_options
+    }
+
+    // Take field
+    pub fn take_select_options(&mut self) -> ::protobuf::RepeatedField<SelectOption> {
+        ::std::mem::replace(&mut self.select_options, ::protobuf::RepeatedField::new())
+    }
+}
+
+impl ::protobuf::Message for SelectOptionContext {
+    fn is_initialized(&self) -> bool {
+        for v in &self.options {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        for v in &self.select_options {
+            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_repeated_message_into(wire_type, is, &mut self.options)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.select_options)?;
+                },
+                _ => {
+                    ::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;
+        for value in &self.options {
+            let len = value.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        };
+        for value in &self.select_options {
+            let len = value.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        };
+        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<()> {
+        for v in &self.options {
+            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        };
+        for v in &self.select_options {
+            os.write_tag(2, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        };
+        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() -> SelectOptionContext {
+        SelectOptionContext::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_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<SelectOption>>(
+                "options",
+                |m: &SelectOptionContext| { &m.options },
+                |m: &mut SelectOptionContext| { &mut m.options },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<SelectOption>>(
+                "select_options",
+                |m: &SelectOptionContext| { &m.select_options },
+                |m: &mut SelectOptionContext| { &mut m.select_options },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SelectOptionContext>(
+                "SelectOptionContext",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static SelectOptionContext {
+        static instance: ::protobuf::rt::LazyV2<SelectOptionContext> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(SelectOptionContext::new)
+    }
+}
+
+impl ::protobuf::Clear for SelectOptionContext {
+    fn clear(&mut self) {
+        self.options.clear();
+        self.select_options.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for SelectOptionContext {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for SelectOptionContext {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(Clone,PartialEq,Eq,Debug,Hash)]
 pub enum SelectOptionColor {
     Purple = 0,
@@ -736,11 +1315,18 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     s\x12#\n\rdisable_color\x18\x02\x20\x01(\x08R\x0cdisableColor\"\\\n\x0cS\
     electOption\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x12\n\x04name\
     \x18\x02\x20\x01(\tR\x04name\x12(\n\x05color\x18\x03\x20\x01(\x0e2\x12.S\
-    electOptionColorR\x05color*y\n\x11SelectOptionColor\x12\n\n\x06Purple\
-    \x10\0\x12\x08\n\x04Pink\x10\x01\x12\r\n\tLightPink\x10\x02\x12\n\n\x06O\
-    range\x10\x03\x12\n\n\x06Yellow\x10\x04\x12\x08\n\x04Lime\x10\x05\x12\t\
-    \n\x05Green\x10\x06\x12\x08\n\x04Aqua\x10\x07\x12\x08\n\x04Blue\x10\x08b\
-    \x06proto3\
+    electOptionColorR\x05color\"\xdc\x01\n\x1cSelectOptionChangesetPayload\
+    \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\
+    \x18\x02\x20\x01(\tR\x05rowId\x12*\n\x10insert_option_id\x18\x03\x20\x01\
+    (\tH\0R\x0einsertOptionId\x12*\n\x10delete_option_id\x18\x04\x20\x01(\tH\
+    \x01R\x0edeleteOptionIdB\x19\n\x17one_of_insert_option_idB\x19\n\x17one_\
+    of_delete_option_id\"t\n\x13SelectOptionContext\x12'\n\x07options\x18\
+    \x01\x20\x03(\x0b2\r.SelectOptionR\x07options\x124\n\x0eselect_options\
+    \x18\x02\x20\x03(\x0b2\r.SelectOptionR\rselectOptions*y\n\x11SelectOptio\
+    nColor\x12\n\n\x06Purple\x10\0\x12\x08\n\x04Pink\x10\x01\x12\r\n\tLightP\
+    ink\x10\x02\x12\n\n\x06Orange\x10\x03\x12\n\n\x06Yellow\x10\x04\x12\x08\
+    \n\x04Lime\x10\x05\x12\t\n\x05Green\x10\x06\x12\x08\n\x04Aqua\x10\x07\
+    \x12\x08\n\x04Blue\x10\x08b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 2 - 0
frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto

@@ -11,7 +11,9 @@ enum GridEvent {
     DuplicateField = 15;
     GetEditFieldContext = 16;
     CreateSelectOption = 30;
+    GetSelectOptions = 31;
     CreateRow = 50;
     GetRow = 51;
     UpdateCell = 70;
+    InsertSelectOption = 71;
 }

+ 10 - 0
frontend/rust-lib/flowy-grid/src/protobuf/proto/selection_type_option.proto

@@ -13,6 +13,16 @@ message SelectOption {
     string name = 2;
     SelectOptionColor color = 3;
 }
+message SelectOptionChangesetPayload {
+    string grid_id = 1;
+    string row_id = 2;
+    oneof one_of_insert_option_id { string insert_option_id = 3; };
+    oneof one_of_delete_option_id { string delete_option_id = 4; };
+}
+message SelectOptionContext {
+    repeated SelectOption options = 1;
+    repeated SelectOption select_options = 2;
+}
 enum SelectOptionColor {
     Purple = 0;
     Pink = 1;

+ 37 - 57
frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs

@@ -1,6 +1,7 @@
 use crate::dart_notification::{send_dart_notification, GridNotification};
 use crate::manager::GridUser;
-use crate::services::row::{make_cell, make_row_ids_per_block, GridBlockSnapshot};
+use crate::services::persistence::block_index::BlockIndexPersistence;
+use crate::services::row::{make_block_row_ids, make_cell_by_field_id, GridBlockSnapshot};
 use bytes::Bytes;
 use dashmap::DashMap;
 use flowy_error::{FlowyError, FlowyResult};
@@ -28,20 +29,24 @@ pub(crate) struct GridBlockMetaEditorManager {
     grid_id: String,
     user: Arc<dyn GridUser>,
     editor_map: DashMap<String, Arc<ClientGridBlockMetaEditor>>,
-    block_id_by_row_id: DashMap<BlockId, RowId>,
+    persistence: Arc<BlockIndexPersistence>,
 }
 
 impl GridBlockMetaEditorManager {
-    pub(crate) async fn new(grid_id: &str, user: &Arc<dyn GridUser>, blocks: Vec<GridBlockMeta>) -> FlowyResult<Self> {
+    pub(crate) async fn new(
+        grid_id: &str,
+        user: &Arc<dyn GridUser>,
+        blocks: Vec<GridBlockMeta>,
+        persistence: Arc<BlockIndexPersistence>,
+    ) -> FlowyResult<Self> {
         let editor_map = make_block_meta_editor_map(user, blocks).await?;
         let user = user.clone();
-        let block_id_by_row_id = DashMap::new();
         let grid_id = grid_id.to_owned();
         let manager = Self {
             grid_id,
             user,
             editor_map,
-            block_id_by_row_id,
+            persistence,
         };
         Ok(manager)
     }
@@ -60,14 +65,18 @@ impl GridBlockMetaEditorManager {
         }
     }
 
+    async fn get_editor_from_row_id(&self, row_id: &str) -> FlowyResult<Arc<ClientGridBlockMetaEditor>> {
+        let block_id = self.persistence.get_block_id(row_id)?;
+        Ok(self.get_editor(&block_id).await?)
+    }
+
     pub(crate) async fn create_row(
         &self,
         block_id: &str,
         row_meta: RowMeta,
         start_row_id: Option<String>,
     ) -> FlowyResult<i32> {
-        self.block_id_by_row_id
-            .insert(row_meta.id.clone(), row_meta.block_id.clone());
+        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_count = editor.create_row(row_meta, start_row_id).await?;
         self.notify_block_did_update_row(block_id).await?;
@@ -83,7 +92,7 @@ impl GridBlockMetaEditorManager {
             let editor = self.get_editor(&block_id).await?;
             let mut row_count = 0;
             for row in &row_metas {
-                self.block_id_by_row_id.insert(row.id.clone(), row.block_id.clone());
+                let _ = self.persistence.insert_or_update(&row.block_id, &row.id)?;
                 row_count = editor.create_row(row.clone(), None).await?;
             }
             changesets.push(GridBlockMetaChangeset::from_row_count(&block_id, row_count));
@@ -95,12 +104,11 @@ impl GridBlockMetaEditorManager {
 
     pub(crate) async fn delete_rows(&self, row_orders: Vec<RowOrder>) -> FlowyResult<Vec<GridBlockMetaChangeset>> {
         let mut changesets = vec![];
-        let row_ids_per_blocks = make_row_ids_per_block(&row_orders);
-        for row_ids_per_block in row_ids_per_blocks {
-            let editor = self.get_editor(&row_ids_per_block.block_id).await?;
-            let row_count = editor.delete_rows(row_ids_per_block.row_ids).await?;
+        for block_row_ids in make_block_row_ids(&row_orders) {
+            let editor = self.get_editor(&block_row_ids.block_id).await?;
+            let row_count = editor.delete_rows(block_row_ids.row_ids).await?;
 
-            let changeset = GridBlockMetaChangeset::from_row_count(&row_ids_per_block.block_id, row_count);
+            let changeset = GridBlockMetaChangeset::from_row_count(&block_row_ids.block_id, row_count);
             changesets.push(changeset);
         }
 
@@ -114,10 +122,17 @@ impl GridBlockMetaEditorManager {
         Ok(())
     }
 
-    pub async fn get_row(&self, block_id: &str, row_id: &str) -> FlowyResult<Option<Arc<RowMeta>>> {
-        let editor = self.get_editor(block_id).await?;
+    pub async fn update_row_cells(&self, field_metas: &[FieldMeta], changeset: RowMetaChangeset) -> FlowyResult<()> {
+        let editor = self.get_editor_from_row_id(&changeset.row_id).await?;
+        let _ = editor.update_row(changeset.clone()).await?;
+        self.notify_did_update_cells(changeset, field_metas)?;
+        Ok(())
+    }
+
+    pub async fn get_row_meta(&self, row_id: &str) -> FlowyResult<Option<Arc<RowMeta>>> {
+        let editor = self.get_editor_from_row_id(row_id).await?;
         let row_ids = vec![row_id.to_owned()];
-        let mut row_metas = editor.get_row_metas(&Some(row_ids)).await?;
+        let mut row_metas = editor.get_row_metas(Some(row_ids)).await?;
         if row_metas.is_empty() {
             Ok(None)
         } else {
@@ -125,23 +140,11 @@ impl GridBlockMetaEditorManager {
         }
     }
 
-    pub async fn update_cells(&self, field_metas: &[FieldMeta], changeset: RowMetaChangeset) -> FlowyResult<()> {
-        let editor = self.get_editor_from_row_id(&changeset.row_id).await?;
-        let _ = editor.update_row(changeset.clone()).await?;
-        self.notify_did_update_cells(changeset, field_metas)?;
-        Ok(())
-    }
-
     pub(crate) async fn make_block_snapshots(&self, block_ids: Vec<String>) -> FlowyResult<Vec<GridBlockSnapshot>> {
         let mut snapshots = vec![];
         for block_id in block_ids {
             let editor = self.get_editor(&block_id).await?;
-            let row_metas = editor.get_row_metas(&None).await?;
-            row_metas.iter().for_each(|row_meta| {
-                self.block_id_by_row_id
-                    .insert(row_meta.id.clone(), row_meta.block_id.clone());
-            });
-
+            let row_metas = editor.get_row_metas(None).await?;
             snapshots.push(GridBlockSnapshot { block_id, row_metas });
         }
         Ok(snapshots)
@@ -164,19 +167,6 @@ impl GridBlockMetaEditorManager {
         Ok(block_cell_metas)
     }
 
-    async fn get_editor_from_row_id(&self, row_id: &str) -> FlowyResult<Arc<ClientGridBlockMetaEditor>> {
-        match self.block_id_by_row_id.get(row_id) {
-            None => {
-                let msg = format!(
-                    "Update Row failed. Can't find the corresponding block with row_id: {}",
-                    row_id
-                );
-                Err(FlowyError::internal().context(msg))
-            }
-            Some(block_id) => Ok(self.get_editor(&block_id).await?),
-        }
-    }
-
     async fn notify_block_did_update_row(&self, block_id: &str) -> FlowyResult<()> {
         let block_order: GridBlockOrder = block_id.into();
         send_dart_notification(&self.grid_id, GridNotification::DidUpdateRow)
@@ -196,7 +186,7 @@ impl GridBlockMetaEditorManager {
             .cell_by_field_id
             .into_iter()
             .for_each(
-                |(field_id, cell_meta)| match make_cell(&field_meta_map, field_id, cell_meta) {
+                |(field_id, cell_meta)| match make_cell_by_field_id(&field_meta_map, field_id, cell_meta) {
                     None => {}
                     Some((_, cell)) => cells.push(cell),
                 },
@@ -290,23 +280,13 @@ impl ClientGridBlockMetaEditor {
         Ok(row_count)
     }
 
-    pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<RowMeta> {
-        let row_id = changeset.row_id.clone();
+    pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
         let _ = self.modify(|pad| Ok(pad.update_row(changeset)?)).await?;
-        let row_ids = vec![row_id.clone()];
-        let mut row_metas = self.get_row_metas(&Some(row_ids)).await?;
-        debug_assert_eq!(row_metas.len(), 1);
-
-        if row_metas.is_empty() {
-            return Err(FlowyError::record_not_found().context(format!("Can't find the row with id: {}", &row_id)));
-        } else {
-            let a = (**row_metas.pop().as_ref().unwrap()).clone();
-            Ok(a)
-        }
+        Ok(())
     }
 
-    pub async fn get_row_metas(&self, row_ids: &Option<Vec<String>>) -> FlowyResult<Vec<Arc<RowMeta>>> {
-        let row_metas = self.pad.read().await.get_row_metas(row_ids)?;
+    pub async fn get_row_metas(&self, row_ids: Option<Vec<String>>) -> FlowyResult<Vec<Arc<RowMeta>>> {
+        let row_metas = self.pad.read().await.get_row_metas(&row_ids)?;
         Ok(row_metas)
     }
 

+ 3 - 3
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs

@@ -1,6 +1,6 @@
 use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{CellDataSerde, TypeOptionCellData};
+use crate::services::row::{CellDataOperation, TypeOptionCellData};
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::FlowyError;
@@ -40,7 +40,7 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox);
 const YES: &str = "Yes";
 const NO: &str = "No";
 
-impl CellDataSerde for CheckboxTypeOption {
+impl CellDataOperation for CheckboxTypeOption {
     fn deserialize_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String {
         if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
             if !type_option_cell_data.is_text() || !type_option_cell_data.is_checkbox() {
@@ -81,7 +81,7 @@ fn string_to_bool(bool_str: &str) -> bool {
 mod tests {
     use crate::services::field::CheckboxTypeOption;
     use crate::services::field::FieldBuilder;
-    use crate::services::row::CellDataSerde;
+    use crate::services::row::CellDataOperation;
     use flowy_grid_data_model::entities::FieldType;
 
     #[test]

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

@@ -1,5 +1,5 @@
 use crate::impl_type_option;
-use crate::services::row::{CellDataSerde, TypeOptionCellData};
+use crate::services::row::{CellDataOperation, TypeOptionCellData};
 use bytes::Bytes;
 use chrono::format::strftime::StrftimeItems;
 use chrono::NaiveDateTime;
@@ -42,7 +42,7 @@ impl DateTypeOption {
     }
 }
 
-impl CellDataSerde for DateTypeOption {
+impl CellDataOperation for DateTypeOption {
     fn deserialize_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String {
         if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
             if !type_option_cell_data.is_date() {
@@ -185,7 +185,7 @@ impl std::default::Default for TimeFormat {
 mod tests {
     use crate::services::field::FieldBuilder;
     use crate::services::field::{DateFormat, DateTypeOption, TimeFormat};
-    use crate::services::row::{CellDataSerde, TypeOptionCellData};
+    use crate::services::row::{CellDataOperation, TypeOptionCellData};
     use flowy_grid_data_model::entities::FieldType;
     use strum::IntoEnumIterator;
 

+ 3 - 3
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs

@@ -1,5 +1,5 @@
 use crate::impl_type_option;
-use crate::services::row::{CellDataSerde, TypeOptionCellData};
+use crate::services::row::{CellDataOperation, TypeOptionCellData};
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::FlowyError;
 use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntity, TypeOptionDataEntry};
@@ -76,7 +76,7 @@ pub struct NumberTypeOption {
 }
 impl_type_option!(NumberTypeOption, FieldType::Number);
 
-impl CellDataSerde for NumberTypeOption {
+impl CellDataOperation for NumberTypeOption {
     fn deserialize_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String {
         if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
             if type_option_cell_data.is_date() {
@@ -205,7 +205,7 @@ fn make_strip_symbol() -> Vec<String> {
 mod tests {
     use crate::services::field::FieldBuilder;
     use crate::services::field::{NumberFormat, NumberTypeOption};
-    use crate::services::row::{CellDataSerde, TypeOptionCellData};
+    use crate::services::row::{CellDataOperation, TypeOptionCellData};
     use flowy_grid_data_model::entities::FieldType;
     use strum::IntoEnumIterator;
 

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

@@ -1,11 +1,12 @@
 use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{CellDataSerde, TypeOptionCellData};
+use crate::services::row::{CellDataOperation, TypeOptionCellData};
 use crate::services::util::*;
 use bytes::Bytes;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use flowy_error::{FlowyError, FlowyResult};
+use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntity, TypeOptionDataEntry};
+use flowy_grid_data_model::parser::{NotEmptyStr, NotEmptyUuid};
 use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
 use serde::{Deserialize, Serialize};
 use std::str::FromStr;
@@ -24,7 +25,7 @@ pub struct SingleSelectTypeOption {
 }
 impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect);
 
-impl CellDataSerde for SingleSelectTypeOption {
+impl CellDataOperation for SingleSelectTypeOption {
     fn deserialize_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String {
         if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
             if !type_option_cell_data.is_single_select() || !type_option_cell_data.is_multi_select() {
@@ -80,7 +81,7 @@ pub struct MultiSelectTypeOption {
 }
 impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect);
 
-impl CellDataSerde for MultiSelectTypeOption {
+impl CellDataOperation for MultiSelectTypeOption {
     fn deserialize_cell_data(&self, data: String, _field_meta: &FieldMeta) -> String {
         if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
             if !type_option_cell_data.is_single_select() || !type_option_cell_data.is_multi_select() {
@@ -190,6 +191,77 @@ impl SelectOption {
     }
 }
 
+#[derive(Clone, Debug, Default, ProtoBuf)]
+pub struct SelectOptionChangesetPayload {
+    #[pb(index = 1)]
+    pub grid_id: String,
+
+    #[pb(index = 2)]
+    pub row_id: String,
+
+    #[pb(index = 3)]
+    pub field_id: String,
+
+    #[pb(index = 4, one_of)]
+    pub insert_option_id: Option<String>,
+
+    #[pb(index = 5, one_of)]
+    pub delete_option_id: Option<String>,
+}
+
+#[derive(Clone)]
+pub struct SelectOptionChangesetParams {
+    pub grid_id: String,
+    pub field_id: String,
+    pub row_id: String,
+    pub insert_option_id: Option<String>,
+    pub delete_option_id: Option<String>,
+}
+
+impl TryInto<SelectOptionChangesetParams> for SelectOptionChangesetPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<SelectOptionChangesetParams, Self::Error> {
+        let grid_id = NotEmptyUuid::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+        let row_id = NotEmptyUuid::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
+        let field_id = NotEmptyUuid::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
+        let insert_option_id = match self.insert_option_id {
+            None => None,
+            Some(insert_option_id) => Some(
+                NotEmptyStr::parse(insert_option_id)
+                    .map_err(|_| ErrorCode::OptionIdIsEmpty)?
+                    .0,
+            ),
+        };
+
+        let delete_option_id = match self.delete_option_id {
+            None => None,
+            Some(delete_option_id) => Some(
+                NotEmptyStr::parse(delete_option_id)
+                    .map_err(|_| ErrorCode::OptionIdIsEmpty)?
+                    .0,
+            ),
+        };
+
+        Ok(SelectOptionChangesetParams {
+            grid_id: grid_id.0,
+            row_id: row_id.0,
+            field_id: field_id.0,
+            insert_option_id,
+            delete_option_id,
+        })
+    }
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct SelectOptionContext {
+    #[pb(index = 1)]
+    pub options: Vec<SelectOption>,
+
+    #[pb(index = 2)]
+    pub select_options: Vec<SelectOption>,
+}
+
 #[derive(ProtoBuf_Enum, Serialize, Deserialize, Debug, Clone)]
 #[repr(u8)]
 pub enum SelectOptionColor {
@@ -213,7 +285,7 @@ impl std::default::Default for SelectOptionColor {
 #[cfg(test)]
 mod tests {
     use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
-    use crate::services::row::CellDataSerde;
+    use crate::services::row::CellDataOperation;
 
     #[test]
     #[should_panic]

+ 3 - 3
frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs

@@ -1,6 +1,6 @@
 use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{deserialize_cell_data, CellDataSerde, TypeOptionCellData};
+use crate::services::row::{deserialize_cell_data, CellDataOperation, TypeOptionCellData};
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
 use flowy_error::FlowyError;
@@ -30,7 +30,7 @@ pub struct RichTextTypeOption {
 }
 impl_type_option!(RichTextTypeOption, FieldType::RichText);
 
-impl CellDataSerde for RichTextTypeOption {
+impl CellDataOperation for RichTextTypeOption {
     fn deserialize_cell_data(&self, data: String, field_meta: &FieldMeta) -> String {
         if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&data) {
             if type_option_cell_data.is_date()
@@ -61,7 +61,7 @@ impl CellDataSerde for RichTextTypeOption {
 mod tests {
     use crate::services::field::FieldBuilder;
     use crate::services::field::*;
-    use crate::services::row::{CellDataSerde, TypeOptionCellData};
+    use crate::services::row::{CellDataOperation, TypeOptionCellData};
     use flowy_grid_data_model::entities::FieldType;
 
     #[test]

+ 33 - 16
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -1,7 +1,10 @@
 use crate::dart_notification::{send_dart_notification, GridNotification};
 use crate::manager::GridUser;
 use crate::services::block_meta_editor::GridBlockMetaEditorManager;
-use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder};
+use crate::services::field::{
+    default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder, SelectOptionChangesetParams,
+};
+use crate::services::persistence::block_index::BlockIndexPersistence;
 use crate::services::row::*;
 use bytes::Bytes;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
@@ -30,17 +33,16 @@ impl ClientGridEditor {
         grid_id: &str,
         user: Arc<dyn GridUser>,
         mut rev_manager: RevisionManager,
+        persistence: Arc<BlockIndexPersistence>,
     ) -> FlowyResult<Arc<Self>> {
         let token = user.token()?;
         let cloud = Arc::new(GridRevisionCloudService { token });
         let grid_pad = rev_manager.load::<GridPadBuilder>(Some(cloud)).await?;
         let rev_manager = Arc::new(rev_manager);
         let pad = Arc::new(RwLock::new(grid_pad));
+        let blocks = pad.read().await.get_block_metas().clone();
 
-        let block_meta_manager = Arc::new(
-            GridBlockMetaEditorManager::new(grid_id, &user, pad.read().await.get_block_metas().clone()).await?,
-        );
-
+        let block_meta_manager = Arc::new(GridBlockMetaEditorManager::new(grid_id, &user, blocks, persistence).await?);
         Ok(Arc::new(Self {
             grid_id: grid_id.to_owned(),
             user,
@@ -223,12 +225,12 @@ impl ClientGridEditor {
         }
     }
 
-    pub async fn get_row(&self, block_id: &str, row_id: &str) -> FlowyResult<Option<Row>> {
-        match self.block_meta_manager.get_row(block_id, row_id).await? {
+    pub async fn get_row(&self, row_id: &str) -> FlowyResult<Option<Row>> {
+        match self.block_meta_manager.get_row_meta(row_id).await? {
             None => Ok(None),
-            Some(row) => {
+            Some(row_meta) => {
                 let field_metas = self.get_field_metas(None).await?;
-                let row_metas = vec![row];
+                let row_metas = vec![row_meta];
                 let mut rows = make_rows_from_row_metas(&field_metas, &row_metas);
                 debug_assert!(rows.len() == 1);
                 Ok(rows.pop())
@@ -236,12 +238,28 @@ impl ClientGridEditor {
         }
     }
 
+    pub async fn get_cell_meta(&self, row_id: &str, field_id: &str) -> FlowyResult<Option<CellMeta>> {
+        let row_meta = self.block_meta_manager.get_row_meta(row_id).await?;
+        match row_meta {
+            None => Ok(None),
+            Some(row_meta) => {
+                let cell_meta = row_meta.cell_by_field_id.get(field_id).cloned();
+                Ok(cell_meta)
+            }
+        }
+    }
+
+    pub async fn apply_select_option(&self, params: SelectOptionChangesetParams) -> FlowyResult<()> {
+        let cell_meta = self.get_cell_meta(&params.row_id, &params.field_id).await?;
+        todo!()
+    }
+
     pub async fn update_cell(&self, mut changeset: CellMetaChangeset) -> FlowyResult<()> {
         if let Some(cell_data) = changeset.data.as_ref() {
             match self.pad.read().await.get_field(&changeset.field_id) {
                 None => {
-                    return Err(FlowyError::internal()
-                        .context(format!("Can not find the field with id: {}", &changeset.field_id)));
+                    let msg = format!("Can not find the field with id: {}", &changeset.field_id);
+                    return Err(FlowyError::internal().context(msg));
                 }
                 Some(field_meta) => {
                     let cell_data = serialize_cell_data(cell_data, field_meta)?;
@@ -254,7 +272,7 @@ impl ClientGridEditor {
         let row_changeset: RowMetaChangeset = changeset.into();
         let _ = self
             .block_meta_manager
-            .update_cells(&field_metas, row_changeset)
+            .update_row_cells(&field_metas, row_changeset)
             .await?;
         Ok(())
     }
@@ -296,7 +314,7 @@ impl ClientGridEditor {
         })
     }
 
-    pub async fn get_field_metas(&self, field_orders: Option<RepeatedFieldOrder>) -> FlowyResult<Vec<FieldMeta>> {
+    pub async fn get_field_metas(&self, field_orders: Option<Vec<FieldOrder>>) -> FlowyResult<Vec<FieldMeta>> {
         let expected_len = match field_orders.as_ref() {
             None => 0,
             Some(field_orders) => field_orders.len(),
@@ -310,7 +328,6 @@ impl ClientGridEditor {
             );
             debug_assert!(field_metas.len() == expected_len);
         }
-        // field_metas.retain(|field_meta| field_meta.visibility);
         Ok(field_metas)
     }
 
@@ -385,8 +402,8 @@ impl ClientGridEditor {
     }
 
     async fn notify_did_update_field(&self, field_id: &str) -> FlowyResult<()> {
-        let field_orders: RepeatedFieldOrder = vec![FieldOrder::from(field_id)].into();
-        let mut field_metas = self.get_field_metas(Some(field_orders)).await?;
+        let field_order = FieldOrder::from(field_id);
+        let mut field_metas = self.get_field_metas(Some(field_order.into())).await?;
         debug_assert!(field_metas.len() == 1);
 
         if let Some(field_meta) = field_metas.pop() {

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

@@ -3,5 +3,5 @@ mod util;
 pub mod block_meta_editor;
 pub mod field;
 pub mod grid_editor;
-pub mod kv_persistence;
+pub mod persistence;
 pub mod row;

+ 49 - 0
frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs

@@ -0,0 +1,49 @@
+use crate::services::persistence::GridDatabase;
+use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
+use flowy_database::{
+    prelude::*,
+    schema::{grid_block_index_table, grid_block_index_table::dsl},
+    ConnectionPool,
+};
+use flowy_error::FlowyResult;
+use std::sync::Arc;
+
+pub struct BlockIndexPersistence {
+    database: Arc<dyn GridDatabase>,
+}
+
+impl BlockIndexPersistence {
+    pub fn new(database: Arc<dyn GridDatabase>) -> Self {
+        Self { database }
+    }
+
+    pub fn get_block_id(&self, row_id: &str) -> FlowyResult<String> {
+        let conn = self.database.db_connection()?;
+        let block_id = dsl::grid_block_index_table
+            .filter(grid_block_index_table::row_id.eq(row_id))
+            .select(grid_block_index_table::block_id)
+            .first::<String>(&*conn)?;
+
+        Ok(block_id)
+    }
+
+    pub fn insert_or_update(&self, block_id: &str, row_id: &str) -> FlowyResult<()> {
+        let conn = self.database.db_connection()?;
+        let item = IndexItem {
+            row_id: row_id.to_string(),
+            block_id: block_id.to_string(),
+        };
+        let _ = diesel::replace_into(grid_block_index_table::table)
+            .values(item)
+            .execute(&*conn)?;
+        Ok(())
+    }
+}
+
+#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
+#[table_name = "grid_block_index_table"]
+#[primary_key(row_id)]
+struct IndexItem {
+    row_id: String,
+    block_id: String,
+}

+ 5 - 4
frontend/rust-lib/flowy-grid/src/services/kv_persistence.rs → frontend/rust-lib/flowy-grid/src/services/persistence/kv.rs

@@ -1,3 +1,4 @@
+use crate::services::persistence::GridDatabase;
 use ::diesel::{query_dsl::*, ExpressionMethods};
 use bytes::Bytes;
 use diesel::SqliteConnection;
@@ -29,19 +30,19 @@ pub trait KVTransaction {
 }
 
 pub struct GridKVPersistence {
-    pool: Arc<ConnectionPool>,
+    database: Arc<dyn GridDatabase>,
 }
 
 impl GridKVPersistence {
-    pub fn new(pool: Arc<ConnectionPool>) -> Self {
-        Self { pool }
+    pub fn new(database: Arc<dyn GridDatabase>) -> Self {
+        Self { database }
     }
 
     pub fn begin_transaction<F, O>(&self, f: F) -> FlowyResult<O>
     where
         F: for<'a> FnOnce(SqliteTransaction<'a>) -> FlowyResult<O>,
     {
-        let conn = self.pool.get()?;
+        let conn = self.database.db_connection()?;
         conn.immediate_transaction::<_, FlowyError, _>(|| {
             let sql_transaction = SqliteTransaction { conn: &conn };
             f(sql_transaction)

+ 16 - 0
frontend/rust-lib/flowy-grid/src/services/persistence/mod.rs

@@ -0,0 +1,16 @@
+use flowy_database::{ConnectionPool, DBConnection};
+use flowy_error::FlowyError;
+use std::sync::Arc;
+
+pub mod block_index;
+pub mod kv;
+
+pub trait GridDatabase: Send + Sync {
+    fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError>;
+
+    fn db_connection(&self) -> Result<DBConnection, FlowyError> {
+        let pool = self.db_pool()?;
+        let conn = pool.get().map_err(|e| FlowyError::internal().context(e))?;
+        Ok(conn)
+    }
+}

+ 3 - 2
frontend/rust-lib/flowy-grid/src/services/row/cell_data_serde.rs → frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs

@@ -1,11 +1,13 @@
 use crate::services::field::*;
+use bytes::Bytes;
 use flowy_error::FlowyError;
 use flowy_grid_data_model::entities::{FieldMeta, FieldType};
 use serde::{Deserialize, Serialize};
 
-pub trait CellDataSerde {
+pub trait CellDataOperation {
     fn deserialize_cell_data(&self, data: String, field_meta: &FieldMeta) -> String;
     fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError>;
+    // fn apply_changeset()
 }
 
 #[derive(Serialize, Deserialize)]
@@ -60,7 +62,6 @@ impl TypeOptionCellData {
     }
 }
 
-#[allow(dead_code)]
 pub fn serialize_cell_data(data: &str, field_meta: &FieldMeta) -> Result<String, FlowyError> {
     match field_meta.field_type {
         FieldType::RichText => RichTextTypeOption::from(field_meta).serialize_cell_data(data),

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

@@ -1,7 +1,7 @@
-mod cell_data_serde;
+mod cell_data_operation;
 mod row_builder;
 mod row_loader;
 
-pub use cell_data_serde::*;
+pub use cell_data_operation::*;
 pub use row_builder::*;
 pub(crate) use row_loader::*;

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

@@ -1,4 +1,5 @@
 use crate::services::row::serialize_cell_data;
+use bytes::Bytes;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{CellMeta, FieldMeta, RowMeta, DEFAULT_ROW_HEIGHT};
 use std::collections::HashMap;

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

@@ -8,14 +8,14 @@ use std::collections::HashMap;
 
 use std::sync::Arc;
 
-pub(crate) struct RowIdsPerBlock {
+pub(crate) struct BlockRowIds {
     pub(crate) block_id: String,
     pub(crate) row_ids: Vec<String>,
 }
 
-impl RowIdsPerBlock {
+impl BlockRowIds {
     pub fn new(block_id: &str) -> Self {
-        RowIdsPerBlock {
+        BlockRowIds {
             block_id: block_id.to_owned(),
             row_ids: vec![],
         }
@@ -27,13 +27,13 @@ pub struct GridBlockSnapshot {
     pub row_metas: Vec<Arc<RowMeta>>,
 }
 
-pub(crate) fn make_row_ids_per_block(row_orders: &[RowOrder]) -> Vec<RowIdsPerBlock> {
-    let mut map: HashMap<&String, RowIdsPerBlock> = HashMap::new();
+pub(crate) fn make_block_row_ids(row_orders: &[RowOrder]) -> Vec<BlockRowIds> {
+    let mut map: HashMap<&String, BlockRowIds> = HashMap::new();
     row_orders.iter().for_each(|row_order| {
         let block_id = &row_order.block_id;
         let row_id = row_order.row_id.clone();
         map.entry(block_id)
-            .or_insert_with(|| RowIdsPerBlock::new(block_id))
+            .or_insert_with(|| BlockRowIds::new(block_id))
             .row_ids
             .push(row_id);
     });
@@ -41,13 +41,13 @@ pub(crate) fn make_row_ids_per_block(row_orders: &[RowOrder]) -> Vec<RowIdsPerBl
 }
 
 #[inline(always)]
-pub fn make_cell(
+pub fn make_cell_by_field_id(
     field_map: &HashMap<&String, &FieldMeta>,
     field_id: String,
-    raw_cell: CellMeta,
+    cell_meta: CellMeta,
 ) -> Option<(String, Cell)> {
     let field_meta = field_map.get(&field_id)?;
-    match deserialize_cell_data(raw_cell.data, field_meta) {
+    match deserialize_cell_data(cell_meta.data, field_meta) {
         Ok(content) => {
             let cell = Cell::new(&field_id, content);
             Some((field_id, cell))
@@ -59,6 +59,18 @@ pub fn make_cell(
     }
 }
 
+#[allow(dead_code)]
+pub fn make_cell(field_id: &str, field_meta: &FieldMeta, row_meta: &RowMeta) -> Option<Cell> {
+    let cell_meta = row_meta.cell_by_field_id.get(field_id)?.clone();
+    match deserialize_cell_data(cell_meta.data, field_meta) {
+        Ok(content) => Some(Cell::new(&field_id, content)),
+        Err(e) => {
+            tracing::error!("{}", e);
+            None
+        }
+    }
+}
+
 pub(crate) fn make_row_orders_from_row_metas(row_metas: &[Arc<RowMeta>]) -> Vec<RowOrder> {
     row_metas.iter().map(RowOrder::from).collect::<Vec<_>>()
 }
@@ -74,7 +86,7 @@ pub(crate) fn make_rows_from_row_metas(fields: &[FieldMeta], row_metas: &[Arc<Ro
             .cell_by_field_id
             .clone()
             .into_par_iter()
-            .flat_map(|(field_id, raw_cell)| make_cell(&field_meta_map, field_id, raw_cell))
+            .flat_map(|(field_id, cell_meta)| make_cell_by_field_id(&field_meta_map, field_id, cell_meta))
             .collect::<HashMap<String, Cell>>();
 
         Row {

+ 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, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR,
 };
-use flowy_grid::services::row::{deserialize_cell_data, serialize_cell_data, CellDataSerde, CreateRowMetaBuilder};
+use flowy_grid::services::row::{deserialize_cell_data, serialize_cell_data, CellDataOperation, CreateRowMetaBuilder};
 use flowy_grid_data_model::entities::{
     CellMetaChangeset, FieldChangesetParams, FieldType, GridBlockMeta, GridBlockMetaChangeset, RowMetaChangeset,
     TypeOptionDataEntry,

+ 14 - 2
frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs

@@ -2,6 +2,7 @@ use crate::FlowyError;
 use bytes::Bytes;
 use flowy_database::ConnectionPool;
 use flowy_grid::manager::{GridManager, GridUser};
+use flowy_grid::services::persistence::GridDatabase;
 use flowy_net::ws::connection::FlowyWebSocketConnect;
 use flowy_revision::{RevisionWebSocket, WSStateReceiver};
 use flowy_sync::entities::ws_data::ClientRevisionWSData;
@@ -16,9 +17,20 @@ pub struct GridDepsResolver();
 
 impl GridDepsResolver {
     pub fn resolve(ws_conn: Arc<FlowyWebSocketConnect>, user_session: Arc<UserSession>) -> Arc<GridManager> {
-        let user = Arc::new(GridUserImpl(user_session));
+        let user = Arc::new(GridUserImpl(user_session.clone()));
         let rev_web_socket = Arc::new(GridWebSocket(ws_conn));
-        Arc::new(GridManager::new(user, rev_web_socket))
+        Arc::new(GridManager::new(
+            user,
+            rev_web_socket,
+            Arc::new(GridDatabaseImpl(user_session)),
+        ))
+    }
+}
+
+struct GridDatabaseImpl(Arc<UserSession>);
+impl GridDatabase for GridDatabaseImpl {
+    fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError> {
+        self.0.db_pool().map_err(|e| FlowyError::internal().context(e))
     }
 }
 

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

@@ -95,6 +95,8 @@ pub enum ErrorCode {
     BlockIdIsEmpty = 420,
     #[display(fmt = "Row id is empty")]
     RowIdIsEmpty = 430,
+    #[display(fmt = "Select option id is empty")]
+    OptionIdIsEmpty = 431,
     #[display(fmt = "Field id is empty")]
     FieldIdIsEmpty = 440,
     #[display(fmt = "Field doesn't exist")]

+ 9 - 5
shared-lib/flowy-error-code/src/protobuf/model/code.rs

@@ -60,6 +60,7 @@ pub enum ErrorCode {
     GridIdIsEmpty = 410,
     BlockIdIsEmpty = 420,
     RowIdIsEmpty = 430,
+    OptionIdIsEmpty = 431,
     FieldIdIsEmpty = 440,
     FieldDoesNotExist = 441,
     SelectOptionNameIsEmpty = 442,
@@ -109,6 +110,7 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             410 => ::std::option::Option::Some(ErrorCode::GridIdIsEmpty),
             420 => ::std::option::Option::Some(ErrorCode::BlockIdIsEmpty),
             430 => ::std::option::Option::Some(ErrorCode::RowIdIsEmpty),
+            431 => ::std::option::Option::Some(ErrorCode::OptionIdIsEmpty),
             440 => ::std::option::Option::Some(ErrorCode::FieldIdIsEmpty),
             441 => ::std::option::Option::Some(ErrorCode::FieldDoesNotExist),
             442 => ::std::option::Option::Some(ErrorCode::SelectOptionNameIsEmpty),
@@ -155,6 +157,7 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             ErrorCode::GridIdIsEmpty,
             ErrorCode::BlockIdIsEmpty,
             ErrorCode::RowIdIsEmpty,
+            ErrorCode::OptionIdIsEmpty,
             ErrorCode::FieldIdIsEmpty,
             ErrorCode::FieldDoesNotExist,
             ErrorCode::SelectOptionNameIsEmpty,
@@ -188,7 +191,7 @@ impl ::protobuf::reflect::ProtobufValue for ErrorCode {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\ncode.proto*\x9e\x07\n\tErrorCode\x12\x0c\n\x08Internal\x10\0\x12\x14\
+    \n\ncode.proto*\xb4\x07\n\tErrorCode\x12\x0c\n\x08Internal\x10\0\x12\x14\
     \n\x10UserUnauthorized\x10\x02\x12\x12\n\x0eRecordNotFound\x10\x03\x12\
     \x11\n\rUserIdIsEmpty\x10\x04\x12\x18\n\x14WorkspaceNameInvalid\x10d\x12\
     \x16\n\x12WorkspaceIdInvalid\x10e\x12\x18\n\x14AppColorStyleInvalid\x10f\
@@ -207,10 +210,11 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     IsEmpty\x10\xb6\x02\x12\x12\n\rUserIdInvalid\x10\xb7\x02\x12\x11\n\x0cUs\
     erNotExist\x10\xb8\x02\x12\x10\n\x0bTextTooLong\x10\x90\x03\x12\x12\n\rG\
     ridIdIsEmpty\x10\x9a\x03\x12\x13\n\x0eBlockIdIsEmpty\x10\xa4\x03\x12\x11\
-    \n\x0cRowIdIsEmpty\x10\xae\x03\x12\x13\n\x0eFieldIdIsEmpty\x10\xb8\x03\
-    \x12\x16\n\x11FieldDoesNotExist\x10\xb9\x03\x12\x1c\n\x17SelectOptionNam\
-    eIsEmpty\x10\xba\x03\x12\x1a\n\x15TypeOptionDataIsEmpty\x10\xc2\x03\x12\
-    \x10\n\x0bInvalidData\x10\xf4\x03b\x06proto3\
+    \n\x0cRowIdIsEmpty\x10\xae\x03\x12\x14\n\x0fOptionIdIsEmpty\x10\xaf\x03\
+    \x12\x13\n\x0eFieldIdIsEmpty\x10\xb8\x03\x12\x16\n\x11FieldDoesNotExist\
+    \x10\xb9\x03\x12\x1c\n\x17SelectOptionNameIsEmpty\x10\xba\x03\x12\x1a\n\
+    \x15TypeOptionDataIsEmpty\x10\xc2\x03\x12\x10\n\x0bInvalidData\x10\xf4\
+    \x03b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 1 - 0
shared-lib/flowy-error-code/src/protobuf/proto/code.proto

@@ -36,6 +36,7 @@ enum ErrorCode {
     GridIdIsEmpty = 410;
     BlockIdIsEmpty = 420;
     RowIdIsEmpty = 430;
+    OptionIdIsEmpty = 431;
     FieldIdIsEmpty = 440;
     FieldDoesNotExist = 441;
     SelectOptionNameIsEmpty = 442;

+ 6 - 0
shared-lib/flowy-grid-data-model/src/entities/grid.rs

@@ -92,6 +92,12 @@ pub struct FieldOrder {
     pub field_id: String,
 }
 
+impl std::convert::Into<Vec<FieldOrder>> for FieldOrder {
+    fn into(self) -> Vec<FieldOrder> {
+        vec![self]
+    }
+}
+
 impl std::convert::From<&FieldMeta> for FieldOrder {
     fn from(field_meta: &FieldMeta) -> Self {
         Self {

+ 6 - 8
shared-lib/flowy-grid-data-model/src/entities/meta.rs

@@ -393,14 +393,12 @@ pub struct CellMetaChangeset {
 impl std::convert::From<CellMetaChangeset> for RowMetaChangeset {
     fn from(changeset: CellMetaChangeset) -> Self {
         let mut cell_by_field_id = HashMap::with_capacity(1);
-        if let Some(data) = changeset.data {
-            let field_id = changeset.field_id;
-            let cell_meta = CellMeta {
-                field_id: field_id.clone(),
-                data,
-            };
-            cell_by_field_id.insert(field_id, cell_meta);
-        }
+        let field_id = changeset.field_id;
+        let cell_meta = CellMeta {
+            field_id: field_id.clone(),
+            data: changeset.data.unwrap_or("".to_owned()),
+        };
+        cell_by_field_id.insert(field_id, cell_meta);
 
         RowMetaChangeset {
             row_id: changeset.row_id,

+ 2 - 1
shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs

@@ -42,6 +42,7 @@ impl GridMetaPad {
         Self::from_delta(grid_delta)
     }
 
+    #[tracing::instrument(level = "debug", skip_all, err)]
     pub fn create_field(
         &mut self,
         new_field_meta: FieldMeta,
@@ -186,7 +187,7 @@ impl GridMetaPad {
         self.grid_meta.fields.iter().map(FieldOrder::from).collect()
     }
 
-    pub fn get_field_metas(&self, field_orders: Option<RepeatedFieldOrder>) -> CollaborateResult<Vec<FieldMeta>> {
+    pub fn get_field_metas(&self, field_orders: Option<Vec<FieldOrder>>) -> CollaborateResult<Vec<FieldMeta>> {
         match field_orders {
             None => Ok(self.grid_meta.fields.clone()),
             Some(field_orders) => {