Browse Source

Merge pull request #500 from AppFlowy-IO/opti_create_option

Nathan.fooo 3 years ago
parent
commit
ef0d59ff30
64 changed files with 1441 additions and 1256 deletions
  1. 2 24
      frontend/app_flowy/lib/startup/deps_resolver.dart
  2. 13 6
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart
  3. 2 0
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_cache.dart
  4. 18 19
      frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart
  5. 14 14
      frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart
  6. 3 3
      frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart
  7. 5 5
      frontend/app_flowy/lib/workspace/application/grid/field/field_action_sheet_bloc.dart
  8. 6 6
      frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart
  9. 21 95
      frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart
  10. 22 18
      frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart
  11. 131 43
      frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart
  12. 12 1
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart
  13. 0 65
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/field_option_pannel_bloc.dart
  14. 0 89
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_bloc.dart
  15. 77 0
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart
  16. 12 1
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart
  17. 77 0
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart
  18. 0 92
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_bloc.dart
  19. 78 0
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart
  20. 70 5
      frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart
  21. 1 1
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  22. 1 1
      frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart
  23. 22 21
      frontend/app_flowy/lib/workspace/application/grid/grid_service.dart
  24. 2 2
      frontend/app_flowy/lib/workspace/application/grid/prelude.dart
  25. 1 1
      frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart
  26. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart
  27. 2 2
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart
  28. 6 15
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart
  29. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart
  30. 37 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart
  31. 7 7
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart
  32. 28 48
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart
  33. 0 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart
  34. 6 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart
  35. 5 4
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart
  36. 47 54
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart
  37. 70 73
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart
  38. 26 4
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_name_input.dart
  39. 2 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart
  40. 20 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart
  41. 7 11
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart
  42. 11 36
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart
  43. 10 12
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart
  44. 21 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/rich_text.dart
  45. 75 84
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart
  46. 3 3
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart
  47. 11 36
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart
  48. 2 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart
  49. 2 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart
  50. 9 4
      frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart
  51. 17 17
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
  52. 56 39
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
  53. 12 13
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
  54. 4 4
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart
  55. 4 4
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart
  56. 41 52
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  57. 7 7
      frontend/rust-lib/flowy-grid/src/event_map.rs
  58. 16 16
      frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs
  59. 3 3
      frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto
  60. 11 1
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  61. 31 7
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  62. 233 179
      shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
  63. 6 4
      shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
  64. 1 0
      shared-lib/lib-infra/Cargo.toml

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

@@ -15,9 +15,6 @@ import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
 import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show EditFieldContext;
-import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
 import 'package:get_it/get_it.dart';
 
@@ -157,21 +154,14 @@ void _resolveGridDeps(GetIt getIt) {
     ),
   );
 
-  getIt.registerFactoryParam<FieldEditorBloc, String, EditFieldContextLoader>(
-    (gridId, fieldLoader) => FieldEditorBloc(
-      gridId: gridId,
-      fieldLoader: fieldLoader,
-    ),
-  );
-
   getIt.registerFactoryParam<TextCellBloc, GridCellContext, void>(
     (context, _) => TextCellBloc(
       cellContext: context,
     ),
   );
 
-  getIt.registerFactoryParam<SelectionCellBloc, GridSelectOptionCellContext, void>(
-    (context, _) => SelectionCellBloc(
+  getIt.registerFactoryParam<SelectOptionCellBloc, GridSelectOptionCellContext, void>(
+    (context, _) => SelectOptionCellBloc(
       cellContext: context,
     ),
   );
@@ -195,18 +185,6 @@ void _resolveGridDeps(GetIt getIt) {
     ),
   );
 
-  getIt.registerFactoryParam<FieldEditorPannelBloc, EditFieldContext, void>(
-    (context, _) => FieldEditorPannelBloc(context),
-  );
-
-  getIt.registerFactoryParam<DateTypeOptionBloc, DateTypeOption, void>(
-    (typeOption, _) => DateTypeOptionBloc(typeOption: typeOption),
-  );
-
-  getIt.registerFactoryParam<NumberTypeOptionBloc, NumberTypeOption, void>(
-    (typeOption, _) => NumberTypeOptionBloc(typeOption: typeOption),
-  );
-
   getIt.registerFactoryParam<GridPropertyBloc, String, GridFieldCache>(
     (gridId, cache) => GridPropertyBloc(gridId: gridId, fieldCache: cache),
   );

+ 13 - 6
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart

@@ -19,7 +19,7 @@ class GridCellContextBuilder {
         return GridCellContext(
           gridCell: _gridCell,
           cellCache: _cellCache,
-          cellDataLoader: CellDataLoader(gridCell: _gridCell),
+          cellDataLoader: GridCellDataLoader(gridCell: _gridCell),
           cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
         );
       case FieldType.DateTime:
@@ -30,17 +30,24 @@ class GridCellContextBuilder {
           cellDataPersistence: DateCellDataPersistence(gridCell: _gridCell),
         );
       case FieldType.Number:
+        final cellDataLoader = GridCellDataLoader(
+          gridCell: _gridCell,
+          config: const GridCellDataConfig(
+            reloadOnCellChanged: true,
+            reloadOnFieldChanged: true,
+          ),
+        );
         return GridCellContext(
           gridCell: _gridCell,
           cellCache: _cellCache,
-          cellDataLoader: CellDataLoader(gridCell: _gridCell, reloadOnCellChanged: true),
+          cellDataLoader: cellDataLoader,
           cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
         );
       case FieldType.RichText:
         return GridCellContext(
           gridCell: _gridCell,
           cellCache: _cellCache,
-          cellDataLoader: CellDataLoader(gridCell: _gridCell),
+          cellDataLoader: GridCellDataLoader(gridCell: _gridCell),
           cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
         );
       case FieldType.MultiSelect:
@@ -62,7 +69,7 @@ class _GridCellContext<T, D> extends Equatable {
   final GridCell gridCell;
   final GridCellCache cellCache;
   final GridCellCacheKey _cacheKey;
-  final _GridCellDataLoader<T> cellDataLoader;
+  final IGridCellDataLoader<T> cellDataLoader;
   final _GridCellDataPersistence<D> cellDataPersistence;
   final FieldService _fieldService;
 
@@ -150,8 +157,8 @@ class _GridCellContext<T, D> extends Equatable {
     return data;
   }
 
-  Future<Either<List<int>, FlowyError>> getTypeOptionData() {
-    return _fieldService.getTypeOptionData(fieldType: fieldType);
+  Future<Either<FieldTypeOptionData, FlowyError>> getTypeOptionData() {
+    return _fieldService.getFieldTypeOptionData(fieldType: fieldType);
   }
 
   Future<Option<FlowyError>> saveCellData(D data) {

+ 2 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_cache.dart

@@ -104,6 +104,8 @@ class GridCellCache {
   }
 
   Future<void> dispose() async {
+    _fieldListenerByFieldId.clear();
+    _cellDataByFieldId.clear();
     fieldDelegate.dispose();
   }
 }

+ 18 - 19
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart

@@ -1,6 +1,6 @@
 part of 'cell_service.dart';
 
-abstract class GridCellDataConfig {
+abstract class IGridCellDataConfig {
   // The cell data will reload if it receives the field's change notification.
   bool get reloadOnFieldChanged;
 
@@ -11,34 +11,36 @@ abstract class GridCellDataConfig {
   bool get reloadOnCellChanged;
 }
 
-class DefaultCellDataConfig implements GridCellDataConfig {
+class GridCellDataConfig implements IGridCellDataConfig {
   @override
   final bool reloadOnCellChanged;
 
   @override
   final bool reloadOnFieldChanged;
 
-  DefaultCellDataConfig({
+  const GridCellDataConfig({
     this.reloadOnCellChanged = false,
     this.reloadOnFieldChanged = false,
   });
 }
 
-abstract class _GridCellDataLoader<T> {
+abstract class IGridCellDataLoader<T> {
   Future<T?> loadData();
 
-  GridCellDataConfig get config;
+  IGridCellDataConfig get config;
 }
 
-class CellDataLoader extends _GridCellDataLoader<Cell> {
+class GridCellDataLoader extends IGridCellDataLoader<Cell> {
   final CellService service = CellService();
   final GridCell gridCell;
-  final GridCellDataConfig _config;
 
-  CellDataLoader({
+  @override
+  final IGridCellDataConfig config;
+
+  GridCellDataLoader({
     required this.gridCell,
-    bool reloadOnCellChanged = false,
-  }) : _config = DefaultCellDataConfig(reloadOnCellChanged: reloadOnCellChanged);
+    this.config = const GridCellDataConfig(),
+  });
 
   @override
   Future<Cell?> loadData() {
@@ -54,20 +56,17 @@ class CellDataLoader extends _GridCellDataLoader<Cell> {
       });
     });
   }
-
-  @override
-  GridCellDataConfig get config => _config;
 }
 
-class DateCellDataLoader extends _GridCellDataLoader<DateCellData> {
+class DateCellDataLoader extends IGridCellDataLoader<DateCellData> {
   final GridCell gridCell;
-  final GridCellDataConfig _config;
+  final IGridCellDataConfig _config;
   DateCellDataLoader({
     required this.gridCell,
-  }) : _config = DefaultCellDataConfig(reloadOnFieldChanged: true);
+  }) : _config = const GridCellDataConfig(reloadOnFieldChanged: true);
 
   @override
-  GridCellDataConfig get config => _config;
+  IGridCellDataConfig get config => _config;
 
   @override
   Future<DateCellData?> loadData() {
@@ -88,7 +87,7 @@ class DateCellDataLoader extends _GridCellDataLoader<DateCellData> {
   }
 }
 
-class SelectOptionCellDataLoader extends _GridCellDataLoader<SelectOptionCellData> {
+class SelectOptionCellDataLoader extends IGridCellDataLoader<SelectOptionCellData> {
   final SelectOptionService service;
   final GridCell gridCell;
   SelectOptionCellDataLoader({
@@ -108,5 +107,5 @@ class SelectOptionCellDataLoader extends _GridCellDataLoader<SelectOptionCellDat
   }
 
   @override
-  GridCellDataConfig get config => DefaultCellDataConfig();
+  IGridCellDataConfig get config => const GridCellDataConfig(reloadOnFieldChanged: true);
 }

+ 14 - 14
frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart → frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart

@@ -4,16 +4,16 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 
-part 'selection_cell_bloc.freezed.dart';
+part 'select_option_cell_bloc.freezed.dart';
 
-class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
+class SelectOptionCellBloc extends Bloc<SelectOptionCellEvent, SelectOptionCellState> {
   final GridSelectOptionCellContext cellContext;
   void Function()? _onCellChangedFn;
 
-  SelectionCellBloc({
+  SelectOptionCellBloc({
     required this.cellContext,
-  }) : super(SelectionCellState.initial(cellContext)) {
-    on<SelectionCellEvent>(
+  }) : super(SelectOptionCellState.initial(cellContext)) {
+    on<SelectOptionCellEvent>(
       (event, emit) async {
         await event.map(
           initial: (_InitialCell value) async {
@@ -44,7 +44,7 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
     _onCellChangedFn = cellContext.startListening(
       onCellChanged: ((selectOptionContext) {
         if (!isClosed) {
-          add(SelectionCellEvent.didReceiveOptions(
+          add(SelectOptionCellEvent.didReceiveOptions(
             selectOptionContext.options,
             selectOptionContext.selectOptions,
           ));
@@ -55,25 +55,25 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
 }
 
 @freezed
-class SelectionCellEvent with _$SelectionCellEvent {
-  const factory SelectionCellEvent.initial() = _InitialCell;
-  const factory SelectionCellEvent.didReceiveOptions(
+class SelectOptionCellEvent with _$SelectOptionCellEvent {
+  const factory SelectOptionCellEvent.initial() = _InitialCell;
+  const factory SelectOptionCellEvent.didReceiveOptions(
     List<SelectOption> options,
     List<SelectOption> selectedOptions,
   ) = _DidReceiveOptions;
 }
 
 @freezed
-class SelectionCellState with _$SelectionCellState {
-  const factory SelectionCellState({
+class SelectOptionCellState with _$SelectOptionCellState {
+  const factory SelectOptionCellState({
     required List<SelectOption> options,
     required List<SelectOption> selectedOptions,
-  }) = _SelectionCellState;
+  }) = _SelectOptionCellState;
 
-  factory SelectionCellState.initial(GridSelectOptionCellContext context) {
+  factory SelectOptionCellState.initial(GridSelectOptionCellContext context) {
     final data = context.getCellData();
 
-    return SelectionCellState(
+    return SelectOptionCellState(
       options: data?.options ?? [],
       selectedOptions: data?.selectOptions ?? [],
     );

+ 3 - 3
frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart → frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart

@@ -7,14 +7,14 @@ import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'select_option_service.dart';
 
-part 'selection_editor_bloc.freezed.dart';
+part 'select_option_editor_bloc.freezed.dart';
 
-class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
+class SelectOptionCellEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
   final SelectOptionService _selectOptionService;
   final GridSelectOptionCellContext cellContext;
   void Function()? _onCellChangedFn;
 
-  SelectOptionEditorBloc({
+  SelectOptionCellEditorBloc({
     required this.cellContext,
   })  : _selectOptionService = SelectOptionService(gridCell: cellContext.gridCell),
         super(SelectOptionEditorState.initial(cellContext)) {

+ 5 - 5
frontend/app_flowy/lib/workspace/application/grid/field/field_action_sheet_bloc.dart

@@ -11,7 +11,7 @@ class FieldActionSheetBloc extends Bloc<FieldActionSheetEvent, FieldActionSheetS
   final FieldService fieldService;
 
   FieldActionSheetBloc({required Field field, required this.fieldService})
-      : super(FieldActionSheetState.initial(EditFieldContext.create()..gridField = field)) {
+      : super(FieldActionSheetState.initial(FieldTypeOptionData.create()..field_2 = field)) {
     on<FieldActionSheetEvent>(
       (event, emit) async {
         await event.map(
@@ -67,14 +67,14 @@ class FieldActionSheetEvent with _$FieldActionSheetEvent {
 @freezed
 class FieldActionSheetState with _$FieldActionSheetState {
   const factory FieldActionSheetState({
-    required EditFieldContext editContext,
+    required FieldTypeOptionData fieldTypeOptionData,
     required String errorText,
     required String fieldName,
   }) = _FieldActionSheetState;
 
-  factory FieldActionSheetState.initial(EditFieldContext editContext) => FieldActionSheetState(
-        editContext: editContext,
+  factory FieldActionSheetState.initial(FieldTypeOptionData data) => FieldActionSheetState(
+        fieldTypeOptionData: data,
         errorText: '',
-        fieldName: editContext.gridField.name,
+        fieldName: data.field_2.name,
       );
 }

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

@@ -19,16 +19,16 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
         super(FieldCellState.initial(cellContext)) {
     on<FieldCellEvent>(
       (event, emit) async {
-        await event.map(
-          initial: (_InitialCell value) async {
+        event.when(
+          initial: () {
             _startListening();
           },
-          didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
-            emit(state.copyWith(field: value.field));
+          didReceiveFieldUpdate: (field) {
+            emit(state.copyWith(field: field));
           },
-          updateWidth: (_UpdateWidth value) {
+          updateWidth: (offset) {
             final defaultWidth = state.field.width.toDouble();
-            final width = defaultWidth + value.offset;
+            final width = defaultWidth + offset;
             if (width > defaultWidth && width < 300) {
               _fieldService.updateField(width: width);
             }

+ 21 - 95
frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart

@@ -1,41 +1,31 @@
-import 'dart:typed_data';
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
 import 'field_service.dart';
 import 'package:dartz/dartz.dart';
-import 'package:protobuf/protobuf.dart';
-
 part 'field_editor_bloc.freezed.dart';
 
 class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
-  final String gridId;
-  final EditFieldContextLoader _loader;
-
   FieldEditorBloc({
-    required this.gridId,
-    required EditFieldContextLoader fieldLoader,
-  })  : _loader = fieldLoader,
-        super(FieldEditorState.initial(gridId)) {
+    required String gridId,
+    required String fieldName,
+    required IFieldContextLoader fieldContextLoader,
+  }) : super(FieldEditorState.initial(gridId, fieldName, fieldContextLoader)) {
     on<FieldEditorEvent>(
       (event, emit) async {
-        await event.map(
-          initial: (_InitialField value) async {
-            await _getEditFieldContext(emit);
+        await event.when(
+          initial: () async {
+            final fieldContext = GridFieldContext(gridId: gridId, loader: fieldContextLoader);
+            await fieldContext.loadData().then((result) {
+              result.fold(
+                (l) => emit(state.copyWith(fieldContext: Some(fieldContext), name: fieldContext.field.name)),
+                (r) => null,
+              );
+            });
           },
-          updateName: (_UpdateName value) {
-            final newContext = _updateEditContext(name: value.name);
-            emit(state.copyWith(editFieldContext: newContext));
-          },
-          updateField: (_UpdateField value) {
-            final newContext = _updateEditContext(field: value.field, typeOptionData: value.typeOptionData);
-
-            emit(state.copyWith(editFieldContext: newContext));
-          },
-          done: (_Done value) async {
-            await _saveField(emit);
+          updateName: (name) {
+            state.fieldContext.fold(() => null, (fieldContext) => fieldContext.fieldName = name);
+            emit(state.copyWith(name: name));
           },
         );
       },
@@ -46,78 +36,12 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
   Future<void> close() async {
     return super.close();
   }
-
-  Option<EditFieldContext> _updateEditContext({
-    String? name,
-    Field? field,
-    List<int>? typeOptionData,
-  }) {
-    return state.editFieldContext.fold(
-      () => none(),
-      (context) {
-        context.freeze();
-        final newContext = context.rebuild((newContext) {
-          newContext.gridField.rebuild((newField) {
-            if (name != null) {
-              newField.name = name;
-            }
-
-            newContext.gridField = newField;
-          });
-
-          if (field != null) {
-            newContext.gridField = field;
-          }
-
-          if (typeOptionData != null) {
-            newContext.typeOptionData = typeOptionData;
-          }
-        });
-
-        FieldService.insertField(
-          gridId: gridId,
-          field: newContext.gridField,
-          typeOptionData: newContext.typeOptionData,
-        );
-
-        return Some(newContext);
-      },
-    );
-  }
-
-  Future<void> _saveField(Emitter<FieldEditorState> emit) async {
-    await state.editFieldContext.fold(
-      () async => null,
-      (context) async {
-        final result = await FieldService.insertField(
-          gridId: gridId,
-          field: context.gridField,
-          typeOptionData: context.typeOptionData,
-        );
-        result.fold((l) => null, (r) => null);
-      },
-    );
-  }
-
-  Future<void> _getEditFieldContext(Emitter<FieldEditorState> emit) async {
-    final result = await _loader.load();
-    result.fold(
-      (context) {
-        emit(state.copyWith(
-          editFieldContext: Some(context),
-        ));
-      },
-      (err) => Log.error(err),
-    );
-  }
 }
 
 @freezed
 class FieldEditorEvent with _$FieldEditorEvent {
   const factory FieldEditorEvent.initial() = _InitialField;
   const factory FieldEditorEvent.updateName(String name) = _UpdateName;
-  const factory FieldEditorEvent.updateField(Field field, Uint8List typeOptionData) = _UpdateField;
-  const factory FieldEditorEvent.done() = _Done;
 }
 
 @freezed
@@ -125,12 +49,14 @@ class FieldEditorState with _$FieldEditorState {
   const factory FieldEditorState({
     required String gridId,
     required String errorText,
-    required Option<EditFieldContext> editFieldContext,
+    required String name,
+    required Option<GridFieldContext> fieldContext,
   }) = _FieldEditorState;
 
-  factory FieldEditorState.initial(String gridId) => FieldEditorState(
+  factory FieldEditorState.initial(String gridId, String fieldName, IFieldContextLoader loader) => FieldEditorState(
         gridId: gridId,
-        editFieldContext: none(),
+        fieldContext: none(),
         errorText: '',
+        name: fieldName,
       );
 }

+ 22 - 18
frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart

@@ -1,24 +1,29 @@
-import 'dart:typed_data';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
 
+import 'field_service.dart';
+
 part 'field_editor_pannel_bloc.freezed.dart';
 
 class FieldEditorPannelBloc extends Bloc<FieldEditorPannelEvent, FieldEditorPannelState> {
-  FieldEditorPannelBloc(EditFieldContext editContext) : super(FieldEditorPannelState.initial(editContext)) {
+  final GridFieldContext _fieldContext;
+  void Function()? _fieldListenFn;
+
+  FieldEditorPannelBloc(GridFieldContext fieldContext)
+      : _fieldContext = fieldContext,
+        super(FieldEditorPannelState.initial(fieldContext)) {
     on<FieldEditorPannelEvent>(
       (event, emit) async {
-        await event.map(
-          toFieldType: (_ToFieldType value) async {
-            emit(state.copyWith(
-              field: value.field,
-              typeOptionData: Uint8List.fromList(value.typeOptionData),
-            ));
+        event.when(
+          initial: () {
+            _fieldListenFn = fieldContext.addFieldListener((field) {
+              add(FieldEditorPannelEvent.didReceiveFieldUpdated(field));
+            });
           },
-          didUpdateTypeOptionData: (_DidUpdateTypeOptionData value) {
-            emit(state.copyWith(typeOptionData: value.typeOptionData));
+          didReceiveFieldUpdated: (field) {
+            emit(state.copyWith(field: field));
           },
         );
       },
@@ -27,27 +32,26 @@ class FieldEditorPannelBloc extends Bloc<FieldEditorPannelEvent, FieldEditorPann
 
   @override
   Future<void> close() async {
+    if (_fieldListenFn != null) {
+      _fieldContext.removeFieldListener(_fieldListenFn!);
+    }
     return super.close();
   }
 }
 
 @freezed
 class FieldEditorPannelEvent with _$FieldEditorPannelEvent {
-  const factory FieldEditorPannelEvent.toFieldType(Field field, List<int> typeOptionData) = _ToFieldType;
-  const factory FieldEditorPannelEvent.didUpdateTypeOptionData(Uint8List typeOptionData) = _DidUpdateTypeOptionData;
+  const factory FieldEditorPannelEvent.initial() = _Initial;
+  const factory FieldEditorPannelEvent.didReceiveFieldUpdated(Field field) = _DidReceiveFieldUpdated;
 }
 
 @freezed
 class FieldEditorPannelState with _$FieldEditorPannelState {
   const factory FieldEditorPannelState({
-    required String gridId,
     required Field field,
-    required Uint8List typeOptionData,
   }) = _FieldEditorPannelState;
 
-  factory FieldEditorPannelState.initial(EditFieldContext context) => FieldEditorPannelState(
-        gridId: context.gridId,
-        field: context.gridField,
-        typeOptionData: Uint8List.fromList(context.typeOptionData),
+  factory FieldEditorPannelState.initial(GridFieldContext fieldContext) => FieldEditorPannelState(
+        field: fieldContext.field,
       );
 }

+ 131 - 43
frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart

@@ -1,9 +1,12 @@
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:flutter/foundation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:protobuf/protobuf.dart';
 part 'field_service.freezed.dart';
 
 class FieldService {
@@ -12,24 +15,6 @@ class FieldService {
 
   FieldService({required this.gridId, required this.fieldId});
 
-  Future<Either<EditFieldContext, FlowyError>> switchToField(FieldType fieldType) {
-    final payload = EditFieldPayload.create()
-      ..gridId = gridId
-      ..fieldId = fieldId
-      ..fieldType = fieldType;
-
-    return GridEventSwitchToField(payload).send();
-  }
-
-  Future<Either<EditFieldContext, FlowyError>> getEditFieldContext(FieldType fieldType) {
-    final payload = EditFieldPayload.create()
-      ..gridId = gridId
-      ..fieldId = fieldId
-      ..fieldType = fieldType;
-
-    return GridEventGetEditFieldContext(payload).send();
-  }
-
   Future<Either<Unit, FlowyError>> moveField(int fromIndex, int toIndex) {
     final payload = MoveItemPayload.create()
       ..gridId = gridId
@@ -128,7 +113,7 @@ class FieldService {
     return GridEventDuplicateField(payload).send();
   }
 
-  Future<Either<List<int>, FlowyError>> getTypeOptionData({
+  Future<Either<FieldTypeOptionData, FlowyError>> getFieldTypeOptionData({
     required FieldType fieldType,
   }) {
     final payload = EditFieldPayload.create()
@@ -137,7 +122,7 @@ class FieldService {
       ..fieldType = fieldType;
     return GridEventGetFieldTypeOption(payload).send().then((result) {
       return result.fold(
-        (data) => left(data.typeOptionData),
+        (data) => left(data),
         (err) => right(err),
       );
     });
@@ -152,59 +137,162 @@ class GridFieldCellContext with _$GridFieldCellContext {
   }) = _GridFieldCellContext;
 }
 
-abstract class EditFieldContextLoader {
-  Future<Either<EditFieldContext, FlowyError>> load();
+abstract class IFieldContextLoader {
+  String get gridId;
+  Future<Either<FieldTypeOptionData, FlowyError>> load();
 
-  Future<Either<EditFieldContext, FlowyError>> switchToField(String fieldId, FieldType fieldType);
+  Future<Either<FieldTypeOptionData, FlowyError>> switchToField(String fieldId, FieldType fieldType) {
+    final payload = EditFieldPayload.create()
+      ..gridId = gridId
+      ..fieldId = fieldId
+      ..fieldType = fieldType;
+
+    return GridEventSwitchToField(payload).send();
+  }
 }
 
-class NewFieldContextLoader extends EditFieldContextLoader {
+class NewFieldContextLoader extends IFieldContextLoader {
+  @override
   final String gridId;
   NewFieldContextLoader({
     required this.gridId,
   });
 
   @override
-  Future<Either<EditFieldContext, FlowyError>> load() {
+  Future<Either<FieldTypeOptionData, FlowyError>> load() {
     final payload = EditFieldPayload.create()
       ..gridId = gridId
       ..fieldType = FieldType.RichText;
 
-    return GridEventGetEditFieldContext(payload).send();
-  }
-
-  @override
-  Future<Either<EditFieldContext, FlowyError>> switchToField(String fieldId, FieldType fieldType) {
-    final payload = EditFieldPayload.create()
-      ..gridId = gridId
-      ..fieldType = fieldType;
-
-    return GridEventGetEditFieldContext(payload).send();
+    return GridEventCreateFieldTypeOption(payload).send();
   }
 }
 
-class FieldContextLoaderAdaptor extends EditFieldContextLoader {
+class FieldContextLoader extends IFieldContextLoader {
+  @override
   final String gridId;
   final Field field;
 
-  FieldContextLoaderAdaptor({
+  FieldContextLoader({
     required this.gridId,
     required this.field,
   });
 
   @override
-  Future<Either<EditFieldContext, FlowyError>> load() {
+  Future<Either<FieldTypeOptionData, FlowyError>> load() {
     final payload = EditFieldPayload.create()
       ..gridId = gridId
       ..fieldId = field.id
       ..fieldType = field.fieldType;
 
-    return GridEventGetEditFieldContext(payload).send();
+    return GridEventGetFieldTypeOption(payload).send();
   }
+}
 
-  @override
-  Future<Either<EditFieldContext, FlowyError>> switchToField(String fieldId, FieldType fieldType) async {
-    final fieldService = FieldService(gridId: gridId, fieldId: fieldId);
-    return fieldService.switchToField(fieldType);
+class GridFieldContext {
+  final String gridId;
+  final IFieldContextLoader _loader;
+
+  late FieldTypeOptionData _data;
+  ValueNotifier<Field>? _fieldNotifier;
+
+  GridFieldContext({
+    required this.gridId,
+    required IFieldContextLoader loader,
+  }) : _loader = loader;
+
+  Future<Either<Unit, FlowyError>> loadData() async {
+    final result = await _loader.load();
+    return result.fold(
+      (data) {
+        data.freeze();
+        _data = data;
+
+        if (_fieldNotifier == null) {
+          _fieldNotifier = ValueNotifier(data.field_2);
+        } else {
+          _fieldNotifier?.value = data.field_2;
+        }
+
+        return left(unit);
+      },
+      (err) {
+        Log.error(err);
+        return right(err);
+      },
+    );
+  }
+
+  Field get field => _data.field_2;
+
+  set field(Field field) {
+    _updateData(newField: field);
+  }
+
+  List<int> get typeOptionData => _data.typeOptionData;
+
+  set fieldName(String name) {
+    _updateData(newName: name);
+  }
+
+  set typeOptionData(List<int> typeOptionData) {
+    _updateData(newTypeOptionData: typeOptionData);
+  }
+
+  void _updateData({String? newName, Field? newField, List<int>? newTypeOptionData}) {
+    _data = _data.rebuild((rebuildData) {
+      if (newName != null) {
+        rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) {
+          rebuildField.name = newName;
+        });
+      }
+
+      if (newField != null) {
+        rebuildData.field_2 = newField;
+      }
+
+      if (newTypeOptionData != null) {
+        rebuildData.typeOptionData = newTypeOptionData;
+      }
+    });
+
+    if (_data.field_2 != _fieldNotifier?.value) {
+      _fieldNotifier?.value = _data.field_2;
+    }
+
+    FieldService.insertField(
+      gridId: gridId,
+      field: field,
+      typeOptionData: typeOptionData,
+    );
+  }
+
+  Future<void> switchToField(FieldType newFieldType) {
+    return _loader.switchToField(field.id, newFieldType).then((result) {
+      return result.fold(
+        (fieldTypeOptionData) {
+          _updateData(
+            newField: fieldTypeOptionData.field_2,
+            newTypeOptionData: fieldTypeOptionData.typeOptionData,
+          );
+        },
+        (err) {
+          Log.error(err);
+        },
+      );
+    });
+  }
+
+  void Function() addFieldListener(void Function(Field) callback) {
+    listener() {
+      callback(field);
+    }
+
+    _fieldNotifier?.addListener(listener);
+    return listener;
+  }
+
+  void removeFieldListener(void Function() listener) {
+    _fieldNotifier?.removeListener(listener);
   }
 }

+ 12 - 1
frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart

@@ -1,3 +1,4 @@
+import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -5,8 +6,18 @@ import 'dart:async';
 import 'package:protobuf/protobuf.dart';
 part 'date_bloc.freezed.dart';
 
+typedef DateTypeOptionContext = TypeOptionContext<DateTypeOption>;
+
+class DateTypeOptionDataBuilder extends TypeOptionDataBuilder<DateTypeOption> {
+  @override
+  DateTypeOption fromBuffer(List<int> buffer) {
+    return DateTypeOption.fromBuffer(buffer);
+  }
+}
+
 class DateTypeOptionBloc extends Bloc<DateTypeOptionEvent, DateTypeOptionState> {
-  DateTypeOptionBloc({required DateTypeOption typeOption}) : super(DateTypeOptionState.initial(typeOption)) {
+  DateTypeOptionBloc({required DateTypeOptionContext typeOptionContext})
+      : super(DateTypeOptionState.initial(typeOptionContext.typeOption)) {
     on<DateTypeOptionEvent>(
       (event, emit) async {
         event.map(

+ 0 - 65
frontend/app_flowy/lib/workspace/application/grid/field/type_option/field_option_pannel_bloc.dart

@@ -1,65 +0,0 @@
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:freezed_annotation/freezed_annotation.dart';
-import 'dart:async';
-import 'package:dartz/dartz.dart';
-part 'field_option_pannel_bloc.freezed.dart';
-
-class FieldOptionPannelBloc extends Bloc<FieldOptionPannelEvent, FieldOptionPannelState> {
-  FieldOptionPannelBloc({required List<SelectOption> options}) : super(FieldOptionPannelState.initial(options)) {
-    on<FieldOptionPannelEvent>(
-      (event, emit) async {
-        await event.map(
-          createOption: (_CreateOption value) async {
-            emit(state.copyWith(isEditingOption: false, newOptionName: Some(value.optionName)));
-          },
-          beginAddingOption: (_BeginAddingOption value) {
-            emit(state.copyWith(isEditingOption: true, newOptionName: none()));
-          },
-          endAddingOption: (_EndAddingOption value) {
-            emit(state.copyWith(isEditingOption: false, newOptionName: none()));
-          },
-          updateOption: (_UpdateOption value) {
-            emit(state.copyWith(updateOption: Some(value.option)));
-          },
-          deleteOption: (_DeleteOption value) {
-            emit(state.copyWith(deleteOption: Some(value.option)));
-          },
-        );
-      },
-    );
-  }
-
-  @override
-  Future<void> close() async {
-    return super.close();
-  }
-}
-
-@freezed
-class FieldOptionPannelEvent with _$FieldOptionPannelEvent {
-  const factory FieldOptionPannelEvent.createOption(String optionName) = _CreateOption;
-  const factory FieldOptionPannelEvent.beginAddingOption() = _BeginAddingOption;
-  const factory FieldOptionPannelEvent.endAddingOption() = _EndAddingOption;
-  const factory FieldOptionPannelEvent.updateOption(SelectOption option) = _UpdateOption;
-  const factory FieldOptionPannelEvent.deleteOption(SelectOption option) = _DeleteOption;
-}
-
-@freezed
-class FieldOptionPannelState with _$FieldOptionPannelState {
-  const factory FieldOptionPannelState({
-    required List<SelectOption> options,
-    required bool isEditingOption,
-    required Option<String> newOptionName,
-    required Option<SelectOption> updateOption,
-    required Option<SelectOption> deleteOption,
-  }) = _FieldOptionPannelState;
-
-  factory FieldOptionPannelState.initial(List<SelectOption> options) => FieldOptionPannelState(
-        options: options,
-        isEditingOption: false,
-        newOptionName: none(),
-        updateOption: none(),
-        deleteOption: none(),
-      );
-}

+ 0 - 89
frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_bloc.dart

@@ -1,89 +0,0 @@
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:freezed_annotation/freezed_annotation.dart';
-import 'dart:async';
-import 'package:protobuf/protobuf.dart';
-import 'type_option_service.dart';
-
-part 'multi_select_bloc.freezed.dart';
-
-class MultiSelectTypeOptionBloc extends Bloc<MultiSelectTypeOptionEvent, MultiSelectTypeOptionState> {
-  final TypeOptionService service;
-
-  MultiSelectTypeOptionBloc(TypeOptionContext typeOptionContext)
-      : service = TypeOptionService(gridId: typeOptionContext.gridId, fieldId: typeOptionContext.field.id),
-        super(MultiSelectTypeOptionState.initial(MultiSelectTypeOption.fromBuffer(typeOptionContext.data))) {
-    on<MultiSelectTypeOptionEvent>(
-      (event, emit) async {
-        await event.map(
-          createOption: (_CreateOption value) async {
-            final result = await service.newOption(name: value.optionName);
-            result.fold(
-              (option) {
-                emit(state.copyWith(typeOption: _insertOption(option)));
-              },
-              (err) => Log.error(err),
-            );
-          },
-          updateOption: (_UpdateOption value) async {
-            emit(state.copyWith(typeOption: _updateOption(value.option)));
-          },
-          deleteOption: (_DeleteOption value) {
-            emit(state.copyWith(typeOption: _deleteOption(value.option)));
-          },
-        );
-      },
-    );
-  }
-
-  @override
-  Future<void> close() async {
-    return super.close();
-  }
-
-  MultiSelectTypeOption _insertOption(SelectOption option) {
-    state.typeOption.freeze();
-    return state.typeOption.rebuild((typeOption) {
-      typeOption.options.insert(0, option);
-    });
-  }
-
-  MultiSelectTypeOption _updateOption(SelectOption option) {
-    state.typeOption.freeze();
-    return state.typeOption.rebuild((typeOption) {
-      final index = typeOption.options.indexWhere((element) => element.id == option.id);
-      if (index != -1) {
-        typeOption.options[index] = option;
-      }
-    });
-  }
-
-  MultiSelectTypeOption _deleteOption(SelectOption option) {
-    state.typeOption.freeze();
-    return state.typeOption.rebuild((typeOption) {
-      final index = typeOption.options.indexWhere((element) => element.id == option.id);
-      if (index != -1) {
-        typeOption.options.removeAt(index);
-      }
-    });
-  }
-}
-
-@freezed
-class MultiSelectTypeOptionEvent with _$MultiSelectTypeOptionEvent {
-  const factory MultiSelectTypeOptionEvent.createOption(String optionName) = _CreateOption;
-  const factory MultiSelectTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption;
-  const factory MultiSelectTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption;
-}
-
-@freezed
-class MultiSelectTypeOptionState with _$MultiSelectTypeOptionState {
-  const factory MultiSelectTypeOptionState({
-    required MultiSelectTypeOption typeOption,
-  }) = _MultiSelectTypeOptionState;
-
-  factory MultiSelectTypeOptionState.initial(MultiSelectTypeOption typeOption) => MultiSelectTypeOptionState(
-        typeOption: typeOption,
-      );
-}

+ 77 - 0
frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart

@@ -0,0 +1,77 @@
+import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'dart:async';
+import 'package:protobuf/protobuf.dart';
+import 'select_option_type_option_bloc.dart';
+import 'type_option_service.dart';
+
+class MultiSelectTypeOptionContext extends TypeOptionContext<MultiSelectTypeOption> with SelectOptionTypeOptionAction {
+  final TypeOptionService service;
+
+  MultiSelectTypeOptionContext({
+    required MultiSelectTypeOptionDataBuilder dataBuilder,
+    required GridFieldContext fieldContext,
+  })  : service = TypeOptionService(
+          gridId: fieldContext.gridId,
+          fieldId: fieldContext.field.id,
+        ),
+        super(dataBuilder: dataBuilder, fieldContext: fieldContext);
+
+  @override
+  List<SelectOption> Function(SelectOption) get deleteOption {
+    return (SelectOption option) {
+      typeOption.freeze();
+      typeOption = typeOption.rebuild((typeOption) {
+        final index = typeOption.options.indexWhere((element) => element.id == option.id);
+        if (index != -1) {
+          typeOption.options.removeAt(index);
+        }
+      });
+      return typeOption.options;
+    };
+  }
+
+  @override
+  Future<List<SelectOption>> Function(String) get insertOption {
+    return (String optionName) {
+      return service.newOption(name: optionName).then((result) {
+        return result.fold(
+          (option) {
+            typeOption.freeze();
+            typeOption = typeOption.rebuild((typeOption) {
+              typeOption.options.insert(0, option);
+            });
+
+            return typeOption.options;
+          },
+          (err) {
+            Log.error(err);
+            return typeOption.options;
+          },
+        );
+      });
+    };
+  }
+
+  @override
+  List<SelectOption> Function(SelectOption) get udpateOption {
+    return (SelectOption option) {
+      typeOption.freeze();
+      typeOption = typeOption.rebuild((typeOption) {
+        final index = typeOption.options.indexWhere((element) => element.id == option.id);
+        if (index != -1) {
+          typeOption.options[index] = option;
+        }
+      });
+      return typeOption.options;
+    };
+  }
+}
+
+class MultiSelectTypeOptionDataBuilder extends TypeOptionDataBuilder<MultiSelectTypeOption> {
+  @override
+  MultiSelectTypeOption fromBuffer(List<int> buffer) {
+    return MultiSelectTypeOption.fromBuffer(buffer);
+  }
+}

+ 12 - 1
frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart

@@ -1,3 +1,4 @@
+import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -6,8 +7,18 @@ import 'package:protobuf/protobuf.dart';
 
 part 'number_bloc.freezed.dart';
 
+typedef NumberTypeOptionContext = TypeOptionContext<NumberTypeOption>;
+
+class NumberTypeOptionDataBuilder extends TypeOptionDataBuilder<NumberTypeOption> {
+  @override
+  NumberTypeOption fromBuffer(List<int> buffer) {
+    return NumberTypeOption.fromBuffer(buffer);
+  }
+}
+
 class NumberTypeOptionBloc extends Bloc<NumberTypeOptionEvent, NumberTypeOptionState> {
-  NumberTypeOptionBloc({required NumberTypeOption typeOption}) : super(NumberTypeOptionState.initial(typeOption)) {
+  NumberTypeOptionBloc({required NumberTypeOptionContext typeOptionContext})
+      : super(NumberTypeOptionState.initial(typeOptionContext.typeOption)) {
     on<NumberTypeOptionEvent>(
       (event, emit) async {
         event.map(

+ 77 - 0
frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart

@@ -0,0 +1,77 @@
+import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'dart:async';
+import 'package:dartz/dartz.dart';
+part 'select_option_type_option_bloc.freezed.dart';
+
+abstract class SelectOptionTypeOptionAction {
+  Future<List<SelectOption>> Function(String) get insertOption;
+
+  List<SelectOption> Function(SelectOption) get deleteOption;
+
+  List<SelectOption> Function(SelectOption) get udpateOption;
+}
+
+class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, SelectOptionTypeOptionState> {
+  final SelectOptionTypeOptionAction typeOptionAction;
+
+  SelectOptionTypeOptionBloc({
+    required List<SelectOption> options,
+    required this.typeOptionAction,
+  }) : super(SelectOptionTypeOptionState.initial(options)) {
+    on<SelectOptionTypeOptionEvent>(
+      (event, emit) async {
+        await event.when(
+          createOption: (optionName) async {
+            final List<SelectOption> options = await typeOptionAction.insertOption(optionName);
+            emit(state.copyWith(options: options));
+          },
+          addingOption: () {
+            emit(state.copyWith(isEditingOption: true, newOptionName: none()));
+          },
+          endAddingOption: () {
+            emit(state.copyWith(isEditingOption: false, newOptionName: none()));
+          },
+          updateOption: (option) {
+            final List<SelectOption> options = typeOptionAction.udpateOption(option);
+            emit(state.copyWith(options: options));
+          },
+          deleteOption: (option) {
+            final List<SelectOption> options = typeOptionAction.deleteOption(option);
+            emit(state.copyWith(options: options));
+          },
+        );
+      },
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    return super.close();
+  }
+}
+
+@freezed
+class SelectOptionTypeOptionEvent with _$SelectOptionTypeOptionEvent {
+  const factory SelectOptionTypeOptionEvent.createOption(String optionName) = _CreateOption;
+  const factory SelectOptionTypeOptionEvent.addingOption() = _AddingOption;
+  const factory SelectOptionTypeOptionEvent.endAddingOption() = _EndAddingOption;
+  const factory SelectOptionTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption;
+  const factory SelectOptionTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption;
+}
+
+@freezed
+class SelectOptionTypeOptionState with _$SelectOptionTypeOptionState {
+  const factory SelectOptionTypeOptionState({
+    required List<SelectOption> options,
+    required bool isEditingOption,
+    required Option<String> newOptionName,
+  }) = _SelectOptionTyepOptionState;
+
+  factory SelectOptionTypeOptionState.initial(List<SelectOption> options) => SelectOptionTypeOptionState(
+        options: options,
+        isEditingOption: false,
+        newOptionName: none(),
+      );
+}

+ 0 - 92
frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_bloc.dart

@@ -1,92 +0,0 @@
-import 'package:flowy_sdk/log.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:freezed_annotation/freezed_annotation.dart';
-import 'dart:async';
-import 'package:protobuf/protobuf.dart';
-import 'type_option_service.dart';
-
-part 'single_select_bloc.freezed.dart';
-
-class SingleSelectTypeOptionBloc extends Bloc<SingleSelectTypeOptionEvent, SingleSelectTypeOptionState> {
-  final TypeOptionService service;
-
-  SingleSelectTypeOptionBloc(
-    TypeOptionContext typeOptionContext,
-  )   : service = TypeOptionService(gridId: typeOptionContext.gridId, fieldId: typeOptionContext.field.id),
-        super(
-          SingleSelectTypeOptionState.initial(SingleSelectTypeOption.fromBuffer(typeOptionContext.data)),
-        ) {
-    on<SingleSelectTypeOptionEvent>(
-      (event, emit) async {
-        await event.map(
-          createOption: (_CreateOption value) async {
-            final result = await service.newOption(name: value.optionName);
-            result.fold(
-              (option) {
-                emit(state.copyWith(typeOption: _insertOption(option)));
-              },
-              (err) => Log.error(err),
-            );
-          },
-          updateOption: (_UpdateOption value) async {
-            emit(state.copyWith(typeOption: _updateOption(value.option)));
-          },
-          deleteOption: (_DeleteOption value) {
-            emit(state.copyWith(typeOption: _deleteOption(value.option)));
-          },
-        );
-      },
-    );
-  }
-
-  @override
-  Future<void> close() async {
-    return super.close();
-  }
-
-  SingleSelectTypeOption _insertOption(SelectOption option) {
-    state.typeOption.freeze();
-    return state.typeOption.rebuild((typeOption) {
-      typeOption.options.insert(0, option);
-    });
-  }
-
-  SingleSelectTypeOption _updateOption(SelectOption option) {
-    state.typeOption.freeze();
-    return state.typeOption.rebuild((typeOption) {
-      final index = typeOption.options.indexWhere((element) => element.id == option.id);
-      if (index != -1) {
-        typeOption.options[index] = option;
-      }
-    });
-  }
-
-  SingleSelectTypeOption _deleteOption(SelectOption option) {
-    state.typeOption.freeze();
-    return state.typeOption.rebuild((typeOption) {
-      final index = typeOption.options.indexWhere((element) => element.id == option.id);
-      if (index != -1) {
-        typeOption.options.removeAt(index);
-      }
-    });
-  }
-}
-
-@freezed
-class SingleSelectTypeOptionEvent with _$SingleSelectTypeOptionEvent {
-  const factory SingleSelectTypeOptionEvent.createOption(String optionName) = _CreateOption;
-  const factory SingleSelectTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption;
-  const factory SingleSelectTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption;
-}
-
-@freezed
-class SingleSelectTypeOptionState with _$SingleSelectTypeOptionState {
-  const factory SingleSelectTypeOptionState({
-    required SingleSelectTypeOption typeOption,
-  }) = _SingleSelectTypeOptionState;
-
-  factory SingleSelectTypeOptionState.initial(SingleSelectTypeOption typeOption) => SingleSelectTypeOptionState(
-        typeOption: typeOption,
-      );
-}

+ 78 - 0
frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart

@@ -0,0 +1,78 @@
+import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'dart:async';
+import 'package:protobuf/protobuf.dart';
+import 'select_option_type_option_bloc.dart';
+import 'type_option_service.dart';
+
+class SingleSelectTypeOptionContext extends TypeOptionContext<SingleSelectTypeOption>
+    with SelectOptionTypeOptionAction {
+  final TypeOptionService service;
+
+  SingleSelectTypeOptionContext({
+    required SingleSelectTypeOptionDataBuilder dataBuilder,
+    required GridFieldContext fieldContext,
+  })  : service = TypeOptionService(
+          gridId: fieldContext.gridId,
+          fieldId: fieldContext.field.id,
+        ),
+        super(dataBuilder: dataBuilder, fieldContext: fieldContext);
+
+  @override
+  List<SelectOption> Function(SelectOption) get deleteOption {
+    return (SelectOption option) {
+      typeOption.freeze();
+      typeOption = typeOption.rebuild((typeOption) {
+        final index = typeOption.options.indexWhere((element) => element.id == option.id);
+        if (index != -1) {
+          typeOption.options.removeAt(index);
+        }
+      });
+      return typeOption.options;
+    };
+  }
+
+  @override
+  Future<List<SelectOption>> Function(String) get insertOption {
+    return (String optionName) {
+      return service.newOption(name: optionName).then((result) {
+        return result.fold(
+          (option) {
+            typeOption.freeze();
+            typeOption = typeOption.rebuild((typeOption) {
+              typeOption.options.insert(0, option);
+            });
+
+            return typeOption.options;
+          },
+          (err) {
+            Log.error(err);
+            return typeOption.options;
+          },
+        );
+      });
+    };
+  }
+
+  @override
+  List<SelectOption> Function(SelectOption) get udpateOption {
+    return (SelectOption option) {
+      typeOption.freeze();
+      typeOption = typeOption.rebuild((typeOption) {
+        final index = typeOption.options.indexWhere((element) => element.id == option.id);
+        if (index != -1) {
+          typeOption.options[index] = option;
+        }
+      });
+      return typeOption.options;
+    };
+  }
+}
+
+class SingleSelectTypeOptionDataBuilder extends TypeOptionDataBuilder<SingleSelectTypeOption> {
+  @override
+  SingleSelectTypeOption fromBuffer(List<int> buffer) {
+    return SingleSelectTypeOption.fromBuffer(buffer);
+  }
+}

+ 70 - 5
frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart

@@ -1,5 +1,6 @@
 import 'dart:typed_data';
 
+import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
@@ -7,6 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
+import 'package:protobuf/protobuf.dart';
 
 class TypeOptionService {
   final String gridId;
@@ -32,13 +34,76 @@ class TypeOptionService {
   }
 }
 
-class TypeOptionContext {
+abstract class TypeOptionDataBuilder<T> {
+  T fromBuffer(List<int> buffer);
+}
+
+class TypeOptionContext<T extends GeneratedMessage> {
+  T? _typeOptionObject;
+  final GridFieldContext _fieldContext;
+  final TypeOptionDataBuilder<T> dataBuilder;
+
+  TypeOptionContext({
+    required this.dataBuilder,
+    required GridFieldContext fieldContext,
+  }) : _fieldContext = fieldContext;
+
+  String get gridId => _fieldContext.gridId;
+
+  Field get field => _fieldContext.field;
+
+  T get typeOption {
+    if (_typeOptionObject != null) {
+      return _typeOptionObject!;
+    }
+
+    final T object = dataBuilder.fromBuffer(_fieldContext.typeOptionData);
+    _typeOptionObject = object;
+    return object;
+  }
+
+  set typeOption(T typeOption) {
+    _fieldContext.typeOptionData = typeOption.writeToBuffer();
+    _typeOptionObject = typeOption;
+  }
+}
+
+abstract class TypeOptionFieldDelegate {
+  void onFieldChanged(void Function(String) callback);
+  void dispose();
+}
+
+class TypeOptionContext2<T> {
   final String gridId;
   final Field field;
-  final Uint8List data;
-  const TypeOptionContext({
+  final FieldService _fieldService;
+  T? _data;
+  final TypeOptionDataBuilder dataBuilder;
+
+  TypeOptionContext2({
     required this.gridId,
     required this.field,
-    required this.data,
-  });
+    required this.dataBuilder,
+    Uint8List? data,
+  }) : _fieldService = FieldService(gridId: gridId, fieldId: field.id) {
+    if (data != null) {
+      _data = dataBuilder.fromBuffer(data);
+    }
+  }
+
+  Future<Either<T, FlowyError>> typeOptionData() {
+    if (_data != null) {
+      return Future(() => left(_data!));
+    }
+
+    return _fieldService.getFieldTypeOptionData(fieldType: field.fieldType).then((result) {
+      return result.fold(
+        (data) {
+          _data = dataBuilder.fromBuffer(data.typeOptionData);
+          return left(_data!);
+        },
+        (err) => right(err),
+      );
+    });
+  }
 }

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

@@ -93,7 +93,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
 
           emit(state.copyWith(
             grid: Some(grid),
-            fields: fieldCache.clonedFields,
+            fields: fieldCache.fields,
             rows: rowCache.clonedRows,
             loadingState: GridLoadingState.finish(left(unit)),
           ));

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

@@ -15,7 +15,7 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
   GridHeaderBloc({
     required this.gridId,
     required this.fieldCache,
-  }) : super(GridHeaderState.initial(fieldCache.clonedFields)) {
+  }) : super(GridHeaderState.initial(fieldCache.fields)) {
     on<GridHeaderEvent>(
       (event, emit) async {
         await event.map(

+ 22 - 21
frontend/app_flowy/lib/workspace/application/grid/grid_service.dart

@@ -59,7 +59,7 @@ typedef ChangesetListener = void Function(GridFieldChangeset);
 class GridFieldCache {
   final String gridId;
   late final GridFieldsListener _fieldListener;
-  final FieldsNotifier _fieldNotifier = FieldsNotifier();
+  FieldsNotifier? _fieldNotifier = FieldsNotifier();
   final List<ChangesetListener> _changesetListener = [];
 
   GridFieldCache({required this.gridId}) {
@@ -81,15 +81,16 @@ class GridFieldCache {
 
   Future<void> dispose() async {
     await _fieldListener.stop();
-    _fieldNotifier.dispose();
+    _fieldNotifier?.dispose();
+    _fieldNotifier = null;
   }
 
-  UnmodifiableListView<Field> get unmodifiableFields => UnmodifiableListView(_fieldNotifier.fields);
+  UnmodifiableListView<Field> get unmodifiableFields => UnmodifiableListView(_fieldNotifier?.fields ?? []);
 
-  List<Field> get clonedFields => [..._fieldNotifier.fields];
+  List<Field> get fields => [..._fieldNotifier?.fields ?? []];
 
   set fields(List<Field> fields) {
-    _fieldNotifier.fields = [...fields];
+    _fieldNotifier?.fields = [...fields];
   }
 
   VoidCallback addListener(
@@ -100,7 +101,7 @@ class GridFieldCache {
       }
 
       if (onChanged != null) {
-        onChanged(clonedFields);
+        onChanged(fields);
       }
 
       if (listener != null) {
@@ -108,12 +109,12 @@ class GridFieldCache {
       }
     }
 
-    _fieldNotifier.addListener(f);
+    _fieldNotifier?.addListener(f);
     return f;
   }
 
   void removeListener(VoidCallback f) {
-    _fieldNotifier.removeListener(f);
+    _fieldNotifier?.removeListener(f);
   }
 
   void addChangesetListener(ChangesetListener listener) {
@@ -131,43 +132,43 @@ class GridFieldCache {
     if (deletedFields.isEmpty) {
       return;
     }
-    final List<Field> fields = _fieldNotifier.fields;
+    final List<Field> newFields = fields;
     final Map<String, FieldOrder> deletedFieldMap = {
       for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
     };
 
-    fields.retainWhere((field) => (deletedFieldMap[field.id] == null));
-    _fieldNotifier.fields = fields;
+    newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
+    _fieldNotifier?.fields = newFields;
   }
 
   void _insertFields(List<IndexField> insertedFields) {
     if (insertedFields.isEmpty) {
       return;
     }
-    final List<Field> fields = _fieldNotifier.fields;
+    final List<Field> newFields = fields;
     for (final indexField in insertedFields) {
-      if (fields.length > indexField.index) {
-        fields.insert(indexField.index, indexField.field_1);
+      if (newFields.length > indexField.index) {
+        newFields.insert(indexField.index, indexField.field_1);
       } else {
-        fields.add(indexField.field_1);
+        newFields.add(indexField.field_1);
       }
     }
-    _fieldNotifier.fields = fields;
+    _fieldNotifier?.fields = newFields;
   }
 
   void _updateFields(List<Field> updatedFields) {
     if (updatedFields.isEmpty) {
       return;
     }
-    final List<Field> fields = _fieldNotifier.fields;
+    final List<Field> newFields = fields;
     for (final updatedField in updatedFields) {
-      final index = fields.indexWhere((field) => field.id == updatedField.id);
+      final index = newFields.indexWhere((field) => field.id == updatedField.id);
       if (index != -1) {
-        fields.removeAt(index);
-        fields.insert(index, updatedField);
+        newFields.removeAt(index);
+        newFields.insert(index, updatedField);
       }
     }
-    _fieldNotifier.fields = fields;
+    _fieldNotifier?.fields = newFields;
   }
 }
 

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

@@ -13,12 +13,12 @@ export 'field/field_editor_pannel_bloc.dart';
 // Field Type Option
 export 'field/type_option/date_bloc.dart';
 export 'field/type_option/number_bloc.dart';
-export 'field/type_option/single_select_bloc.dart';
+export 'field/type_option/single_select_type_option.dart';
 
 // Cell
 export 'cell/text_cell_bloc.dart';
 export 'cell/number_cell_bloc.dart';
-export 'cell/selection_cell_bloc.dart';
+export 'cell/select_option_cell_bloc.dart';
 export 'cell/date_cell_bloc.dart';
 export 'cell/checkbox_cell_bloc.dart';
 export 'cell/cell_service/cell_service.dart';

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

@@ -14,7 +14,7 @@ class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
 
   GridPropertyBloc({required String gridId, required GridFieldCache fieldCache})
       : _fieldCache = fieldCache,
-        super(GridPropertyState.initial(gridId, fieldCache.clonedFields)) {
+        super(GridPropertyState.initial(gridId, fieldCache.fields)) {
     on<GridPropertyEvent>(
       (event, emit) async {
         await event.map(

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart

@@ -11,7 +11,7 @@ import 'package:styled_widget/styled_widget.dart';
 import 'checkbox_cell.dart';
 import 'date_cell/date_cell.dart';
 import 'number_cell.dart';
-import 'selection_cell/selection_cell.dart';
+import 'select_option_cell/select_option_cell.dart';
 import 'text_cell.dart';
 
 GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) {

+ 2 - 2
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart

@@ -5,7 +5,7 @@ import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
 
 import '../cell_builder.dart';
-import 'calendar.dart';
+import 'date_editor.dart';
 
 class DateCellStyle extends GridCellStyle {
   Alignment alignment;
@@ -77,7 +77,7 @@ class _DateCellState extends State<DateCell> {
   void _showCalendar(BuildContext context) {
     final bloc = context.read<DateCellBloc>();
     widget.onFocus.value = true;
-    final calendar = CellCalendar(onDismissed: () => widget.onFocus.value = false);
+    final calendar = DateCellEditor(onDismissed: () => widget.onFocus.value = false);
     calendar.show(
       context,
       cellContext: bloc.cellContext.clone(),

+ 6 - 15
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/calendar.dart → frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart

@@ -22,10 +22,10 @@ final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day);
 final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day);
 const kMargin = EdgeInsets.symmetric(horizontal: 6, vertical: 10);
 
-class CellCalendar with FlowyOverlayDelegate {
+class DateCellEditor with FlowyOverlayDelegate {
   final VoidCallback onDismissed;
 
-  const CellCalendar({
+  const DateCellEditor({
     required this.onDismissed,
   });
 
@@ -33,23 +33,14 @@ class CellCalendar with FlowyOverlayDelegate {
     BuildContext context, {
     required GridDateCellContext cellContext,
   }) async {
-    CellCalendar.remove(context);
+    DateCellEditor.remove(context);
 
     final result = await cellContext.getTypeOptionData();
     result.fold(
       (data) {
-        final typeOptionData = DateTypeOption.fromBuffer(data);
-        // DateTime? selectedDay;
-        // final cellData = cellContext.getCellData();
-
-        // if (cellData != null) {
-        //   final timestamp = $fixnum.Int64.parseInt(cellData).toInt();
-        //   selectedDay = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
-        // }
-
         final calendar = _CellCalendarWidget(
           cellContext: cellContext,
-          dateTypeOption: typeOptionData,
+          dateTypeOption: DateTypeOption.fromBuffer(data.typeOptionData),
         );
 
         FlowyOverlay.of(context).insertWithAnchor(
@@ -57,7 +48,7 @@ class CellCalendar with FlowyOverlayDelegate {
             child: calendar,
             constraints: BoxConstraints.loose(const Size(320, 500)),
           ),
-          identifier: CellCalendar.identifier(),
+          identifier: DateCellEditor.identifier(),
           anchorContext: context,
           anchorDirection: AnchorDirection.leftWithCenterAligned,
           style: FlowyOverlayStyle(blur: false),
@@ -73,7 +64,7 @@ class CellCalendar with FlowyOverlayDelegate {
   }
 
   static String identifier() {
-    return (CellCalendar).toString();
+    return (DateCellEditor).toString();
   }
 
   @override

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart

@@ -3,4 +3,4 @@ export 'text_cell.dart';
 export 'number_cell.dart';
 export 'date_cell/date_cell.dart';
 export 'checkbox_cell.dart';
-export 'selection_cell/selection_cell.dart';
+export 'select_option_cell/select_option_cell.dart';

+ 37 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart → frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart

@@ -1,4 +1,5 @@
 import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter/material.dart';
@@ -106,3 +107,39 @@ class SelectOptionTag extends StatelessWidget {
     // );
   }
 }
+
+class SelectOptionTagCell extends StatelessWidget {
+  final List<Widget> children;
+  final void Function(SelectOption) onSelected;
+  final SelectOption option;
+  const SelectOptionTagCell({
+    required this.option,
+    required this.onSelected,
+    this.children = const [],
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return Stack(
+      fit: StackFit.expand,
+      children: [
+        FlowyHover(
+          style: HoverStyle(hoverColor: theme.hover),
+          child: InkWell(
+            child: Padding(
+              padding: const EdgeInsets.symmetric(horizontal: 3),
+              child: Row(children: [
+                SelectOptionTag.fromSelectOption(context: context, option: option),
+                const Spacer(),
+                ...children,
+              ]),
+            ),
+            onTap: () => onSelected(option),
+          ),
+        ),
+      ],
+    );
+  }
+}

+ 7 - 7
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart → frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart

@@ -10,7 +10,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 import 'extension.dart';
-import 'selection_editor.dart';
+import 'select_option_editor.dart';
 
 class SelectOptionCellStyle extends GridCellStyle {
   String placeholder;
@@ -41,12 +41,12 @@ class SingleSelectCell extends GridCellWidget {
 }
 
 class _SingleSelectCellState extends State<SingleSelectCell> {
-  late SelectionCellBloc _cellBloc;
+  late SelectOptionCellBloc _cellBloc;
 
   @override
   void initState() {
     final cellContext = widget.cellContextBuilder.build() as GridSelectOptionCellContext;
-    _cellBloc = getIt<SelectionCellBloc>(param1: cellContext)..add(const SelectionCellEvent.initial());
+    _cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)..add(const SelectOptionCellEvent.initial());
     super.initState();
   }
 
@@ -54,7 +54,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
   Widget build(BuildContext context) {
     return BlocProvider.value(
       value: _cellBloc,
-      child: BlocBuilder<SelectionCellBloc, SelectionCellState>(
+      child: BlocBuilder<SelectOptionCellBloc, SelectOptionCellState>(
         builder: (context, state) {
           return _SelectOptionCell(
               selectOptions: state.selectedOptions,
@@ -95,12 +95,12 @@ class MultiSelectCell extends GridCellWidget {
 }
 
 class _MultiSelectCellState extends State<MultiSelectCell> {
-  late SelectionCellBloc _cellBloc;
+  late SelectOptionCellBloc _cellBloc;
 
   @override
   void initState() {
     final cellContext = widget.cellContextBuilder.build() as GridSelectOptionCellContext;
-    _cellBloc = getIt<SelectionCellBloc>(param1: cellContext)..add(const SelectionCellEvent.initial());
+    _cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)..add(const SelectOptionCellEvent.initial());
     super.initState();
   }
 
@@ -108,7 +108,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
   Widget build(BuildContext context) {
     return BlocProvider.value(
       value: _cellBloc,
-      child: BlocBuilder<SelectionCellBloc, SelectionCellState>(
+      child: BlocBuilder<SelectOptionCellBloc, SelectOptionCellState>(
         builder: (context, state) {
           return _SelectOptionCell(
               selectOptions: state.selectedOptions,

+ 28 - 48
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart → frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart

@@ -1,13 +1,12 @@
 import 'dart:collection';
 import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
-import 'package:app_flowy/workspace/application/grid/cell/selection_editor_bloc.dart';
+import 'package:app_flowy/workspace/application/grid/cell/select_option_editor_bloc.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
-import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
@@ -37,10 +36,10 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => SelectOptionEditorBloc(
+      create: (context) => SelectOptionCellEditorBloc(
         cellContext: cellContext,
       )..add(const SelectOptionEditorEvent.initial()),
-      child: BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
+      child: BlocBuilder<SelectOptionCellEditorBloc, SelectOptionEditorState>(
         builder: (context, state) {
           return CustomScrollView(
             shrinkWrap: true,
@@ -102,7 +101,7 @@ class _OptionList extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
+    return BlocBuilder<SelectOptionCellEditorBloc, SelectOptionEditorState>(
       builder: (context, state) {
         List<Widget> cells = [];
         cells.addAll(state.options.map((option) {
@@ -145,7 +144,7 @@ class _TextField extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
+    return BlocBuilder<SelectOptionCellEditorBloc, SelectOptionEditorState>(
       builder: (context, state) {
         final optionMap = LinkedHashMap<String, SelectOption>.fromIterable(state.selectedOptions,
             key: (option) => option.name, value: (option) => option);
@@ -158,10 +157,10 @@ class _TextField extends StatelessWidget {
             distanceToText: _editorPannelWidth * 0.7,
             tagController: _tagController,
             newText: (text) {
-              context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.filterOption(text));
+              context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.filterOption(text));
             },
             onNewTag: (tagName) {
-              context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.newOption(tagName));
+              context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.newOption(tagName));
             },
           ),
         );
@@ -224,63 +223,44 @@ class _SelectOptionCell extends StatelessWidget {
     final theme = context.watch<AppTheme>();
     return SizedBox(
       height: GridSize.typeOptionItemHeight,
-      child: Stack(
-        fit: StackFit.expand,
+      child: Row(
         children: [
-          _body(theme, context),
-          InkWell(
-            onTap: () {
-              context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.selectOption(option.id));
-            },
-          ),
-        ],
-      ),
-    );
-  }
-
-  FlowyHover _body(AppTheme theme, BuildContext context) {
-    return FlowyHover(
-      style: HoverStyle(hoverColor: theme.hover),
-      builder: (_, onHover) {
-        List<Widget> children = [
-          SelectOptionTag(
-            name: option.name,
-            color: option.color.make(context),
-            isSelected: isSelected,
-          ),
-          const Spacer(),
-        ];
-
-        if (isSelected) {
-          children.add(svgWidget("grid/checkmark"));
-        }
-
-        if (onHover) {
-          children.add(FlowyIconButton(
+          Expanded(child: _body(theme, context)),
+          FlowyIconButton(
             width: 30,
             onPressed: () => _showEditPannel(context),
             iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
             icon: svgWidget("editor/details", color: theme.iconColor),
-          ));
-        }
+          )
+        ],
+      ),
+    );
+  }
 
-        return Row(children: children);
+  Widget _body(AppTheme theme, BuildContext context) {
+    return SelectOptionTagCell(
+      option: option,
+      onSelected: (option) {
+        context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.selectOption(option.id));
       },
+      children: [
+        if (isSelected) svgWidget("grid/checkmark"),
+      ],
     );
   }
 
   void _showEditPannel(BuildContext context) {
-    final pannel = EditSelectOptionPannel(
+    final pannel = SelectOptionTypeOptionEditor(
       option: option,
       onDeleted: () {
-        context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.deleteOption(option));
+        context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.deleteOption(option));
       },
       onUpdated: (updatedOption) {
-        context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.updateOption(updatedOption));
+        context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.updateOption(updatedOption));
       },
       key: ValueKey(option.id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
     );
-    final overlayIdentifier = (EditSelectOptionPannel).toString();
+    final overlayIdentifier = (SelectOptionTypeOptionEditor).toString();
 
     FlowyOverlay.of(context).remove(overlayIdentifier);
     FlowyOverlay.of(context).insertWithAnchor(

+ 0 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/text_field.dart → frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart


+ 6 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart

@@ -7,6 +7,7 @@ class InputTextField extends StatefulWidget {
   final void Function(String)? onDone;
   final void Function(String)? onChanged;
   final void Function() onCanceled;
+  final bool autoClearWhenDone;
   final String text;
 
   const InputTextField({
@@ -14,6 +15,7 @@ class InputTextField extends StatefulWidget {
     this.onDone,
     required this.onCanceled,
     this.onChanged,
+    this.autoClearWhenDone = false,
     Key? key,
   }) : super(key: key);
 
@@ -57,6 +59,10 @@ class _InputTextFieldState extends State<InputTextField> {
         if (widget.onDone != null) {
           widget.onDone!(_controller.text);
         }
+
+        if (widget.autoClearWhenDone) {
+          _controller.text = "";
+        }
       },
     );
   }

+ 5 - 4
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart

@@ -37,7 +37,7 @@ class GridFieldCell extends StatelessWidget {
             child: _DragToExpandLine(),
           );
 
-          return _CellContainer(
+          return _GridHeaderCellContainer(
             width: state.field.width.toDouble(),
             child: Stack(
               alignment: Alignment.centerRight,
@@ -63,7 +63,8 @@ class GridFieldCell extends StatelessWidget {
 
     FieldEditor(
       gridId: state.gridId,
-      fieldContextLoader: FieldContextLoaderAdaptor(
+      fieldName: state.field.name,
+      contextLoader: FieldContextLoader(
         gridId: state.gridId,
         field: state.field,
       ),
@@ -71,10 +72,10 @@ class GridFieldCell extends StatelessWidget {
   }
 }
 
-class _CellContainer extends StatelessWidget {
+class _GridHeaderCellContainer extends StatelessWidget {
   final Widget child;
   final double width;
-  const _CellContainer({
+  const _GridHeaderCellContainer({
     required this.child,
     required this.width,
     Key? key,

+ 47 - 54
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart

@@ -1,4 +1,3 @@
-import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_editor_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'package:easy_localization/easy_localization.dart';
@@ -11,16 +10,42 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'field_name_input.dart';
 import 'field_editor_pannel.dart';
 
-class FieldEditor extends FlowyOverlayDelegate {
+class FieldEditor extends StatelessWidget with FlowyOverlayDelegate {
   final String gridId;
-  final FieldEditorBloc _fieldEditorBloc;
-  final EditFieldContextLoader fieldContextLoader;
-  FieldEditor({
+  final String fieldName;
+
+  final IFieldContextLoader contextLoader;
+  const FieldEditor({
     required this.gridId,
-    required this.fieldContextLoader,
+    required this.fieldName,
+    required this.contextLoader,
     Key? key,
-  }) : _fieldEditorBloc = getIt<FieldEditorBloc>(param1: gridId, param2: fieldContextLoader) {
-    _fieldEditorBloc.add(const FieldEditorEvent.initial());
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider(
+      create: (context) => FieldEditorBloc(
+        gridId: gridId,
+        fieldName: fieldName,
+        fieldContextLoader: contextLoader,
+      )..add(const FieldEditorEvent.initial()),
+      child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
+        buildWhen: (p, c) => false,
+        builder: (context, state) {
+          return ListView(
+            shrinkWrap: true,
+            children: [
+              FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
+              const VSpace(10),
+              const _FieldNameTextField(),
+              const VSpace(10),
+              const _FieldPannel(),
+            ],
+          );
+        },
+      ),
+    );
   }
 
   void show(
@@ -30,7 +55,7 @@ class FieldEditor extends FlowyOverlayDelegate {
     FlowyOverlay.of(context).remove(identifier());
     FlowyOverlay.of(context).insertWithAnchor(
       widget: OverlayContainer(
-        child: _FieldEditorWidget(_fieldEditorBloc, fieldContextLoader),
+        child: this,
         constraints: BoxConstraints.loose(const Size(280, 400)),
       ),
       identifier: identifier(),
@@ -45,49 +70,23 @@ class FieldEditor extends FlowyOverlayDelegate {
     return (FieldEditor).toString();
   }
 
-  @override
-  void didRemove() {
-    _fieldEditorBloc.add(const FieldEditorEvent.done());
-  }
-
   @override
   bool asBarrier() => true;
 }
 
-class _FieldEditorWidget extends StatelessWidget {
-  final FieldEditorBloc editorBloc;
-  final EditFieldContextLoader fieldContextLoader;
-  const _FieldEditorWidget(this.editorBloc, this.fieldContextLoader, {Key? key}) : super(key: key);
+class _FieldPannel extends StatelessWidget {
+  const _FieldPannel({Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return BlocProvider.value(
-      value: editorBloc,
-      child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
-        builder: (context, state) {
-          return state.editFieldContext.fold(
-            () => const SizedBox(),
-            (editFieldContext) => ListView(
-              shrinkWrap: true,
-              children: [
-                FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
-                const VSpace(10),
-                const _FieldNameTextField(),
-                const VSpace(10),
-                FieldEditorPannel(
-                  editFieldContext: editFieldContext,
-                  onSwitchToField: (fieldId, fieldType) {
-                    return fieldContextLoader.switchToField(fieldId, fieldType);
-                  },
-                  onUpdated: (field, typeOptionData) {
-                    context.read<FieldEditorBloc>().add(FieldEditorEvent.updateField(field, typeOptionData));
-                  },
-                ),
-              ],
-            ),
-          );
-        },
-      ),
+    return BlocBuilder<FieldEditorBloc, FieldEditorState>(
+      buildWhen: (p, c) => p.fieldContext != c.fieldContext,
+      builder: (context, state) {
+        return state.fieldContext.fold(
+          () => const SizedBox(),
+          (fieldContext) => FieldEditorPannel(fieldContext: fieldContext),
+        );
+      },
     );
   }
 }
@@ -97,16 +96,10 @@ class _FieldNameTextField extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return BlocSelector<FieldEditorBloc, FieldEditorState, String>(
-      selector: (state) {
-        return state.editFieldContext.fold(
-          () => "",
-          (editFieldContext) => editFieldContext.gridField.name,
-        );
-      },
-      builder: (context, name) {
+    return BlocBuilder<FieldEditorBloc, FieldEditorState>(
+      builder: (context, state) {
         return FieldNameTextField(
-          name: name,
+          name: state.name,
           errorText: context.read<FieldEditorBloc>().state.errorText,
           onNameChanged: (newName) {
             context.read<FieldEditorBloc>().add(FieldEditorEvent.updateName(newName));

+ 70 - 73
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart

@@ -1,20 +1,18 @@
 import 'dart:typed_data';
 
+import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_type_option.dart';
 import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart';
 import 'package:dartz/dartz.dart' show Either;
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
-import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pbserver.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart';
@@ -22,23 +20,20 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header
 import 'field_type_extension.dart';
 import 'type_option/multi_select.dart';
 import 'type_option/number.dart';
+import 'type_option/rich_text.dart';
 import 'type_option/single_select.dart';
 
 typedef UpdateFieldCallback = void Function(Field, Uint8List);
-typedef SwitchToFieldCallback = Future<Either<EditFieldContext, FlowyError>> Function(
+typedef SwitchToFieldCallback = Future<Either<FieldTypeOptionData, FlowyError>> Function(
   String fieldId,
   FieldType fieldType,
 );
 
 class FieldEditorPannel extends StatefulWidget {
-  final EditFieldContext editFieldContext;
-  final UpdateFieldCallback onUpdated;
-  final SwitchToFieldCallback onSwitchToField;
+  final GridFieldContext fieldContext;
 
   const FieldEditorPannel({
-    required this.editFieldContext,
-    required this.onUpdated,
-    required this.onSwitchToField,
+    required this.fieldContext,
     Key? key,
   }) : super(key: key);
 
@@ -52,13 +47,10 @@ class _FieldEditorPannelState extends State<FieldEditorPannel> {
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => getIt<FieldEditorPannelBloc>(param1: widget.editFieldContext),
-      child: BlocConsumer<FieldEditorPannelBloc, FieldEditorPannelState>(
-        listener: (context, state) {
-          widget.onUpdated(state.field, state.typeOptionData);
-        },
+      create: (context) => FieldEditorPannelBloc(widget.fieldContext)..add(const FieldEditorPannelEvent.initial()),
+      child: BlocBuilder<FieldEditorPannelBloc, FieldEditorPannelState>(
         builder: (context, state) {
-          List<Widget> children = [_switchFieldTypeButton(context, state.field)];
+          List<Widget> children = [_switchFieldTypeButton(context, widget.fieldContext.field)];
           final typeOptionWidget = _typeOptionWidget(context: context, state: state);
 
           if (typeOptionWidget != null) {
@@ -84,19 +76,7 @@ class _FieldEditorPannelState extends State<FieldEditorPannel> {
         hoverColor: theme.hover,
         onTap: () {
           final list = FieldTypeList(onSelectField: (newFieldType) {
-            widget.onSwitchToField(field.id, newFieldType).then((result) {
-              result.fold(
-                (editFieldContext) {
-                  context.read<FieldEditorPannelBloc>().add(
-                        FieldEditorPannelEvent.toFieldType(
-                          editFieldContext.gridField,
-                          editFieldContext.typeOptionData,
-                        ),
-                      );
-                },
-                (err) => Log.error(err),
-              );
-            });
+            widget.fieldContext.switchToField(newFieldType);
           });
           _showOverlay(context, list);
         },
@@ -115,18 +95,9 @@ class _FieldEditorPannelState extends State<FieldEditorPannel> {
       hideOverlay: _hideOverlay,
     );
 
-    final dataDelegate = TypeOptionDataDelegate(didUpdateTypeOptionData: (data) {
-      context.read<FieldEditorPannelBloc>().add(FieldEditorPannelEvent.didUpdateTypeOptionData(data));
-    });
-
     final builder = _makeTypeOptionBuild(
-      typeOptionContext: TypeOptionContext(
-        gridId: state.gridId,
-        field: state.field,
-        data: state.typeOptionData,
-      ),
+      typeOptionContext: _makeTypeOptionContext(widget.fieldContext),
       overlayDelegate: overlayDelegate,
-      dataDelegate: dataDelegate,
     );
 
     return builder.customWidget;
@@ -166,27 +137,79 @@ abstract class TypeOptionBuilder {
 TypeOptionBuilder _makeTypeOptionBuild({
   required TypeOptionContext typeOptionContext,
   required TypeOptionOverlayDelegate overlayDelegate,
-  required TypeOptionDataDelegate dataDelegate,
 }) {
   switch (typeOptionContext.field.fieldType) {
     case FieldType.Checkbox:
-      return CheckboxTypeOptionBuilder(typeOptionContext.data);
+      return CheckboxTypeOptionBuilder(
+        typeOptionContext as CheckboxTypeOptionContext,
+      );
     case FieldType.DateTime:
-      return DateTypeOptionBuilder(typeOptionContext.data, overlayDelegate, dataDelegate);
+      return DateTypeOptionBuilder(
+        typeOptionContext as DateTypeOptionContext,
+        overlayDelegate,
+      );
     case FieldType.SingleSelect:
-      return SingleSelectTypeOptionBuilder(typeOptionContext, overlayDelegate, dataDelegate);
+      return SingleSelectTypeOptionBuilder(
+        typeOptionContext as SingleSelectTypeOptionContext,
+        overlayDelegate,
+      );
     case FieldType.MultiSelect:
-      return MultiSelectTypeOptionBuilder(typeOptionContext, overlayDelegate, dataDelegate);
+      return MultiSelectTypeOptionBuilder(
+        typeOptionContext as MultiSelectTypeOptionContext,
+        overlayDelegate,
+      );
     case FieldType.Number:
-      return NumberTypeOptionBuilder(typeOptionContext.data, overlayDelegate, dataDelegate);
+      return NumberTypeOptionBuilder(
+        typeOptionContext as NumberTypeOptionContext,
+        overlayDelegate,
+      );
     case FieldType.RichText:
-      return RichTextTypeOptionBuilder(typeOptionContext.data);
+      return RichTextTypeOptionBuilder(
+        typeOptionContext as RichTextTypeOptionContext,
+      );
 
     default:
       throw UnimplementedError;
   }
 }
 
+TypeOptionContext _makeTypeOptionContext(GridFieldContext fieldContext) {
+  switch (fieldContext.field.fieldType) {
+    case FieldType.Checkbox:
+      return CheckboxTypeOptionContext(
+        fieldContext: fieldContext,
+        dataBuilder: CheckboxTypeOptionDataBuilder(),
+      );
+    case FieldType.DateTime:
+      return DateTypeOptionContext(
+        fieldContext: fieldContext,
+        dataBuilder: DateTypeOptionDataBuilder(),
+      );
+    case FieldType.MultiSelect:
+      return MultiSelectTypeOptionContext(
+        fieldContext: fieldContext,
+        dataBuilder: MultiSelectTypeOptionDataBuilder(),
+      );
+    case FieldType.Number:
+      return NumberTypeOptionContext(
+        fieldContext: fieldContext,
+        dataBuilder: NumberTypeOptionDataBuilder(),
+      );
+    case FieldType.RichText:
+      return RichTextTypeOptionContext(
+        fieldContext: fieldContext,
+        dataBuilder: RichTextTypeOptionDataBuilder(),
+      );
+    case FieldType.SingleSelect:
+      return SingleSelectTypeOptionContext(
+        fieldContext: fieldContext,
+        dataBuilder: SingleSelectTypeOptionDataBuilder(),
+      );
+    default:
+      throw UnimplementedError();
+  }
+}
+
 abstract class TypeOptionWidget extends StatelessWidget {
   const TypeOptionWidget({Key? key}) : super(key: key);
 }
@@ -208,29 +231,3 @@ class TypeOptionOverlayDelegate {
     required this.hideOverlay,
   });
 }
-
-class TypeOptionDataDelegate {
-  TypeOptionDataCallback didUpdateTypeOptionData;
-
-  TypeOptionDataDelegate({
-    required this.didUpdateTypeOptionData,
-  });
-}
-
-class RichTextTypeOptionBuilder extends TypeOptionBuilder {
-  RichTextTypeOption typeOption;
-
-  RichTextTypeOptionBuilder(TypeOptionData typeOptionData) : typeOption = RichTextTypeOption.fromBuffer(typeOptionData);
-
-  @override
-  Widget? get customWidget => null;
-}
-
-class CheckboxTypeOptionBuilder extends TypeOptionBuilder {
-  CheckboxTypeOption typeOption;
-
-  CheckboxTypeOptionBuilder(TypeOptionData typeOptionData) : typeOption = CheckboxTypeOption.fromBuffer(typeOptionData);
-
-  @override
-  Widget? get customWidget => null;
-}

+ 26 - 4
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_name_input.dart

@@ -3,7 +3,7 @@ import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-class FieldNameTextField extends StatelessWidget {
+class FieldNameTextField extends StatefulWidget {
   final void Function(String) onNameChanged;
   final String name;
   final String errorText;
@@ -14,19 +14,41 @@ class FieldNameTextField extends StatelessWidget {
     Key? key,
   }) : super(key: key);
 
+  @override
+  State<FieldNameTextField> createState() => _FieldNameTextFieldState();
+}
+
+class _FieldNameTextFieldState extends State<FieldNameTextField> {
+  late String name;
+  TextEditingController controller = TextEditingController();
+
+  @override
+  void initState() {
+    controller.text = widget.name;
+    super.initState();
+  }
+
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     return RoundedInputField(
       height: 36,
       style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
-      initialValue: name,
+      controller: controller,
       normalBorderColor: theme.shader4,
       errorBorderColor: theme.red,
       focusBorderColor: theme.main1,
       cursorColor: theme.main1,
-      errorText: errorText,
-      onChanged: onNameChanged,
+      errorText: widget.errorText,
+      onChanged: widget.onNameChanged,
     );
   }
+
+  @override
+  void didUpdateWidget(covariant FieldNameTextField oldWidget) {
+    controller.text = widget.name;
+    controller.selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length));
+
+    super.didUpdateWidget(oldWidget);
+  }
 }

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

@@ -150,7 +150,8 @@ class CreateFieldButton extends StatelessWidget {
       hoverColor: theme.hover,
       onTap: () => FieldEditor(
         gridId: gridId,
-        fieldContextLoader: NewFieldContextLoader(gridId: gridId),
+        fieldName: "",
+        contextLoader: NewFieldContextLoader(gridId: gridId),
       ).show(context),
       leftIcon: svgWidget("home/add"),
     );

+ 20 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart

@@ -0,0 +1,20 @@
+import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
+import 'package:flutter/material.dart';
+
+typedef CheckboxTypeOptionContext = TypeOptionContext<CheckboxTypeOption>;
+
+class CheckboxTypeOptionDataBuilder extends TypeOptionDataBuilder<CheckboxTypeOption> {
+  @override
+  CheckboxTypeOption fromBuffer(List<int> buffer) {
+    return CheckboxTypeOption.fromBuffer(buffer);
+  }
+}
+
+class CheckboxTypeOptionBuilder extends TypeOptionBuilder {
+  CheckboxTypeOptionBuilder(CheckboxTypeOptionContext typeOptionContext);
+
+  @override
+  Widget? get customWidget => null;
+}

+ 7 - 11
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart

@@ -1,4 +1,3 @@
-import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/field/type_option/date_bloc.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
@@ -18,12 +17,10 @@ class DateTypeOptionBuilder extends TypeOptionBuilder {
   final DateTypeOptionWidget _widget;
 
   DateTypeOptionBuilder(
-    TypeOptionData typeOptionData,
+    DateTypeOptionContext typeOptionContext,
     TypeOptionOverlayDelegate overlayDelegate,
-    TypeOptionDataDelegate dataDelegate,
   ) : _widget = DateTypeOptionWidget(
-          typeOption: DateTypeOption.fromBuffer(typeOptionData),
-          dataDelegate: dataDelegate,
+          typeOptionContext: typeOptionContext,
           overlayDelegate: overlayDelegate,
         );
 
@@ -32,12 +29,11 @@ class DateTypeOptionBuilder extends TypeOptionBuilder {
 }
 
 class DateTypeOptionWidget extends TypeOptionWidget {
-  final DateTypeOption typeOption;
+  final DateTypeOptionContext typeOptionContext;
   final TypeOptionOverlayDelegate overlayDelegate;
-  final TypeOptionDataDelegate dataDelegate;
+
   const DateTypeOptionWidget({
-    required this.typeOption,
-    required this.dataDelegate,
+    required this.typeOptionContext,
     required this.overlayDelegate,
     Key? key,
   }) : super(key: key);
@@ -45,9 +41,9 @@ class DateTypeOptionWidget extends TypeOptionWidget {
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => getIt<DateTypeOptionBloc>(param1: typeOption),
+      create: (context) => DateTypeOptionBloc(typeOptionContext: typeOptionContext),
       child: BlocConsumer<DateTypeOptionBloc, DateTypeOptionState>(
-        listener: (context, state) => dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()),
+        listener: (context, state) => typeOptionContext.typeOption = state.typeOption,
         builder: (context, state) {
           return Column(children: [
             _renderDateFormatButton(context, state.typeOption.dateFormat),

+ 11 - 36
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart

@@ -1,22 +1,18 @@
-import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_bloc.dart';
-import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
+import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_type_option.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
 
-import 'field_option_pannel.dart';
+import 'select_option.dart';
 
 class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
   final MultiSelectTypeOptionWidget _widget;
 
   MultiSelectTypeOptionBuilder(
-    TypeOptionContext typeOptionContext,
+    MultiSelectTypeOptionContext typeOptionContext,
     TypeOptionOverlayDelegate overlayDelegate,
-    TypeOptionDataDelegate dataDelegate,
   ) : _widget = MultiSelectTypeOptionWidget(
           typeOptionContext: typeOptionContext,
           overlayDelegate: overlayDelegate,
-          dataDelegate: dataDelegate,
         );
 
   @override
@@ -24,44 +20,23 @@ class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
 }
 
 class MultiSelectTypeOptionWidget extends TypeOptionWidget {
-  final TypeOptionContext typeOptionContext;
+  final MultiSelectTypeOptionContext typeOptionContext;
   final TypeOptionOverlayDelegate overlayDelegate;
-  final TypeOptionDataDelegate dataDelegate;
+
   const MultiSelectTypeOptionWidget({
     required this.typeOptionContext,
     required this.overlayDelegate,
-    required this.dataDelegate,
     Key? key,
   }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return BlocProvider(
-      create: (context) => MultiSelectTypeOptionBloc(typeOptionContext),
-      child: BlocConsumer<MultiSelectTypeOptionBloc, MultiSelectTypeOptionState>(
-        listener: (context, state) {
-          dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer());
-        },
-        builder: (context, state) {
-          return FieldSelectOptionPannel(
-            options: state.typeOption.options,
-            beginEdit: () {
-              overlayDelegate.hideOverlay(context);
-            },
-            createOptionCallback: (name) {
-              context.read<MultiSelectTypeOptionBloc>().add(MultiSelectTypeOptionEvent.createOption(name));
-            },
-            updateOptionCallback: (updateOption) {
-              context.read<MultiSelectTypeOptionBloc>().add(MultiSelectTypeOptionEvent.updateOption(updateOption));
-            },
-            deleteOptionCallback: (deleteOption) {
-              context.read<MultiSelectTypeOptionBloc>().add(MultiSelectTypeOptionEvent.deleteOption(deleteOption));
-            },
-            overlayDelegate: overlayDelegate,
-            key: ValueKey(state.typeOption.hashCode),
-          );
-        },
-      ),
+    return SelectOptionTypeOptionWidget(
+      options: typeOptionContext.typeOption.options,
+      beginEdit: () => overlayDelegate.hideOverlay(context),
+      overlayDelegate: overlayDelegate,
+      typeOptionAction: typeOptionContext,
+      // key: ValueKey(state.typeOption.hashCode),
     );
   }
 }

+ 10 - 12
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart

@@ -1,4 +1,3 @@
-import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/field/type_option/number_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/field/type_option/number_format_bloc.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
@@ -20,12 +19,10 @@ class NumberTypeOptionBuilder extends TypeOptionBuilder {
   final NumberTypeOptionWidget _widget;
 
   NumberTypeOptionBuilder(
-    TypeOptionData typeOptionData,
+    NumberTypeOptionContext typeOptionContext,
     TypeOptionOverlayDelegate overlayDelegate,
-    TypeOptionDataDelegate dataDelegate,
   ) : _widget = NumberTypeOptionWidget(
-          typeOption: NumberTypeOption.fromBuffer(typeOptionData),
-          dataDelegate: dataDelegate,
+          typeOptionContext: typeOptionContext,
           overlayDelegate: overlayDelegate,
         );
 
@@ -34,22 +31,23 @@ class NumberTypeOptionBuilder extends TypeOptionBuilder {
 }
 
 class NumberTypeOptionWidget extends TypeOptionWidget {
-  final TypeOptionDataDelegate dataDelegate;
   final TypeOptionOverlayDelegate overlayDelegate;
-  final NumberTypeOption typeOption;
-  const NumberTypeOptionWidget(
-      {required this.typeOption, required this.dataDelegate, required this.overlayDelegate, Key? key})
-      : super(key: key);
+  final NumberTypeOptionContext typeOptionContext;
+  const NumberTypeOptionWidget({
+    required this.typeOptionContext,
+    required this.overlayDelegate,
+    Key? key,
+  }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     return BlocProvider(
-      create: (context) => getIt<NumberTypeOptionBloc>(param1: typeOption),
+      create: (context) => NumberTypeOptionBloc(typeOptionContext: typeOptionContext),
       child: SizedBox(
         height: GridSize.typeOptionItemHeight,
         child: BlocConsumer<NumberTypeOptionBloc, NumberTypeOptionState>(
-          listener: (context, state) => dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()),
+          listener: (context, state) => typeOptionContext.typeOption = state.typeOption,
           builder: (context, state) {
             return FlowyButton(
               text: Row(

+ 21 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/rich_text.dart

@@ -0,0 +1,21 @@
+import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
+
+import 'package:flutter/material.dart';
+
+typedef RichTextTypeOptionContext = TypeOptionContext<RichTextTypeOption>;
+
+class RichTextTypeOptionDataBuilder extends TypeOptionDataBuilder<RichTextTypeOption> {
+  @override
+  RichTextTypeOption fromBuffer(List<int> buffer) {
+    return RichTextTypeOption.fromBuffer(buffer);
+  }
+}
+
+class RichTextTypeOptionBuilder extends TypeOptionBuilder {
+  RichTextTypeOptionBuilder(RichTextTypeOptionContext typeOptionContext);
+
+  @override
+  Widget? get customWidget => null;
+}

+ 75 - 84
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/field_option_pannel.dart → frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart

@@ -1,5 +1,6 @@
-import 'package:app_flowy/workspace/application/grid/field/type_option/field_option_pannel_bloc.dart';
+import 'package:app_flowy/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
 import 'package:flowy_infra/image.dart';
@@ -13,66 +14,39 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 
-import 'edit_option_pannel.dart';
+import 'select_option_editor.dart';
 
-class FieldSelectOptionPannel extends StatelessWidget {
+class SelectOptionTypeOptionWidget extends StatelessWidget {
   final List<SelectOption> options;
   final VoidCallback beginEdit;
-  final Function(String optionName) createOptionCallback;
-  final Function(SelectOption) updateOptionCallback;
-  final Function(SelectOption) deleteOptionCallback;
   final TypeOptionOverlayDelegate overlayDelegate;
+  final SelectOptionTypeOptionAction typeOptionAction;
 
-  const FieldSelectOptionPannel({
+  const SelectOptionTypeOptionWidget({
     required this.options,
     required this.beginEdit,
-    required this.createOptionCallback,
-    required this.updateOptionCallback,
-    required this.deleteOptionCallback,
     required this.overlayDelegate,
+    required this.typeOptionAction,
     Key? key,
   }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => FieldOptionPannelBloc(options: options),
-      child: BlocConsumer<FieldOptionPannelBloc, FieldOptionPannelState>(
-        listener: (context, state) {
-          if (state.isEditingOption) {
-            beginEdit();
-          }
-          state.newOptionName.fold(
-            () => null,
-            (optionName) => createOptionCallback(optionName),
-          );
-
-          state.updateOption.fold(
-            () => null,
-            (updateOption) => updateOptionCallback(updateOption),
-          );
-
-          state.deleteOption.fold(
-            () => null,
-            (deleteOption) => deleteOptionCallback(deleteOption),
-          );
-        },
+      create: (context) => SelectOptionTypeOptionBloc(options: options, typeOptionAction: typeOptionAction),
+      child: BlocBuilder<SelectOptionTypeOptionBloc, SelectOptionTypeOptionState>(
         builder: (context, state) {
           List<Widget> children = [
             const TypeOptionSeparator(),
             const OptionTitle(),
+            if (state.isEditingOption)
+              const Padding(
+                padding: EdgeInsets.only(bottom: 10),
+                child: _CreateOptionTextField(),
+              ),
+            if (state.options.isEmpty && !state.isEditingOption) const _AddOptionButton(),
+            _OptionList(overlayDelegate)
           ];
-          if (state.isEditingOption) {
-            children.add(const _OptionNameTextField());
-          }
-
-          if (state.options.isEmpty && !state.isEditingOption) {
-            children.add(const _AddOptionButton());
-          }
-
-          if (state.options.isNotEmpty) {
-            children.add(_OptionList(overlayDelegate));
-          }
 
           return Column(children: children);
         },
@@ -86,30 +60,12 @@ class OptionTitle extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-
-    return BlocBuilder<FieldOptionPannelBloc, FieldOptionPannelState>(
+    return BlocBuilder<SelectOptionTypeOptionBloc, SelectOptionTypeOptionState>(
       builder: (context, state) {
         List<Widget> children = [FlowyText.medium(LocaleKeys.grid_field_optionTitle.tr(), fontSize: 12)];
         if (state.options.isNotEmpty) {
           children.add(const Spacer());
-          children.add(
-            SizedBox(
-              width: 100,
-              height: 26,
-              child: FlowyButton(
-                text: FlowyText.medium(
-                  LocaleKeys.grid_field_addOption.tr(),
-                  fontSize: 12,
-                  textAlign: TextAlign.center,
-                ),
-                hoverColor: theme.hover,
-                onTap: () {
-                  context.read<FieldOptionPannelBloc>().add(const FieldOptionPannelEvent.beginAddingOption());
-                },
-              ),
-            ),
-          );
+          children.add(const _OptionTitleButton());
         }
 
         return SizedBox(
@@ -121,13 +77,37 @@ class OptionTitle extends StatelessWidget {
   }
 }
 
+class _OptionTitleButton extends StatelessWidget {
+  const _OptionTitleButton({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return SizedBox(
+      width: 100,
+      height: 26,
+      child: FlowyButton(
+        text: FlowyText.medium(
+          LocaleKeys.grid_field_addOption.tr(),
+          fontSize: 12,
+          textAlign: TextAlign.center,
+        ),
+        hoverColor: theme.hover,
+        onTap: () {
+          context.read<SelectOptionTypeOptionBloc>().add(const SelectOptionTypeOptionEvent.addingOption());
+        },
+      ),
+    );
+  }
+}
+
 class _OptionList extends StatelessWidget {
   final TypeOptionOverlayDelegate delegate;
   const _OptionList(this.delegate, {Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return BlocBuilder<FieldOptionPannelBloc, FieldOptionPannelState>(
+    return BlocBuilder<SelectOptionTypeOptionBloc, SelectOptionTypeOptionState>(
       buildWhen: (previous, current) {
         return previous.options != current.options;
       },
@@ -154,16 +134,16 @@ class _OptionList extends StatelessWidget {
   _OptionCell _makeOptionCell(BuildContext context, SelectOption option) {
     return _OptionCell(
       option: option,
-      onEdited: (option) {
-        final pannel = EditSelectOptionPannel(
+      onSelected: (option) {
+        final pannel = SelectOptionTypeOptionEditor(
           option: option,
           onDeleted: () {
             delegate.hideOverlay(context);
-            context.read<FieldOptionPannelBloc>().add(FieldOptionPannelEvent.deleteOption(option));
+            context.read<SelectOptionTypeOptionBloc>().add(SelectOptionTypeOptionEvent.deleteOption(option));
           },
           onUpdated: (updatedOption) {
             delegate.hideOverlay(context);
-            context.read<FieldOptionPannelBloc>().add(FieldOptionPannelEvent.updateOption(updatedOption));
+            context.read<SelectOptionTypeOptionBloc>().add(SelectOptionTypeOptionEvent.updateOption(updatedOption));
           },
           key: ValueKey(option.id),
         );
@@ -175,23 +155,28 @@ class _OptionList extends StatelessWidget {
 
 class _OptionCell extends StatelessWidget {
   final SelectOption option;
-  final Function(SelectOption) onEdited;
+  final Function(SelectOption) onSelected;
   const _OptionCell({
     required this.option,
-    required this.onEdited,
+    required this.onSelected,
     Key? key,
   }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
+
     return SizedBox(
       height: GridSize.typeOptionItemHeight,
-      child: FlowyButton(
-        text: FlowyText.medium(option.name, fontSize: 12),
-        hoverColor: theme.hover,
-        onTap: () => onEdited(option),
-        rightIcon: svgWidget("grid/details", color: theme.iconColor),
+      child: SelectOptionTagCell(
+        option: option,
+        onSelected: onSelected,
+        children: [
+          svgWidget(
+            "grid/details",
+            color: theme.iconColor,
+          ),
+        ],
       ),
     );
   }
@@ -209,7 +194,7 @@ class _AddOptionButton extends StatelessWidget {
         text: FlowyText.medium(LocaleKeys.grid_field_addSelectOption.tr(), fontSize: 12),
         hoverColor: theme.hover,
         onTap: () {
-          context.read<FieldOptionPannelBloc>().add(const FieldOptionPannelEvent.beginAddingOption());
+          context.read<SelectOptionTypeOptionBloc>().add(const SelectOptionTypeOptionEvent.addingOption());
         },
         leftIcon: svgWidget("home/add", color: theme.iconColor),
       ),
@@ -217,18 +202,24 @@ class _AddOptionButton extends StatelessWidget {
   }
 }
 
-class _OptionNameTextField extends StatelessWidget {
-  const _OptionNameTextField({Key? key}) : super(key: key);
+class _CreateOptionTextField extends StatelessWidget {
+  const _CreateOptionTextField({Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return InputTextField(
-      text: "",
-      onCanceled: () {
-        context.read<FieldOptionPannelBloc>().add(const FieldOptionPannelEvent.endAddingOption());
-      },
-      onDone: (optionName) {
-        context.read<FieldOptionPannelBloc>().add(FieldOptionPannelEvent.createOption(optionName));
+    return BlocBuilder<SelectOptionTypeOptionBloc, SelectOptionTypeOptionState>(
+      builder: (context, state) {
+        final text = state.newOptionName.foldRight("", (a, previous) => a);
+        return InputTextField(
+          autoClearWhenDone: true,
+          text: text,
+          onCanceled: () {
+            context.read<SelectOptionTypeOptionBloc>().add(const SelectOptionTypeOptionEvent.endAddingOption());
+          },
+          onDone: (optionName) {
+            context.read<SelectOptionTypeOptionBloc>().add(SelectOptionTypeOptionEvent.createOption(optionName));
+          },
+        );
       },
     );
   }

+ 3 - 3
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/edit_option_pannel.dart → frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart

@@ -1,6 +1,6 @@
 import 'package:app_flowy/workspace/application/grid/field/type_option/edit_select_option_bloc.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
@@ -14,11 +14,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 
-class EditSelectOptionPannel extends StatelessWidget {
+class SelectOptionTypeOptionEditor extends StatelessWidget {
   final SelectOption option;
   final VoidCallback onDeleted;
   final Function(SelectOption) onUpdated;
-  const EditSelectOptionPannel({
+  const SelectOptionTypeOptionEditor({
     required this.option,
     required this.onDeleted,
     required this.onUpdated,

+ 11 - 36
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart

@@ -1,20 +1,16 @@
-import 'package:app_flowy/workspace/application/grid/field/type_option/single_select_bloc.dart';
-import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
+import 'package:app_flowy/workspace/application/grid/field/type_option/single_select_type_option.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'field_option_pannel.dart';
+import 'select_option.dart';
 
 class SingleSelectTypeOptionBuilder extends TypeOptionBuilder {
   final SingleSelectTypeOptionWidget _widget;
 
   SingleSelectTypeOptionBuilder(
-    TypeOptionContext typeOptionContext,
+    SingleSelectTypeOptionContext typeOptionContext,
     TypeOptionOverlayDelegate overlayDelegate,
-    TypeOptionDataDelegate dataDelegate,
   ) : _widget = SingleSelectTypeOptionWidget(
           typeOptionContext: typeOptionContext,
-          dataDelegate: dataDelegate,
           overlayDelegate: overlayDelegate,
         );
 
@@ -23,44 +19,23 @@ class SingleSelectTypeOptionBuilder extends TypeOptionBuilder {
 }
 
 class SingleSelectTypeOptionWidget extends TypeOptionWidget {
-  final TypeOptionContext typeOptionContext;
+  final SingleSelectTypeOptionContext typeOptionContext;
   final TypeOptionOverlayDelegate overlayDelegate;
-  final TypeOptionDataDelegate dataDelegate;
+
   const SingleSelectTypeOptionWidget({
     required this.typeOptionContext,
-    required this.dataDelegate,
     required this.overlayDelegate,
     Key? key,
   }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return BlocProvider(
-      create: (context) => SingleSelectTypeOptionBloc(typeOptionContext),
-      child: BlocConsumer<SingleSelectTypeOptionBloc, SingleSelectTypeOptionState>(
-        listener: (context, state) {
-          dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer());
-        },
-        builder: (context, state) {
-          return FieldSelectOptionPannel(
-            options: state.typeOption.options,
-            beginEdit: () {
-              overlayDelegate.hideOverlay(context);
-            },
-            createOptionCallback: (name) {
-              context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.createOption(name));
-            },
-            updateOptionCallback: (updateOption) {
-              context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.updateOption(updateOption));
-            },
-            deleteOptionCallback: (deleteOption) {
-              context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.deleteOption(deleteOption));
-            },
-            overlayDelegate: overlayDelegate,
-            key: ValueKey(state.typeOption.hashCode),
-          );
-        },
-      ),
+    return SelectOptionTypeOptionWidget(
+      options: typeOptionContext.typeOption.options,
+      beginEdit: () => overlayDelegate.hideOverlay(context),
+      overlayDelegate: overlayDelegate,
+      typeOptionAction: typeOptionContext,
+      // key: ValueKey(state.typeOption.hashCode),
     );
   }
 }

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

@@ -178,7 +178,8 @@ class _RowDetailCell extends StatelessWidget {
   void _showFieldEditor(BuildContext context) {
     FieldEditor(
       gridId: gridCell.gridId,
-      fieldContextLoader: FieldContextLoaderAdaptor(
+      fieldName: gridCell.field.name,
+      contextLoader: FieldContextLoader(
         gridId: gridCell.gridId,
         field: gridCell.field,
       ),

+ 2 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart

@@ -115,7 +115,8 @@ class _GridPropertyCell extends StatelessWidget {
       onTap: () {
         FieldEditor(
           gridId: gridId,
-          fieldContextLoader: FieldContextLoaderAdaptor(gridId: gridId, field: field),
+          fieldName: field.name,
+          contextLoader: FieldContextLoader(gridId: gridId, field: field),
         ).show(context, anchorDirection: AnchorDirection.bottomRight);
       },
     );

+ 9 - 4
frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart

@@ -109,7 +109,7 @@ abstract class HoverWidget extends StatefulWidget {
 }
 
 class FlowyHover2 extends StatefulWidget {
-  final HoverWidget child;
+  final Widget child;
   final EdgeInsets contentPadding;
   const FlowyHover2({
     required this.child,
@@ -127,9 +127,14 @@ class _FlowyHover2State extends State<FlowyHover2> {
   @override
   void initState() {
     _hoverState = FlowyHoverState();
-    widget.child.onFocus.addListener(() {
-      _hoverState.onFocus = widget.child.onFocus.value;
-    });
+
+    if (widget.child is HoverWidget) {
+      final hoverWidget = widget.child as HoverWidget;
+      hoverWidget.onFocus.addListener(() {
+        _hoverState.onFocus = hoverWidget.onFocus.value;
+      });
+    }
+
     super.initState();
   }
 

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

@@ -124,14 +124,14 @@ class GridEventSwitchToField {
      EditFieldPayload request;
      GridEventSwitchToField(this.request);
 
-    Future<Either<EditFieldContext, FlowyError>> send() {
+    Future<Either<FieldTypeOptionData, FlowyError>> send() {
     final request = FFIRequest.create()
           ..event = GridEvent.SwitchToField.toString()
           ..payload = requestToBytes(this.request);
 
     return Dispatch.asyncRequest(request)
         .then((bytesResult) => bytesResult.fold(
-           (okBytes) => left(EditFieldContext.fromBuffer(okBytes)),
+           (okBytes) => left(FieldTypeOptionData.fromBuffer(okBytes)),
            (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
     }
@@ -154,47 +154,47 @@ class GridEventDuplicateField {
     }
 }
 
-class GridEventGetEditFieldContext {
-     EditFieldPayload request;
-     GridEventGetEditFieldContext(this.request);
+class GridEventMoveItem {
+     MoveItemPayload request;
+     GridEventMoveItem(this.request);
 
-    Future<Either<EditFieldContext, FlowyError>> send() {
+    Future<Either<Unit, FlowyError>> send() {
     final request = FFIRequest.create()
-          ..event = GridEvent.GetEditFieldContext.toString()
+          ..event = GridEvent.MoveItem.toString()
           ..payload = requestToBytes(this.request);
 
     return Dispatch.asyncRequest(request)
         .then((bytesResult) => bytesResult.fold(
-           (okBytes) => left(EditFieldContext.fromBuffer(okBytes)),
+           (bytes) => left(unit),
            (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
     }
 }
 
-class GridEventMoveItem {
-     MoveItemPayload request;
-     GridEventMoveItem(this.request);
+class GridEventGetFieldTypeOption {
+     EditFieldPayload request;
+     GridEventGetFieldTypeOption(this.request);
 
-    Future<Either<Unit, FlowyError>> send() {
+    Future<Either<FieldTypeOptionData, FlowyError>> send() {
     final request = FFIRequest.create()
-          ..event = GridEvent.MoveItem.toString()
+          ..event = GridEvent.GetFieldTypeOption.toString()
           ..payload = requestToBytes(this.request);
 
     return Dispatch.asyncRequest(request)
         .then((bytesResult) => bytesResult.fold(
-           (bytes) => left(unit),
+           (okBytes) => left(FieldTypeOptionData.fromBuffer(okBytes)),
            (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
     }
 }
 
-class GridEventGetFieldTypeOption {
+class GridEventCreateFieldTypeOption {
      EditFieldPayload request;
-     GridEventGetFieldTypeOption(this.request);
+     GridEventCreateFieldTypeOption(this.request);
 
     Future<Either<FieldTypeOptionData, FlowyError>> send() {
     final request = FFIRequest.create()
-          ..event = GridEvent.GetFieldTypeOption.toString()
+          ..event = GridEvent.CreateFieldTypeOption.toString()
           ..payload = requestToBytes(this.request);
 
     return Dispatch.asyncRequest(request)

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

@@ -490,21 +490,12 @@ class GetEditFieldContextPayload extends $pb.GeneratedMessage {
   void clearFieldType() => clearField(3);
 }
 
-enum EditFieldPayload_OneOfFieldId {
-  fieldId, 
-  notSet
-}
-
 class EditFieldPayload extends $pb.GeneratedMessage {
-  static const $core.Map<$core.int, EditFieldPayload_OneOfFieldId> _EditFieldPayload_OneOfFieldIdByTag = {
-    2 : EditFieldPayload_OneOfFieldId.fieldId,
-    0 : EditFieldPayload_OneOfFieldId.notSet
-  };
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'EditFieldPayload', createEmptyInstance: create)
-    ..oo(0, [2])
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
     ..e<FieldType>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldType', $pb.PbFieldType.OE, defaultOrMaker: FieldType.RichText, valueOf: FieldType.valueOf, enumValues: FieldType.values)
+    ..aOB(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'createIfNotExist')
     ..hasRequiredFields = false
   ;
 
@@ -513,6 +504,7 @@ class EditFieldPayload extends $pb.GeneratedMessage {
     $core.String? gridId,
     $core.String? fieldId,
     FieldType? fieldType,
+    $core.bool? createIfNotExist,
   }) {
     final _result = create();
     if (gridId != null) {
@@ -524,6 +516,9 @@ class EditFieldPayload extends $pb.GeneratedMessage {
     if (fieldType != null) {
       _result.fieldType = fieldType;
     }
+    if (createIfNotExist != null) {
+      _result.createIfNotExist = createIfNotExist;
+    }
     return _result;
   }
   factory EditFieldPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -547,9 +542,6 @@ class EditFieldPayload extends $pb.GeneratedMessage {
   static EditFieldPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EditFieldPayload>(create);
   static EditFieldPayload? _defaultInstance;
 
-  EditFieldPayload_OneOfFieldId whichOneOfFieldId() => _EditFieldPayload_OneOfFieldIdByTag[$_whichOneof(0)]!;
-  void clearOneOfFieldId() => clearField($_whichOneof(0));
-
   @$pb.TagNumber(1)
   $core.String get gridId => $_getSZ(0);
   @$pb.TagNumber(1)
@@ -576,18 +568,27 @@ class EditFieldPayload extends $pb.GeneratedMessage {
   $core.bool hasFieldType() => $_has(2);
   @$pb.TagNumber(3)
   void clearFieldType() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.bool get createIfNotExist => $_getBF(3);
+  @$pb.TagNumber(4)
+  set createIfNotExist($core.bool v) { $_setBool(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasCreateIfNotExist() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearCreateIfNotExist() => clearField(4);
 }
 
-class EditFieldContext extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'EditFieldContext', createEmptyInstance: create)
+class FieldTypeOptionContext extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'FieldTypeOptionContext', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
     ..aOM<Field>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridField', subBuilder: Field.create)
     ..a<$core.List<$core.int>>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptionData', $pb.PbFieldType.OY)
     ..hasRequiredFields = false
   ;
 
-  EditFieldContext._() : super();
-  factory EditFieldContext({
+  FieldTypeOptionContext._() : super();
+  factory FieldTypeOptionContext({
     $core.String? gridId,
     Field? gridField,
     $core.List<$core.int>? typeOptionData,
@@ -604,26 +605,26 @@ class EditFieldContext extends $pb.GeneratedMessage {
     }
     return _result;
   }
-  factory EditFieldContext.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory EditFieldContext.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory FieldTypeOptionContext.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory FieldTypeOptionContext.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')
-  EditFieldContext clone() => EditFieldContext()..mergeFromMessage(this);
+  FieldTypeOptionContext clone() => FieldTypeOptionContext()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  EditFieldContext copyWith(void Function(EditFieldContext) updates) => super.copyWith((message) => updates(message as EditFieldContext)) as EditFieldContext; // ignore: deprecated_member_use
+  FieldTypeOptionContext copyWith(void Function(FieldTypeOptionContext) updates) => super.copyWith((message) => updates(message as FieldTypeOptionContext)) as FieldTypeOptionContext; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static EditFieldContext create() => EditFieldContext._();
-  EditFieldContext createEmptyInstance() => create();
-  static $pb.PbList<EditFieldContext> createRepeated() => $pb.PbList<EditFieldContext>();
+  static FieldTypeOptionContext create() => FieldTypeOptionContext._();
+  FieldTypeOptionContext createEmptyInstance() => create();
+  static $pb.PbList<FieldTypeOptionContext> createRepeated() => $pb.PbList<FieldTypeOptionContext>();
   @$core.pragma('dart2js:noInline')
-  static EditFieldContext getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EditFieldContext>(create);
-  static EditFieldContext? _defaultInstance;
+  static FieldTypeOptionContext getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FieldTypeOptionContext>(create);
+  static FieldTypeOptionContext? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.String get gridId => $_getSZ(0);
@@ -657,19 +658,24 @@ class EditFieldContext extends $pb.GeneratedMessage {
 
 class FieldTypeOptionData extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'FieldTypeOptionData', createEmptyInstance: create)
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
-    ..a<$core.List<$core.int>>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptionData', $pb.PbFieldType.OY)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..aOM<Field>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'field', subBuilder: Field.create)
+    ..a<$core.List<$core.int>>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptionData', $pb.PbFieldType.OY)
     ..hasRequiredFields = false
   ;
 
   FieldTypeOptionData._() : super();
   factory FieldTypeOptionData({
-    $core.String? fieldId,
+    $core.String? gridId,
+    Field? field_2,
     $core.List<$core.int>? typeOptionData,
   }) {
     final _result = create();
-    if (fieldId != null) {
-      _result.fieldId = fieldId;
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
+    if (field_2 != null) {
+      _result.field_2 = field_2;
     }
     if (typeOptionData != null) {
       _result.typeOptionData = typeOptionData;
@@ -698,22 +704,33 @@ class FieldTypeOptionData extends $pb.GeneratedMessage {
   static FieldTypeOptionData? _defaultInstance;
 
   @$pb.TagNumber(1)
-  $core.String get fieldId => $_getSZ(0);
+  $core.String get gridId => $_getSZ(0);
   @$pb.TagNumber(1)
-  set fieldId($core.String v) { $_setString(0, v); }
+  set gridId($core.String v) { $_setString(0, v); }
   @$pb.TagNumber(1)
-  $core.bool hasFieldId() => $_has(0);
+  $core.bool hasGridId() => $_has(0);
   @$pb.TagNumber(1)
-  void clearFieldId() => clearField(1);
+  void clearGridId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.List<$core.int> get typeOptionData => $_getN(1);
+  Field get field_2 => $_getN(1);
   @$pb.TagNumber(2)
-  set typeOptionData($core.List<$core.int> v) { $_setBytes(1, v); }
+  set field_2(Field v) { setField(2, v); }
   @$pb.TagNumber(2)
-  $core.bool hasTypeOptionData() => $_has(1);
+  $core.bool hasField_2() => $_has(1);
   @$pb.TagNumber(2)
-  void clearTypeOptionData() => clearField(2);
+  void clearField_2() => clearField(2);
+  @$pb.TagNumber(2)
+  Field ensureField_2() => $_ensure(1);
+
+  @$pb.TagNumber(3)
+  $core.List<$core.int> get typeOptionData => $_getN(2);
+  @$pb.TagNumber(3)
+  set typeOptionData($core.List<$core.int> v) { $_setBytes(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasTypeOptionData() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearTypeOptionData() => clearField(3);
 }
 
 class RepeatedField extends $pb.GeneratedMessage {

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

@@ -117,19 +117,17 @@ const EditFieldPayload$json = const {
   '1': 'EditFieldPayload',
   '2': const [
     const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
-    const {'1': 'field_id', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'fieldId'},
+    const {'1': 'field_id', '3': 2, '4': 1, '5': 9, '10': 'fieldId'},
     const {'1': 'field_type', '3': 3, '4': 1, '5': 14, '6': '.FieldType', '10': 'fieldType'},
-  ],
-  '8': const [
-    const {'1': 'one_of_field_id'},
+    const {'1': 'create_if_not_exist', '3': 4, '4': 1, '5': 8, '10': 'createIfNotExist'},
   ],
 };
 
 /// Descriptor for `EditFieldPayload`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List editFieldPayloadDescriptor = $convert.base64Decode('ChBFZGl0RmllbGRQYXlsb2FkEhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIbCghmaWVsZF9pZBgCIAEoCUgAUgdmaWVsZElkEikKCmZpZWxkX3R5cGUYAyABKA4yCi5GaWVsZFR5cGVSCWZpZWxkVHlwZUIRCg9vbmVfb2ZfZmllbGRfaWQ=');
-@$core.Deprecated('Use editFieldContextDescriptor instead')
-const EditFieldContext$json = const {
-  '1': 'EditFieldContext',
+final $typed_data.Uint8List editFieldPayloadDescriptor = $convert.base64Decode('ChBFZGl0RmllbGRQYXlsb2FkEhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIZCghmaWVsZF9pZBgCIAEoCVIHZmllbGRJZBIpCgpmaWVsZF90eXBlGAMgASgOMgouRmllbGRUeXBlUglmaWVsZFR5cGUSLQoTY3JlYXRlX2lmX25vdF9leGlzdBgEIAEoCFIQY3JlYXRlSWZOb3RFeGlzdA==');
+@$core.Deprecated('Use fieldTypeOptionContextDescriptor instead')
+const FieldTypeOptionContext$json = const {
+  '1': 'FieldTypeOptionContext',
   '2': const [
     const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
     const {'1': 'grid_field', '3': 2, '4': 1, '5': 11, '6': '.Field', '10': 'gridField'},
@@ -137,19 +135,20 @@ const EditFieldContext$json = const {
   ],
 };
 
-/// Descriptor for `EditFieldContext`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List editFieldContextDescriptor = $convert.base64Decode('ChBFZGl0RmllbGRDb250ZXh0EhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIlCgpncmlkX2ZpZWxkGAIgASgLMgYuRmllbGRSCWdyaWRGaWVsZBIoChB0eXBlX29wdGlvbl9kYXRhGAMgASgMUg50eXBlT3B0aW9uRGF0YQ==');
+/// Descriptor for `FieldTypeOptionContext`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List fieldTypeOptionContextDescriptor = $convert.base64Decode('ChZGaWVsZFR5cGVPcHRpb25Db250ZXh0EhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIlCgpncmlkX2ZpZWxkGAIgASgLMgYuRmllbGRSCWdyaWRGaWVsZBIoChB0eXBlX29wdGlvbl9kYXRhGAMgASgMUg50eXBlT3B0aW9uRGF0YQ==');
 @$core.Deprecated('Use fieldTypeOptionDataDescriptor instead')
 const FieldTypeOptionData$json = const {
   '1': 'FieldTypeOptionData',
   '2': const [
-    const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'},
-    const {'1': 'type_option_data', '3': 2, '4': 1, '5': 12, '10': 'typeOptionData'},
+    const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
+    const {'1': 'field', '3': 2, '4': 1, '5': 11, '6': '.Field', '10': 'field'},
+    const {'1': 'type_option_data', '3': 3, '4': 1, '5': 12, '10': 'typeOptionData'},
   ],
 };
 
 /// Descriptor for `FieldTypeOptionData`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List fieldTypeOptionDataDescriptor = $convert.base64Decode('ChNGaWVsZFR5cGVPcHRpb25EYXRhEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEigKEHR5cGVfb3B0aW9uX2RhdGEYAiABKAxSDnR5cGVPcHRpb25EYXRh');
+final $typed_data.Uint8List fieldTypeOptionDataDescriptor = $convert.base64Decode('ChNGaWVsZFR5cGVPcHRpb25EYXRhEhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIcCgVmaWVsZBgCIAEoCzIGLkZpZWxkUgVmaWVsZBIoChB0eXBlX29wdGlvbl9kYXRhGAMgASgMUg50eXBlT3B0aW9uRGF0YQ==');
 @$core.Deprecated('Use repeatedFieldDescriptor instead')
 const RepeatedField$json = const {
   '1': 'RepeatedField',

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

@@ -19,9 +19,9 @@ class GridEvent extends $pb.ProtobufEnum {
   static const GridEvent DeleteField = GridEvent._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteField');
   static const GridEvent SwitchToField = GridEvent._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SwitchToField');
   static const GridEvent DuplicateField = GridEvent._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateField');
-  static const GridEvent GetEditFieldContext = GridEvent._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetEditFieldContext');
-  static const GridEvent MoveItem = GridEvent._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MoveItem');
-  static const GridEvent GetFieldTypeOption = GridEvent._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetFieldTypeOption');
+  static const GridEvent MoveItem = GridEvent._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MoveItem');
+  static const GridEvent GetFieldTypeOption = GridEvent._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetFieldTypeOption');
+  static const GridEvent CreateFieldTypeOption = GridEvent._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateFieldTypeOption');
   static const GridEvent NewSelectOption = GridEvent._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NewSelectOption');
   static const GridEvent GetSelectOptionCellData = GridEvent._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetSelectOptionCellData');
   static const GridEvent UpdateSelectOption = GridEvent._(32, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateSelectOption');
@@ -45,9 +45,9 @@ class GridEvent extends $pb.ProtobufEnum {
     DeleteField,
     SwitchToField,
     DuplicateField,
-    GetEditFieldContext,
     MoveItem,
     GetFieldTypeOption,
+    CreateFieldTypeOption,
     NewSelectOption,
     GetSelectOptionCellData,
     UpdateSelectOption,

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

@@ -21,9 +21,9 @@ const GridEvent$json = const {
     const {'1': 'DeleteField', '2': 14},
     const {'1': 'SwitchToField', '2': 20},
     const {'1': 'DuplicateField', '2': 21},
-    const {'1': 'GetEditFieldContext', '2': 22},
-    const {'1': 'MoveItem', '2': 23},
-    const {'1': 'GetFieldTypeOption', '2': 24},
+    const {'1': 'MoveItem', '2': 22},
+    const {'1': 'GetFieldTypeOption', '2': 23},
+    const {'1': 'CreateFieldTypeOption', '2': 24},
     const {'1': 'NewSelectOption', '2': 30},
     const {'1': 'GetSelectOptionCellData', '2': 31},
     const {'1': 'UpdateSelectOption', '2': 32},
@@ -40,4 +40,4 @@ const GridEvent$json = const {
 };
 
 /// Descriptor for `GridEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIZChVVcGRhdGVGaWVsZFR5cGVPcHRpb24QDBIPCgtJbnNlcnRGaWVsZBANEg8KC0RlbGV0ZUZpZWxkEA4SEQoNU3dpdGNoVG9GaWVsZBAUEhIKDkR1cGxpY2F0ZUZpZWxkEBUSFwoTR2V0RWRpdEZpZWxkQ29udGV4dBAWEgwKCE1vdmVJdGVtEBcSFgoSR2V0RmllbGRUeXBlT3B0aW9uEBgSEwoPTmV3U2VsZWN0T3B0aW9uEB4SGwoXR2V0U2VsZWN0T3B0aW9uQ2VsbERhdGEQHxIWChJVcGRhdGVTZWxlY3RPcHRpb24QIBINCglDcmVhdGVSb3cQMhIKCgZHZXRSb3cQMxINCglEZWxldGVSb3cQNBIQCgxEdXBsaWNhdGVSb3cQNRILCgdHZXRDZWxsEEYSDgoKVXBkYXRlQ2VsbBBHEhoKFlVwZGF0ZVNlbGVjdE9wdGlvbkNlbGwQSBISCg5VcGRhdGVEYXRlQ2VsbBBQEhMKD0dldERhdGVDZWxsRGF0YRBa');
+final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIZChVVcGRhdGVGaWVsZFR5cGVPcHRpb24QDBIPCgtJbnNlcnRGaWVsZBANEg8KC0RlbGV0ZUZpZWxkEA4SEQoNU3dpdGNoVG9GaWVsZBAUEhIKDkR1cGxpY2F0ZUZpZWxkEBUSDAoITW92ZUl0ZW0QFhIWChJHZXRGaWVsZFR5cGVPcHRpb24QFxIZChVDcmVhdGVGaWVsZFR5cGVPcHRpb24QGBITCg9OZXdTZWxlY3RPcHRpb24QHhIbChdHZXRTZWxlY3RPcHRpb25DZWxsRGF0YRAfEhYKElVwZGF0ZVNlbGVjdE9wdGlvbhAgEg0KCUNyZWF0ZVJvdxAyEgoKBkdldFJvdxAzEg0KCURlbGV0ZVJvdxA0EhAKDER1cGxpY2F0ZVJvdxA1EgsKB0dldENlbGwQRhIOCgpVcGRhdGVDZWxsEEcSGgoWVXBkYXRlU2VsZWN0T3B0aW9uQ2VsbBBIEhIKDlVwZGF0ZURhdGVDZWxsEFASEwoPR2V0RGF0ZUNlbGxEYXRhEFo=');

+ 41 - 52
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -2,7 +2,6 @@ use crate::manager::GridManager;
 use crate::services::entities::*;
 use crate::services::field::type_options::*;
 use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_json_str};
-use crate::services::grid_editor::ClientGridEditor;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::*;
 use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
@@ -98,18 +97,27 @@ pub(crate) async fn delete_field_handler(
 pub(crate) async fn switch_to_field_handler(
     data: Data<EditFieldPayload>,
     manager: AppData<Arc<GridManager>>,
-) -> DataResult<EditFieldContext, FlowyError> {
+) -> DataResult<FieldTypeOptionData, FlowyError> {
     let params: EditFieldParams = data.into_inner().try_into()?;
-    if params.field_id.is_none() {
-        return Err(ErrorCode::FieldIdIsEmpty.into());
-    }
-    let field_id = params.field_id.unwrap();
     let editor = manager.get_grid_editor(&params.grid_id)?;
-    editor.switch_to_field_type(&field_id, &params.field_type).await?;
-    let field_meta = editor.get_field_meta(&field_id).await;
-    let edit_context =
-        make_edit_field_context(&params.grid_id, Some(field_id), params.field_type, editor, field_meta).await?;
-    data_result(edit_context)
+    editor
+        .switch_to_field_type(&params.field_id, &params.field_type)
+        .await?;
+
+    // Get the FieldMeta with field_id, if it doesn't exist, we create the default FieldMeta from the FieldType.
+    let field_meta = editor
+        .get_field_meta(&params.field_id)
+        .await
+        .unwrap_or(editor.next_field_meta(&params.field_type).await?);
+
+    let type_option_data = get_type_option_data(&field_meta, &params.field_type).await?;
+    let data = FieldTypeOptionData {
+        grid_id: params.grid_id,
+        field: field_meta.into(),
+        type_option_data,
+    };
+
+    data_result(data)
 }
 
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
@@ -123,31 +131,42 @@ pub(crate) async fn duplicate_field_handler(
     Ok(())
 }
 
+/// Return the FieldTypeOptionData if the Field exists otherwise return record not found error.
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
-pub(crate) async fn get_field_context_handler(
+pub(crate) async fn get_field_type_option_data_handler(
     data: Data<EditFieldPayload>,
     manager: AppData<Arc<GridManager>>,
-) -> DataResult<EditFieldContext, FlowyError> {
+) -> DataResult<FieldTypeOptionData, FlowyError> {
     let params: EditFieldParams = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
-    let edit_context =
-        make_edit_field_context(&params.grid_id, params.field_id, params.field_type, editor, None).await?;
-
-    data_result(edit_context)
+    match editor.get_field_meta(&params.field_id).await {
+        None => Err(FlowyError::record_not_found()),
+        Some(field_meta) => {
+            let type_option_data = get_type_option_data(&field_meta, &field_meta.field_type).await?;
+            let data = FieldTypeOptionData {
+                grid_id: params.grid_id,
+                field: field_meta.into(),
+                type_option_data,
+            };
+            data_result(data)
+        }
+    }
 }
 
+/// Create FieldMeta and save it. Return the FieldTypeOptionData.
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
-pub(crate) async fn get_field_type_option_data_handler(
+pub(crate) async fn create_field_type_option_data_handler(
     data: Data<EditFieldPayload>,
     manager: AppData<Arc<GridManager>>,
 ) -> DataResult<FieldTypeOptionData, FlowyError> {
-    let params: EditFieldParams = data.into_inner().try_into()?;
+    let params: CreateFieldParams = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
-    let field_meta = get_or_create_field_meta(params.field_id, &params.field_type, editor).await?;
+    let field_meta = editor.create_next_field_meta(&params.field_type).await?;
     let type_option_data = get_type_option_data(&field_meta, &field_meta.field_type).await?;
 
     data_result(FieldTypeOptionData {
-        field_id: field_meta.id.clone(),
+        grid_id: params.grid_id,
+        field: field_meta.into(),
         type_option_data,
     })
 }
@@ -163,23 +182,7 @@ pub(crate) async fn move_item_handler(
     Ok(())
 }
 
-async fn make_edit_field_context(
-    grid_id: &str,
-    field_id: Option<String>,
-    field_type: FieldType,
-    editor: Arc<ClientGridEditor>,
-    field_meta: Option<FieldMeta>,
-) -> FlowyResult<EditFieldContext> {
-    let field_meta = field_meta.unwrap_or(get_or_create_field_meta(field_id, &field_type, editor).await?);
-    let type_option_data = get_type_option_data(&field_meta, &field_type).await?;
-    let field: Field = field_meta.into();
-    Ok(EditFieldContext {
-        grid_id: grid_id.to_string(),
-        grid_field: field,
-        type_option_data,
-    })
-}
-
+/// The FieldMeta contains multiple data, each of them belongs to a specific FieldType.
 async fn get_type_option_data(field_meta: &FieldMeta, field_type: &FieldType) -> FlowyResult<Vec<u8>> {
     let s = field_meta
         .get_type_option_str(field_type)
@@ -190,20 +193,6 @@ async fn get_type_option_data(field_meta: &FieldMeta, field_type: &FieldType) ->
     Ok(type_option_data)
 }
 
-async fn get_or_create_field_meta(
-    field_id: Option<String>,
-    field_type: &FieldType,
-    editor: Arc<ClientGridEditor>,
-) -> FlowyResult<FieldMeta> {
-    match field_id {
-        None => editor.create_next_field_meta(field_type).await,
-        Some(field_id) => match editor.get_field_meta(&field_id).await {
-            None => editor.create_next_field_meta(field_type).await,
-            Some(field_meta) => Ok(field_meta),
-        },
-    }
-}
-
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn get_row_handler(
     data: Data<RowIdentifierPayload>,

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

@@ -18,9 +18,9 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
         .event(GridEvent::DeleteField, delete_field_handler)
         .event(GridEvent::SwitchToField, switch_to_field_handler)
         .event(GridEvent::DuplicateField, duplicate_field_handler)
-        .event(GridEvent::GetEditFieldContext, get_field_context_handler)
         .event(GridEvent::MoveItem, move_item_handler)
         .event(GridEvent::GetFieldTypeOption, get_field_type_option_data_handler)
+        .event(GridEvent::CreateFieldTypeOption, create_field_type_option_data_handler)
         // Row
         .event(GridEvent::CreateRow, create_row_handler)
         .event(GridEvent::GetRow, get_row_handler)
@@ -65,20 +65,20 @@ pub enum GridEvent {
     #[event(input = "FieldIdentifierPayload")]
     DeleteField = 14,
 
-    #[event(input = "EditFieldPayload", output = "EditFieldContext")]
+    #[event(input = "EditFieldPayload", output = "FieldTypeOptionData")]
     SwitchToField = 20,
 
     #[event(input = "FieldIdentifierPayload")]
     DuplicateField = 21,
 
-    #[event(input = "EditFieldPayload", output = "EditFieldContext")]
-    GetEditFieldContext = 22,
-
     #[event(input = "MoveItemPayload")]
-    MoveItem = 23,
+    MoveItem = 22,
+
+    #[event(input = "EditFieldPayload", output = "FieldTypeOptionData")]
+    GetFieldTypeOption = 23,
 
     #[event(input = "EditFieldPayload", output = "FieldTypeOptionData")]
-    GetFieldTypeOption = 24,
+    CreateFieldTypeOption = 24,
 
     #[event(input = "CreateSelectOptionPayload", output = "SelectOption")]
     NewSelectOption = 30,

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

@@ -34,9 +34,9 @@ pub enum GridEvent {
     DeleteField = 14,
     SwitchToField = 20,
     DuplicateField = 21,
-    GetEditFieldContext = 22,
-    MoveItem = 23,
-    GetFieldTypeOption = 24,
+    MoveItem = 22,
+    GetFieldTypeOption = 23,
+    CreateFieldTypeOption = 24,
     NewSelectOption = 30,
     GetSelectOptionCellData = 31,
     UpdateSelectOption = 32,
@@ -67,9 +67,9 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             14 => ::std::option::Option::Some(GridEvent::DeleteField),
             20 => ::std::option::Option::Some(GridEvent::SwitchToField),
             21 => ::std::option::Option::Some(GridEvent::DuplicateField),
-            22 => ::std::option::Option::Some(GridEvent::GetEditFieldContext),
-            23 => ::std::option::Option::Some(GridEvent::MoveItem),
-            24 => ::std::option::Option::Some(GridEvent::GetFieldTypeOption),
+            22 => ::std::option::Option::Some(GridEvent::MoveItem),
+            23 => ::std::option::Option::Some(GridEvent::GetFieldTypeOption),
+            24 => ::std::option::Option::Some(GridEvent::CreateFieldTypeOption),
             30 => ::std::option::Option::Some(GridEvent::NewSelectOption),
             31 => ::std::option::Option::Some(GridEvent::GetSelectOptionCellData),
             32 => ::std::option::Option::Some(GridEvent::UpdateSelectOption),
@@ -97,9 +97,9 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             GridEvent::DeleteField,
             GridEvent::SwitchToField,
             GridEvent::DuplicateField,
-            GridEvent::GetEditFieldContext,
             GridEvent::MoveItem,
             GridEvent::GetFieldTypeOption,
+            GridEvent::CreateFieldTypeOption,
             GridEvent::NewSelectOption,
             GridEvent::GetSelectOptionCellData,
             GridEvent::UpdateSelectOption,
@@ -140,18 +140,18 @@ impl ::protobuf::reflect::ProtobufValue for GridEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0fevent_map.proto*\xda\x03\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
+    \n\x0fevent_map.proto*\xdc\x03\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\x19\n\x15UpdateFieldTypeOption\x10\x0c\x12\
     \x0f\n\x0bInsertField\x10\r\x12\x0f\n\x0bDeleteField\x10\x0e\x12\x11\n\r\
-    SwitchToField\x10\x14\x12\x12\n\x0eDuplicateField\x10\x15\x12\x17\n\x13G\
-    etEditFieldContext\x10\x16\x12\x0c\n\x08MoveItem\x10\x17\x12\x16\n\x12Ge\
-    tFieldTypeOption\x10\x18\x12\x13\n\x0fNewSelectOption\x10\x1e\x12\x1b\n\
-    \x17GetSelectOptionCellData\x10\x1f\x12\x16\n\x12UpdateSelectOption\x10\
-    \x20\x12\r\n\tCreateRow\x102\x12\n\n\x06GetRow\x103\x12\r\n\tDeleteRow\
-    \x104\x12\x10\n\x0cDuplicateRow\x105\x12\x0b\n\x07GetCell\x10F\x12\x0e\n\
-    \nUpdateCell\x10G\x12\x1a\n\x16UpdateSelectOptionCell\x10H\x12\x12\n\x0e\
-    UpdateDateCell\x10P\x12\x13\n\x0fGetDateCellData\x10Zb\x06proto3\
+    SwitchToField\x10\x14\x12\x12\n\x0eDuplicateField\x10\x15\x12\x0c\n\x08M\
+    oveItem\x10\x16\x12\x16\n\x12GetFieldTypeOption\x10\x17\x12\x19\n\x15Cre\
+    ateFieldTypeOption\x10\x18\x12\x13\n\x0fNewSelectOption\x10\x1e\x12\x1b\
+    \n\x17GetSelectOptionCellData\x10\x1f\x12\x16\n\x12UpdateSelectOption\
+    \x10\x20\x12\r\n\tCreateRow\x102\x12\n\n\x06GetRow\x103\x12\r\n\tDeleteR\
+    ow\x104\x12\x10\n\x0cDuplicateRow\x105\x12\x0b\n\x07GetCell\x10F\x12\x0e\
+    \n\nUpdateCell\x10G\x12\x1a\n\x16UpdateSelectOptionCell\x10H\x12\x12\n\
+    \x0eUpdateDateCell\x10P\x12\x13\n\x0fGetDateCellData\x10Zb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -10,9 +10,9 @@ enum GridEvent {
     DeleteField = 14;
     SwitchToField = 20;
     DuplicateField = 21;
-    GetEditFieldContext = 22;
-    MoveItem = 23;
-    GetFieldTypeOption = 24;
+    MoveItem = 22;
+    GetFieldTypeOption = 23;
+    CreateFieldTypeOption = 24;
     NewSelectOption = 30;
     GetSelectOptionCellData = 31;
     UpdateSelectOption = 32;

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

@@ -121,12 +121,22 @@ impl ClientGridEditor {
         Ok(())
     }
 
-    pub async fn create_next_field_meta(&self, field_type: &FieldType) -> FlowyResult<FieldMeta> {
+    pub async fn next_field_meta(&self, field_type: &FieldType) -> FlowyResult<FieldMeta> {
         let name = format!("Property {}", self.grid_pad.read().await.fields().len() + 1);
         let field_meta = FieldBuilder::from_field_type(field_type).name(&name).build();
         Ok(field_meta)
     }
 
+    pub async fn create_next_field_meta(&self, field_type: &FieldType) -> FlowyResult<FieldMeta> {
+        let field_meta = self.next_field_meta(field_type).await?;
+        let _ = self
+            .modify(|grid| Ok(grid.create_field_meta(field_meta.clone(), None)?))
+            .await?;
+        let _ = self.notify_did_insert_grid_field(&field_meta.id).await?;
+
+        Ok(field_meta)
+    }
+
     pub async fn contain_field(&self, field_id: &str) -> bool {
         self.grid_pad.read().await.contain_field(field_id)
     }

+ 31 - 7
shared-lib/flowy-grid-data-model/src/entities/grid.rs

@@ -167,16 +167,19 @@ pub struct EditFieldPayload {
     #[pb(index = 1)]
     pub grid_id: String,
 
-    #[pb(index = 2, one_of)]
-    pub field_id: Option<String>,
+    #[pb(index = 2)]
+    pub field_id: String,
 
     #[pb(index = 3)]
     pub field_type: FieldType,
+
+    #[pb(index = 4)]
+    pub create_if_not_exist: bool,
 }
 
 pub struct EditFieldParams {
     pub grid_id: String,
-    pub field_id: Option<String>,
+    pub field_id: String,
     pub field_type: FieldType,
 }
 
@@ -185,17 +188,35 @@ impl TryInto<EditFieldParams> for EditFieldPayload {
 
     fn try_into(self) -> Result<EditFieldParams, Self::Error> {
         let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
-        // let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
+        let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
         Ok(EditFieldParams {
             grid_id: grid_id.0,
-            field_id: self.field_id,
+            field_id: field_id.0,
+            field_type: self.field_type,
+        })
+    }
+}
+
+pub struct CreateFieldParams {
+    pub grid_id: String,
+    pub field_type: FieldType,
+}
+
+impl TryInto<CreateFieldParams> for EditFieldPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<CreateFieldParams, Self::Error> {
+        let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+
+        Ok(CreateFieldParams {
+            grid_id: grid_id.0,
             field_type: self.field_type,
         })
     }
 }
 
 #[derive(Debug, Default, ProtoBuf)]
-pub struct EditFieldContext {
+pub struct FieldTypeOptionContext {
     #[pb(index = 1)]
     pub grid_id: String,
 
@@ -209,9 +230,12 @@ pub struct EditFieldContext {
 #[derive(Debug, Default, ProtoBuf)]
 pub struct FieldTypeOptionData {
     #[pb(index = 1)]
-    pub field_id: String,
+    pub grid_id: String,
 
     #[pb(index = 2)]
+    pub field: Field,
+
+    #[pb(index = 3)]
     pub type_option_data: Vec<u8>,
 }
 

+ 233 - 179
shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs

@@ -1644,9 +1644,9 @@ impl ::protobuf::reflect::ProtobufValue for GetEditFieldContextPayload {
 pub struct EditFieldPayload {
     // message fields
     pub grid_id: ::std::string::String,
+    pub field_id: ::std::string::String,
     pub field_type: FieldType,
-    // message oneof groups
-    pub one_of_field_id: ::std::option::Option<EditFieldPayload_oneof_one_of_field_id>,
+    pub create_if_not_exist: bool,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -1658,11 +1658,6 @@ impl<'a> ::std::default::Default for &'a EditFieldPayload {
     }
 }
 
-#[derive(Clone,PartialEq,Debug)]
-pub enum EditFieldPayload_oneof_one_of_field_id {
-    field_id(::std::string::String),
-}
-
 impl EditFieldPayload {
     pub fn new() -> EditFieldPayload {
         ::std::default::Default::default()
@@ -1698,49 +1693,26 @@ impl EditFieldPayload {
 
 
     pub fn get_field_id(&self) -> &str {
-        match self.one_of_field_id {
-            ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(ref v)) => v,
-            _ => "",
-        }
+        &self.field_id
     }
     pub fn clear_field_id(&mut self) {
-        self.one_of_field_id = ::std::option::Option::None;
-    }
-
-    pub fn has_field_id(&self) -> bool {
-        match self.one_of_field_id {
-            ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(..)) => true,
-            _ => false,
-        }
+        self.field_id.clear();
     }
 
     // Param is passed by value, moved
     pub fn set_field_id(&mut self, v: ::std::string::String) {
-        self.one_of_field_id = ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(v))
+        self.field_id = v;
     }
 
     // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
     pub fn mut_field_id(&mut self) -> &mut ::std::string::String {
-        if let ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(_)) = self.one_of_field_id {
-        } else {
-            self.one_of_field_id = ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(::std::string::String::new()));
-        }
-        match self.one_of_field_id {
-            ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(ref mut v)) => v,
-            _ => panic!(),
-        }
+        &mut self.field_id
     }
 
     // Take field
     pub fn take_field_id(&mut self) -> ::std::string::String {
-        if self.has_field_id() {
-            match self.one_of_field_id.take() {
-                ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(v)) => v,
-                _ => panic!(),
-            }
-        } else {
-            ::std::string::String::new()
-        }
+        ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
     }
 
     // .FieldType field_type = 3;
@@ -1757,6 +1729,21 @@ impl EditFieldPayload {
     pub fn set_field_type(&mut self, v: FieldType) {
         self.field_type = v;
     }
+
+    // bool create_if_not_exist = 4;
+
+
+    pub fn get_create_if_not_exist(&self) -> bool {
+        self.create_if_not_exist
+    }
+    pub fn clear_create_if_not_exist(&mut self) {
+        self.create_if_not_exist = false;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_create_if_not_exist(&mut self, v: bool) {
+        self.create_if_not_exist = v;
+    }
 }
 
 impl ::protobuf::Message for EditFieldPayload {
@@ -1772,14 +1759,18 @@ impl ::protobuf::Message for EditFieldPayload {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?;
                 },
                 2 => {
-                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
-                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
-                    }
-                    self.one_of_field_id = ::std::option::Option::Some(EditFieldPayload_oneof_one_of_field_id::field_id(is.read_string()?));
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
                 },
                 3 => {
                     ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.field_type, 3, &mut self.unknown_fields)?
                 },
+                4 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_bool()?;
+                    self.create_if_not_exist = tmp;
+                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -1795,15 +1786,14 @@ impl ::protobuf::Message for EditFieldPayload {
         if !self.grid_id.is_empty() {
             my_size += ::protobuf::rt::string_size(1, &self.grid_id);
         }
+        if !self.field_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.field_id);
+        }
         if self.field_type != FieldType::RichText {
             my_size += ::protobuf::rt::enum_size(3, self.field_type);
         }
-        if let ::std::option::Option::Some(ref v) = self.one_of_field_id {
-            match v {
-                &EditFieldPayload_oneof_one_of_field_id::field_id(ref v) => {
-                    my_size += ::protobuf::rt::string_size(2, &v);
-                },
-            };
+        if self.create_if_not_exist != false {
+            my_size += 2;
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -1814,15 +1804,14 @@ impl ::protobuf::Message for EditFieldPayload {
         if !self.grid_id.is_empty() {
             os.write_string(1, &self.grid_id)?;
         }
+        if !self.field_id.is_empty() {
+            os.write_string(2, &self.field_id)?;
+        }
         if self.field_type != FieldType::RichText {
             os.write_enum(3, ::protobuf::ProtobufEnum::value(&self.field_type))?;
         }
-        if let ::std::option::Option::Some(ref v) = self.one_of_field_id {
-            match v {
-                &EditFieldPayload_oneof_one_of_field_id::field_id(ref v) => {
-                    os.write_string(2, v)?;
-                },
-            };
+        if self.create_if_not_exist != false {
+            os.write_bool(4, self.create_if_not_exist)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -1867,16 +1856,21 @@ impl ::protobuf::Message for EditFieldPayload {
                 |m: &EditFieldPayload| { &m.grid_id },
                 |m: &mut EditFieldPayload| { &mut m.grid_id },
             ));
-            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "field_id",
-                EditFieldPayload::has_field_id,
-                EditFieldPayload::get_field_id,
+                |m: &EditFieldPayload| { &m.field_id },
+                |m: &mut EditFieldPayload| { &mut m.field_id },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum<FieldType>>(
                 "field_type",
                 |m: &EditFieldPayload| { &m.field_type },
                 |m: &mut EditFieldPayload| { &mut m.field_type },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
+                "create_if_not_exist",
+                |m: &EditFieldPayload| { &m.create_if_not_exist },
+                |m: &mut EditFieldPayload| { &mut m.create_if_not_exist },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<EditFieldPayload>(
                 "EditFieldPayload",
                 fields,
@@ -1894,8 +1888,9 @@ impl ::protobuf::Message for EditFieldPayload {
 impl ::protobuf::Clear for EditFieldPayload {
     fn clear(&mut self) {
         self.grid_id.clear();
-        self.one_of_field_id = ::std::option::Option::None;
+        self.field_id.clear();
         self.field_type = FieldType::RichText;
+        self.create_if_not_exist = false;
         self.unknown_fields.clear();
     }
 }
@@ -1913,7 +1908,7 @@ impl ::protobuf::reflect::ProtobufValue for EditFieldPayload {
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct EditFieldContext {
+pub struct FieldTypeOptionContext {
     // message fields
     pub grid_id: ::std::string::String,
     pub grid_field: ::protobuf::SingularPtrField<Field>,
@@ -1923,14 +1918,14 @@ pub struct EditFieldContext {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a EditFieldContext {
-    fn default() -> &'a EditFieldContext {
-        <EditFieldContext as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a FieldTypeOptionContext {
+    fn default() -> &'a FieldTypeOptionContext {
+        <FieldTypeOptionContext as ::protobuf::Message>::default_instance()
     }
 }
 
-impl EditFieldContext {
-    pub fn new() -> EditFieldContext {
+impl FieldTypeOptionContext {
+    pub fn new() -> FieldTypeOptionContext {
         ::std::default::Default::default()
     }
 
@@ -2020,7 +2015,7 @@ impl EditFieldContext {
     }
 }
 
-impl ::protobuf::Message for EditFieldContext {
+impl ::protobuf::Message for FieldTypeOptionContext {
     fn is_initialized(&self) -> bool {
         for v in &self.grid_field {
             if !v.is_initialized() {
@@ -2112,8 +2107,8 @@ impl ::protobuf::Message for EditFieldContext {
         Self::descriptor_static()
     }
 
-    fn new() -> EditFieldContext {
-        EditFieldContext::new()
+    fn new() -> FieldTypeOptionContext {
+        FieldTypeOptionContext::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -2122,34 +2117,34 @@ impl ::protobuf::Message for EditFieldContext {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "grid_id",
-                |m: &EditFieldContext| { &m.grid_id },
-                |m: &mut EditFieldContext| { &mut m.grid_id },
+                |m: &FieldTypeOptionContext| { &m.grid_id },
+                |m: &mut FieldTypeOptionContext| { &mut m.grid_id },
             ));
             fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<Field>>(
                 "grid_field",
-                |m: &EditFieldContext| { &m.grid_field },
-                |m: &mut EditFieldContext| { &mut m.grid_field },
+                |m: &FieldTypeOptionContext| { &m.grid_field },
+                |m: &mut FieldTypeOptionContext| { &mut m.grid_field },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
                 "type_option_data",
-                |m: &EditFieldContext| { &m.type_option_data },
-                |m: &mut EditFieldContext| { &mut m.type_option_data },
+                |m: &FieldTypeOptionContext| { &m.type_option_data },
+                |m: &mut FieldTypeOptionContext| { &mut m.type_option_data },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<EditFieldContext>(
-                "EditFieldContext",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<FieldTypeOptionContext>(
+                "FieldTypeOptionContext",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static EditFieldContext {
-        static instance: ::protobuf::rt::LazyV2<EditFieldContext> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(EditFieldContext::new)
+    fn default_instance() -> &'static FieldTypeOptionContext {
+        static instance: ::protobuf::rt::LazyV2<FieldTypeOptionContext> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(FieldTypeOptionContext::new)
     }
 }
 
-impl ::protobuf::Clear for EditFieldContext {
+impl ::protobuf::Clear for FieldTypeOptionContext {
     fn clear(&mut self) {
         self.grid_id.clear();
         self.grid_field.clear();
@@ -2158,13 +2153,13 @@ impl ::protobuf::Clear for EditFieldContext {
     }
 }
 
-impl ::std::fmt::Debug for EditFieldContext {
+impl ::std::fmt::Debug for FieldTypeOptionContext {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for EditFieldContext {
+impl ::protobuf::reflect::ProtobufValue for FieldTypeOptionContext {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
@@ -2173,7 +2168,8 @@ impl ::protobuf::reflect::ProtobufValue for EditFieldContext {
 #[derive(PartialEq,Clone,Default)]
 pub struct FieldTypeOptionData {
     // message fields
-    pub field_id: ::std::string::String,
+    pub grid_id: ::std::string::String,
+    pub field: ::protobuf::SingularPtrField<Field>,
     pub type_option_data: ::std::vec::Vec<u8>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
@@ -2191,33 +2187,66 @@ impl FieldTypeOptionData {
         ::std::default::Default::default()
     }
 
-    // string field_id = 1;
+    // string grid_id = 1;
 
 
-    pub fn get_field_id(&self) -> &str {
-        &self.field_id
+    pub fn get_grid_id(&self) -> &str {
+        &self.grid_id
     }
-    pub fn clear_field_id(&mut self) {
-        self.field_id.clear();
+    pub fn clear_grid_id(&mut self) {
+        self.grid_id.clear();
     }
 
     // Param is passed by value, moved
-    pub fn set_field_id(&mut self, v: ::std::string::String) {
-        self.field_id = v;
+    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_field_id(&mut self) -> &mut ::std::string::String {
-        &mut self.field_id
+    pub fn mut_grid_id(&mut self) -> &mut ::std::string::String {
+        &mut self.grid_id
     }
 
     // Take field
-    pub fn take_field_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
+    pub fn take_grid_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.grid_id, ::std::string::String::new())
     }
 
-    // bytes type_option_data = 2;
+    // .Field field = 2;
+
+
+    pub fn get_field(&self) -> &Field {
+        self.field.as_ref().unwrap_or_else(|| <Field as ::protobuf::Message>::default_instance())
+    }
+    pub fn clear_field(&mut self) {
+        self.field.clear();
+    }
+
+    pub fn has_field(&self) -> bool {
+        self.field.is_some()
+    }
+
+    // Param is passed by value, moved
+    pub fn set_field(&mut self, v: Field) {
+        self.field = ::protobuf::SingularPtrField::some(v);
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_field(&mut self) -> &mut Field {
+        if self.field.is_none() {
+            self.field.set_default();
+        }
+        self.field.as_mut().unwrap()
+    }
+
+    // Take field
+    pub fn take_field(&mut self) -> Field {
+        self.field.take().unwrap_or_else(|| Field::new())
+    }
+
+    // bytes type_option_data = 3;
 
 
     pub fn get_type_option_data(&self) -> &[u8] {
@@ -2246,6 +2275,11 @@ impl FieldTypeOptionData {
 
 impl ::protobuf::Message for FieldTypeOptionData {
     fn is_initialized(&self) -> bool {
+        for v in &self.field {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
         true
     }
 
@@ -2254,9 +2288,12 @@ impl ::protobuf::Message for FieldTypeOptionData {
             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.field_id)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?;
                 },
                 2 => {
+                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.field)?;
+                },
+                3 => {
                     ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.type_option_data)?;
                 },
                 _ => {
@@ -2271,11 +2308,15 @@ impl ::protobuf::Message for FieldTypeOptionData {
     #[allow(unused_variables)]
     fn compute_size(&self) -> u32 {
         let mut my_size = 0;
-        if !self.field_id.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.field_id);
+        if !self.grid_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.grid_id);
+        }
+        if let Some(ref v) = self.field.as_ref() {
+            let len = v.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
         }
         if !self.type_option_data.is_empty() {
-            my_size += ::protobuf::rt::bytes_size(2, &self.type_option_data);
+            my_size += ::protobuf::rt::bytes_size(3, &self.type_option_data);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -2283,11 +2324,16 @@ impl ::protobuf::Message for FieldTypeOptionData {
     }
 
     fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if !self.field_id.is_empty() {
-            os.write_string(1, &self.field_id)?;
+        if !self.grid_id.is_empty() {
+            os.write_string(1, &self.grid_id)?;
+        }
+        if let Some(ref v) = self.field.as_ref() {
+            os.write_tag(2, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
         }
         if !self.type_option_data.is_empty() {
-            os.write_bytes(2, &self.type_option_data)?;
+            os.write_bytes(3, &self.type_option_data)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -2328,9 +2374,14 @@ impl ::protobuf::Message for FieldTypeOptionData {
         descriptor.get(|| {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "field_id",
-                |m: &FieldTypeOptionData| { &m.field_id },
-                |m: &mut FieldTypeOptionData| { &mut m.field_id },
+                "grid_id",
+                |m: &FieldTypeOptionData| { &m.grid_id },
+                |m: &mut FieldTypeOptionData| { &mut m.grid_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<Field>>(
+                "field",
+                |m: &FieldTypeOptionData| { &m.field },
+                |m: &mut FieldTypeOptionData| { &mut m.field },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
                 "type_option_data",
@@ -2353,7 +2404,8 @@ impl ::protobuf::Message for FieldTypeOptionData {
 
 impl ::protobuf::Clear for FieldTypeOptionData {
     fn clear(&mut self) {
-        self.field_id.clear();
+        self.grid_id.clear();
+        self.field.clear();
         self.type_option_data.clear();
         self.unknown_fields.clear();
     }
@@ -8254,82 +8306,84 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     extPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x1b\n\
     \x08field_id\x18\x02\x20\x01(\tH\0R\x07fieldId\x12)\n\nfield_type\x18\
     \x03\x20\x01(\x0e2\n.FieldTypeR\tfieldTypeB\x11\n\x0fone_of_field_id\"\
-    \x86\x01\n\x10EditFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
-    \x06gridId\x12\x1b\n\x08field_id\x18\x02\x20\x01(\tH\0R\x07fieldId\x12)\
-    \n\nfield_type\x18\x03\x20\x01(\x0e2\n.FieldTypeR\tfieldTypeB\x11\n\x0fo\
-    ne_of_field_id\"|\n\x10EditFieldContext\x12\x17\n\x07grid_id\x18\x01\x20\
-    \x01(\tR\x06gridId\x12%\n\ngrid_field\x18\x02\x20\x01(\x0b2\x06.FieldR\t\
-    gridField\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOption\
-    Data\"Z\n\x13FieldTypeOptionData\x12\x19\n\x08field_id\x18\x01\x20\x01(\
-    \tR\x07fieldId\x12(\n\x10type_option_data\x18\x02\x20\x01(\x0cR\x0etypeO\
-    ptionData\"-\n\rRepeatedField\x12\x1c\n\x05items\x18\x01\x20\x03(\x0b2\
-    \x06.FieldR\x05items\"7\n\x12RepeatedFieldOrder\x12!\n\x05items\x18\x01\
-    \x20\x03(\x0b2\x0b.FieldOrderR\x05items\"T\n\x08RowOrder\x12\x15\n\x06ro\
-    w_id\x18\x01\x20\x01(\tR\x05rowId\x12\x19\n\x08block_id\x18\x02\x20\x01(\
-    \tR\x07blockId\x12\x16\n\x06height\x18\x03\x20\x01(\x05R\x06height\"\xb8\
-    \x01\n\x03Row\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12@\n\x10cell_b\
-    y_field_id\x18\x02\x20\x03(\x0b2\x17.Row.CellByFieldIdEntryR\rcellByFiel\
-    dId\x12\x16\n\x06height\x18\x03\x20\x01(\x05R\x06height\x1aG\n\x12CellBy\
-    FieldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x1b\n\x05va\
-    lue\x18\x02\x20\x01(\x0b2\x05.CellR\x05value:\x028\x01\")\n\x0bRepeatedR\
-    ow\x12\x1a\n\x05items\x18\x01\x20\x03(\x0b2\x04.RowR\x05items\"5\n\x11Re\
-    peatedGridBlock\x12\x20\n\x05items\x18\x01\x20\x03(\x0b2\n.GridBlockR\
-    \x05items\"U\n\x0eGridBlockOrder\x12\x19\n\x08block_id\x18\x01\x20\x01(\
-    \tR\x07blockId\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trow\
-    Orders\"_\n\rIndexRowOrder\x12&\n\trow_order\x18\x01\x20\x01(\x0b2\t.Row\
-    OrderR\x08rowOrder\x12\x16\n\x05index\x18\x02\x20\x01(\x05H\0R\x05indexB\
-    \x0e\n\x0cone_of_index\"Q\n\x0fUpdatedRowOrder\x12&\n\trow_order\x18\x01\
-    \x20\x01(\x0b2\t.RowOrderR\x08rowOrder\x12\x16\n\x03row\x18\x02\x20\x01(\
-    \x0b2\x04.RowR\x03row\"\xc6\x01\n\x11GridRowsChangeset\x12\x19\n\x08bloc\
-    k_id\x18\x01\x20\x01(\tR\x07blockId\x123\n\rinserted_rows\x18\x02\x20\
-    \x03(\x0b2\x0e.IndexRowOrderR\x0cinsertedRows\x12,\n\x0cdeleted_rows\x18\
-    \x03\x20\x03(\x0b2\t.RowOrderR\x0bdeletedRows\x123\n\x0cupdated_rows\x18\
-    \x04\x20\x03(\x0b2\x10.UpdatedRowOrderR\x0bupdatedRows\"E\n\tGridBlock\
-    \x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12(\n\nrow_orders\x18\x02\
-    \x20\x03(\x0b2\t.RowOrderR\trowOrders\"O\n\x04Cell\x12\x19\n\x08field_id\
-    \x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\x07content\x18\x02\x20\x01(\tR\
-    \x07content\x12\x12\n\x04data\x18\x03\x20\x01(\tR\x04data\"+\n\x0cRepeat\
-    edCell\x12\x1b\n\x05items\x18\x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\
-    \x11CreateGridPayload\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\"\
-    \x1e\n\x06GridId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"#\n\
-    \x0bGridBlockId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\"f\n\x10\
-    CreateRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\
-    \"\n\x0cstart_row_id\x18\x02\x20\x01(\tH\0R\nstartRowIdB\x15\n\x13one_of\
-    _start_row_id\"\xb6\x01\n\x12InsertFieldPayload\x12\x17\n\x07grid_id\x18\
-    \x01\x20\x01(\tR\x06gridId\x12\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.\
-    FieldR\x05field\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etype\
-    OptionData\x12&\n\x0estart_field_id\x18\x04\x20\x01(\tH\0R\x0cstartField\
-    IdB\x17\n\x15one_of_start_field_id\"|\n\x1cUpdateFieldTypeOptionPayload\
-    \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_i\
-    d\x18\x02\x20\x01(\tR\x07fieldId\x12(\n\x10type_option_data\x18\x03\x20\
-    \x01(\x0cR\x0etypeOptionData\"d\n\x11QueryFieldPayload\x12\x17\n\x07grid\
-    _id\x18\x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_orders\x18\x02\x20\x01\
-    (\x0b2\x13.RepeatedFieldOrderR\x0bfieldOrders\"e\n\x16QueryGridBlocksPay\
-    load\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_\
-    orders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrders\"\xa8\
-    \x03\n\x15FieldChangesetPayload\x12\x19\n\x08field_id\x18\x01\x20\x01(\t\
-    R\x07fieldId\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\
-    \n\x04name\x18\x03\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\x04\x20\
-    \x01(\tH\x01R\x04desc\x12+\n\nfield_type\x18\x05\x20\x01(\x0e2\n.FieldTy\
-    peH\x02R\tfieldType\x12\x18\n\x06frozen\x18\x06\x20\x01(\x08H\x03R\x06fr\
-    ozen\x12\x20\n\nvisibility\x18\x07\x20\x01(\x08H\x04R\nvisibility\x12\
-    \x16\n\x05width\x18\x08\x20\x01(\x05H\x05R\x05width\x12*\n\x10type_optio\
-    n_data\x18\t\x20\x01(\x0cH\x06R\x0etypeOptionDataB\r\n\x0bone_of_nameB\r\
-    \n\x0bone_of_descB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\
-    \n\x11one_of_visibilityB\x0e\n\x0cone_of_widthB\x19\n\x17one_of_type_opt\
-    ion_data\"\x9c\x01\n\x0fMoveItemPayload\x12\x17\n\x07grid_id\x18\x01\x20\
-    \x01(\tR\x06gridId\x12\x17\n\x07item_id\x18\x02\x20\x01(\tR\x06itemId\
-    \x12\x1d\n\nfrom_index\x18\x03\x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_\
-    index\x18\x04\x20\x01(\x05R\x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\
-    \x0e2\r.MoveItemTypeR\x02ty\"\xb3\x01\n\rCellChangeset\x12\x17\n\x07grid\
-    _id\x18\x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\t\
-    R\x05rowId\x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x126\n\
-    \x16cell_content_changeset\x18\x04\x20\x01(\tH\0R\x14cellContentChangese\
-    tB\x1f\n\x1done_of_cell_content_changeset**\n\x0cMoveItemType\x12\r\n\tM\
-    oveField\x10\0\x12\x0b\n\x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\x08\
-    RichText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\
-    \x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\
-    \x0c\n\x08Checkbox\x10\x05b\x06proto3\
+    \xa0\x01\n\x10EditFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
+    \x06gridId\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07fieldId\x12)\n\n\
+    field_type\x18\x03\x20\x01(\x0e2\n.FieldTypeR\tfieldType\x12-\n\x13creat\
+    e_if_not_exist\x18\x04\x20\x01(\x08R\x10createIfNotExist\"\x82\x01\n\x16\
+    FieldTypeOptionContext\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridI\
+    d\x12%\n\ngrid_field\x18\x02\x20\x01(\x0b2\x06.FieldR\tgridField\x12(\n\
+    \x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\"v\n\x13Fie\
+    ldTypeOptionData\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\
+    \x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\x12(\n\x10type\
+    _option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\"-\n\rRepeatedField\
+    \x12\x1c\n\x05items\x18\x01\x20\x03(\x0b2\x06.FieldR\x05items\"7\n\x12Re\
+    peatedFieldOrder\x12!\n\x05items\x18\x01\x20\x03(\x0b2\x0b.FieldOrderR\
+    \x05items\"T\n\x08RowOrder\x12\x15\n\x06row_id\x18\x01\x20\x01(\tR\x05ro\
+    wId\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07blockId\x12\x16\n\x06he\
+    ight\x18\x03\x20\x01(\x05R\x06height\"\xb8\x01\n\x03Row\x12\x0e\n\x02id\
+    \x18\x01\x20\x01(\tR\x02id\x12@\n\x10cell_by_field_id\x18\x02\x20\x03(\
+    \x0b2\x17.Row.CellByFieldIdEntryR\rcellByFieldId\x12\x16\n\x06height\x18\
+    \x03\x20\x01(\x05R\x06height\x1aG\n\x12CellByFieldIdEntry\x12\x10\n\x03k\
+    ey\x18\x01\x20\x01(\tR\x03key\x12\x1b\n\x05value\x18\x02\x20\x01(\x0b2\
+    \x05.CellR\x05value:\x028\x01\")\n\x0bRepeatedRow\x12\x1a\n\x05items\x18\
+    \x01\x20\x03(\x0b2\x04.RowR\x05items\"5\n\x11RepeatedGridBlock\x12\x20\n\
+    \x05items\x18\x01\x20\x03(\x0b2\n.GridBlockR\x05items\"U\n\x0eGridBlockO\
+    rder\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x12(\n\nrow_or\
+    ders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\"_\n\rIndexRowOrder\
+    \x12&\n\trow_order\x18\x01\x20\x01(\x0b2\t.RowOrderR\x08rowOrder\x12\x16\
+    \n\x05index\x18\x02\x20\x01(\x05H\0R\x05indexB\x0e\n\x0cone_of_index\"Q\
+    \n\x0fUpdatedRowOrder\x12&\n\trow_order\x18\x01\x20\x01(\x0b2\t.RowOrder\
+    R\x08rowOrder\x12\x16\n\x03row\x18\x02\x20\x01(\x0b2\x04.RowR\x03row\"\
+    \xc6\x01\n\x11GridRowsChangeset\x12\x19\n\x08block_id\x18\x01\x20\x01(\t\
+    R\x07blockId\x123\n\rinserted_rows\x18\x02\x20\x03(\x0b2\x0e.IndexRowOrd\
+    erR\x0cinsertedRows\x12,\n\x0cdeleted_rows\x18\x03\x20\x03(\x0b2\t.RowOr\
+    derR\x0bdeletedRows\x123\n\x0cupdated_rows\x18\x04\x20\x03(\x0b2\x10.Upd\
+    atedRowOrderR\x0bupdatedRows\"E\n\tGridBlock\x12\x0e\n\x02id\x18\x01\x20\
+    \x01(\tR\x02id\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trow\
+    Orders\"O\n\x04Cell\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\
+    \x12\x18\n\x07content\x18\x02\x20\x01(\tR\x07content\x12\x12\n\x04data\
+    \x18\x03\x20\x01(\tR\x04data\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\
+    \x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\x12\
+    \n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\x05va\
+    lue\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\x05value\
+    \x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\x07grid\
+    _id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\x02\x20\
+    \x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\"\xb6\x01\n\x12Ins\
+    ertFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\
+    \x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\x12(\n\x10type\
+    _option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\x12&\n\x0estart_fie\
+    ld_id\x18\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\x15one_of_start_fiel\
+    d_id\"|\n\x1cUpdateFieldTypeOptionPayload\x12\x17\n\x07grid_id\x18\x01\
+    \x20\x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07fiel\
+    dId\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\"\
+    d\n\x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gri\
+    dId\x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFieldOrderR\
+    \x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\x12\x17\n\x07grid_id\x18\
+    \x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\x0b2\
+    \x0f.GridBlockOrderR\x0bblockOrders\"\xa8\x03\n\x15FieldChangesetPayload\
+    \x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x17\n\x07grid_\
+    id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04name\x18\x03\x20\x01(\tH\0\
+    R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\x01R\x04desc\x12+\n\nfie\
+    ld_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\tfieldType\x12\x18\n\x06\
+    frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\x20\n\nvisibility\x18\
+    \x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05width\x18\x08\x20\x01(\
+    \x05H\x05R\x05width\x12*\n\x10type_option_data\x18\t\x20\x01(\x0cH\x06R\
+    \x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_of_descB\x13\n\x11one\
+    _of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_of_visibilityB\x0e\n\
+    \x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\x9c\x01\n\x0fMoveIt\
+    emPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x17\n\
+    \x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\nfrom_index\x18\x03\
+    \x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\x04\x20\x01(\x05R\
+    \x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.MoveItemTypeR\x02ty\
+    \"\xb3\x01\n\rCellChangeset\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06\
+    gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08fie\
+    ld_id\x18\x03\x20\x01(\tR\x07fieldId\x126\n\x16cell_content_changeset\
+    \x18\x04\x20\x01(\tH\0R\x14cellContentChangesetB\x1f\n\x1done_of_cell_co\
+    ntent_changeset**\n\x0cMoveItemType\x12\r\n\tMoveField\x10\0\x12\x0b\n\
+    \x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\x08RichText\x10\0\x12\n\n\
+    \x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0cSingleSele\
+    ct\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Checkbox\x10\
+    \x05b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -35,17 +35,19 @@ message GetEditFieldContextPayload {
 }
 message EditFieldPayload {
     string grid_id = 1;
-    oneof one_of_field_id { string field_id = 2; };
+    string field_id = 2;
     FieldType field_type = 3;
+    bool create_if_not_exist = 4;
 }
-message EditFieldContext {
+message FieldTypeOptionContext {
     string grid_id = 1;
     Field grid_field = 2;
     bytes type_option_data = 3;
 }
 message FieldTypeOptionData {
-    string field_id = 1;
-    bytes type_option_data = 2;
+    string grid_id = 1;
+    Field field = 2;
+    bytes type_option_data = 3;
 }
 message RepeatedField {
     repeated Field items = 1;

+ 1 - 0
shared-lib/lib-infra/Cargo.toml

@@ -16,6 +16,7 @@ rand = "0.8.5"
 serde = { version = "1.0", features = ["derive"]}
 serde_json = "1.0"
 
+
 cmd_lib = { version = "1", optional = true }
 protoc-rust = { version = "2", optional = true }
 walkdir = { version = "2", optional = true }