Przeglądaj źródła

chore: add update field and create field handler

appflowy 3 lat temu
rodzic
commit
694963a47d
97 zmienionych plików z 2513 dodań i 991 usunięć
  1. 3 3
      frontend/app_flowy/lib/startup/home_deps_resolver.dart
  2. 1 1
      frontend/app_flowy/lib/workspace/application/app/app_bloc.dart
  3. 1 1
      frontend/app_flowy/lib/workspace/application/app/app_listener.dart
  4. 4 2
      frontend/app_flowy/lib/workspace/application/grid/field/grid_header_bloc.dart
  5. 48 18
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  6. 16 33
      frontend/app_flowy/lib/workspace/application/grid/grid_block_service.dart
  7. 47 2
      frontend/app_flowy/lib/workspace/application/grid/grid_listenr.dart
  8. 1 1
      frontend/app_flowy/lib/workspace/application/trash/trash_bloc.dart
  9. 1 1
      frontend/app_flowy/lib/workspace/application/trash/trash_listener.dart
  10. 20 14
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
  11. 20 29
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart
  12. 7 20
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header_cell.dart
  13. 34 0
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
  14. 9 5
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbenum.dart
  15. 7 5
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbjson.dart
  16. 104 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
  17. 16 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
  18. 71 57
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pb.dart
  19. 11 10
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pbjson.dart
  20. 13 13
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_description.pb.dart
  21. 5 5
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_description.pbjson.dart
  22. 58 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_type_option.pb.dart
  23. 7 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_type_option.pbenum.dart
  24. 20 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_type_option.pbjson.dart
  25. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_type_option.pbserver.dart
  26. 4 4
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/dart_notification.pbenum.dart
  27. 3 3
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/dart_notification.pbjson.dart
  28. 76 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_type_option.pb.dart
  29. 45 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_type_option.pbenum.dart
  30. 45 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_type_option.pbjson.dart
  31. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_type_option.pbserver.dart
  32. 7 3
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart
  33. 6 4
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart
  34. 118 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pb.dart
  35. 30 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbenum.dart
  36. 37 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbjson.dart
  37. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbserver.dart
  38. 4 4
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/protobuf.dart
  39. 196 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pb.dart
  40. 7 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pbenum.dart
  41. 44 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pbjson.dart
  42. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pbserver.dart
  43. 13 13
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_description.pb.dart
  44. 5 5
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_description.pbjson.dart
  45. 1 1
      frontend/rust-lib/flowy-grid/Flowy.toml
  46. 2 2
      frontend/rust-lib/flowy-grid/src/dart_notification.rs
  47. 39 15
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  48. 11 3
      frontend/rust-lib/flowy-grid/src/event_map.rs
  49. 1 1
      frontend/rust-lib/flowy-grid/src/macros.rs
  50. 21 21
      frontend/rust-lib/flowy-grid/src/protobuf/model/checkbox_type_option.rs
  51. 10 10
      frontend/rust-lib/flowy-grid/src/protobuf/model/dart_notification.rs
  52. 25 25
      frontend/rust-lib/flowy-grid/src/protobuf/model/date_type_option.rs
  53. 17 10
      frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs
  54. 8 8
      frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs
  55. 29 29
      frontend/rust-lib/flowy-grid/src/protobuf/model/number_type_option.rs
  56. 48 48
      frontend/rust-lib/flowy-grid/src/protobuf/model/selection_type_option.rs
  57. 21 21
      frontend/rust-lib/flowy-grid/src/protobuf/model/text_description.rs
  58. 1 1
      frontend/rust-lib/flowy-grid/src/protobuf/proto/checkbox_type_option.proto
  59. 2 2
      frontend/rust-lib/flowy-grid/src/protobuf/proto/dart_notification.proto
  60. 1 1
      frontend/rust-lib/flowy-grid/src/protobuf/proto/date_type_option.proto
  61. 5 3
      frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto
  62. 1 1
      frontend/rust-lib/flowy-grid/src/protobuf/proto/number_type_option.proto
  63. 2 2
      frontend/rust-lib/flowy-grid/src/protobuf/proto/selection_type_option.proto
  64. 1 1
      frontend/rust-lib/flowy-grid/src/protobuf/proto/text_description.proto
  65. 5 4
      frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs
  66. 0 137
      frontend/rust-lib/flowy-grid/src/services/cell/builder/mod.rs
  67. 0 11
      frontend/rust-lib/flowy-grid/src/services/cell/description/mod.rs
  68. 0 5
      frontend/rust-lib/flowy-grid/src/services/cell/mod.rs
  69. 1 1
      frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs
  70. 2 0
      frontend/rust-lib/flowy-grid/src/services/field/mod.rs
  71. 25 5
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs
  72. 32 8
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs
  73. 11 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs
  74. 72 36
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs
  75. 47 8
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs
  76. 17 3
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_description.rs
  77. 8 5
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  78. 0 1
      frontend/rust-lib/flowy-grid/src/services/mod.rs
  79. 13 13
      frontend/rust-lib/flowy-grid/src/services/row/cell_data_serde.rs
  80. 0 1
      frontend/rust-lib/flowy-grid/src/util.rs
  81. 27 32
      frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs
  82. 57 17
      frontend/rust-lib/flowy-grid/tests/grid/script.rs
  83. 11 5
      shared-lib/flowy-error-code/src/code.rs
  84. 21 14
      shared-lib/flowy-error-code/src/protobuf/model/code.rs
  85. 6 4
      shared-lib/flowy-error-code/src/protobuf/proto/code.proto
  86. 21 0
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  87. 12 9
      shared-lib/flowy-grid-data-model/src/entities/meta.rs
  88. 0 82
      shared-lib/flowy-grid-data-model/src/parser/grid.rs
  89. 116 0
      shared-lib/flowy-grid-data-model/src/parser/grid_params.rs
  90. 0 18
      shared-lib/flowy-grid-data-model/src/parser/id.rs
  91. 22 0
      shared-lib/flowy-grid-data-model/src/parser/id_parser.rs
  92. 4 4
      shared-lib/flowy-grid-data-model/src/parser/mod.rs
  93. 353 8
      shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
  94. 126 83
      shared-lib/flowy-grid-data-model/src/protobuf/model/meta.rs
  95. 6 0
      shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
  96. 9 8
      shared-lib/flowy-grid-data-model/src/protobuf/proto/meta.proto
  97. 43 8
      shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs

+ 3 - 3
frontend/app_flowy/lib/startup/home_deps_resolver.dart

@@ -101,9 +101,9 @@ class HomeDepsResolver {
       ),
     );
 
-    getIt.registerFactoryParam<GridHeaderBloc, List<Field>, void>(
-      (data, _) => GridHeaderBloc(
-        data: GridColumnData(fields: data),
+    getIt.registerFactoryParam<GridHeaderBloc, String, List<Field>>(
+      (gridId, fields) => GridHeaderBloc(
+        data: GridColumnData(fields: fields),
         service: FieldService(),
       ),
     );

+ 1 - 1
frontend/app_flowy/lib/workspace/application/app/app_bloc.dart

@@ -19,7 +19,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
   AppBloc({required this.app, required this.service, required this.listener}) : super(AppState.initial(app)) {
     on<AppEvent>((event, emit) async {
       await event.map(initial: (e) async {
-        listener.startListening(
+        listener.start(
           viewsChanged: _handleViewsChanged,
           appUpdated: (app) => add(AppEvent.appDidUpdate(app)),
         );

+ 1 - 1
frontend/app_flowy/lib/workspace/application/app/app_listener.dart

@@ -24,7 +24,7 @@ class AppListener {
     required this.appId,
   });
 
-  void startListening({ViewsDidChangeCallback? viewsChanged, AppDidUpdateCallback? appUpdated}) {
+  void start({ViewsDidChangeCallback? viewsChanged, AppDidUpdateCallback? appUpdated}) {
     _viewsChanged = viewsChanged;
     _updated = appUpdated;
     _parser = FolderNotificationParser(id: appId, callback: _bservableCallback);

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

@@ -9,9 +9,11 @@ part 'grid_header_bloc.freezed.dart';
 
 class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
   final FieldService service;
-  final GridColumnData data;
 
-  GridHeaderBloc({required this.data, required this.service}) : super(GridHeaderState.initial(data.fields)) {
+  GridHeaderBloc({
+    required GridColumnData data,
+    required this.service,
+  }) : super(GridHeaderState.initial(data.fields)) {
     on<GridHeaderEvent>(
       (event, emit) async {
         await event.map(

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

@@ -19,13 +19,13 @@ class GridBloc extends Bloc<GridEvent, GridState> {
   late GridBlockService _blockService;
 
   GridBloc({required this.view, required this.service}) : super(GridState.initial()) {
-    _gridListener = GridListener();
+    _gridListener = GridListener(gridId: view.id);
 
     on<GridEvent>(
       (event, emit) async {
         await event.map(
           initial: (InitialGrid value) async {
-            await _loadGrid(emit);
+            await _initGrid(emit);
           },
           createRow: (_CreateRow value) {
             service.createRow(gridId: view.id);
@@ -36,6 +36,9 @@ class GridBloc extends Bloc<GridEvent, GridState> {
           rowsDidUpdate: (_RowsDidUpdate value) {
             emit(state.copyWith(rows: value.rows));
           },
+          fieldsDidUpdate: (_FieldsDidUpdate value) {
+            emit(state.copyWith(fields: value.fields));
+          },
         );
       },
     );
@@ -48,26 +51,20 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     return super.close();
   }
 
-  Future<void> _initGridBlockService(Grid grid, List<Field> fields) async {
-    _blockService = GridBlockService(
-      gridId: grid.id,
-      fields: fields,
-      blockOrders: grid.blockOrders,
-    );
-
-    _blockService.rowsUpdateNotifier.addPublishListener((result) {
+  Future<void> _initGrid(Emitter<GridState> emit) async {
+    _gridListener.fieldsUpdateNotifier.addPublishListener((result) {
       result.fold(
-        (rows) => add(GridEvent.rowsDidUpdate(rows)),
-        (err) => Log.error('$err'),
+        (fields) => add(GridEvent.fieldsDidUpdate(fields)),
+        (err) => Log.error(err),
       );
     });
-
     _gridListener.start();
+
+    await _loadGrid(emit);
   }
 
   Future<void> _loadGrid(Emitter<GridState> emit) async {
     final result = await service.openGrid(gridId: view.id);
-
     return Future(
       () => result.fold(
         (grid) async => await _loadFields(grid, emit),
@@ -81,10 +78,10 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     return Future(
       () => result.fold(
         (fields) {
-          _initGridBlockService(grid, fields.items);
+          _initGridBlockService(grid);
           emit(state.copyWith(
             grid: Some(grid),
-            fields: Some(fields.items),
+            fields: fields.items,
             loadingState: GridLoadingState.finish(left(unit)),
           ));
         },
@@ -92,6 +89,38 @@ class GridBloc extends Bloc<GridEvent, GridState> {
       ),
     );
   }
+
+  Future<void> _initGridBlockService(Grid grid) async {
+    _blockService = GridBlockService(
+      gridId: grid.id,
+      blockOrders: grid.blockOrders,
+    );
+
+    _blockService.blocksUpdateNotifier.addPublishListener((result) {
+      result.fold(
+        (blockMap) => add(GridEvent.rowsDidUpdate(_buildRows(blockMap))),
+        (err) => Log.error('$err'),
+      );
+    });
+
+    _gridListener.start();
+  }
+
+  List<GridRowData> _buildRows(GridBlockMap blockMap) {
+    List<GridRowData> rows = [];
+    blockMap.forEach((_, GridBlock gridBlock) {
+      rows.addAll(gridBlock.rowOrders.map(
+        (rowOrder) => GridRowData(
+          gridId: view.id,
+          fields: state.fields,
+          blockId: gridBlock.id,
+          rowId: rowOrder.rowId,
+          height: rowOrder.height.toDouble(),
+        ),
+      ));
+    });
+    return rows;
+  }
 }
 
 @freezed
@@ -102,20 +131,21 @@ abstract class GridEvent with _$GridEvent {
   const factory GridEvent.delete(String gridId) = _Delete;
   const factory GridEvent.createRow() = _CreateRow;
   const factory GridEvent.rowsDidUpdate(List<GridRowData> rows) = _RowsDidUpdate;
+  const factory GridEvent.fieldsDidUpdate(List<Field> fields) = _FieldsDidUpdate;
 }
 
 @freezed
 abstract class GridState with _$GridState {
   const factory GridState({
     required GridLoadingState loadingState,
-    required Option<List<Field>> fields,
+    required List<Field> fields,
     required List<GridRowData> rows,
     required Option<Grid> grid,
   }) = _GridState;
 
   factory GridState.initial() => GridState(
         loadingState: const _Loading(),
-        fields: none(),
+        fields: [],
         rows: [],
         grid: none(),
       );

+ 16 - 33
frontend/app_flowy/lib/workspace/application/grid/grid_block_service.dart

@@ -11,51 +11,34 @@ import 'package:flowy_infra/notifier.dart';
 import 'dart:async';
 import 'dart:typed_data';
 import 'package:app_flowy/core/notification_helper.dart';
-import 'grid_service.dart';
 
-typedef RowsUpdateNotifierValue = Either<List<GridRowData>, FlowyError>;
+typedef GridBlockMap = LinkedHashMap<String, GridBlock>;
+typedef BlocksUpdateNotifierValue = Either<GridBlockMap, FlowyError>;
 
 class GridBlockService {
   String gridId;
-  List<Field> fields;
-  LinkedHashMap<String, GridBlock> blockMap = LinkedHashMap();
+  GridBlockMap blockMap = GridBlockMap();
   late GridBlockListener _blockListener;
-  PublishNotifier<RowsUpdateNotifierValue> rowsUpdateNotifier = PublishNotifier<RowsUpdateNotifierValue>();
+  PublishNotifier<BlocksUpdateNotifierValue> blocksUpdateNotifier = PublishNotifier();
 
-  GridBlockService({required this.gridId, required this.fields, required List<GridBlockOrder> blockOrders}) {
-    _loadGridBlocks(blockOrders: blockOrders);
+  GridBlockService({required this.gridId, required List<GridBlockOrder> blockOrders}) {
+    _loadGridBlocks(blockOrders);
 
     _blockListener = GridBlockListener(gridId: gridId);
-    _blockListener.rowsUpdateNotifier.addPublishListener((result) {
+    _blockListener.blockUpdateNotifier.addPublishListener((result) {
       result.fold(
-        (blockId) => _loadGridBlocks(blockOrders: [GridBlockOrder.create()..blockId = blockId.value]),
+        (blockOrder) => _loadGridBlocks(blockOrder),
         (err) => Log.error(err),
       );
     });
     _blockListener.start();
   }
 
-  List<GridRowData> buildRows() {
-    List<GridRowData> rows = [];
-    blockMap.forEach((_, GridBlock gridBlock) {
-      rows.addAll(gridBlock.rowOrders.map(
-        (rowOrder) => GridRowData(
-          gridId: gridId,
-          fields: fields,
-          blockId: gridBlock.id,
-          rowId: rowOrder.rowId,
-          height: rowOrder.height.toDouble(),
-        ),
-      ));
-    });
-    return rows;
-  }
-
   Future<void> stop() async {
     await _blockListener.stop();
   }
 
-  void _loadGridBlocks({required List<GridBlockOrder> blockOrders}) {
+  void _loadGridBlocks(List<GridBlockOrder> blockOrders) {
     final payload = QueryGridBlocksPayload.create()
       ..gridId = gridId
       ..blockOrders.addAll(blockOrders);
@@ -66,9 +49,9 @@ class GridBlockService {
           for (final gridBlock in repeatedBlocks.items) {
             blockMap[gridBlock.id] = gridBlock;
           }
-          rowsUpdateNotifier.value = left(buildRows());
+          blocksUpdateNotifier.value = left(blockMap);
         },
-        (err) => rowsUpdateNotifier.value = right(err),
+        (err) => blocksUpdateNotifier.value = right(err),
       );
     });
   }
@@ -76,7 +59,7 @@ class GridBlockService {
 
 class GridBlockListener {
   final String gridId;
-  PublishNotifier<Either<GridBlockId, FlowyError>> rowsUpdateNotifier = PublishNotifier(comparable: null);
+  PublishNotifier<Either<List<GridBlockOrder>, FlowyError>> blockUpdateNotifier = PublishNotifier(comparable: null);
   StreamSubscription<SubscribeObject>? _subscription;
   late GridNotificationParser _parser;
 
@@ -95,10 +78,10 @@ class GridBlockListener {
 
   void _handleObservableType(GridNotification ty, Either<Uint8List, FlowyError> result) {
     switch (ty) {
-      case GridNotification.BlockDidUpdateRow:
+      case GridNotification.DidUpdateRow:
         result.fold(
-          (payload) => rowsUpdateNotifier.value = left(GridBlockId.fromBuffer(payload)),
-          (error) => rowsUpdateNotifier.value = right(error),
+          (payload) => blockUpdateNotifier.value = left([GridBlockOrder.fromBuffer(payload)]),
+          (error) => blockUpdateNotifier.value = right(error),
         );
         break;
 
@@ -109,6 +92,6 @@ class GridBlockListener {
 
   Future<void> stop() async {
     await _subscription?.cancel();
-    rowsUpdateNotifier.dispose();
+    blockUpdateNotifier.dispose();
   }
 }

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

@@ -1,4 +1,49 @@
+import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:flowy_sdk/protobuf/dart-notify/subject.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
+import 'package:flowy_sdk/rust_stream.dart';
+import 'package:flowy_infra/notifier.dart';
+import 'dart:async';
+import 'dart:typed_data';
+import 'package:app_flowy/core/notification_helper.dart';
+
+// typedef RowsUpdateNotifierValue = Either<List<GridRowData>, FlowyError>;
+
 class GridListener {
-  void start() {}
-  Future<void> stop() async {}
+  final String gridId;
+  PublishNotifier<Either<List<Field>, FlowyError>> fieldsUpdateNotifier = PublishNotifier(comparable: null);
+  StreamSubscription<SubscribeObject>? _subscription;
+  late GridNotificationParser _parser;
+  GridListener({required this.gridId});
+
+  void start() {
+    _parser = GridNotificationParser(
+      id: gridId,
+      callback: (ty, result) {
+        _handleObservableType(ty, result);
+      },
+    );
+
+    _subscription = RustStreamReceiver.listen((observable) => _parser.parse(observable));
+  }
+
+  void _handleObservableType(GridNotification ty, Either<Uint8List, FlowyError> result) {
+    switch (ty) {
+      case GridNotification.DidUpdateRow:
+        result.fold(
+          (payload) => fieldsUpdateNotifier.value = left(GridBlockId.fromBuffer(payload)),
+          (error) => fieldsUpdateNotifier.value = right(error),
+        );
+        break;
+      default:
+        break;
+    }
+  }
+
+  Future<void> stop() async {
+    await _subscription?.cancel();
+    fieldsUpdateNotifier.dispose();
+  }
 }

+ 1 - 1
frontend/app_flowy/lib/workspace/application/trash/trash_bloc.dart

@@ -14,7 +14,7 @@ class TrashBloc extends Bloc<TrashEvent, TrashState> {
   TrashBloc({required this.service, required this.listener}) : super(TrashState.init()) {
     on<TrashEvent>((event, emit) async {
       await event.map(initial: (e) async {
-        listener.startListening(trashUpdated: _listenTrashUpdated);
+        listener.start(trashUpdated: _listenTrashUpdated);
         final result = await service.readTrash();
         emit(result.fold(
           (object) => state.copyWith(objects: object.items, successOrFailure: left(unit)),

+ 1 - 1
frontend/app_flowy/lib/workspace/application/trash/trash_listener.dart

@@ -15,7 +15,7 @@ class TrashListener {
   TrashUpdatedCallback? _trashUpdated;
   late FolderNotificationParser _parser;
 
-  void startListening({TrashUpdatedCallback? trashUpdated}) {
+  void start({TrashUpdatedCallback? trashUpdated}) {
     _trashUpdated = trashUpdated;
     _parser = FolderNotificationParser(callback: _bservableCallback);
     _subscription = RustStreamReceiver.listen((observable) => _parser.parse(observable));

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

@@ -83,17 +83,20 @@ class _FlowyGridState extends State<FlowyGrid> {
 
   @override
   Widget build(BuildContext context) {
+    final gridId = context.read<GridBloc>().view.id;
+
     return BlocBuilder<GridBloc, GridState>(
       buildWhen: (previous, current) => previous.fields != current.fields,
       builder: (context, state) {
-        return state.fields.fold(
-          () => const Center(child: CircularProgressIndicator.adaptive()),
-          (fields) => _wrapScrollbar(fields, [
-            _buildHeader(fields),
-            _buildRows(context),
-            const GridFooter(),
-          ]),
-        );
+        if (state.fields.isEmpty) {
+          return const Center(child: CircularProgressIndicator.adaptive());
+        }
+
+        return _wrapScrollbar(state.fields, [
+          _buildHeader(gridId, state.fields),
+          _buildRows(context),
+          const GridFooter(),
+        ]);
       },
     );
   }
@@ -108,19 +111,22 @@ class _FlowyGridState extends State<FlowyGrid> {
         axis: Axis.horizontal,
         child: SizedBox(
           width: GridLayout.headerWidth(fields),
-          child: CustomScrollView(
-            physics: StyledScrollPhysics(),
-            controller: _scrollController.verticalController,
-            slivers: <Widget>[...children],
+          child: ScrollConfiguration(
+            behavior: const ScrollBehavior().copyWith(scrollbars: false),
+            child: CustomScrollView(
+              physics: StyledScrollPhysics(),
+              controller: _scrollController.verticalController,
+              slivers: <Widget>[...children],
+            ),
           ),
         ),
       ),
     ).padding(right: 0, top: GridSize.headerHeight, bottom: GridSize.scrollBarSize);
   }
 
-  Widget _buildHeader(List<Field> fields) {
+  Widget _buildHeader(String gridId, List<Field> fields) {
     return SliverPersistentHeader(
-      delegate: GridHeaderDelegate(fields),
+      delegate: GridHeaderDelegate(gridId: gridId, fields: fields),
       floating: true,
       pinned: true,
     );

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

@@ -12,13 +12,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'header_cell.dart';
 
 class GridHeaderDelegate extends SliverPersistentHeaderDelegate {
+  final String gridId;
   final List<Field> fields;
 
-  GridHeaderDelegate(this.fields);
+  GridHeaderDelegate({required this.gridId, required this.fields});
 
   @override
   Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
-    return GridHeader(fields: fields);
+    return GridHeader(gridId: gridId, fields: fields);
   }
 
   @override
@@ -38,36 +39,26 @@ class GridHeaderDelegate extends SliverPersistentHeaderDelegate {
 
 class GridHeader extends StatelessWidget {
   final List<Field> fields;
-  const GridHeader({required this.fields, Key? key}) : super(key: key);
+  final String gridId;
+  const GridHeader({required this.gridId, required this.fields, Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     return BlocProvider(
-      create: (context) => getIt<GridHeaderBloc>(param1: fields)..add(const GridHeaderEvent.initial()),
+      create: (context) => getIt<GridHeaderBloc>(param1: gridId, param2: fields)..add(const GridHeaderEvent.initial()),
       child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
-        builder: (context, state) {
-          final headers = state.fields
-              .map(
-                (field) => HeaderCellContainer(
-                  width: field.width.toDouble(),
-                  child: HeaderCell(field),
-                ),
-              )
-              .toList();
-
-          return Container(
-            color: theme.surface,
-            child: Row(
-              crossAxisAlignment: CrossAxisAlignment.stretch,
-              children: [
-                const _HeaderLeading(),
-                ...headers,
-                const _HeaderTrailing(),
-              ],
-            ),
-          );
-        },
+        builder: (context, state) => Container(
+          color: theme.surface,
+          child: Row(
+            crossAxisAlignment: CrossAxisAlignment.stretch,
+            children: [
+              const _HeaderLeading(),
+              ...state.fields.map((field) => HeaderCell(field)),
+              const _HeaderTrailing(),
+            ],
+          ),
+        ),
       ),
     );
   }
@@ -97,13 +88,13 @@ class _HeaderTrailing extends StatelessWidget {
         border: Border(top: borderSide, bottom: borderSide),
       ),
       padding: GridSize.headerContentInsets,
-      child: const CreateColumnButton(),
+      child: const CreateFieldButton(),
     );
   }
 }
 
-class CreateColumnButton extends StatelessWidget {
-  const CreateColumnButton({Key? key}) : super(key: key);
+class CreateFieldButton extends StatelessWidget {
+  const CreateFieldButton({Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {

+ 7 - 20
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header_cell.dart

@@ -17,34 +17,21 @@ class HeaderCell extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
-    return FlowyButton(
-      text: Padding(
-        padding: GridSize.cellContentInsets,
-        child: FlowyText.medium(field.name, fontSize: 12),
-      ),
+    final button = FlowyButton(
       hoverColor: theme.hover,
       onTap: () => FieldEditor.show(context, field),
       rightIcon: svg("editor/details", color: theme.iconColor),
+      text: Padding(padding: GridSize.cellContentInsets, child: FlowyText.medium(field.name, fontSize: 12)),
     );
-  }
-}
 
-class HeaderCellContainer extends StatelessWidget {
-  final HeaderCell child;
-  final double width;
-  const HeaderCellContainer({Key? key, required this.child, required this.width}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
     final borderSide = BorderSide(color: theme.shader4, width: 0.4);
+    final decoration = BoxDecoration(border: Border(top: borderSide, right: borderSide, bottom: borderSide));
+
     return Container(
-      width: width,
-      decoration: BoxDecoration(
-        border: Border(top: borderSide, right: borderSide, bottom: borderSide),
-      ),
+      width: field.width.toDouble(),
+      decoration: decoration,
       padding: GridSize.headerContentInsets,
-      child: child,
+      child: button,
     );
   }
 }

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

@@ -52,6 +52,40 @@ class GridEventGetFields {
     }
 }
 
+class GridEventUpdateField {
+     FieldChangeset request;
+     GridEventUpdateField(this.request);
+
+    Future<Either<Unit, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.UpdateField.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
+class GridEventCreateField {
+     CreateFieldPayload request;
+     GridEventCreateField(this.request);
+
+    Future<Either<Unit, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.CreateField.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
 class GridEventCreateRow {
      CreateRowPayload request;
      GridEventCreateRow(this.request);

+ 9 - 5
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbenum.dart

@@ -42,10 +42,12 @@ class ErrorCode extends $pb.ProtobufEnum {
   static const ErrorCode UserIdInvalid = ErrorCode._(311, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserIdInvalid');
   static const ErrorCode UserNotExist = ErrorCode._(312, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotExist');
   static const ErrorCode TextTooLong = ErrorCode._(400, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TextTooLong');
-  static const ErrorCode BlockIdIsEmpty = ErrorCode._(401, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'BlockIdIsEmpty');
-  static const ErrorCode RowIdIsEmpty = ErrorCode._(402, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RowIdIsEmpty');
-  static const ErrorCode GridIdIsEmpty = ErrorCode._(403, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridIdIsEmpty');
-  static const ErrorCode InvalidData = ErrorCode._(404, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InvalidData');
+  static const ErrorCode GridIdIsEmpty = ErrorCode._(410, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridIdIsEmpty');
+  static const ErrorCode BlockIdIsEmpty = ErrorCode._(420, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'BlockIdIsEmpty');
+  static const ErrorCode RowIdIsEmpty = ErrorCode._(430, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RowIdIsEmpty');
+  static const ErrorCode FieldIdIsEmpty = ErrorCode._(440, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'FieldIdIsEmpty');
+  static const ErrorCode TypeOptionIsEmpty = ErrorCode._(441, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TypeOptionIsEmpty');
+  static const ErrorCode InvalidData = ErrorCode._(500, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InvalidData');
 
   static const $core.List<ErrorCode> values = <ErrorCode> [
     Internal,
@@ -80,9 +82,11 @@ class ErrorCode extends $pb.ProtobufEnum {
     UserIdInvalid,
     UserNotExist,
     TextTooLong,
+    GridIdIsEmpty,
     BlockIdIsEmpty,
     RowIdIsEmpty,
-    GridIdIsEmpty,
+    FieldIdIsEmpty,
+    TypeOptionIsEmpty,
     InvalidData,
   ];
 

+ 7 - 5
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-error-code/code.pbjson.dart

@@ -44,12 +44,14 @@ const ErrorCode$json = const {
     const {'1': 'UserIdInvalid', '2': 311},
     const {'1': 'UserNotExist', '2': 312},
     const {'1': 'TextTooLong', '2': 400},
-    const {'1': 'BlockIdIsEmpty', '2': 401},
-    const {'1': 'RowIdIsEmpty', '2': 402},
-    const {'1': 'GridIdIsEmpty', '2': 403},
-    const {'1': 'InvalidData', '2': 404},
+    const {'1': 'GridIdIsEmpty', '2': 410},
+    const {'1': 'BlockIdIsEmpty', '2': 420},
+    const {'1': 'RowIdIsEmpty', '2': 430},
+    const {'1': 'FieldIdIsEmpty', '2': 440},
+    const {'1': 'TypeOptionIsEmpty', '2': 441},
+    const {'1': 'InvalidData', '2': 500},
   ],
 };
 
 /// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSDAoISW50ZXJuYWwQABIUChBVc2VyVW5hdXRob3JpemVkEAISEgoOUmVjb3JkTm90Rm91bmQQAxIRCg1Vc2VySWRJc0VtcHR5EAQSGAoUV29ya3NwYWNlTmFtZUludmFsaWQQZBIWChJXb3Jrc3BhY2VJZEludmFsaWQQZRIYChRBcHBDb2xvclN0eWxlSW52YWxpZBBmEhgKFFdvcmtzcGFjZURlc2NUb29Mb25nEGcSGAoUV29ya3NwYWNlTmFtZVRvb0xvbmcQaBIQCgxBcHBJZEludmFsaWQQbhISCg5BcHBOYW1lSW52YWxpZBBvEhMKD1ZpZXdOYW1lSW52YWxpZBB4EhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEHkSEQoNVmlld0lkSW52YWxpZBB6EhMKD1ZpZXdEZXNjVG9vTG9uZxB7EhMKD1ZpZXdEYXRhSW52YWxpZBB8EhMKD1ZpZXdOYW1lVG9vTG9uZxB9EhEKDENvbm5lY3RFcnJvchDIARIRCgxFbWFpbElzRW1wdHkQrAISFwoSRW1haWxGb3JtYXRJbnZhbGlkEK0CEhcKEkVtYWlsQWxyZWFkeUV4aXN0cxCuAhIUCg9QYXNzd29yZElzRW1wdHkQrwISFAoPUGFzc3dvcmRUb29Mb25nELACEiUKIFBhc3N3b3JkQ29udGFpbnNGb3JiaWRDaGFyYWN0ZXJzELECEhoKFVBhc3N3b3JkRm9ybWF0SW52YWxpZBCyAhIVChBQYXNzd29yZE5vdE1hdGNoELMCEhQKD1VzZXJOYW1lVG9vTG9uZxC0AhInCiJVc2VyTmFtZUNvbnRhaW5Gb3JiaWRkZW5DaGFyYWN0ZXJzELUCEhQKD1VzZXJOYW1lSXNFbXB0eRC2AhISCg1Vc2VySWRJbnZhbGlkELcCEhEKDFVzZXJOb3RFeGlzdBC4AhIQCgtUZXh0VG9vTG9uZxCQAxITCg5CbG9ja0lkSXNFbXB0eRCRAxIRCgxSb3dJZElzRW1wdHkQkgMSEgoNR3JpZElkSXNFbXB0eRCTAxIQCgtJbnZhbGlkRGF0YRCUAw==');
+final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSDAoISW50ZXJuYWwQABIUChBVc2VyVW5hdXRob3JpemVkEAISEgoOUmVjb3JkTm90Rm91bmQQAxIRCg1Vc2VySWRJc0VtcHR5EAQSGAoUV29ya3NwYWNlTmFtZUludmFsaWQQZBIWChJXb3Jrc3BhY2VJZEludmFsaWQQZRIYChRBcHBDb2xvclN0eWxlSW52YWxpZBBmEhgKFFdvcmtzcGFjZURlc2NUb29Mb25nEGcSGAoUV29ya3NwYWNlTmFtZVRvb0xvbmcQaBIQCgxBcHBJZEludmFsaWQQbhISCg5BcHBOYW1lSW52YWxpZBBvEhMKD1ZpZXdOYW1lSW52YWxpZBB4EhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEHkSEQoNVmlld0lkSW52YWxpZBB6EhMKD1ZpZXdEZXNjVG9vTG9uZxB7EhMKD1ZpZXdEYXRhSW52YWxpZBB8EhMKD1ZpZXdOYW1lVG9vTG9uZxB9EhEKDENvbm5lY3RFcnJvchDIARIRCgxFbWFpbElzRW1wdHkQrAISFwoSRW1haWxGb3JtYXRJbnZhbGlkEK0CEhcKEkVtYWlsQWxyZWFkeUV4aXN0cxCuAhIUCg9QYXNzd29yZElzRW1wdHkQrwISFAoPUGFzc3dvcmRUb29Mb25nELACEiUKIFBhc3N3b3JkQ29udGFpbnNGb3JiaWRDaGFyYWN0ZXJzELECEhoKFVBhc3N3b3JkRm9ybWF0SW52YWxpZBCyAhIVChBQYXNzd29yZE5vdE1hdGNoELMCEhQKD1VzZXJOYW1lVG9vTG9uZxC0AhInCiJVc2VyTmFtZUNvbnRhaW5Gb3JiaWRkZW5DaGFyYWN0ZXJzELUCEhQKD1VzZXJOYW1lSXNFbXB0eRC2AhISCg1Vc2VySWRJbnZhbGlkELcCEhEKDFVzZXJOb3RFeGlzdBC4AhIQCgtUZXh0VG9vTG9uZxCQAxISCg1HcmlkSWRJc0VtcHR5EJoDEhMKDkJsb2NrSWRJc0VtcHR5EKQDEhEKDFJvd0lkSXNFbXB0eRCuAxITCg5GaWVsZElkSXNFbXB0eRC4AxIWChFUeXBlT3B0aW9uSXNFbXB0eRC5AxIQCgtJbnZhbGlkRGF0YRD0Aw==');

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

@@ -979,6 +979,110 @@ class CreateRowPayload extends $pb.GeneratedMessage {
   void clearStartRowId() => clearField(2);
 }
 
+enum CreateFieldPayload_OneOfStartFieldId {
+  startFieldId, 
+  notSet
+}
+
+class CreateFieldPayload extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, CreateFieldPayload_OneOfStartFieldId> _CreateFieldPayload_OneOfStartFieldIdByTag = {
+    4 : CreateFieldPayload_OneOfStartFieldId.startFieldId,
+    0 : CreateFieldPayload_OneOfStartFieldId.notSet
+  };
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CreateFieldPayload', createEmptyInstance: create)
+    ..oo(0, [4])
+    ..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)
+    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'startFieldId')
+    ..hasRequiredFields = false
+  ;
+
+  CreateFieldPayload._() : super();
+  factory CreateFieldPayload({
+    $core.String? gridId,
+    Field? field_2,
+    $core.List<$core.int>? typeOptionData,
+    $core.String? startFieldId,
+  }) {
+    final _result = create();
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
+    if (field_2 != null) {
+      _result.field_2 = field_2;
+    }
+    if (typeOptionData != null) {
+      _result.typeOptionData = typeOptionData;
+    }
+    if (startFieldId != null) {
+      _result.startFieldId = startFieldId;
+    }
+    return _result;
+  }
+  factory CreateFieldPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory CreateFieldPayload.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')
+  CreateFieldPayload clone() => CreateFieldPayload()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  CreateFieldPayload copyWith(void Function(CreateFieldPayload) updates) => super.copyWith((message) => updates(message as CreateFieldPayload)) as CreateFieldPayload; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static CreateFieldPayload create() => CreateFieldPayload._();
+  CreateFieldPayload createEmptyInstance() => create();
+  static $pb.PbList<CreateFieldPayload> createRepeated() => $pb.PbList<CreateFieldPayload>();
+  @$core.pragma('dart2js:noInline')
+  static CreateFieldPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateFieldPayload>(create);
+  static CreateFieldPayload? _defaultInstance;
+
+  CreateFieldPayload_OneOfStartFieldId whichOneOfStartFieldId() => _CreateFieldPayload_OneOfStartFieldIdByTag[$_whichOneof(0)]!;
+  void clearOneOfStartFieldId() => clearField($_whichOneof(0));
+
+  @$pb.TagNumber(1)
+  $core.String get gridId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set gridId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasGridId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearGridId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  Field get field_2 => $_getN(1);
+  @$pb.TagNumber(2)
+  set field_2(Field v) { setField(2, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasField_2() => $_has(1);
+  @$pb.TagNumber(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);
+
+  @$pb.TagNumber(4)
+  $core.String get startFieldId => $_getSZ(3);
+  @$pb.TagNumber(4)
+  set startFieldId($core.String v) { $_setString(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasStartFieldId() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearStartFieldId() => clearField(4);
+}
+
 class QueryFieldPayload extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'QueryFieldPayload', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')

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

@@ -207,6 +207,22 @@ const CreateRowPayload$json = const {
 
 /// Descriptor for `CreateRowPayload`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List createRowPayloadDescriptor = $convert.base64Decode('ChBDcmVhdGVSb3dQYXlsb2FkEhcKB2dyaWRfaWQYASABKAlSBmdyaWRJZBIiCgxzdGFydF9yb3dfaWQYAiABKAlIAFIKc3RhcnRSb3dJZEIVChNvbmVfb2Zfc3RhcnRfcm93X2lk');
+@$core.Deprecated('Use createFieldPayloadDescriptor instead')
+const CreateFieldPayload$json = const {
+  '1': 'CreateFieldPayload',
+  '2': const [
+    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'},
+    const {'1': 'start_field_id', '3': 4, '4': 1, '5': 9, '9': 0, '10': 'startFieldId'},
+  ],
+  '8': const [
+    const {'1': 'one_of_start_field_id'},
+  ],
+};
+
+/// Descriptor for `CreateFieldPayload`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List createFieldPayloadDescriptor = $convert.base64Decode('ChJDcmVhdGVGaWVsZFBheWxvYWQSFwoHZ3JpZF9pZBgBIAEoCVIGZ3JpZElkEhwKBWZpZWxkGAIgASgLMgYuRmllbGRSBWZpZWxkEigKEHR5cGVfb3B0aW9uX2RhdGEYAyABKAxSDnR5cGVPcHRpb25EYXRhEiYKDnN0YXJ0X2ZpZWxkX2lkGAQgASgJSABSDHN0YXJ0RmllbGRJZEIXChVvbmVfb2Zfc3RhcnRfZmllbGRfaWQ=');
 @$core.Deprecated('Use queryFieldPayloadDescriptor instead')
 const QueryFieldPayload$json = const {
   '1': 'QueryFieldPayload',

+ 71 - 57
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pb.dart

@@ -215,7 +215,7 @@ class FieldMeta extends $pb.GeneratedMessage {
     ..aOB(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'frozen')
     ..aOB(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'visibility')
     ..a<$core.int>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'width', $pb.PbFieldType.O3)
-    ..aOS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptions')
+    ..aOS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOption')
     ..hasRequiredFields = false
   ;
 
@@ -228,7 +228,7 @@ class FieldMeta extends $pb.GeneratedMessage {
     $core.bool? frozen,
     $core.bool? visibility,
     $core.int? width,
-    $core.String? typeOptions,
+    $core.String? typeOption,
   }) {
     final _result = create();
     if (id != null) {
@@ -252,8 +252,8 @@ class FieldMeta extends $pb.GeneratedMessage {
     if (width != null) {
       _result.width = width;
     }
-    if (typeOptions != null) {
-      _result.typeOptions = typeOptions;
+    if (typeOption != null) {
+      _result.typeOption = typeOption;
     }
     return _result;
   }
@@ -342,13 +342,13 @@ class FieldMeta extends $pb.GeneratedMessage {
   void clearWidth() => clearField(7);
 
   @$pb.TagNumber(8)
-  $core.String get typeOptions => $_getSZ(7);
+  $core.String get typeOption => $_getSZ(7);
   @$pb.TagNumber(8)
-  set typeOptions($core.String v) { $_setString(7, v); }
+  set typeOption($core.String v) { $_setString(7, v); }
   @$pb.TagNumber(8)
-  $core.bool hasTypeOptions() => $_has(7);
+  $core.bool hasTypeOption() => $_has(7);
   @$pb.TagNumber(8)
-  void clearTypeOptions() => clearField(8);
+  void clearTypeOption() => clearField(8);
 }
 
 enum FieldChangeset_OneOfName {
@@ -388,55 +388,57 @@ enum FieldChangeset_OneOfTypeOptions {
 
 class FieldChangeset extends $pb.GeneratedMessage {
   static const $core.Map<$core.int, FieldChangeset_OneOfName> _FieldChangeset_OneOfNameByTag = {
-    2 : FieldChangeset_OneOfName.name,
+    3 : FieldChangeset_OneOfName.name,
     0 : FieldChangeset_OneOfName.notSet
   };
   static const $core.Map<$core.int, FieldChangeset_OneOfDesc> _FieldChangeset_OneOfDescByTag = {
-    3 : FieldChangeset_OneOfDesc.desc,
+    4 : FieldChangeset_OneOfDesc.desc,
     0 : FieldChangeset_OneOfDesc.notSet
   };
   static const $core.Map<$core.int, FieldChangeset_OneOfFieldType> _FieldChangeset_OneOfFieldTypeByTag = {
-    4 : FieldChangeset_OneOfFieldType.fieldType,
+    5 : FieldChangeset_OneOfFieldType.fieldType,
     0 : FieldChangeset_OneOfFieldType.notSet
   };
   static const $core.Map<$core.int, FieldChangeset_OneOfFrozen> _FieldChangeset_OneOfFrozenByTag = {
-    5 : FieldChangeset_OneOfFrozen.frozen,
+    6 : FieldChangeset_OneOfFrozen.frozen,
     0 : FieldChangeset_OneOfFrozen.notSet
   };
   static const $core.Map<$core.int, FieldChangeset_OneOfVisibility> _FieldChangeset_OneOfVisibilityByTag = {
-    6 : FieldChangeset_OneOfVisibility.visibility,
+    7 : FieldChangeset_OneOfVisibility.visibility,
     0 : FieldChangeset_OneOfVisibility.notSet
   };
   static const $core.Map<$core.int, FieldChangeset_OneOfWidth> _FieldChangeset_OneOfWidthByTag = {
-    7 : FieldChangeset_OneOfWidth.width,
+    8 : FieldChangeset_OneOfWidth.width,
     0 : FieldChangeset_OneOfWidth.notSet
   };
   static const $core.Map<$core.int, FieldChangeset_OneOfTypeOptions> _FieldChangeset_OneOfTypeOptionsByTag = {
-    8 : FieldChangeset_OneOfTypeOptions.typeOptions,
+    9 : FieldChangeset_OneOfTypeOptions.typeOptions,
     0 : FieldChangeset_OneOfTypeOptions.notSet
   };
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'FieldChangeset', createEmptyInstance: create)
-    ..oo(0, [2])
-    ..oo(1, [3])
-    ..oo(2, [4])
-    ..oo(3, [5])
-    ..oo(4, [6])
-    ..oo(5, [7])
-    ..oo(6, [8])
+    ..oo(0, [3])
+    ..oo(1, [4])
+    ..oo(2, [5])
+    ..oo(3, [6])
+    ..oo(4, [7])
+    ..oo(5, [8])
+    ..oo(6, [9])
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
-    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
-    ..e<FieldType>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldType', $pb.PbFieldType.OE, defaultOrMaker: FieldType.RichText, valueOf: FieldType.valueOf, enumValues: FieldType.values)
-    ..aOB(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'frozen')
-    ..aOB(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'visibility')
-    ..a<$core.int>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'width', $pb.PbFieldType.O3)
-    ..aOS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptions')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
+    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
+    ..e<FieldType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldType', $pb.PbFieldType.OE, defaultOrMaker: FieldType.RichText, valueOf: FieldType.valueOf, enumValues: FieldType.values)
+    ..aOB(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'frozen')
+    ..aOB(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'visibility')
+    ..a<$core.int>(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'width', $pb.PbFieldType.O3)
+    ..aOS(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptions')
     ..hasRequiredFields = false
   ;
 
   FieldChangeset._() : super();
   factory FieldChangeset({
     $core.String? fieldId,
+    $core.String? gridId,
     $core.String? name,
     $core.String? desc,
     FieldType? fieldType,
@@ -449,6 +451,9 @@ class FieldChangeset extends $pb.GeneratedMessage {
     if (fieldId != null) {
       _result.fieldId = fieldId;
     }
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
     if (name != null) {
       _result.name = name;
     }
@@ -524,67 +529,76 @@ class FieldChangeset extends $pb.GeneratedMessage {
   void clearFieldId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.String get name => $_getSZ(1);
+  $core.String get gridId => $_getSZ(1);
   @$pb.TagNumber(2)
-  set name($core.String v) { $_setString(1, v); }
+  set gridId($core.String v) { $_setString(1, v); }
   @$pb.TagNumber(2)
-  $core.bool hasName() => $_has(1);
+  $core.bool hasGridId() => $_has(1);
   @$pb.TagNumber(2)
-  void clearName() => clearField(2);
+  void clearGridId() => clearField(2);
 
   @$pb.TagNumber(3)
-  $core.String get desc => $_getSZ(2);
+  $core.String get name => $_getSZ(2);
   @$pb.TagNumber(3)
-  set desc($core.String v) { $_setString(2, v); }
+  set name($core.String v) { $_setString(2, v); }
   @$pb.TagNumber(3)
-  $core.bool hasDesc() => $_has(2);
+  $core.bool hasName() => $_has(2);
   @$pb.TagNumber(3)
-  void clearDesc() => clearField(3);
+  void clearName() => clearField(3);
 
   @$pb.TagNumber(4)
-  FieldType get fieldType => $_getN(3);
+  $core.String get desc => $_getSZ(3);
   @$pb.TagNumber(4)
-  set fieldType(FieldType v) { setField(4, v); }
+  set desc($core.String v) { $_setString(3, v); }
   @$pb.TagNumber(4)
-  $core.bool hasFieldType() => $_has(3);
+  $core.bool hasDesc() => $_has(3);
   @$pb.TagNumber(4)
-  void clearFieldType() => clearField(4);
+  void clearDesc() => clearField(4);
 
   @$pb.TagNumber(5)
-  $core.bool get frozen => $_getBF(4);
+  FieldType get fieldType => $_getN(4);
   @$pb.TagNumber(5)
-  set frozen($core.bool v) { $_setBool(4, v); }
+  set fieldType(FieldType v) { setField(5, v); }
   @$pb.TagNumber(5)
-  $core.bool hasFrozen() => $_has(4);
+  $core.bool hasFieldType() => $_has(4);
   @$pb.TagNumber(5)
-  void clearFrozen() => clearField(5);
+  void clearFieldType() => clearField(5);
 
   @$pb.TagNumber(6)
-  $core.bool get visibility => $_getBF(5);
+  $core.bool get frozen => $_getBF(5);
   @$pb.TagNumber(6)
-  set visibility($core.bool v) { $_setBool(5, v); }
+  set frozen($core.bool v) { $_setBool(5, v); }
   @$pb.TagNumber(6)
-  $core.bool hasVisibility() => $_has(5);
+  $core.bool hasFrozen() => $_has(5);
   @$pb.TagNumber(6)
-  void clearVisibility() => clearField(6);
+  void clearFrozen() => clearField(6);
 
   @$pb.TagNumber(7)
-  $core.int get width => $_getIZ(6);
+  $core.bool get visibility => $_getBF(6);
   @$pb.TagNumber(7)
-  set width($core.int v) { $_setSignedInt32(6, v); }
+  set visibility($core.bool v) { $_setBool(6, v); }
   @$pb.TagNumber(7)
-  $core.bool hasWidth() => $_has(6);
+  $core.bool hasVisibility() => $_has(6);
   @$pb.TagNumber(7)
-  void clearWidth() => clearField(7);
+  void clearVisibility() => clearField(7);
 
   @$pb.TagNumber(8)
-  $core.String get typeOptions => $_getSZ(7);
+  $core.int get width => $_getIZ(7);
   @$pb.TagNumber(8)
-  set typeOptions($core.String v) { $_setString(7, v); }
+  set width($core.int v) { $_setSignedInt32(7, v); }
   @$pb.TagNumber(8)
-  $core.bool hasTypeOptions() => $_has(7);
+  $core.bool hasWidth() => $_has(7);
   @$pb.TagNumber(8)
-  void clearTypeOptions() => clearField(8);
+  void clearWidth() => clearField(8);
+
+  @$pb.TagNumber(9)
+  $core.String get typeOptions => $_getSZ(8);
+  @$pb.TagNumber(9)
+  set typeOptions($core.String v) { $_setString(8, v); }
+  @$pb.TagNumber(9)
+  $core.bool hasTypeOptions() => $_has(8);
+  @$pb.TagNumber(9)
+  void clearTypeOptions() => clearField(9);
 }
 
 class AnyData extends $pb.GeneratedMessage {

+ 11 - 10
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pbjson.dart

@@ -69,24 +69,25 @@ const FieldMeta$json = const {
     const {'1': 'frozen', '3': 5, '4': 1, '5': 8, '10': 'frozen'},
     const {'1': 'visibility', '3': 6, '4': 1, '5': 8, '10': 'visibility'},
     const {'1': 'width', '3': 7, '4': 1, '5': 5, '10': 'width'},
-    const {'1': 'type_options', '3': 8, '4': 1, '5': 9, '10': 'typeOptions'},
+    const {'1': 'type_option', '3': 8, '4': 1, '5': 9, '10': 'typeOption'},
   ],
 };
 
 /// Descriptor for `FieldMeta`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List fieldMetaDescriptor = $convert.base64Decode('CglGaWVsZE1ldGESDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEZGVzYxgDIAEoCVIEZGVzYxIpCgpmaWVsZF90eXBlGAQgASgOMgouRmllbGRUeXBlUglmaWVsZFR5cGUSFgoGZnJvemVuGAUgASgIUgZmcm96ZW4SHgoKdmlzaWJpbGl0eRgGIAEoCFIKdmlzaWJpbGl0eRIUCgV3aWR0aBgHIAEoBVIFd2lkdGgSIQoMdHlwZV9vcHRpb25zGAggASgJUgt0eXBlT3B0aW9ucw==');
+final $typed_data.Uint8List fieldMetaDescriptor = $convert.base64Decode('CglGaWVsZE1ldGESDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEZGVzYxgDIAEoCVIEZGVzYxIpCgpmaWVsZF90eXBlGAQgASgOMgouRmllbGRUeXBlUglmaWVsZFR5cGUSFgoGZnJvemVuGAUgASgIUgZmcm96ZW4SHgoKdmlzaWJpbGl0eRgGIAEoCFIKdmlzaWJpbGl0eRIUCgV3aWR0aBgHIAEoBVIFd2lkdGgSHwoLdHlwZV9vcHRpb24YCCABKAlSCnR5cGVPcHRpb24=');
 @$core.Deprecated('Use fieldChangesetDescriptor instead')
 const FieldChangeset$json = const {
   '1': 'FieldChangeset',
   '2': const [
     const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'},
-    const {'1': 'name', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'name'},
-    const {'1': 'desc', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'desc'},
-    const {'1': 'field_type', '3': 4, '4': 1, '5': 14, '6': '.FieldType', '9': 2, '10': 'fieldType'},
-    const {'1': 'frozen', '3': 5, '4': 1, '5': 8, '9': 3, '10': 'frozen'},
-    const {'1': 'visibility', '3': 6, '4': 1, '5': 8, '9': 4, '10': 'visibility'},
-    const {'1': 'width', '3': 7, '4': 1, '5': 5, '9': 5, '10': 'width'},
-    const {'1': 'type_options', '3': 8, '4': 1, '5': 9, '9': 6, '10': 'typeOptions'},
+    const {'1': 'grid_id', '3': 2, '4': 1, '5': 9, '10': 'gridId'},
+    const {'1': 'name', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'name'},
+    const {'1': 'desc', '3': 4, '4': 1, '5': 9, '9': 1, '10': 'desc'},
+    const {'1': 'field_type', '3': 5, '4': 1, '5': 14, '6': '.FieldType', '9': 2, '10': 'fieldType'},
+    const {'1': 'frozen', '3': 6, '4': 1, '5': 8, '9': 3, '10': 'frozen'},
+    const {'1': 'visibility', '3': 7, '4': 1, '5': 8, '9': 4, '10': 'visibility'},
+    const {'1': 'width', '3': 8, '4': 1, '5': 5, '9': 5, '10': 'width'},
+    const {'1': 'type_options', '3': 9, '4': 1, '5': 9, '9': 6, '10': 'typeOptions'},
   ],
   '8': const [
     const {'1': 'one_of_name'},
@@ -100,7 +101,7 @@ const FieldChangeset$json = const {
 };
 
 /// Descriptor for `FieldChangeset`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List fieldChangesetDescriptor = $convert.base64Decode('Cg5GaWVsZENoYW5nZXNldBIZCghmaWVsZF9pZBgBIAEoCVIHZmllbGRJZBIUCgRuYW1lGAIgASgJSABSBG5hbWUSFAoEZGVzYxgDIAEoCUgBUgRkZXNjEisKCmZpZWxkX3R5cGUYBCABKA4yCi5GaWVsZFR5cGVIAlIJZmllbGRUeXBlEhgKBmZyb3plbhgFIAEoCEgDUgZmcm96ZW4SIAoKdmlzaWJpbGl0eRgGIAEoCEgEUgp2aXNpYmlsaXR5EhYKBXdpZHRoGAcgASgFSAVSBXdpZHRoEiMKDHR5cGVfb3B0aW9ucxgIIAEoCUgGUgt0eXBlT3B0aW9uc0INCgtvbmVfb2ZfbmFtZUINCgtvbmVfb2ZfZGVzY0ITChFvbmVfb2ZfZmllbGRfdHlwZUIPCg1vbmVfb2ZfZnJvemVuQhMKEW9uZV9vZl92aXNpYmlsaXR5Qg4KDG9uZV9vZl93aWR0aEIVChNvbmVfb2ZfdHlwZV9vcHRpb25z');
+final $typed_data.Uint8List fieldChangesetDescriptor = $convert.base64Decode('Cg5GaWVsZENoYW5nZXNldBIZCghmaWVsZF9pZBgBIAEoCVIHZmllbGRJZBIXCgdncmlkX2lkGAIgASgJUgZncmlkSWQSFAoEbmFtZRgDIAEoCUgAUgRuYW1lEhQKBGRlc2MYBCABKAlIAVIEZGVzYxIrCgpmaWVsZF90eXBlGAUgASgOMgouRmllbGRUeXBlSAJSCWZpZWxkVHlwZRIYCgZmcm96ZW4YBiABKAhIA1IGZnJvemVuEiAKCnZpc2liaWxpdHkYByABKAhIBFIKdmlzaWJpbGl0eRIWCgV3aWR0aBgIIAEoBUgFUgV3aWR0aBIjCgx0eXBlX29wdGlvbnMYCSABKAlIBlILdHlwZU9wdGlvbnNCDQoLb25lX29mX25hbWVCDQoLb25lX29mX2Rlc2NCEwoRb25lX29mX2ZpZWxkX3R5cGVCDwoNb25lX29mX2Zyb3plbkITChFvbmVfb2ZfdmlzaWJpbGl0eUIOCgxvbmVfb2Zfd2lkdGhCFQoTb25lX29mX3R5cGVfb3B0aW9ucw==');
 @$core.Deprecated('Use anyDataDescriptor instead')
 const AnyData$json = const {
   '1': 'AnyData',

+ 13 - 13
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_description.pb.dart

@@ -9,14 +9,14 @@ import 'dart:core' as $core;
 
 import 'package:protobuf/protobuf.dart' as $pb;
 
-class CheckboxDescription extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CheckboxDescription', createEmptyInstance: create)
+class CheckboxTypeOption extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CheckboxTypeOption', createEmptyInstance: create)
     ..aOB(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'isSelected')
     ..hasRequiredFields = false
   ;
 
-  CheckboxDescription._() : super();
-  factory CheckboxDescription({
+  CheckboxTypeOption._() : super();
+  factory CheckboxTypeOption({
     $core.bool? isSelected,
   }) {
     final _result = create();
@@ -25,26 +25,26 @@ class CheckboxDescription extends $pb.GeneratedMessage {
     }
     return _result;
   }
-  factory CheckboxDescription.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory CheckboxDescription.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory CheckboxTypeOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory CheckboxTypeOption.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')
-  CheckboxDescription clone() => CheckboxDescription()..mergeFromMessage(this);
+  CheckboxTypeOption clone() => CheckboxTypeOption()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  CheckboxDescription copyWith(void Function(CheckboxDescription) updates) => super.copyWith((message) => updates(message as CheckboxDescription)) as CheckboxDescription; // ignore: deprecated_member_use
+  CheckboxTypeOption copyWith(void Function(CheckboxTypeOption) updates) => super.copyWith((message) => updates(message as CheckboxTypeOption)) as CheckboxTypeOption; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static CheckboxDescription create() => CheckboxDescription._();
-  CheckboxDescription createEmptyInstance() => create();
-  static $pb.PbList<CheckboxDescription> createRepeated() => $pb.PbList<CheckboxDescription>();
+  static CheckboxTypeOption create() => CheckboxTypeOption._();
+  CheckboxTypeOption createEmptyInstance() => create();
+  static $pb.PbList<CheckboxTypeOption> createRepeated() => $pb.PbList<CheckboxTypeOption>();
   @$core.pragma('dart2js:noInline')
-  static CheckboxDescription getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CheckboxDescription>(create);
-  static CheckboxDescription? _defaultInstance;
+  static CheckboxTypeOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CheckboxTypeOption>(create);
+  static CheckboxTypeOption? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.bool get isSelected => $_getBF(0);

+ 5 - 5
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_description.pbjson.dart

@@ -8,13 +8,13 @@
 import 'dart:core' as $core;
 import 'dart:convert' as $convert;
 import 'dart:typed_data' as $typed_data;
-@$core.Deprecated('Use checkboxDescriptionDescriptor instead')
-const CheckboxDescription$json = const {
-  '1': 'CheckboxDescription',
+@$core.Deprecated('Use checkboxTypeOptionDescriptor instead')
+const CheckboxTypeOption$json = const {
+  '1': 'CheckboxTypeOption',
   '2': const [
     const {'1': 'is_selected', '3': 1, '4': 1, '5': 8, '10': 'isSelected'},
   ],
 };
 
-/// Descriptor for `CheckboxDescription`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List checkboxDescriptionDescriptor = $convert.base64Decode('ChNDaGVja2JveERlc2NyaXB0aW9uEh8KC2lzX3NlbGVjdGVkGAEgASgIUgppc1NlbGVjdGVk');
+/// Descriptor for `CheckboxTypeOption`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List checkboxTypeOptionDescriptor = $convert.base64Decode('ChJDaGVja2JveFR5cGVPcHRpb24SHwoLaXNfc2VsZWN0ZWQYASABKAhSCmlzU2VsZWN0ZWQ=');

+ 58 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_type_option.pb.dart

@@ -0,0 +1,58 @@
+///
+//  Generated code. Do not modify.
+//  source: checkbox_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class CheckboxTypeOption extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CheckboxTypeOption', createEmptyInstance: create)
+    ..aOB(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'isSelected')
+    ..hasRequiredFields = false
+  ;
+
+  CheckboxTypeOption._() : super();
+  factory CheckboxTypeOption({
+    $core.bool? isSelected,
+  }) {
+    final _result = create();
+    if (isSelected != null) {
+      _result.isSelected = isSelected;
+    }
+    return _result;
+  }
+  factory CheckboxTypeOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory CheckboxTypeOption.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')
+  CheckboxTypeOption clone() => CheckboxTypeOption()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  CheckboxTypeOption copyWith(void Function(CheckboxTypeOption) updates) => super.copyWith((message) => updates(message as CheckboxTypeOption)) as CheckboxTypeOption; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static CheckboxTypeOption create() => CheckboxTypeOption._();
+  CheckboxTypeOption createEmptyInstance() => create();
+  static $pb.PbList<CheckboxTypeOption> createRepeated() => $pb.PbList<CheckboxTypeOption>();
+  @$core.pragma('dart2js:noInline')
+  static CheckboxTypeOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CheckboxTypeOption>(create);
+  static CheckboxTypeOption? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.bool get isSelected => $_getBF(0);
+  @$pb.TagNumber(1)
+  set isSelected($core.bool v) { $_setBool(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasIsSelected() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearIsSelected() => clearField(1);
+}
+

+ 7 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_type_option.pbenum.dart

@@ -0,0 +1,7 @@
+///
+//  Generated code. Do not modify.
+//  source: checkbox_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+

+ 20 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_type_option.pbjson.dart

@@ -0,0 +1,20 @@
+///
+//  Generated code. Do not modify.
+//  source: checkbox_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+import 'dart:core' as $core;
+import 'dart:convert' as $convert;
+import 'dart:typed_data' as $typed_data;
+@$core.Deprecated('Use checkboxTypeOptionDescriptor instead')
+const CheckboxTypeOption$json = const {
+  '1': 'CheckboxTypeOption',
+  '2': const [
+    const {'1': 'is_selected', '3': 1, '4': 1, '5': 8, '10': 'isSelected'},
+  ],
+};
+
+/// Descriptor for `CheckboxTypeOption`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List checkboxTypeOptionDescriptor = $convert.base64Decode('ChJDaGVja2JveFR5cGVPcHRpb24SHwoLaXNfc2VsZWN0ZWQYASABKAhSCmlzU2VsZWN0ZWQ=');

+ 9 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/checkbox_type_option.pbserver.dart

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: checkbox_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+export 'checkbox_type_option.pb.dart';
+

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

@@ -12,16 +12,16 @@ import 'package:protobuf/protobuf.dart' as $pb;
 class GridNotification extends $pb.ProtobufEnum {
   static const GridNotification Unknown = GridNotification._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unknown');
   static const GridNotification GridDidCreateBlock = GridNotification._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidCreateBlock');
-  static const GridNotification BlockDidUpdateRow = GridNotification._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'BlockDidUpdateRow');
+  static const GridNotification DidUpdateRow = GridNotification._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateRow');
   static const GridNotification GridDidUpdateCells = GridNotification._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidUpdateCells');
-  static const GridNotification GridDidUpdateFields = GridNotification._(40, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidUpdateFields');
+  static const GridNotification DidUpdateFields = GridNotification._(40, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateFields');
 
   static const $core.List<GridNotification> values = <GridNotification> [
     Unknown,
     GridDidCreateBlock,
-    BlockDidUpdateRow,
+    DidUpdateRow,
     GridDidUpdateCells,
-    GridDidUpdateFields,
+    DidUpdateFields,
   ];
 
   static final $core.Map<$core.int, GridNotification> _byValue = $pb.ProtobufEnum.initByValue(values);

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

@@ -14,11 +14,11 @@ const GridNotification$json = const {
   '2': const [
     const {'1': 'Unknown', '2': 0},
     const {'1': 'GridDidCreateBlock', '2': 11},
-    const {'1': 'BlockDidUpdateRow', '2': 20},
+    const {'1': 'DidUpdateRow', '2': 20},
     const {'1': 'GridDidUpdateCells', '2': 30},
-    const {'1': 'GridDidUpdateFields', '2': 40},
+    const {'1': 'DidUpdateFields', '2': 40},
   ],
 };
 
 /// Descriptor for `GridNotification`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List gridNotificationDescriptor = $convert.base64Decode('ChBHcmlkTm90aWZpY2F0aW9uEgsKB1Vua25vd24QABIWChJHcmlkRGlkQ3JlYXRlQmxvY2sQCxIVChFCbG9ja0RpZFVwZGF0ZVJvdxAUEhYKEkdyaWREaWRVcGRhdGVDZWxscxAeEhcKE0dyaWREaWRVcGRhdGVGaWVsZHMQKA==');
+final $typed_data.Uint8List gridNotificationDescriptor = $convert.base64Decode('ChBHcmlkTm90aWZpY2F0aW9uEgsKB1Vua25vd24QABIWChJHcmlkRGlkQ3JlYXRlQmxvY2sQCxIQCgxEaWRVcGRhdGVSb3cQFBIWChJHcmlkRGlkVXBkYXRlQ2VsbHMQHhITCg9EaWRVcGRhdGVGaWVsZHMQKA==');

+ 76 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_type_option.pb.dart

@@ -0,0 +1,76 @@
+///
+//  Generated code. Do not modify.
+//  source: date_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'date_type_option.pbenum.dart';
+
+export 'date_type_option.pbenum.dart';
+
+class DateTypeOption extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DateTypeOption', createEmptyInstance: create)
+    ..e<DateFormat>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dateFormat', $pb.PbFieldType.OE, defaultOrMaker: DateFormat.Local, valueOf: DateFormat.valueOf, enumValues: DateFormat.values)
+    ..e<TimeFormat>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'timeFormat', $pb.PbFieldType.OE, defaultOrMaker: TimeFormat.TwelveHour, valueOf: TimeFormat.valueOf, enumValues: TimeFormat.values)
+    ..hasRequiredFields = false
+  ;
+
+  DateTypeOption._() : super();
+  factory DateTypeOption({
+    DateFormat? dateFormat,
+    TimeFormat? timeFormat,
+  }) {
+    final _result = create();
+    if (dateFormat != null) {
+      _result.dateFormat = dateFormat;
+    }
+    if (timeFormat != null) {
+      _result.timeFormat = timeFormat;
+    }
+    return _result;
+  }
+  factory DateTypeOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory DateTypeOption.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')
+  DateTypeOption clone() => DateTypeOption()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  DateTypeOption copyWith(void Function(DateTypeOption) updates) => super.copyWith((message) => updates(message as DateTypeOption)) as DateTypeOption; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static DateTypeOption create() => DateTypeOption._();
+  DateTypeOption createEmptyInstance() => create();
+  static $pb.PbList<DateTypeOption> createRepeated() => $pb.PbList<DateTypeOption>();
+  @$core.pragma('dart2js:noInline')
+  static DateTypeOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DateTypeOption>(create);
+  static DateTypeOption? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  DateFormat get dateFormat => $_getN(0);
+  @$pb.TagNumber(1)
+  set dateFormat(DateFormat v) { setField(1, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasDateFormat() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearDateFormat() => clearField(1);
+
+  @$pb.TagNumber(2)
+  TimeFormat get timeFormat => $_getN(1);
+  @$pb.TagNumber(2)
+  set timeFormat(TimeFormat v) { setField(2, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasTimeFormat() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearTimeFormat() => clearField(2);
+}
+

+ 45 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_type_option.pbenum.dart

@@ -0,0 +1,45 @@
+///
+//  Generated code. Do not modify.
+//  source: date_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+// ignore_for_file: UNDEFINED_SHOWN_NAME
+import 'dart:core' as $core;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class DateFormat extends $pb.ProtobufEnum {
+  static const DateFormat Local = DateFormat._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Local');
+  static const DateFormat US = DateFormat._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'US');
+  static const DateFormat ISO = DateFormat._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ISO');
+  static const DateFormat Friendly = DateFormat._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Friendly');
+
+  static const $core.List<DateFormat> values = <DateFormat> [
+    Local,
+    US,
+    ISO,
+    Friendly,
+  ];
+
+  static final $core.Map<$core.int, DateFormat> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static DateFormat? valueOf($core.int value) => _byValue[value];
+
+  const DateFormat._($core.int v, $core.String n) : super(v, n);
+}
+
+class TimeFormat extends $pb.ProtobufEnum {
+  static const TimeFormat TwelveHour = TimeFormat._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TwelveHour');
+  static const TimeFormat TwentyFourHour = TimeFormat._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TwentyFourHour');
+
+  static const $core.List<TimeFormat> values = <TimeFormat> [
+    TwelveHour,
+    TwentyFourHour,
+  ];
+
+  static final $core.Map<$core.int, TimeFormat> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static TimeFormat? valueOf($core.int value) => _byValue[value];
+
+  const TimeFormat._($core.int v, $core.String n) : super(v, n);
+}
+

+ 45 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_type_option.pbjson.dart

@@ -0,0 +1,45 @@
+///
+//  Generated code. Do not modify.
+//  source: date_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+import 'dart:core' as $core;
+import 'dart:convert' as $convert;
+import 'dart:typed_data' as $typed_data;
+@$core.Deprecated('Use dateFormatDescriptor instead')
+const DateFormat$json = const {
+  '1': 'DateFormat',
+  '2': const [
+    const {'1': 'Local', '2': 0},
+    const {'1': 'US', '2': 1},
+    const {'1': 'ISO', '2': 2},
+    const {'1': 'Friendly', '2': 3},
+  ],
+};
+
+/// Descriptor for `DateFormat`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List dateFormatDescriptor = $convert.base64Decode('CgpEYXRlRm9ybWF0EgkKBUxvY2FsEAASBgoCVVMQARIHCgNJU08QAhIMCghGcmllbmRseRAD');
+@$core.Deprecated('Use timeFormatDescriptor instead')
+const TimeFormat$json = const {
+  '1': 'TimeFormat',
+  '2': const [
+    const {'1': 'TwelveHour', '2': 0},
+    const {'1': 'TwentyFourHour', '2': 1},
+  ],
+};
+
+/// Descriptor for `TimeFormat`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List timeFormatDescriptor = $convert.base64Decode('CgpUaW1lRm9ybWF0Eg4KClR3ZWx2ZUhvdXIQABISCg5Ud2VudHlGb3VySG91chAB');
+@$core.Deprecated('Use dateTypeOptionDescriptor instead')
+const DateTypeOption$json = const {
+  '1': 'DateTypeOption',
+  '2': const [
+    const {'1': 'date_format', '3': 1, '4': 1, '5': 14, '6': '.DateFormat', '10': 'dateFormat'},
+    const {'1': 'time_format', '3': 2, '4': 1, '5': 14, '6': '.TimeFormat', '10': 'timeFormat'},
+  ],
+};
+
+/// Descriptor for `DateTypeOption`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List dateTypeOptionDescriptor = $convert.base64Decode('Cg5EYXRlVHlwZU9wdGlvbhIsCgtkYXRlX2Zvcm1hdBgBIAEoDjILLkRhdGVGb3JtYXRSCmRhdGVGb3JtYXQSLAoLdGltZV9mb3JtYXQYAiABKA4yCy5UaW1lRm9ybWF0Ugp0aW1lRm9ybWF0');

+ 9 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/date_type_option.pbserver.dart

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: date_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+export 'date_type_option.pb.dart';
+

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

@@ -13,14 +13,18 @@ class GridEvent extends $pb.ProtobufEnum {
   static const GridEvent GetGridData = GridEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetGridData');
   static const GridEvent GetGridBlocks = GridEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetGridBlocks');
   static const GridEvent GetFields = GridEvent._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetFields');
-  static const GridEvent CreateRow = GridEvent._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateRow');
-  static const GridEvent GetRow = GridEvent._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetRow');
-  static const GridEvent UpdateCell = GridEvent._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateCell');
+  static const GridEvent UpdateField = GridEvent._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateField');
+  static const GridEvent CreateField = GridEvent._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateField');
+  static const GridEvent CreateRow = GridEvent._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateRow');
+  static const GridEvent GetRow = GridEvent._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetRow');
+  static const GridEvent UpdateCell = GridEvent._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateCell');
 
   static const $core.List<GridEvent> values = <GridEvent> [
     GetGridData,
     GetGridBlocks,
     GetFields,
+    UpdateField,
+    CreateField,
     CreateRow,
     GetRow,
     UpdateCell,

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

@@ -15,11 +15,13 @@ const GridEvent$json = const {
     const {'1': 'GetGridData', '2': 0},
     const {'1': 'GetGridBlocks', '2': 1},
     const {'1': 'GetFields', '2': 10},
-    const {'1': 'CreateRow', '2': 11},
-    const {'1': 'GetRow', '2': 12},
-    const {'1': 'UpdateCell', '2': 20},
+    const {'1': 'UpdateField', '2': 11},
+    const {'1': 'CreateField', '2': 12},
+    const {'1': 'CreateRow', '2': 21},
+    const {'1': 'GetRow', '2': 22},
+    const {'1': 'UpdateCell', '2': 30},
   ],
 };
 
 /// Descriptor for `GridEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDQoJQ3JlYXRlUm93EAsSCgoGR2V0Um93EAwSDgoKVXBkYXRlQ2VsbBAU');
+final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIPCgtDcmVhdGVGaWVsZBAMEg0KCUNyZWF0ZVJvdxAVEgoKBkdldFJvdxAWEg4KClVwZGF0ZUNlbGwQHg==');

+ 118 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pb.dart

@@ -0,0 +1,118 @@
+///
+//  Generated code. Do not modify.
+//  source: number_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'number_type_option.pbenum.dart';
+
+export 'number_type_option.pbenum.dart';
+
+class NumberTypeOption extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'NumberTypeOption', createEmptyInstance: create)
+    ..e<NumberFormat>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'format', $pb.PbFieldType.OE, defaultOrMaker: NumberFormat.Number, valueOf: NumberFormat.valueOf, enumValues: NumberFormat.values)
+    ..a<$core.int>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'scale', $pb.PbFieldType.OU3)
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'symbol')
+    ..aOB(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'signPositive')
+    ..aOS(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
+    ..hasRequiredFields = false
+  ;
+
+  NumberTypeOption._() : super();
+  factory NumberTypeOption({
+    NumberFormat? format,
+    $core.int? scale,
+    $core.String? symbol,
+    $core.bool? signPositive,
+    $core.String? name,
+  }) {
+    final _result = create();
+    if (format != null) {
+      _result.format = format;
+    }
+    if (scale != null) {
+      _result.scale = scale;
+    }
+    if (symbol != null) {
+      _result.symbol = symbol;
+    }
+    if (signPositive != null) {
+      _result.signPositive = signPositive;
+    }
+    if (name != null) {
+      _result.name = name;
+    }
+    return _result;
+  }
+  factory NumberTypeOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory NumberTypeOption.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')
+  NumberTypeOption clone() => NumberTypeOption()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  NumberTypeOption copyWith(void Function(NumberTypeOption) updates) => super.copyWith((message) => updates(message as NumberTypeOption)) as NumberTypeOption; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static NumberTypeOption create() => NumberTypeOption._();
+  NumberTypeOption createEmptyInstance() => create();
+  static $pb.PbList<NumberTypeOption> createRepeated() => $pb.PbList<NumberTypeOption>();
+  @$core.pragma('dart2js:noInline')
+  static NumberTypeOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<NumberTypeOption>(create);
+  static NumberTypeOption? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  NumberFormat get format => $_getN(0);
+  @$pb.TagNumber(1)
+  set format(NumberFormat v) { setField(1, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasFormat() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearFormat() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.int get scale => $_getIZ(1);
+  @$pb.TagNumber(2)
+  set scale($core.int v) { $_setUnsignedInt32(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasScale() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearScale() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get symbol => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set symbol($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasSymbol() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearSymbol() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.bool get signPositive => $_getBF(3);
+  @$pb.TagNumber(4)
+  set signPositive($core.bool v) { $_setBool(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasSignPositive() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearSignPositive() => clearField(4);
+
+  @$pb.TagNumber(5)
+  $core.String get name => $_getSZ(4);
+  @$pb.TagNumber(5)
+  set name($core.String v) { $_setString(4, v); }
+  @$pb.TagNumber(5)
+  $core.bool hasName() => $_has(4);
+  @$pb.TagNumber(5)
+  void clearName() => clearField(5);
+}
+

+ 30 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbenum.dart

@@ -0,0 +1,30 @@
+///
+//  Generated code. Do not modify.
+//  source: number_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+// ignore_for_file: UNDEFINED_SHOWN_NAME
+import 'dart:core' as $core;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class NumberFormat extends $pb.ProtobufEnum {
+  static const NumberFormat Number = NumberFormat._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Number');
+  static const NumberFormat USD = NumberFormat._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'USD');
+  static const NumberFormat CNY = NumberFormat._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CNY');
+  static const NumberFormat EUR = NumberFormat._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EUR');
+
+  static const $core.List<NumberFormat> values = <NumberFormat> [
+    Number,
+    USD,
+    CNY,
+    EUR,
+  ];
+
+  static final $core.Map<$core.int, NumberFormat> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static NumberFormat? valueOf($core.int value) => _byValue[value];
+
+  const NumberFormat._($core.int v, $core.String n) : super(v, n);
+}
+

+ 37 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbjson.dart

@@ -0,0 +1,37 @@
+///
+//  Generated code. Do not modify.
+//  source: number_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+import 'dart:core' as $core;
+import 'dart:convert' as $convert;
+import 'dart:typed_data' as $typed_data;
+@$core.Deprecated('Use numberFormatDescriptor instead')
+const NumberFormat$json = const {
+  '1': 'NumberFormat',
+  '2': const [
+    const {'1': 'Number', '2': 0},
+    const {'1': 'USD', '2': 1},
+    const {'1': 'CNY', '2': 2},
+    const {'1': 'EUR', '2': 3},
+  ],
+};
+
+/// Descriptor for `NumberFormat`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List numberFormatDescriptor = $convert.base64Decode('CgxOdW1iZXJGb3JtYXQSCgoGTnVtYmVyEAASBwoDVVNEEAESBwoDQ05ZEAISBwoDRVVSEAM=');
+@$core.Deprecated('Use numberTypeOptionDescriptor instead')
+const NumberTypeOption$json = const {
+  '1': 'NumberTypeOption',
+  '2': const [
+    const {'1': 'format', '3': 1, '4': 1, '5': 14, '6': '.NumberFormat', '10': 'format'},
+    const {'1': 'scale', '3': 2, '4': 1, '5': 13, '10': 'scale'},
+    const {'1': 'symbol', '3': 3, '4': 1, '5': 9, '10': 'symbol'},
+    const {'1': 'sign_positive', '3': 4, '4': 1, '5': 8, '10': 'signPositive'},
+    const {'1': 'name', '3': 5, '4': 1, '5': 9, '10': 'name'},
+  ],
+};
+
+/// Descriptor for `NumberTypeOption`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List numberTypeOptionDescriptor = $convert.base64Decode('ChBOdW1iZXJUeXBlT3B0aW9uEiUKBmZvcm1hdBgBIAEoDjINLk51bWJlckZvcm1hdFIGZm9ybWF0EhQKBXNjYWxlGAIgASgNUgVzY2FsZRIWCgZzeW1ib2wYAyABKAlSBnN5bWJvbBIjCg1zaWduX3Bvc2l0aXZlGAQgASgIUgxzaWduUG9zaXRpdmUSEgoEbmFtZRgFIAEoCVIEbmFtZQ==');

+ 9 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/number_type_option.pbserver.dart

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: number_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+export 'number_type_option.pb.dart';
+

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

@@ -1,8 +1,8 @@
 // Auto-generated, do not edit 
-export './date_description.pb.dart';
+export './number_type_option.pb.dart';
 export './text_description.pb.dart';
 export './dart_notification.pb.dart';
-export './checkbox_description.pb.dart';
-export './selection_description.pb.dart';
+export './selection_type_option.pb.dart';
+export './checkbox_type_option.pb.dart';
 export './event_map.pb.dart';
-export './number_description.pb.dart';
+export './date_type_option.pb.dart';

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

@@ -0,0 +1,196 @@
+///
+//  Generated code. Do not modify.
+//  source: selection_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class SingleSelectTypeOption extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SingleSelectTypeOption', createEmptyInstance: create)
+    ..pc<SelectOption>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'options', $pb.PbFieldType.PM, subBuilder: SelectOption.create)
+    ..aOB(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'disableColor')
+    ..hasRequiredFields = false
+  ;
+
+  SingleSelectTypeOption._() : super();
+  factory SingleSelectTypeOption({
+    $core.Iterable<SelectOption>? options,
+    $core.bool? disableColor,
+  }) {
+    final _result = create();
+    if (options != null) {
+      _result.options.addAll(options);
+    }
+    if (disableColor != null) {
+      _result.disableColor = disableColor;
+    }
+    return _result;
+  }
+  factory SingleSelectTypeOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory SingleSelectTypeOption.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')
+  SingleSelectTypeOption clone() => SingleSelectTypeOption()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  SingleSelectTypeOption copyWith(void Function(SingleSelectTypeOption) updates) => super.copyWith((message) => updates(message as SingleSelectTypeOption)) as SingleSelectTypeOption; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static SingleSelectTypeOption create() => SingleSelectTypeOption._();
+  SingleSelectTypeOption createEmptyInstance() => create();
+  static $pb.PbList<SingleSelectTypeOption> createRepeated() => $pb.PbList<SingleSelectTypeOption>();
+  @$core.pragma('dart2js:noInline')
+  static SingleSelectTypeOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SingleSelectTypeOption>(create);
+  static SingleSelectTypeOption? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<SelectOption> get options => $_getList(0);
+
+  @$pb.TagNumber(2)
+  $core.bool get disableColor => $_getBF(1);
+  @$pb.TagNumber(2)
+  set disableColor($core.bool v) { $_setBool(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasDisableColor() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearDisableColor() => clearField(2);
+}
+
+class MultiSelectTypeOption extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'MultiSelectTypeOption', createEmptyInstance: create)
+    ..pc<SelectOption>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'options', $pb.PbFieldType.PM, subBuilder: SelectOption.create)
+    ..aOB(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'disableColor')
+    ..hasRequiredFields = false
+  ;
+
+  MultiSelectTypeOption._() : super();
+  factory MultiSelectTypeOption({
+    $core.Iterable<SelectOption>? options,
+    $core.bool? disableColor,
+  }) {
+    final _result = create();
+    if (options != null) {
+      _result.options.addAll(options);
+    }
+    if (disableColor != null) {
+      _result.disableColor = disableColor;
+    }
+    return _result;
+  }
+  factory MultiSelectTypeOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory MultiSelectTypeOption.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')
+  MultiSelectTypeOption clone() => MultiSelectTypeOption()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  MultiSelectTypeOption copyWith(void Function(MultiSelectTypeOption) updates) => super.copyWith((message) => updates(message as MultiSelectTypeOption)) as MultiSelectTypeOption; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static MultiSelectTypeOption create() => MultiSelectTypeOption._();
+  MultiSelectTypeOption createEmptyInstance() => create();
+  static $pb.PbList<MultiSelectTypeOption> createRepeated() => $pb.PbList<MultiSelectTypeOption>();
+  @$core.pragma('dart2js:noInline')
+  static MultiSelectTypeOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<MultiSelectTypeOption>(create);
+  static MultiSelectTypeOption? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<SelectOption> get options => $_getList(0);
+
+  @$pb.TagNumber(2)
+  $core.bool get disableColor => $_getBF(1);
+  @$pb.TagNumber(2)
+  set disableColor($core.bool v) { $_setBool(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasDisableColor() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearDisableColor() => clearField(2);
+}
+
+class SelectOption extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SelectOption', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'color')
+    ..hasRequiredFields = false
+  ;
+
+  SelectOption._() : super();
+  factory SelectOption({
+    $core.String? id,
+    $core.String? name,
+    $core.String? color,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (name != null) {
+      _result.name = name;
+    }
+    if (color != null) {
+      _result.color = color;
+    }
+    return _result;
+  }
+  factory SelectOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory SelectOption.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')
+  SelectOption clone() => SelectOption()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  SelectOption copyWith(void Function(SelectOption) updates) => super.copyWith((message) => updates(message as SelectOption)) as SelectOption; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static SelectOption create() => SelectOption._();
+  SelectOption createEmptyInstance() => create();
+  static $pb.PbList<SelectOption> createRepeated() => $pb.PbList<SelectOption>();
+  @$core.pragma('dart2js:noInline')
+  static SelectOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SelectOption>(create);
+  static SelectOption? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get id => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set id($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get name => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set name($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasName() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearName() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get color => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set color($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasColor() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearColor() => clearField(3);
+}
+

+ 7 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pbenum.dart

@@ -0,0 +1,7 @@
+///
+//  Generated code. Do not modify.
+//  source: selection_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+

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

@@ -0,0 +1,44 @@
+///
+//  Generated code. Do not modify.
+//  source: selection_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+import 'dart:core' as $core;
+import 'dart:convert' as $convert;
+import 'dart:typed_data' as $typed_data;
+@$core.Deprecated('Use singleSelectTypeOptionDescriptor instead')
+const SingleSelectTypeOption$json = const {
+  '1': 'SingleSelectTypeOption',
+  '2': const [
+    const {'1': 'options', '3': 1, '4': 3, '5': 11, '6': '.SelectOption', '10': 'options'},
+    const {'1': 'disable_color', '3': 2, '4': 1, '5': 8, '10': 'disableColor'},
+  ],
+};
+
+/// Descriptor for `SingleSelectTypeOption`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List singleSelectTypeOptionDescriptor = $convert.base64Decode('ChZTaW5nbGVTZWxlY3RUeXBlT3B0aW9uEicKB29wdGlvbnMYASADKAsyDS5TZWxlY3RPcHRpb25SB29wdGlvbnMSIwoNZGlzYWJsZV9jb2xvchgCIAEoCFIMZGlzYWJsZUNvbG9y');
+@$core.Deprecated('Use multiSelectTypeOptionDescriptor instead')
+const MultiSelectTypeOption$json = const {
+  '1': 'MultiSelectTypeOption',
+  '2': const [
+    const {'1': 'options', '3': 1, '4': 3, '5': 11, '6': '.SelectOption', '10': 'options'},
+    const {'1': 'disable_color', '3': 2, '4': 1, '5': 8, '10': 'disableColor'},
+  ],
+};
+
+/// Descriptor for `MultiSelectTypeOption`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List multiSelectTypeOptionDescriptor = $convert.base64Decode('ChVNdWx0aVNlbGVjdFR5cGVPcHRpb24SJwoHb3B0aW9ucxgBIAMoCzINLlNlbGVjdE9wdGlvblIHb3B0aW9ucxIjCg1kaXNhYmxlX2NvbG9yGAIgASgIUgxkaXNhYmxlQ29sb3I=');
+@$core.Deprecated('Use selectOptionDescriptor instead')
+const SelectOption$json = const {
+  '1': 'SelectOption',
+  '2': const [
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+    const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
+    const {'1': 'color', '3': 3, '4': 1, '5': 9, '10': 'color'},
+  ],
+};
+
+/// Descriptor for `SelectOption`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List selectOptionDescriptor = $convert.base64Decode('CgxTZWxlY3RPcHRpb24SDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSFAoFY29sb3IYAyABKAlSBWNvbG9y');

+ 9 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/selection_type_option.pbserver.dart

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: selection_type_option.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+export 'selection_type_option.pb.dart';
+

+ 13 - 13
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_description.pb.dart

@@ -9,14 +9,14 @@ import 'dart:core' as $core;
 
 import 'package:protobuf/protobuf.dart' as $pb;
 
-class RichTextDescription extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RichTextDescription', createEmptyInstance: create)
+class RichTextTypeOption extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RichTextTypeOption', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'format')
     ..hasRequiredFields = false
   ;
 
-  RichTextDescription._() : super();
-  factory RichTextDescription({
+  RichTextTypeOption._() : super();
+  factory RichTextTypeOption({
     $core.String? format,
   }) {
     final _result = create();
@@ -25,26 +25,26 @@ class RichTextDescription extends $pb.GeneratedMessage {
     }
     return _result;
   }
-  factory RichTextDescription.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory RichTextDescription.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory RichTextTypeOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RichTextTypeOption.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')
-  RichTextDescription clone() => RichTextDescription()..mergeFromMessage(this);
+  RichTextTypeOption clone() => RichTextTypeOption()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  RichTextDescription copyWith(void Function(RichTextDescription) updates) => super.copyWith((message) => updates(message as RichTextDescription)) as RichTextDescription; // ignore: deprecated_member_use
+  RichTextTypeOption copyWith(void Function(RichTextTypeOption) updates) => super.copyWith((message) => updates(message as RichTextTypeOption)) as RichTextTypeOption; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static RichTextDescription create() => RichTextDescription._();
-  RichTextDescription createEmptyInstance() => create();
-  static $pb.PbList<RichTextDescription> createRepeated() => $pb.PbList<RichTextDescription>();
+  static RichTextTypeOption create() => RichTextTypeOption._();
+  RichTextTypeOption createEmptyInstance() => create();
+  static $pb.PbList<RichTextTypeOption> createRepeated() => $pb.PbList<RichTextTypeOption>();
   @$core.pragma('dart2js:noInline')
-  static RichTextDescription getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RichTextDescription>(create);
-  static RichTextDescription? _defaultInstance;
+  static RichTextTypeOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RichTextTypeOption>(create);
+  static RichTextTypeOption? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.String get format => $_getSZ(0);

+ 5 - 5
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/text_description.pbjson.dart

@@ -8,13 +8,13 @@
 import 'dart:core' as $core;
 import 'dart:convert' as $convert;
 import 'dart:typed_data' as $typed_data;
-@$core.Deprecated('Use richTextDescriptionDescriptor instead')
-const RichTextDescription$json = const {
-  '1': 'RichTextDescription',
+@$core.Deprecated('Use richTextTypeOptionDescriptor instead')
+const RichTextTypeOption$json = const {
+  '1': 'RichTextTypeOption',
   '2': const [
     const {'1': 'format', '3': 1, '4': 1, '5': 9, '10': 'format'},
   ],
 };
 
-/// Descriptor for `RichTextDescription`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List richTextDescriptionDescriptor = $convert.base64Decode('ChNSaWNoVGV4dERlc2NyaXB0aW9uEhYKBmZvcm1hdBgBIAEoCVIGZm9ybWF0');
+/// Descriptor for `RichTextTypeOption`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List richTextTypeOptionDescriptor = $convert.base64Decode('ChJSaWNoVGV4dFR5cGVPcHRpb24SFgoGZm9ybWF0GAEgASgJUgZmb3JtYXQ=');

+ 1 - 1
frontend/rust-lib/flowy-grid/Flowy.toml

@@ -1,3 +1,3 @@
 
-proto_crates = ["src/event_map.rs", "src/services/cell/description", "src/dart_notification.rs"]
+proto_crates = ["src/event_map.rs", "src/services/field/type_options", "src/dart_notification.rs"]
 event_files = ["src/event_map.rs"]

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

@@ -8,10 +8,10 @@ pub enum GridNotification {
 
     GridDidCreateBlock = 11,
 
-    BlockDidUpdateRow = 20,
+    DidUpdateRow = 20,
 
     GridDidUpdateCells = 30,
-    GridDidUpdateFields = 40,
+    DidUpdateFields = 40,
 }
 
 impl std::default::Default for GridNotification {

+ 39 - 15
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -1,10 +1,12 @@
 use crate::manager::GridManager;
 use flowy_error::FlowyError;
 use flowy_grid_data_model::entities::{
-    CellMetaChangeset, CreateRowPayload, Field, Grid, GridId, QueryFieldPayload, QueryGridBlocksPayload,
-    QueryRowPayload, RepeatedField, RepeatedGridBlock, Row,
+    CellMetaChangeset, CreateFieldPayload, CreateRowPayload, Field, FieldChangeset, Grid, GridId, QueryFieldPayload,
+    QueryGridBlocksPayload, QueryRowPayload, RepeatedField, RepeatedGridBlock, Row,
+};
+use flowy_grid_data_model::parser::{
+    CreateFieldParams, CreateRowParams, QueryFieldParams, QueryGridBlocksParams, QueryRowParams,
 };
-use flowy_grid_data_model::parser::{CreateRowParams, QueryFieldParams, QueryGridBlocksParams, QueryRowParams};
 use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
 use std::sync::Arc;
 
@@ -35,6 +37,40 @@ pub(crate) async fn get_grid_blocks_handler(
     data_result(repeated_grid_block)
 }
 
+#[tracing::instrument(level = "debug", skip(data, manager), err)]
+pub(crate) async fn get_fields_handler(
+    data: Data<QueryFieldPayload>,
+    manager: AppData<Arc<GridManager>>,
+) -> DataResult<RepeatedField, FlowyError> {
+    let params: QueryFieldParams = data.into_inner().try_into()?;
+    let editor = manager.get_grid_editor(&params.grid_id)?;
+    let field_metas = editor.get_field_metas(Some(params.field_orders)).await?;
+    let repeated_field: RepeatedField = field_metas.into_iter().map(Field::from).collect::<Vec<_>>().into();
+    data_result(repeated_field)
+}
+
+#[tracing::instrument(level = "debug", skip(data, manager), err)]
+pub(crate) async fn update_field_handler(
+    data: Data<FieldChangeset>,
+    manager: AppData<Arc<GridManager>>,
+) -> Result<(), FlowyError> {
+    let changeset: FieldChangeset = data.into_inner();
+    let editor = manager.get_grid_editor(&changeset.grid_id)?;
+    let _ = editor.update_field(changeset).await?;
+    Ok(())
+}
+
+#[tracing::instrument(level = "debug", skip(data, manager), err)]
+pub(crate) async fn create_field_handler(
+    data: Data<CreateFieldPayload>,
+    manager: AppData<Arc<GridManager>>,
+) -> Result<(), FlowyError> {
+    let params: CreateFieldParams = data.into_inner().try_into()?;
+    let editor = manager.get_grid_editor(&params.grid_id)?;
+    let _ = editor.create_field(params).await?;
+    Ok(())
+}
+
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn get_row_handler(
     data: Data<QueryRowPayload>,
@@ -48,18 +84,6 @@ pub(crate) async fn get_row_handler(
     }
 }
 
-#[tracing::instrument(level = "debug", skip(data, manager), err)]
-pub(crate) async fn get_fields_handler(
-    data: Data<QueryFieldPayload>,
-    manager: AppData<Arc<GridManager>>,
-) -> DataResult<RepeatedField, FlowyError> {
-    let params: QueryFieldParams = data.into_inner().try_into()?;
-    let editor = manager.get_grid_editor(&params.grid_id)?;
-    let field_metas = editor.get_field_metas(Some(params.field_orders)).await?;
-    let repeated_field: RepeatedField = field_metas.into_iter().map(Field::from).collect::<Vec<_>>().into();
-    data_result(repeated_field)
-}
-
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn create_row_handler(
     data: Data<CreateRowPayload>,

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

@@ -11,6 +11,8 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
         .event(GridEvent::GetGridData, get_grid_data_handler)
         .event(GridEvent::GetGridBlocks, get_grid_blocks_handler)
         .event(GridEvent::GetFields, get_fields_handler)
+        .event(GridEvent::UpdateField, update_field_handler)
+        .event(GridEvent::CreateField, create_field_handler)
         .event(GridEvent::CreateRow, create_row_handler)
         .event(GridEvent::GetRow, get_row_handler)
         .event(GridEvent::UpdateCell, update_cell_handler);
@@ -30,12 +32,18 @@ pub enum GridEvent {
     #[event(input = "QueryFieldPayload", output = "RepeatedField")]
     GetFields = 10,
 
+    #[event(input = "FieldChangeset")]
+    UpdateField = 11,
+
+    #[event(input = "CreateFieldPayload")]
+    CreateField = 12,
+
     #[event(input = "CreateRowPayload", output = "Row")]
-    CreateRow = 11,
+    CreateRow = 21,
 
     #[event(input = "QueryRowPayload", output = "Row")]
-    GetRow = 12,
+    GetRow = 22,
 
     #[event(input = "CellMetaChangeset")]
-    UpdateCell = 20,
+    UpdateCell = 30,
 }

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

@@ -11,7 +11,7 @@ macro_rules! impl_from_field_type_option {
     ($target: ident) => {
         impl std::convert::From<&FieldMeta> for $target {
             fn from(field_meta: &FieldMeta) -> $target {
-                match serde_json::from_str(&field_meta.type_options) {
+                match serde_json::from_str(&field_meta.type_option) {
                     Ok(obj) => obj,
                     Err(err) => {
                         tracing::error!("{} convert from any data failed, {:?}", stringify!($target), err);

+ 21 - 21
frontend/rust-lib/flowy-grid/src/protobuf/model/checkbox_description.rs → frontend/rust-lib/flowy-grid/src/protobuf/model/checkbox_type_option.rs

@@ -17,14 +17,14 @@
 #![allow(trivial_casts)]
 #![allow(unused_imports)]
 #![allow(unused_results)]
-//! Generated file from `checkbox_description.proto`
+//! Generated file from `checkbox_type_option.proto`
 
 /// Generated files are compatible only with the same version
 /// of protobuf runtime.
 // const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
 
 #[derive(PartialEq,Clone,Default)]
-pub struct CheckboxDescription {
+pub struct CheckboxTypeOption {
     // message fields
     pub is_selected: bool,
     // special fields
@@ -32,14 +32,14 @@ pub struct CheckboxDescription {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a CheckboxDescription {
-    fn default() -> &'a CheckboxDescription {
-        <CheckboxDescription as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a CheckboxTypeOption {
+    fn default() -> &'a CheckboxTypeOption {
+        <CheckboxTypeOption as ::protobuf::Message>::default_instance()
     }
 }
 
-impl CheckboxDescription {
-    pub fn new() -> CheckboxDescription {
+impl CheckboxTypeOption {
+    pub fn new() -> CheckboxTypeOption {
         ::std::default::Default::default()
     }
 
@@ -59,7 +59,7 @@ impl CheckboxDescription {
     }
 }
 
-impl ::protobuf::Message for CheckboxDescription {
+impl ::protobuf::Message for CheckboxTypeOption {
     fn is_initialized(&self) -> bool {
         true
     }
@@ -129,8 +129,8 @@ impl ::protobuf::Message for CheckboxDescription {
         Self::descriptor_static()
     }
 
-    fn new() -> CheckboxDescription {
-        CheckboxDescription::new()
+    fn new() -> CheckboxTypeOption {
+        CheckboxTypeOption::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -139,44 +139,44 @@ impl ::protobuf::Message for CheckboxDescription {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
                 "is_selected",
-                |m: &CheckboxDescription| { &m.is_selected },
-                |m: &mut CheckboxDescription| { &mut m.is_selected },
+                |m: &CheckboxTypeOption| { &m.is_selected },
+                |m: &mut CheckboxTypeOption| { &mut m.is_selected },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CheckboxDescription>(
-                "CheckboxDescription",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CheckboxTypeOption>(
+                "CheckboxTypeOption",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static CheckboxDescription {
-        static instance: ::protobuf::rt::LazyV2<CheckboxDescription> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(CheckboxDescription::new)
+    fn default_instance() -> &'static CheckboxTypeOption {
+        static instance: ::protobuf::rt::LazyV2<CheckboxTypeOption> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(CheckboxTypeOption::new)
     }
 }
 
-impl ::protobuf::Clear for CheckboxDescription {
+impl ::protobuf::Clear for CheckboxTypeOption {
     fn clear(&mut self) {
         self.is_selected = false;
         self.unknown_fields.clear();
     }
 }
 
-impl ::std::fmt::Debug for CheckboxDescription {
+impl ::std::fmt::Debug for CheckboxTypeOption {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for CheckboxDescription {
+impl ::protobuf::reflect::ProtobufValue for CheckboxTypeOption {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x1acheckbox_description.proto\"6\n\x13CheckboxDescription\x12\x1f\n\
+    \n\x1acheckbox_type_option.proto\"5\n\x12CheckboxTypeOption\x12\x1f\n\
     \x0bis_selected\x18\x01\x20\x01(\x08R\nisSelectedb\x06proto3\
 ";
 

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

@@ -27,9 +27,9 @@
 pub enum GridNotification {
     Unknown = 0,
     GridDidCreateBlock = 11,
-    BlockDidUpdateRow = 20,
+    DidUpdateRow = 20,
     GridDidUpdateCells = 30,
-    GridDidUpdateFields = 40,
+    DidUpdateFields = 40,
 }
 
 impl ::protobuf::ProtobufEnum for GridNotification {
@@ -41,9 +41,9 @@ impl ::protobuf::ProtobufEnum for GridNotification {
         match value {
             0 => ::std::option::Option::Some(GridNotification::Unknown),
             11 => ::std::option::Option::Some(GridNotification::GridDidCreateBlock),
-            20 => ::std::option::Option::Some(GridNotification::BlockDidUpdateRow),
+            20 => ::std::option::Option::Some(GridNotification::DidUpdateRow),
             30 => ::std::option::Option::Some(GridNotification::GridDidUpdateCells),
-            40 => ::std::option::Option::Some(GridNotification::GridDidUpdateFields),
+            40 => ::std::option::Option::Some(GridNotification::DidUpdateFields),
             _ => ::std::option::Option::None
         }
     }
@@ -52,9 +52,9 @@ impl ::protobuf::ProtobufEnum for GridNotification {
         static values: &'static [GridNotification] = &[
             GridNotification::Unknown,
             GridNotification::GridDidCreateBlock,
-            GridNotification::BlockDidUpdateRow,
+            GridNotification::DidUpdateRow,
             GridNotification::GridDidUpdateCells,
-            GridNotification::GridDidUpdateFields,
+            GridNotification::DidUpdateFields,
         ];
         values
     }
@@ -83,10 +83,10 @@ impl ::protobuf::reflect::ProtobufValue for GridNotification {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x17dart_notification.proto*\x7f\n\x10GridNotification\x12\x0b\n\x07Un\
-    known\x10\0\x12\x16\n\x12GridDidCreateBlock\x10\x0b\x12\x15\n\x11BlockDi\
-    dUpdateRow\x10\x14\x12\x16\n\x12GridDidUpdateCells\x10\x1e\x12\x17\n\x13\
-    GridDidUpdateFields\x10(b\x06proto3\
+    \n\x17dart_notification.proto*v\n\x10GridNotification\x12\x0b\n\x07Unkno\
+    wn\x10\0\x12\x16\n\x12GridDidCreateBlock\x10\x0b\x12\x10\n\x0cDidUpdateR\
+    ow\x10\x14\x12\x16\n\x12GridDidUpdateCells\x10\x1e\x12\x13\n\x0fDidUpdat\
+    eFields\x10(b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 25 - 25
frontend/rust-lib/flowy-grid/src/protobuf/model/date_description.rs → frontend/rust-lib/flowy-grid/src/protobuf/model/date_type_option.rs

@@ -17,14 +17,14 @@
 #![allow(trivial_casts)]
 #![allow(unused_imports)]
 #![allow(unused_results)]
-//! Generated file from `date_description.proto`
+//! Generated file from `date_type_option.proto`
 
 /// Generated files are compatible only with the same version
 /// of protobuf runtime.
 // const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
 
 #[derive(PartialEq,Clone,Default)]
-pub struct DateDescription {
+pub struct DateTypeOption {
     // message fields
     pub date_format: DateFormat,
     pub time_format: TimeFormat,
@@ -33,14 +33,14 @@ pub struct DateDescription {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a DateDescription {
-    fn default() -> &'a DateDescription {
-        <DateDescription as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a DateTypeOption {
+    fn default() -> &'a DateTypeOption {
+        <DateTypeOption as ::protobuf::Message>::default_instance()
     }
 }
 
-impl DateDescription {
-    pub fn new() -> DateDescription {
+impl DateTypeOption {
+    pub fn new() -> DateTypeOption {
         ::std::default::Default::default()
     }
 
@@ -75,7 +75,7 @@ impl DateDescription {
     }
 }
 
-impl ::protobuf::Message for DateDescription {
+impl ::protobuf::Message for DateTypeOption {
     fn is_initialized(&self) -> bool {
         true
     }
@@ -150,8 +150,8 @@ impl ::protobuf::Message for DateDescription {
         Self::descriptor_static()
     }
 
-    fn new() -> DateDescription {
-        DateDescription::new()
+    fn new() -> DateTypeOption {
+        DateTypeOption::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -160,29 +160,29 @@ impl ::protobuf::Message for DateDescription {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum<DateFormat>>(
                 "date_format",
-                |m: &DateDescription| { &m.date_format },
-                |m: &mut DateDescription| { &mut m.date_format },
+                |m: &DateTypeOption| { &m.date_format },
+                |m: &mut DateTypeOption| { &mut m.date_format },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum<TimeFormat>>(
                 "time_format",
-                |m: &DateDescription| { &m.time_format },
-                |m: &mut DateDescription| { &mut m.time_format },
+                |m: &DateTypeOption| { &m.time_format },
+                |m: &mut DateTypeOption| { &mut m.time_format },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<DateDescription>(
-                "DateDescription",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<DateTypeOption>(
+                "DateTypeOption",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static DateDescription {
-        static instance: ::protobuf::rt::LazyV2<DateDescription> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(DateDescription::new)
+    fn default_instance() -> &'static DateTypeOption {
+        static instance: ::protobuf::rt::LazyV2<DateTypeOption> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(DateTypeOption::new)
     }
 }
 
-impl ::protobuf::Clear for DateDescription {
+impl ::protobuf::Clear for DateTypeOption {
     fn clear(&mut self) {
         self.date_format = DateFormat::Local;
         self.time_format = TimeFormat::TwelveHour;
@@ -190,13 +190,13 @@ impl ::protobuf::Clear for DateDescription {
     }
 }
 
-impl ::std::fmt::Debug for DateDescription {
+impl ::std::fmt::Debug for DateTypeOption {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for DateDescription {
+impl ::protobuf::reflect::ProtobufValue for DateTypeOption {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
@@ -309,9 +309,9 @@ impl ::protobuf::reflect::ProtobufValue for TimeFormat {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x16date_description.proto\"m\n\x0fDateDescription\x12,\n\x0bdate_form\
-    at\x18\x01\x20\x01(\x0e2\x0b.DateFormatR\ndateFormat\x12,\n\x0btime_form\
-    at\x18\x02\x20\x01(\x0e2\x0b.TimeFormatR\ntimeFormat*6\n\nDateFormat\x12\
+    \n\x16date_type_option.proto\"l\n\x0eDateTypeOption\x12,\n\x0bdate_forma\
+    t\x18\x01\x20\x01(\x0e2\x0b.DateFormatR\ndateFormat\x12,\n\x0btime_forma\
+    t\x18\x02\x20\x01(\x0e2\x0b.TimeFormatR\ntimeFormat*6\n\nDateFormat\x12\
     \t\n\x05Local\x10\0\x12\x06\n\x02US\x10\x01\x12\x07\n\x03ISO\x10\x02\x12\
     \x0c\n\x08Friendly\x10\x03*0\n\nTimeFormat\x12\x0e\n\nTwelveHour\x10\0\
     \x12\x12\n\x0eTwentyFourHour\x10\x01b\x06proto3\

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

@@ -28,9 +28,11 @@ pub enum GridEvent {
     GetGridData = 0,
     GetGridBlocks = 1,
     GetFields = 10,
-    CreateRow = 11,
-    GetRow = 12,
-    UpdateCell = 20,
+    UpdateField = 11,
+    CreateField = 12,
+    CreateRow = 21,
+    GetRow = 22,
+    UpdateCell = 30,
 }
 
 impl ::protobuf::ProtobufEnum for GridEvent {
@@ -43,9 +45,11 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             0 => ::std::option::Option::Some(GridEvent::GetGridData),
             1 => ::std::option::Option::Some(GridEvent::GetGridBlocks),
             10 => ::std::option::Option::Some(GridEvent::GetFields),
-            11 => ::std::option::Option::Some(GridEvent::CreateRow),
-            12 => ::std::option::Option::Some(GridEvent::GetRow),
-            20 => ::std::option::Option::Some(GridEvent::UpdateCell),
+            11 => ::std::option::Option::Some(GridEvent::UpdateField),
+            12 => ::std::option::Option::Some(GridEvent::CreateField),
+            21 => ::std::option::Option::Some(GridEvent::CreateRow),
+            22 => ::std::option::Option::Some(GridEvent::GetRow),
+            30 => ::std::option::Option::Some(GridEvent::UpdateCell),
             _ => ::std::option::Option::None
         }
     }
@@ -55,6 +59,8 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             GridEvent::GetGridData,
             GridEvent::GetGridBlocks,
             GridEvent::GetFields,
+            GridEvent::UpdateField,
+            GridEvent::CreateField,
             GridEvent::CreateRow,
             GridEvent::GetRow,
             GridEvent::UpdateCell,
@@ -86,10 +92,11 @@ impl ::protobuf::reflect::ProtobufValue for GridEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0fevent_map.proto*i\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\0\x12\
-    \x11\n\rGetGridBlocks\x10\x01\x12\r\n\tGetFields\x10\n\x12\r\n\tCreateRo\
-    w\x10\x0b\x12\n\n\x06GetRow\x10\x0c\x12\x0e\n\nUpdateCell\x10\x14b\x06pr\
-    oto3\
+    \n\x0fevent_map.proto*\x8b\x01\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
+    \0\x12\x11\n\rGetGridBlocks\x10\x01\x12\r\n\tGetFields\x10\n\x12\x0f\n\
+    \x0bUpdateField\x10\x0b\x12\x0f\n\x0bCreateField\x10\x0c\x12\r\n\tCreate\
+    Row\x10\x15\x12\n\n\x06GetRow\x10\x16\x12\x0e\n\nUpdateCell\x10\x1eb\x06\
+    proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 8 - 8
frontend/rust-lib/flowy-grid/src/protobuf/model/mod.rs

@@ -1,8 +1,8 @@
 #![cfg_attr(rustfmt, rustfmt::skip)]
 // Auto-generated, do not edit
 
-mod date_description;
-pub use date_description::*;
+mod number_type_option;
+pub use number_type_option::*;
 
 mod text_description;
 pub use text_description::*;
@@ -10,14 +10,14 @@ pub use text_description::*;
 mod dart_notification;
 pub use dart_notification::*;
 
-mod checkbox_description;
-pub use checkbox_description::*;
+mod selection_type_option;
+pub use selection_type_option::*;
 
-mod selection_description;
-pub use selection_description::*;
+mod checkbox_type_option;
+pub use checkbox_type_option::*;
 
 mod event_map;
 pub use event_map::*;
 
-mod number_description;
-pub use number_description::*;
+mod date_type_option;
+pub use date_type_option::*;

+ 29 - 29
frontend/rust-lib/flowy-grid/src/protobuf/model/number_description.rs → frontend/rust-lib/flowy-grid/src/protobuf/model/number_type_option.rs

@@ -17,14 +17,14 @@
 #![allow(trivial_casts)]
 #![allow(unused_imports)]
 #![allow(unused_results)]
-//! Generated file from `number_description.proto`
+//! Generated file from `number_type_option.proto`
 
 /// Generated files are compatible only with the same version
 /// of protobuf runtime.
 // const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
 
 #[derive(PartialEq,Clone,Default)]
-pub struct NumberDescription {
+pub struct NumberTypeOption {
     // message fields
     pub format: NumberFormat,
     pub scale: u32,
@@ -36,14 +36,14 @@ pub struct NumberDescription {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a NumberDescription {
-    fn default() -> &'a NumberDescription {
-        <NumberDescription as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a NumberTypeOption {
+    fn default() -> &'a NumberTypeOption {
+        <NumberTypeOption as ::protobuf::Message>::default_instance()
     }
 }
 
-impl NumberDescription {
-    pub fn new() -> NumberDescription {
+impl NumberTypeOption {
+    pub fn new() -> NumberTypeOption {
         ::std::default::Default::default()
     }
 
@@ -145,7 +145,7 @@ impl NumberDescription {
     }
 }
 
-impl ::protobuf::Message for NumberDescription {
+impl ::protobuf::Message for NumberTypeOption {
     fn is_initialized(&self) -> bool {
         true
     }
@@ -255,8 +255,8 @@ impl ::protobuf::Message for NumberDescription {
         Self::descriptor_static()
     }
 
-    fn new() -> NumberDescription {
-        NumberDescription::new()
+    fn new() -> NumberTypeOption {
+        NumberTypeOption::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -265,44 +265,44 @@ impl ::protobuf::Message for NumberDescription {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum<NumberFormat>>(
                 "format",
-                |m: &NumberDescription| { &m.format },
-                |m: &mut NumberDescription| { &mut m.format },
+                |m: &NumberTypeOption| { &m.format },
+                |m: &mut NumberTypeOption| { &mut m.format },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeUint32>(
                 "scale",
-                |m: &NumberDescription| { &m.scale },
-                |m: &mut NumberDescription| { &mut m.scale },
+                |m: &NumberTypeOption| { &m.scale },
+                |m: &mut NumberTypeOption| { &mut m.scale },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "symbol",
-                |m: &NumberDescription| { &m.symbol },
-                |m: &mut NumberDescription| { &mut m.symbol },
+                |m: &NumberTypeOption| { &m.symbol },
+                |m: &mut NumberTypeOption| { &mut m.symbol },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
                 "sign_positive",
-                |m: &NumberDescription| { &m.sign_positive },
-                |m: &mut NumberDescription| { &mut m.sign_positive },
+                |m: &NumberTypeOption| { &m.sign_positive },
+                |m: &mut NumberTypeOption| { &mut m.sign_positive },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "name",
-                |m: &NumberDescription| { &m.name },
-                |m: &mut NumberDescription| { &mut m.name },
+                |m: &NumberTypeOption| { &m.name },
+                |m: &mut NumberTypeOption| { &mut m.name },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<NumberDescription>(
-                "NumberDescription",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<NumberTypeOption>(
+                "NumberTypeOption",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static NumberDescription {
-        static instance: ::protobuf::rt::LazyV2<NumberDescription> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(NumberDescription::new)
+    fn default_instance() -> &'static NumberTypeOption {
+        static instance: ::protobuf::rt::LazyV2<NumberTypeOption> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(NumberTypeOption::new)
     }
 }
 
-impl ::protobuf::Clear for NumberDescription {
+impl ::protobuf::Clear for NumberTypeOption {
     fn clear(&mut self) {
         self.format = NumberFormat::Number;
         self.scale = 0;
@@ -313,13 +313,13 @@ impl ::protobuf::Clear for NumberDescription {
     }
 }
 
-impl ::std::fmt::Debug for NumberDescription {
+impl ::std::fmt::Debug for NumberTypeOption {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for NumberDescription {
+impl ::protobuf::reflect::ProtobufValue for NumberTypeOption {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
@@ -382,7 +382,7 @@ impl ::protobuf::reflect::ProtobufValue for NumberFormat {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x18number_description.proto\"\xa1\x01\n\x11NumberDescription\x12%\n\
+    \n\x18number_type_option.proto\"\xa0\x01\n\x10NumberTypeOption\x12%\n\
     \x06format\x18\x01\x20\x01(\x0e2\r.NumberFormatR\x06format\x12\x14\n\x05\
     scale\x18\x02\x20\x01(\rR\x05scale\x12\x16\n\x06symbol\x18\x03\x20\x01(\
     \tR\x06symbol\x12#\n\rsign_positive\x18\x04\x20\x01(\x08R\x0csignPositiv\

+ 48 - 48
frontend/rust-lib/flowy-grid/src/protobuf/model/selection_description.rs → frontend/rust-lib/flowy-grid/src/protobuf/model/selection_type_option.rs

@@ -17,14 +17,14 @@
 #![allow(trivial_casts)]
 #![allow(unused_imports)]
 #![allow(unused_results)]
-//! Generated file from `selection_description.proto`
+//! Generated file from `selection_type_option.proto`
 
 /// Generated files are compatible only with the same version
 /// of protobuf runtime.
 // const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
 
 #[derive(PartialEq,Clone,Default)]
-pub struct SingleSelectDescription {
+pub struct SingleSelectTypeOption {
     // message fields
     pub options: ::protobuf::RepeatedField<SelectOption>,
     pub disable_color: bool,
@@ -33,14 +33,14 @@ pub struct SingleSelectDescription {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a SingleSelectDescription {
-    fn default() -> &'a SingleSelectDescription {
-        <SingleSelectDescription as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a SingleSelectTypeOption {
+    fn default() -> &'a SingleSelectTypeOption {
+        <SingleSelectTypeOption as ::protobuf::Message>::default_instance()
     }
 }
 
-impl SingleSelectDescription {
-    pub fn new() -> SingleSelectDescription {
+impl SingleSelectTypeOption {
+    pub fn new() -> SingleSelectTypeOption {
         ::std::default::Default::default()
     }
 
@@ -85,7 +85,7 @@ impl SingleSelectDescription {
     }
 }
 
-impl ::protobuf::Message for SingleSelectDescription {
+impl ::protobuf::Message for SingleSelectTypeOption {
     fn is_initialized(&self) -> bool {
         for v in &self.options {
             if !v.is_initialized() {
@@ -172,8 +172,8 @@ impl ::protobuf::Message for SingleSelectDescription {
         Self::descriptor_static()
     }
 
-    fn new() -> SingleSelectDescription {
-        SingleSelectDescription::new()
+    fn new() -> SingleSelectTypeOption {
+        SingleSelectTypeOption::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -182,29 +182,29 @@ impl ::protobuf::Message for SingleSelectDescription {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<SelectOption>>(
                 "options",
-                |m: &SingleSelectDescription| { &m.options },
-                |m: &mut SingleSelectDescription| { &mut m.options },
+                |m: &SingleSelectTypeOption| { &m.options },
+                |m: &mut SingleSelectTypeOption| { &mut m.options },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
                 "disable_color",
-                |m: &SingleSelectDescription| { &m.disable_color },
-                |m: &mut SingleSelectDescription| { &mut m.disable_color },
+                |m: &SingleSelectTypeOption| { &m.disable_color },
+                |m: &mut SingleSelectTypeOption| { &mut m.disable_color },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SingleSelectDescription>(
-                "SingleSelectDescription",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<SingleSelectTypeOption>(
+                "SingleSelectTypeOption",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static SingleSelectDescription {
-        static instance: ::protobuf::rt::LazyV2<SingleSelectDescription> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(SingleSelectDescription::new)
+    fn default_instance() -> &'static SingleSelectTypeOption {
+        static instance: ::protobuf::rt::LazyV2<SingleSelectTypeOption> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(SingleSelectTypeOption::new)
     }
 }
 
-impl ::protobuf::Clear for SingleSelectDescription {
+impl ::protobuf::Clear for SingleSelectTypeOption {
     fn clear(&mut self) {
         self.options.clear();
         self.disable_color = false;
@@ -212,20 +212,20 @@ impl ::protobuf::Clear for SingleSelectDescription {
     }
 }
 
-impl ::std::fmt::Debug for SingleSelectDescription {
+impl ::std::fmt::Debug for SingleSelectTypeOption {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for SingleSelectDescription {
+impl ::protobuf::reflect::ProtobufValue for SingleSelectTypeOption {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
 }
 
 #[derive(PartialEq,Clone,Default)]
-pub struct MultiSelectDescription {
+pub struct MultiSelectTypeOption {
     // message fields
     pub options: ::protobuf::RepeatedField<SelectOption>,
     pub disable_color: bool,
@@ -234,14 +234,14 @@ pub struct MultiSelectDescription {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a MultiSelectDescription {
-    fn default() -> &'a MultiSelectDescription {
-        <MultiSelectDescription as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a MultiSelectTypeOption {
+    fn default() -> &'a MultiSelectTypeOption {
+        <MultiSelectTypeOption as ::protobuf::Message>::default_instance()
     }
 }
 
-impl MultiSelectDescription {
-    pub fn new() -> MultiSelectDescription {
+impl MultiSelectTypeOption {
+    pub fn new() -> MultiSelectTypeOption {
         ::std::default::Default::default()
     }
 
@@ -286,7 +286,7 @@ impl MultiSelectDescription {
     }
 }
 
-impl ::protobuf::Message for MultiSelectDescription {
+impl ::protobuf::Message for MultiSelectTypeOption {
     fn is_initialized(&self) -> bool {
         for v in &self.options {
             if !v.is_initialized() {
@@ -373,8 +373,8 @@ impl ::protobuf::Message for MultiSelectDescription {
         Self::descriptor_static()
     }
 
-    fn new() -> MultiSelectDescription {
-        MultiSelectDescription::new()
+    fn new() -> MultiSelectTypeOption {
+        MultiSelectTypeOption::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -383,29 +383,29 @@ impl ::protobuf::Message for MultiSelectDescription {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<SelectOption>>(
                 "options",
-                |m: &MultiSelectDescription| { &m.options },
-                |m: &mut MultiSelectDescription| { &mut m.options },
+                |m: &MultiSelectTypeOption| { &m.options },
+                |m: &mut MultiSelectTypeOption| { &mut m.options },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
                 "disable_color",
-                |m: &MultiSelectDescription| { &m.disable_color },
-                |m: &mut MultiSelectDescription| { &mut m.disable_color },
+                |m: &MultiSelectTypeOption| { &m.disable_color },
+                |m: &mut MultiSelectTypeOption| { &mut m.disable_color },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<MultiSelectDescription>(
-                "MultiSelectDescription",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<MultiSelectTypeOption>(
+                "MultiSelectTypeOption",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static MultiSelectDescription {
-        static instance: ::protobuf::rt::LazyV2<MultiSelectDescription> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(MultiSelectDescription::new)
+    fn default_instance() -> &'static MultiSelectTypeOption {
+        static instance: ::protobuf::rt::LazyV2<MultiSelectTypeOption> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(MultiSelectTypeOption::new)
     }
 }
 
-impl ::protobuf::Clear for MultiSelectDescription {
+impl ::protobuf::Clear for MultiSelectTypeOption {
     fn clear(&mut self) {
         self.options.clear();
         self.disable_color = false;
@@ -413,13 +413,13 @@ impl ::protobuf::Clear for MultiSelectDescription {
     }
 }
 
-impl ::std::fmt::Debug for MultiSelectDescription {
+impl ::std::fmt::Debug for MultiSelectTypeOption {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for MultiSelectDescription {
+impl ::protobuf::reflect::ProtobufValue for MultiSelectTypeOption {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
@@ -669,12 +669,12 @@ impl ::protobuf::reflect::ProtobufValue for SelectOption {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x1bselection_description.proto\"g\n\x17SingleSelectDescription\x12'\n\
+    \n\x1bselection_type_option.proto\"f\n\x16SingleSelectTypeOption\x12'\n\
     \x07options\x18\x01\x20\x03(\x0b2\r.SelectOptionR\x07options\x12#\n\rdis\
-    able_color\x18\x02\x20\x01(\x08R\x0cdisableColor\"f\n\x16MultiSelectDesc\
-    ription\x12'\n\x07options\x18\x01\x20\x03(\x0b2\r.SelectOptionR\x07optio\
-    ns\x12#\n\rdisable_color\x18\x02\x20\x01(\x08R\x0cdisableColor\"H\n\x0cS\
-    electOption\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x12\n\x04name\
+    able_color\x18\x02\x20\x01(\x08R\x0cdisableColor\"e\n\x15MultiSelectType\
+    Option\x12'\n\x07options\x18\x01\x20\x03(\x0b2\r.SelectOptionR\x07option\
+    s\x12#\n\rdisable_color\x18\x02\x20\x01(\x08R\x0cdisableColor\"H\n\x0cSe\
+    lectOption\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x12\n\x04name\
     \x18\x02\x20\x01(\tR\x04name\x12\x14\n\x05color\x18\x03\x20\x01(\tR\x05c\
     olorb\x06proto3\
 ";

+ 21 - 21
frontend/rust-lib/flowy-grid/src/protobuf/model/text_description.rs

@@ -24,7 +24,7 @@
 // const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
 
 #[derive(PartialEq,Clone,Default)]
-pub struct RichTextDescription {
+pub struct RichTextTypeOption {
     // message fields
     pub format: ::std::string::String,
     // special fields
@@ -32,14 +32,14 @@ pub struct RichTextDescription {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a RichTextDescription {
-    fn default() -> &'a RichTextDescription {
-        <RichTextDescription as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a RichTextTypeOption {
+    fn default() -> &'a RichTextTypeOption {
+        <RichTextTypeOption as ::protobuf::Message>::default_instance()
     }
 }
 
-impl RichTextDescription {
-    pub fn new() -> RichTextDescription {
+impl RichTextTypeOption {
+    pub fn new() -> RichTextTypeOption {
         ::std::default::Default::default()
     }
 
@@ -70,7 +70,7 @@ impl RichTextDescription {
     }
 }
 
-impl ::protobuf::Message for RichTextDescription {
+impl ::protobuf::Message for RichTextTypeOption {
     fn is_initialized(&self) -> bool {
         true
     }
@@ -136,8 +136,8 @@ impl ::protobuf::Message for RichTextDescription {
         Self::descriptor_static()
     }
 
-    fn new() -> RichTextDescription {
-        RichTextDescription::new()
+    fn new() -> RichTextTypeOption {
+        RichTextTypeOption::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -146,45 +146,45 @@ impl ::protobuf::Message for RichTextDescription {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "format",
-                |m: &RichTextDescription| { &m.format },
-                |m: &mut RichTextDescription| { &mut m.format },
+                |m: &RichTextTypeOption| { &m.format },
+                |m: &mut RichTextTypeOption| { &mut m.format },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<RichTextDescription>(
-                "RichTextDescription",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<RichTextTypeOption>(
+                "RichTextTypeOption",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static RichTextDescription {
-        static instance: ::protobuf::rt::LazyV2<RichTextDescription> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(RichTextDescription::new)
+    fn default_instance() -> &'static RichTextTypeOption {
+        static instance: ::protobuf::rt::LazyV2<RichTextTypeOption> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(RichTextTypeOption::new)
     }
 }
 
-impl ::protobuf::Clear for RichTextDescription {
+impl ::protobuf::Clear for RichTextTypeOption {
     fn clear(&mut self) {
         self.format.clear();
         self.unknown_fields.clear();
     }
 }
 
-impl ::std::fmt::Debug for RichTextDescription {
+impl ::std::fmt::Debug for RichTextTypeOption {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for RichTextDescription {
+impl ::protobuf::reflect::ProtobufValue for RichTextTypeOption {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x16text_description.proto\"-\n\x13RichTextDescription\x12\x16\n\x06fo\
-    rmat\x18\x01\x20\x01(\tR\x06formatb\x06proto3\
+    \n\x16text_description.proto\",\n\x12RichTextTypeOption\x12\x16\n\x06for\
+    mat\x18\x01\x20\x01(\tR\x06formatb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 1 - 1
frontend/rust-lib/flowy-grid/src/protobuf/proto/checkbox_description.proto → frontend/rust-lib/flowy-grid/src/protobuf/proto/checkbox_type_option.proto

@@ -1,5 +1,5 @@
 syntax = "proto3";
 
-message CheckboxDescription {
+message CheckboxTypeOption {
     bool is_selected = 1;
 }

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

@@ -3,7 +3,7 @@ syntax = "proto3";
 enum GridNotification {
     Unknown = 0;
     GridDidCreateBlock = 11;
-    BlockDidUpdateRow = 20;
+    DidUpdateRow = 20;
     GridDidUpdateCells = 30;
-    GridDidUpdateFields = 40;
+    DidUpdateFields = 40;
 }

+ 1 - 1
frontend/rust-lib/flowy-grid/src/protobuf/proto/date_description.proto → frontend/rust-lib/flowy-grid/src/protobuf/proto/date_type_option.proto

@@ -1,6 +1,6 @@
 syntax = "proto3";
 
-message DateDescription {
+message DateTypeOption {
     DateFormat date_format = 1;
     TimeFormat time_format = 2;
 }

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

@@ -4,7 +4,9 @@ enum GridEvent {
     GetGridData = 0;
     GetGridBlocks = 1;
     GetFields = 10;
-    CreateRow = 11;
-    GetRow = 12;
-    UpdateCell = 20;
+    UpdateField = 11;
+    CreateField = 12;
+    CreateRow = 21;
+    GetRow = 22;
+    UpdateCell = 30;
 }

+ 1 - 1
frontend/rust-lib/flowy-grid/src/protobuf/proto/number_description.proto → frontend/rust-lib/flowy-grid/src/protobuf/proto/number_type_option.proto

@@ -1,6 +1,6 @@
 syntax = "proto3";
 
-message NumberDescription {
+message NumberTypeOption {
     NumberFormat format = 1;
     uint32 scale = 2;
     string symbol = 3;

+ 2 - 2
frontend/rust-lib/flowy-grid/src/protobuf/proto/selection_description.proto → frontend/rust-lib/flowy-grid/src/protobuf/proto/selection_type_option.proto

@@ -1,10 +1,10 @@
 syntax = "proto3";
 
-message SingleSelectDescription {
+message SingleSelectTypeOption {
     repeated SelectOption options = 1;
     bool disable_color = 2;
 }
-message MultiSelectDescription {
+message MultiSelectTypeOption {
     repeated SelectOption options = 1;
     bool disable_color = 2;
 }

+ 1 - 1
frontend/rust-lib/flowy-grid/src/protobuf/proto/text_description.proto

@@ -1,5 +1,5 @@
 syntax = "proto3";
 
-message RichTextDescription {
+message RichTextTypeOption {
     string format = 1;
 }

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

@@ -6,7 +6,8 @@ use crate::dart_notification::{send_dart_notification, GridNotification};
 use dashmap::DashMap;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
-    FieldMeta, GridBlockId, GridBlockMeta, GridBlockMetaChangeset, RepeatedCell, RowMeta, RowMetaChangeset, RowOrder,
+    FieldMeta, GridBlockId, GridBlockMeta, GridBlockMetaChangeset, GridBlockOrder, RepeatedCell, RowMeta,
+    RowMetaChangeset, RowOrder,
 };
 use flowy_revision::disk::SQLiteGridBlockMetaRevisionPersistence;
 use flowy_revision::{
@@ -186,9 +187,9 @@ impl GridBlockMetaEditorManager {
     }
 
     async fn notify_block_did_update_row(&self, block_id: &str) -> FlowyResult<()> {
-        let block_id: GridBlockId = block_id.into();
-        send_dart_notification(&self.grid_id, GridNotification::BlockDidUpdateRow)
-            .payload(block_id)
+        let block_order: GridBlockOrder = block_id.into();
+        send_dart_notification(&self.grid_id, GridNotification::DidUpdateRow)
+            .payload(block_order)
             .send();
         Ok(())
     }

+ 0 - 137
frontend/rust-lib/flowy-grid/src/services/cell/builder/mod.rs

@@ -1,137 +0,0 @@
-use crate::services::cell::*;
-use crate::services::field::TypeOptionsBuilder;
-use flowy_grid_data_model::entities::FieldType;
-
-// Text
-#[derive(Default)]
-pub struct RichTextTypeOptionsBuilder(RichTextDescription);
-
-impl TypeOptionsBuilder for RichTextTypeOptionsBuilder {
-    fn field_type(&self) -> FieldType {
-        self.0.field_type()
-    }
-
-    fn build(&self) -> String {
-        self.0.clone().into()
-    }
-}
-
-// Number
-#[derive(Default)]
-pub struct NumberTypeOptionsBuilder(NumberDescription);
-
-impl NumberTypeOptionsBuilder {
-    pub fn name(mut self, name: &str) -> Self {
-        self.0.name = name.to_string();
-        self
-    }
-
-    pub fn set_format(mut self, format: NumberFormat) -> Self {
-        self.0.set_format(format);
-        self
-    }
-
-    pub fn scale(mut self, scale: u32) -> Self {
-        self.0.scale = scale;
-        self
-    }
-
-    pub fn positive(mut self, positive: bool) -> Self {
-        self.0.sign_positive = positive;
-        self
-    }
-}
-
-impl TypeOptionsBuilder for NumberTypeOptionsBuilder {
-    fn field_type(&self) -> FieldType {
-        self.0.field_type()
-    }
-
-    fn build(&self) -> String {
-        self.0.clone().into()
-    }
-}
-
-// Date
-#[derive(Default)]
-pub struct DateTypeOptionsBuilder(DateDescription);
-impl DateTypeOptionsBuilder {
-    pub fn date_format(mut self, date_format: DateFormat) -> Self {
-        self.0.date_format = date_format;
-        self
-    }
-
-    pub fn time_format(mut self, time_format: TimeFormat) -> Self {
-        self.0.time_format = time_format;
-        self
-    }
-}
-impl TypeOptionsBuilder for DateTypeOptionsBuilder {
-    fn field_type(&self) -> FieldType {
-        self.0.field_type()
-    }
-
-    fn build(&self) -> String {
-        self.0.clone().into()
-    }
-}
-
-// Single Select
-#[derive(Default)]
-pub struct SingleSelectTypeOptionsBuilder(SingleSelectDescription);
-
-impl SingleSelectTypeOptionsBuilder {
-    pub fn option(mut self, opt: SelectOption) -> Self {
-        self.0.options.push(opt);
-        self
-    }
-}
-impl TypeOptionsBuilder for SingleSelectTypeOptionsBuilder {
-    fn field_type(&self) -> FieldType {
-        self.0.field_type()
-    }
-
-    fn build(&self) -> String {
-        self.0.clone().into()
-    }
-}
-
-// Multi Select
-#[derive(Default)]
-pub struct MultiSelectTypeOptionsBuilder(MultiSelectDescription);
-
-impl MultiSelectTypeOptionsBuilder {
-    pub fn option(mut self, opt: SelectOption) -> Self {
-        self.0.options.push(opt);
-        self
-    }
-}
-
-impl TypeOptionsBuilder for MultiSelectTypeOptionsBuilder {
-    fn field_type(&self) -> FieldType {
-        self.0.field_type()
-    }
-
-    fn build(&self) -> String {
-        self.0.clone().into()
-    }
-}
-
-// Checkbox
-#[derive(Default)]
-pub struct CheckboxTypeOptionsBuilder(CheckboxDescription);
-impl CheckboxTypeOptionsBuilder {
-    pub fn set_selected(mut self, is_selected: bool) -> Self {
-        self.0.is_selected = is_selected;
-        self
-    }
-}
-impl TypeOptionsBuilder for CheckboxTypeOptionsBuilder {
-    fn field_type(&self) -> FieldType {
-        self.0.field_type()
-    }
-
-    fn build(&self) -> String {
-        self.0.clone().into()
-    }
-}

+ 0 - 11
frontend/rust-lib/flowy-grid/src/services/cell/description/mod.rs

@@ -1,11 +0,0 @@
-mod checkbox_description;
-mod date_description;
-mod number_description;
-mod selection_description;
-mod text_description;
-
-pub use checkbox_description::*;
-pub use date_description::*;
-pub use number_description::*;
-pub use selection_description::*;
-pub use text_description::*;

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

@@ -1,5 +0,0 @@
-mod builder;
-mod description;
-
-pub use builder::*;
-pub use description::*;

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

@@ -48,7 +48,7 @@ impl FieldBuilder {
         assert_eq!(self.field_meta.field_type, self.type_options_builder.field_type());
 
         let type_options = self.type_options_builder.build();
-        self.field_meta.type_options = type_options;
+        self.field_meta.type_option = type_options;
         self.field_meta
     }
 }

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

@@ -1,3 +1,5 @@
 mod field_builder;
+mod type_options;
 
 pub use field_builder::*;
+pub use type_options::*;

+ 25 - 5
frontend/rust-lib/flowy-grid/src/services/cell/description/checkbox_description.rs → frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs

@@ -1,18 +1,38 @@
 use crate::impl_from_and_to_type_option;
+use crate::services::field::TypeOptionsBuilder;
 use crate::services::row::CellDataSerde;
 use flowy_derive::ProtoBuf;
 use flowy_error::FlowyError;
 use flowy_grid_data_model::entities::{FieldMeta, FieldType};
 use serde::{Deserialize, Serialize};
 
+#[derive(Default)]
+pub struct CheckboxTypeOptionsBuilder(CheckboxTypeOption);
+impl CheckboxTypeOptionsBuilder {
+    pub fn set_selected(mut self, is_selected: bool) -> Self {
+        self.0.is_selected = is_selected;
+        self
+    }
+}
+
+impl TypeOptionsBuilder for CheckboxTypeOptionsBuilder {
+    fn field_type(&self) -> FieldType {
+        self.0.field_type()
+    }
+
+    fn build(&self) -> String {
+        self.0.clone().into()
+    }
+}
+
 #[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
-pub struct CheckboxDescription {
+pub struct CheckboxTypeOption {
     #[pb(index = 1)]
     pub is_selected: bool,
 }
-impl_from_and_to_type_option!(CheckboxDescription, FieldType::Checkbox);
+impl_from_and_to_type_option!(CheckboxTypeOption, FieldType::Checkbox);
 
-impl CellDataSerde for CheckboxDescription {
+impl CellDataSerde for CheckboxTypeOption {
     fn deserialize_cell_data(&self, data: String) -> String {
         data
     }
@@ -41,12 +61,12 @@ fn string_to_bool(bool_str: &str) -> bool {
 
 #[cfg(test)]
 mod tests {
-    use crate::services::cell::CheckboxDescription;
+    use crate::services::cell::CheckboxTypeOption;
     use crate::services::row::CellDataSerde;
 
     #[test]
     fn checkout_box_description_test() {
-        let description = CheckboxDescription::default();
+        let description = CheckboxTypeOption::default();
         assert_eq!(description.serialize_cell_data("true").unwrap(), "1".to_owned());
         assert_eq!(description.serialize_cell_data("1").unwrap(), "1".to_owned());
         assert_eq!(description.serialize_cell_data("yes").unwrap(), "1".to_owned());

+ 32 - 8
frontend/rust-lib/flowy-grid/src/services/cell/description/date_description.rs → frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs

@@ -8,20 +8,21 @@ use flowy_error::FlowyError;
 use flowy_grid_data_model::entities::{FieldMeta, FieldType};
 use serde::{Deserialize, Serialize};
 
+use crate::services::field::TypeOptionsBuilder;
 use strum_macros::EnumIter;
 
 // Date
 #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct DateDescription {
+pub struct DateTypeOption {
     #[pb(index = 1)]
     pub date_format: DateFormat,
 
     #[pb(index = 2)]
     pub time_format: TimeFormat,
 }
-impl_from_and_to_type_option!(DateDescription, FieldType::DateTime);
+impl_from_and_to_type_option!(DateTypeOption, FieldType::DateTime);
 
-impl DateDescription {
+impl DateTypeOption {
     #[allow(dead_code)]
     fn today_from_timestamp(&self, timestamp: i64) -> String {
         let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0);
@@ -38,7 +39,7 @@ impl DateDescription {
     }
 }
 
-impl CellDataSerde for DateDescription {
+impl CellDataSerde for DateTypeOption {
     fn deserialize_cell_data(&self, data: String) -> String {
         match data.parse::<i64>() {
             Ok(timestamp) => {
@@ -61,6 +62,29 @@ impl CellDataSerde for DateDescription {
     }
 }
 
+#[derive(Default)]
+pub struct DateTypeOptionsBuilder(DateTypeOption);
+impl DateTypeOptionsBuilder {
+    pub fn date_format(mut self, date_format: DateFormat) -> Self {
+        self.0.date_format = date_format;
+        self
+    }
+
+    pub fn time_format(mut self, time_format: TimeFormat) -> Self {
+        self.0.time_format = time_format;
+        self
+    }
+}
+impl TypeOptionsBuilder for DateTypeOptionsBuilder {
+    fn field_type(&self) -> FieldType {
+        self.0.field_type()
+    }
+
+    fn build(&self) -> String {
+        self.0.clone().into()
+    }
+}
+
 #[derive(Clone, Debug, Copy, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
 pub enum DateFormat {
     Local = 0,
@@ -145,13 +169,13 @@ impl std::default::Default for TimeFormat {
 
 #[cfg(test)]
 mod tests {
-    use crate::services::cell::{DateDescription, DateFormat, TimeFormat};
+    use crate::services::cell::{DateFormat, DateTypeOption, TimeFormat};
     use crate::services::row::CellDataSerde;
     use strum::IntoEnumIterator;
 
     #[test]
     fn date_description_date_format_test() {
-        let mut description = DateDescription::default();
+        let mut description = DateTypeOption::default();
         let _timestamp = 1647251762;
 
         for date_format in DateFormat::iter() {
@@ -203,7 +227,7 @@ mod tests {
 
     #[test]
     fn date_description_time_format_test() {
-        let mut description = DateDescription::default();
+        let mut description = DateTypeOption::default();
         for time_format in TimeFormat::iter() {
             description.time_format = time_format;
             match time_format {
@@ -234,7 +258,7 @@ mod tests {
     #[test]
     #[should_panic]
     fn date_description_invalid_data_test() {
-        let description = DateDescription::default();
+        let description = DateTypeOption::default();
         description.serialize_cell_data("he").unwrap();
     }
 }

+ 11 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs

@@ -0,0 +1,11 @@
+mod checkbox_type_option;
+mod date_type_option;
+mod number_type_option;
+mod selection_type_option;
+mod text_description;
+
+pub use checkbox_type_option::*;
+pub use date_type_option::*;
+pub use number_type_option::*;
+pub use selection_type_option::*;
+pub use text_description::*;

+ 72 - 36
frontend/rust-lib/flowy-grid/src/services/cell/description/number_description.rs → frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs

@@ -9,6 +9,7 @@ use rust_decimal::Decimal;
 use rusty_money::iso::{Currency, CNY, EUR, USD};
 use serde::{Deserialize, Serialize};
 
+use crate::services::field::TypeOptionsBuilder;
 use std::str::FromStr;
 use strum::IntoEnumIterator;
 use strum_macros::EnumIter;
@@ -17,44 +18,44 @@ lazy_static! {
     static ref STRIP_SYMBOL: Vec<String> = make_strip_symbol();
 }
 
-#[derive(Clone, Copy, Debug, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
-pub enum NumberFormat {
-    Number = 0,
-    USD = 1,
-    CNY = 2,
-    EUR = 3,
-}
+#[derive(Default)]
+pub struct NumberTypeOptionsBuilder(NumberTypeOption);
 
-impl std::default::Default for NumberFormat {
-    fn default() -> Self {
-        NumberFormat::Number
+impl NumberTypeOptionsBuilder {
+    pub fn name(mut self, name: &str) -> Self {
+        self.0.name = name.to_string();
+        self
+    }
+
+    pub fn set_format(mut self, format: NumberFormat) -> Self {
+        self.0.set_format(format);
+        self
+    }
+
+    pub fn scale(mut self, scale: u32) -> Self {
+        self.0.scale = scale;
+        self
+    }
+
+    pub fn positive(mut self, positive: bool) -> Self {
+        self.0.sign_positive = positive;
+        self
     }
 }
 
-impl NumberFormat {
-    pub fn symbol(&self) -> String {
-        match self {
-            NumberFormat::Number => "".to_string(),
-            NumberFormat::USD => USD.symbol.to_string(),
-            NumberFormat::CNY => CNY.symbol.to_string(),
-            NumberFormat::EUR => EUR.symbol.to_string(),
-        }
+impl TypeOptionsBuilder for NumberTypeOptionsBuilder {
+    fn field_type(&self) -> FieldType {
+        self.0.field_type()
     }
 
-    #[allow(dead_code)]
-    pub fn code(&self) -> String {
-        match self {
-            NumberFormat::Number => "".to_string(),
-            NumberFormat::USD => USD.iso_alpha_code.to_string(),
-            NumberFormat::CNY => CNY.iso_alpha_code.to_string(),
-            NumberFormat::EUR => EUR.iso_alpha_code.to_string(),
-        }
+    fn build(&self) -> String {
+        self.0.clone().into()
     }
 }
 
 // Number
 #[derive(Clone, Debug, Serialize, Deserialize, ProtoBuf)]
-pub struct NumberDescription {
+pub struct NumberTypeOption {
     #[pb(index = 1)]
     pub format: NumberFormat,
 
@@ -70,13 +71,13 @@ pub struct NumberDescription {
     #[pb(index = 5)]
     pub name: String,
 }
-impl_from_and_to_type_option!(NumberDescription, FieldType::Number);
+impl_from_and_to_type_option!(NumberTypeOption, FieldType::Number);
 
-impl std::default::Default for NumberDescription {
+impl std::default::Default for NumberTypeOption {
     fn default() -> Self {
         let format = NumberFormat::default();
         let symbol = format.symbol();
-        NumberDescription {
+        NumberTypeOption {
             format,
             scale: 0,
             symbol,
@@ -86,7 +87,7 @@ impl std::default::Default for NumberDescription {
     }
 }
 
-impl NumberDescription {
+impl NumberTypeOption {
     pub fn set_format(&mut self, format: NumberFormat) {
         self.format = format;
         self.symbol = format.symbol();
@@ -119,7 +120,42 @@ impl NumberDescription {
     }
 }
 
-impl CellDataSerde for NumberDescription {
+#[derive(Clone, Copy, Debug, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
+pub enum NumberFormat {
+    Number = 0,
+    USD = 1,
+    CNY = 2,
+    EUR = 3,
+}
+
+impl std::default::Default for NumberFormat {
+    fn default() -> Self {
+        NumberFormat::Number
+    }
+}
+
+impl NumberFormat {
+    pub fn symbol(&self) -> String {
+        match self {
+            NumberFormat::Number => "".to_string(),
+            NumberFormat::USD => USD.symbol.to_string(),
+            NumberFormat::CNY => CNY.symbol.to_string(),
+            NumberFormat::EUR => EUR.symbol.to_string(),
+        }
+    }
+
+    #[allow(dead_code)]
+    pub fn code(&self) -> String {
+        match self {
+            NumberFormat::Number => "".to_string(),
+            NumberFormat::USD => USD.iso_alpha_code.to_string(),
+            NumberFormat::CNY => CNY.iso_alpha_code.to_string(),
+            NumberFormat::EUR => EUR.iso_alpha_code.to_string(),
+        }
+    }
+}
+
+impl CellDataSerde for NumberTypeOption {
     fn deserialize_cell_data(&self, data: String) -> String {
         match self.format {
             NumberFormat::Number => data,
@@ -149,13 +185,13 @@ fn make_strip_symbol() -> Vec<String> {
 
 #[cfg(test)]
 mod tests {
-    use crate::services::cell::{NumberDescription, NumberFormat};
+    use crate::services::cell::{NumberFormat, NumberTypeOption};
     use crate::services::row::CellDataSerde;
     use strum::IntoEnumIterator;
 
     #[test]
     fn number_description_test() {
-        let mut description = NumberDescription::default();
+        let mut description = NumberTypeOption::default();
         assert_eq!(description.serialize_cell_data("¥18,443").unwrap(), "18443".to_owned());
         assert_eq!(description.serialize_cell_data("$18,443").unwrap(), "18443".to_owned());
         assert_eq!(description.serialize_cell_data("€18.443").unwrap(), "18443".to_owned());
@@ -193,7 +229,7 @@ mod tests {
 
     #[test]
     fn number_description_scale_test() {
-        let mut description = NumberDescription {
+        let mut description = NumberTypeOption {
             scale: 1,
             ..Default::default()
         };
@@ -231,7 +267,7 @@ mod tests {
 
     #[test]
     fn number_description_sign_test() {
-        let mut description = NumberDescription {
+        let mut description = NumberTypeOption {
             sign_positive: false,
             ..Default::default()
         };

+ 47 - 8
frontend/rust-lib/flowy-grid/src/services/cell/description/selection_description.rs → frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs

@@ -1,4 +1,5 @@
 use crate::impl_from_and_to_type_option;
+use crate::services::field::TypeOptionsBuilder;
 use crate::services::row::CellDataSerde;
 use crate::services::util::*;
 use flowy_derive::ProtoBuf;
@@ -12,16 +13,16 @@ pub const SELECTION_IDS_SEPARATOR: &str = ",";
 
 // Single select
 #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct SingleSelectDescription {
+pub struct SingleSelectTypeOption {
     #[pb(index = 1)]
     pub options: Vec<SelectOption>,
 
     #[pb(index = 2)]
     pub disable_color: bool,
 }
-impl_from_and_to_type_option!(SingleSelectDescription, FieldType::SingleSelect);
+impl_from_and_to_type_option!(SingleSelectTypeOption, FieldType::SingleSelect);
 
-impl CellDataSerde for SingleSelectDescription {
+impl CellDataSerde for SingleSelectTypeOption {
     fn deserialize_cell_data(&self, data: String) -> String {
         data
     }
@@ -31,17 +32,36 @@ impl CellDataSerde for SingleSelectDescription {
     }
 }
 
+#[derive(Default)]
+pub struct SingleSelectTypeOptionsBuilder(SingleSelectTypeOption);
+
+impl SingleSelectTypeOptionsBuilder {
+    pub fn option(mut self, opt: SelectOption) -> Self {
+        self.0.options.push(opt);
+        self
+    }
+}
+impl TypeOptionsBuilder for SingleSelectTypeOptionsBuilder {
+    fn field_type(&self) -> FieldType {
+        self.0.field_type()
+    }
+
+    fn build(&self) -> String {
+        self.0.clone().into()
+    }
+}
+
 // Multiple select
 #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct MultiSelectDescription {
+pub struct MultiSelectTypeOption {
     #[pb(index = 1)]
     pub options: Vec<SelectOption>,
 
     #[pb(index = 2)]
     pub disable_color: bool,
 }
-impl_from_and_to_type_option!(MultiSelectDescription, FieldType::MultiSelect);
-impl CellDataSerde for MultiSelectDescription {
+impl_from_and_to_type_option!(MultiSelectTypeOption, FieldType::MultiSelect);
+impl CellDataSerde for MultiSelectTypeOption {
     fn deserialize_cell_data(&self, data: String) -> String {
         data
     }
@@ -51,6 +71,25 @@ impl CellDataSerde for MultiSelectDescription {
     }
 }
 
+#[derive(Default)]
+pub struct MultiSelectTypeOptionsBuilder(MultiSelectTypeOption);
+impl MultiSelectTypeOptionsBuilder {
+    pub fn option(mut self, opt: SelectOption) -> Self {
+        self.0.options.push(opt);
+        self
+    }
+}
+
+impl TypeOptionsBuilder for MultiSelectTypeOptionsBuilder {
+    fn field_type(&self) -> FieldType {
+        self.0.field_type()
+    }
+
+    fn build(&self) -> String {
+        self.0.clone().into()
+    }
+}
+
 fn single_select_option_id_from_data(data: String) -> FlowyResult<String> {
     let select_option_ids = select_option_ids(data)?;
     if select_option_ids.is_empty() {
@@ -112,13 +151,13 @@ impl SelectOption {
 
 #[cfg(test)]
 mod tests {
-    use crate::services::cell::{MultiSelectDescription, SingleSelectDescription};
+    use crate::services::cell::{MultiSelectDescription, SingleSelectTypeOption};
     use crate::services::row::CellDataSerde;
 
     #[test]
     #[should_panic]
     fn selection_description_test() {
-        let description = SingleSelectDescription::default();
+        let description = SingleSelectTypeOption::default();
         assert_eq!(description.serialize_cell_data("1,2,3").unwrap(), "1".to_owned());
 
         let description = MultiSelectDescription::default();

+ 17 - 3
frontend/rust-lib/flowy-grid/src/services/cell/description/text_description.rs → frontend/rust-lib/flowy-grid/src/services/field/type_options/text_description.rs

@@ -1,19 +1,33 @@
 use crate::impl_from_and_to_type_option;
 use crate::services::row::CellDataSerde;
 
+use crate::services::field::TypeOptionsBuilder;
 use flowy_derive::ProtoBuf;
 use flowy_error::FlowyError;
 use flowy_grid_data_model::entities::{FieldMeta, FieldType};
 use serde::{Deserialize, Serialize};
 
+#[derive(Default)]
+pub struct RichTextTypeOptionsBuilder(RichTextTypeOption);
+
+impl TypeOptionsBuilder for RichTextTypeOptionsBuilder {
+    fn field_type(&self) -> FieldType {
+        self.0.field_type()
+    }
+
+    fn build(&self) -> String {
+        self.0.clone().into()
+    }
+}
+
 #[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
-pub struct RichTextDescription {
+pub struct RichTextTypeOption {
     #[pb(index = 1)]
     pub format: String,
 }
-impl_from_and_to_type_option!(RichTextDescription, FieldType::RichText);
+impl_from_and_to_type_option!(RichTextTypeOption, FieldType::RichText);
 
-impl CellDataSerde for RichTextDescription {
+impl CellDataSerde for RichTextTypeOption {
     fn deserialize_cell_data(&self, data: String) -> String {
         data
     }

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

@@ -16,6 +16,7 @@ use crate::services::row::{
     make_grid_block_from_block_metas, make_grid_blocks, make_row_meta_from_context, make_rows_from_row_metas,
     serialize_cell_data, CreateRowMetaBuilder, CreateRowMetaPayload, GridBlockMetaData,
 };
+use flowy_grid_data_model::parser::CreateFieldParams;
 use flowy_revision::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
 use lib_infra::future::FutureResult;
 use lib_ot::core::PlainTextAttributes;
@@ -54,23 +55,25 @@ impl ClientGridEditor {
         }))
     }
 
-    pub async fn create_field(&self, field_meta: FieldMeta) -> FlowyResult<()> {
-        let _ = self.modify(|grid| Ok(grid.create_field(field_meta)?)).await?;
+    pub async fn create_field(&self, params: CreateFieldParams) -> FlowyResult<()> {
+        let _ = self.modify(|grid| Ok(grid.create_field(params)?)).await?;
         let _ = self.notify_did_update_fields().await?;
         Ok(())
     }
 
-    pub async fn contain_field(&self, field_meta: &FieldMeta) -> bool {
-        self.pad.read().await.contain_field(&field_meta.id)
+    pub async fn contain_field(&self, field_id: &str) -> bool {
+        self.pad.read().await.contain_field(field_id)
     }
 
     pub async fn update_field(&self, change: FieldChangeset) -> FlowyResult<()> {
         let _ = self.modify(|grid| Ok(grid.update_field(change)?)).await?;
+        let _ = self.notify_did_update_fields().await?;
         Ok(())
     }
 
     pub async fn delete_field(&self, field_id: &str) -> FlowyResult<()> {
         let _ = self.modify(|grid| Ok(grid.delete_field(field_id)?)).await?;
+        let _ = self.notify_did_update_fields().await?;
         Ok(())
     }
 
@@ -293,7 +296,7 @@ impl ClientGridEditor {
     async fn notify_did_update_fields(&self) -> FlowyResult<()> {
         let field_metas = self.get_field_metas(None).await?;
         let repeated_field: RepeatedField = field_metas.into_iter().map(Field::from).collect::<Vec<_>>().into();
-        send_dart_notification(&self.grid_id, GridNotification::GridDidUpdateFields)
+        send_dart_notification(&self.grid_id, GridNotification::DidUpdateFields)
             .payload(repeated_field)
             .send();
         Ok(())

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

@@ -1,7 +1,6 @@
 mod util;
 
 pub mod block_meta_editor;
-pub mod cell;
 pub mod field;
 pub mod grid_editor;
 pub mod kv_persistence;

+ 13 - 13
frontend/rust-lib/flowy-grid/src/services/row/cell_data_serde.rs

@@ -1,4 +1,4 @@
-use crate::services::cell::*;
+use crate::services::field::*;
 use flowy_error::FlowyError;
 use flowy_grid_data_model::entities::{FieldMeta, FieldType};
 
@@ -10,23 +10,23 @@ pub trait CellDataSerde {
 #[allow(dead_code)]
 pub fn serialize_cell_data(data: &str, field: &FieldMeta) -> Result<String, FlowyError> {
     match field.field_type {
-        FieldType::RichText => RichTextDescription::from(field).serialize_cell_data(data),
-        FieldType::Number => NumberDescription::from(field).serialize_cell_data(data),
-        FieldType::DateTime => DateDescription::from(field).serialize_cell_data(data),
-        FieldType::SingleSelect => SingleSelectDescription::from(field).serialize_cell_data(data),
-        FieldType::MultiSelect => MultiSelectDescription::from(field).serialize_cell_data(data),
-        FieldType::Checkbox => CheckboxDescription::from(field).serialize_cell_data(data),
+        FieldType::RichText => RichTextTypeOption::from(field).serialize_cell_data(data),
+        FieldType::Number => NumberTypeOption::from(field).serialize_cell_data(data),
+        FieldType::DateTime => DateTypeOption::from(field).serialize_cell_data(data),
+        FieldType::SingleSelect => SingleSelectTypeOption::from(field).serialize_cell_data(data),
+        FieldType::MultiSelect => MultiSelectTypeOption::from(field).serialize_cell_data(data),
+        FieldType::Checkbox => CheckboxTypeOption::from(field).serialize_cell_data(data),
     }
 }
 
 pub fn deserialize_cell_data(data: String, field: &FieldMeta) -> Result<String, FlowyError> {
     let s = match field.field_type {
-        FieldType::RichText => RichTextDescription::from(field).deserialize_cell_data(data),
-        FieldType::Number => NumberDescription::from(field).deserialize_cell_data(data),
-        FieldType::DateTime => DateDescription::from(field).deserialize_cell_data(data),
-        FieldType::SingleSelect => SingleSelectDescription::from(field).deserialize_cell_data(data),
-        FieldType::MultiSelect => MultiSelectDescription::from(field).deserialize_cell_data(data),
-        FieldType::Checkbox => CheckboxDescription::from(field).deserialize_cell_data(data),
+        FieldType::RichText => RichTextTypeOption::from(field).deserialize_cell_data(data),
+        FieldType::Number => NumberTypeOption::from(field).deserialize_cell_data(data),
+        FieldType::DateTime => DateTypeOption::from(field).deserialize_cell_data(data),
+        FieldType::SingleSelect => SingleSelectTypeOption::from(field).deserialize_cell_data(data),
+        FieldType::MultiSelect => MultiSelectTypeOption::from(field).deserialize_cell_data(data),
+        FieldType::Checkbox => CheckboxTypeOption::from(field).deserialize_cell_data(data),
     };
     Ok(s)
 }

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

@@ -1,4 +1,3 @@
-use crate::services::cell::*;
 use crate::services::field::*;
 use flowy_grid_data_model::entities::{BuildGridContext, FieldType};
 use flowy_sync::client_grid::GridBuilder;

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

@@ -1,7 +1,9 @@
 use crate::grid::script::EditorScript::*;
 use crate::grid::script::*;
 use chrono::NaiveDateTime;
-use flowy_grid::services::cell::*;
+use flowy_grid::services::field::{
+    MultiSelectTypeOption, SelectOption, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR,
+};
 use flowy_grid::services::row::{deserialize_cell_data, serialize_cell_data, CellDataSerde, CreateRowMetaBuilder};
 use flowy_grid_data_model::entities::{
     CellMetaChangeset, FieldChangeset, FieldType, GridBlockMeta, GridBlockMetaChangeset, RowMetaChangeset,
@@ -10,23 +12,22 @@ use flowy_grid_data_model::entities::{
 #[tokio::test]
 async fn grid_create_field() {
     let mut test = GridEditorTest::new().await;
-    let text_field = create_text_field();
-    let single_select_field = create_single_select_field();
-
+    let (text_field_params, text_field_meta) = create_text_field(&test.grid_id);
+    let (single_select_params, single_select_field) = create_single_select_field(&test.grid_id);
     let scripts = vec![
         CreateField {
-            field_meta: text_field.clone(),
+            params: text_field_params,
         },
         AssertFieldEqual {
             field_index: test.field_count,
-            field_meta: text_field,
+            field_meta: text_field_meta,
         },
     ];
     test.run_scripts(scripts).await;
 
     let scripts = vec![
         CreateField {
-            field_meta: single_select_field.clone(),
+            params: single_select_params,
         },
         AssertFieldEqual {
             field_index: test.field_count,
@@ -39,16 +40,12 @@ async fn grid_create_field() {
 #[tokio::test]
 async fn grid_create_duplicate_field() {
     let mut test = GridEditorTest::new().await;
-    let text_field = create_text_field();
+    let (params, _) = create_text_field(&test.grid_id);
     let field_count = test.field_count;
     let expected_field_count = field_count + 1;
     let scripts = vec![
-        CreateField {
-            field_meta: text_field.clone(),
-        },
-        CreateField {
-            field_meta: text_field.clone(),
-        },
+        CreateField { params: params.clone() },
+        CreateField { params },
         AssertFieldCount(expected_field_count),
     ];
     test.run_scripts(scripts).await;
@@ -57,9 +54,10 @@ async fn grid_create_duplicate_field() {
 #[tokio::test]
 async fn grid_update_field_with_empty_change() {
     let mut test = GridEditorTest::new().await;
-    let single_select_field = create_single_select_field();
+    let (params, field_meta) = create_single_select_field(&test.grid_id);
     let changeset = FieldChangeset {
-        field_id: single_select_field.id.clone(),
+        field_id: field_meta.id.clone(),
+        grid_id: test.grid_id.clone(),
         name: None,
         desc: None,
         field_type: None,
@@ -70,13 +68,11 @@ async fn grid_update_field_with_empty_change() {
     };
 
     let scripts = vec![
-        CreateField {
-            field_meta: single_select_field.clone(),
-        },
+        CreateField { params },
         UpdateField { changeset },
         AssertFieldEqual {
             field_index: test.field_count,
-            field_meta: single_select_field,
+            field_meta,
         },
     ];
     test.run_scripts(scripts).await;
@@ -85,13 +81,14 @@ async fn grid_update_field_with_empty_change() {
 #[tokio::test]
 async fn grid_update_field() {
     let mut test = GridEditorTest::new().await;
-    let single_select_field = create_single_select_field();
+    let (single_select_params, single_select_field) = create_single_select_field(&test.grid_id);
     let mut cloned_field = single_select_field.clone();
 
-    let mut single_select_type_options = SingleSelectDescription::from(&single_select_field);
+    let mut single_select_type_options = SingleSelectTypeOption::from(&single_select_field);
     single_select_type_options.options.push(SelectOption::new("Unknown"));
     let changeset = FieldChangeset {
         field_id: single_select_field.id.clone(),
+        grid_id: test.grid_id.clone(),
         name: None,
         desc: None,
         field_type: None,
@@ -103,11 +100,11 @@ async fn grid_update_field() {
 
     cloned_field.frozen = true;
     cloned_field.width = 1000;
-    cloned_field.type_options = single_select_type_options.into();
+    cloned_field.type_option = single_select_type_options.into();
 
     let scripts = vec![
         CreateField {
-            field_meta: single_select_field.clone(),
+            params: single_select_params,
         },
         UpdateField { changeset },
         AssertFieldEqual {
@@ -122,11 +119,9 @@ async fn grid_update_field() {
 async fn grid_delete_field() {
     let mut test = GridEditorTest::new().await;
     let expected_field_count = test.field_count;
-    let text_field = create_text_field();
+    let (text_params, text_field) = create_text_field(&test.grid_id);
     let scripts = vec![
-        CreateField {
-            field_meta: text_field.clone(),
-        },
+        CreateField { params: text_params },
         DeleteField { field_meta: text_field },
         AssertFieldCount(expected_field_count),
     ];
@@ -258,13 +253,13 @@ async fn grid_row_add_cells_test() {
                 builder.add_cell(&field.id, data).unwrap();
             }
             FieldType::SingleSelect => {
-                let description = SingleSelectDescription::from(field);
+                let description = SingleSelectTypeOption::from(field);
                 let options = description.options.first().unwrap();
                 let data = description.serialize_cell_data(&options.id).unwrap();
                 builder.add_cell(&field.id, data).unwrap();
             }
             FieldType::MultiSelect => {
-                let description = MultiSelectDescription::from(field);
+                let description = MultiSelectTypeOption::from(field);
                 let options = description
                     .options
                     .iter()
@@ -387,11 +382,11 @@ async fn grid_cell_update() {
                     FieldType::Number => "123".to_string(),
                     FieldType::DateTime => "123".to_string(),
                     FieldType::SingleSelect => {
-                        let description = SingleSelectDescription::from(field_meta);
+                        let description = SingleSelectTypeOption::from(field_meta);
                         description.options.first().unwrap().id.clone()
                     }
                     FieldType::MultiSelect => {
-                        let description = MultiSelectDescription::from(field_meta);
+                        let description = MultiSelectTypeOption::from(field_meta);
                         description.options.first().unwrap().id.clone()
                     }
                     FieldType::Checkbox => "1".to_string(),

+ 57 - 17
frontend/rust-lib/flowy-grid/tests/grid/script.rs

@@ -1,18 +1,17 @@
 use bytes::Bytes;
-use flowy_sync::client_grid::GridBuilder;
-use std::collections::HashMap;
-
-use flowy_grid::services::cell::*;
 use flowy_grid::services::field::*;
 use flowy_grid::services::grid_editor::{ClientGridEditor, GridPadBuilder};
 use flowy_grid::services::row::CreateRowMetaPayload;
 use flowy_grid_data_model::entities::{
-    BuildGridContext, CellMetaChangeset, FieldChangeset, FieldMeta, FieldType, GridBlockMeta, GridBlockMetaChangeset,
-    RowMeta, RowMetaChangeset, RowOrder,
+    BuildGridContext, CellMetaChangeset, CreateFieldPayload, Field, FieldChangeset, FieldMeta, FieldType,
+    GridBlockMeta, GridBlockMetaChangeset, RowMeta, RowMetaChangeset, RowOrder,
 };
+use flowy_grid_data_model::parser::CreateFieldParams;
 use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
+use flowy_sync::client_grid::GridBuilder;
 use flowy_test::helper::ViewTest;
 use flowy_test::FlowySDKTest;
+use std::collections::HashMap;
 use std::sync::Arc;
 use std::time::Duration;
 use strum::EnumCount;
@@ -20,7 +19,7 @@ use tokio::time::sleep;
 
 pub enum EditorScript {
     CreateField {
-        field_meta: FieldMeta,
+        params: CreateFieldParams,
     },
     UpdateField {
         changeset: FieldChangeset,
@@ -121,11 +120,12 @@ impl GridEditorTest {
         let _cache = rev_manager.revision_cache().await;
 
         match script {
-            EditorScript::CreateField { field_meta } => {
-                if !self.editor.contain_field(&field_meta).await {
+            EditorScript::CreateField { params } => {
+                if !self.editor.contain_field(&params.field.id).await {
                     self.field_count += 1;
                 }
-                self.editor.create_field(field_meta).await.unwrap();
+
+                self.editor.create_field(params).await.unwrap();
                 self.field_metas = self.editor.get_field_metas(None).await.unwrap();
                 assert_eq!(self.field_count, self.field_metas.len());
             }
@@ -134,7 +134,7 @@ impl GridEditorTest {
                 self.field_metas = self.editor.get_field_metas(None).await.unwrap();
             }
             EditorScript::DeleteField { field_meta } => {
-                if self.editor.contain_field(&field_meta).await {
+                if self.editor.contain_field(&field_meta.id).await {
                     self.field_count -= 1;
                 }
 
@@ -247,24 +247,64 @@ async fn get_row_metas(editor: &Arc<ClientGridEditor>) -> Vec<Arc<RowMeta>> {
         .row_metas
 }
 
-pub fn create_text_field() -> FieldMeta {
-    FieldBuilder::new(RichTextTypeOptionsBuilder::default())
+pub fn create_text_field(grid_id: &str) -> (CreateFieldParams, FieldMeta) {
+    let field_meta = FieldBuilder::new(RichTextTypeOptionsBuilder::default())
         .name("Name")
         .visibility(true)
         .field_type(FieldType::RichText)
-        .build()
+        .build();
+
+    let cloned_field_meta = field_meta.clone();
+
+    let field = Field {
+        id: field_meta.id,
+        name: field_meta.name,
+        desc: field_meta.desc,
+        field_type: field_meta.field_type,
+        frozen: field_meta.frozen,
+        visibility: field_meta.visibility,
+        width: field_meta.width,
+    };
+
+    let params = CreateFieldParams {
+        grid_id: grid_id.to_owned(),
+        field,
+        type_option_data: field_meta.type_option.as_bytes().to_vec(),
+        start_field_id: None,
+    };
+    (params, cloned_field_meta)
 }
 
-pub fn create_single_select_field() -> FieldMeta {
+pub fn create_single_select_field(grid_id: &str) -> (CreateFieldParams, FieldMeta) {
     let single_select = SingleSelectTypeOptionsBuilder::default()
         .option(SelectOption::new("Done"))
         .option(SelectOption::new("Progress"));
 
-    FieldBuilder::new(single_select)
+    let field_meta = FieldBuilder::new(single_select)
         .name("Name")
         .visibility(true)
         .field_type(FieldType::SingleSelect)
-        .build()
+        .build();
+
+    let cloned_field_meta = field_meta.clone();
+
+    let field = Field {
+        id: field_meta.id,
+        name: field_meta.name,
+        desc: field_meta.desc,
+        field_type: field_meta.field_type,
+        frozen: field_meta.frozen,
+        visibility: field_meta.visibility,
+        width: field_meta.width,
+    };
+
+    let params = CreateFieldParams {
+        grid_id: grid_id.to_owned(),
+        field,
+        type_option_data: field_meta.type_option.as_bytes().to_vec(),
+        start_field_id: None,
+    };
+    (params, cloned_field_meta)
 }
 
 fn make_template_1_grid() -> BuildGridContext {

+ 11 - 5
shared-lib/flowy-error-code/src/code.rs

@@ -88,14 +88,20 @@ pub enum ErrorCode {
     UserNotExist = 312,
     #[display(fmt = "Text is too long")]
     TextTooLong = 400,
+
+    #[display(fmt = "Grid id is empty")]
+    GridIdIsEmpty = 410,
     #[display(fmt = "Grid block id is empty")]
-    BlockIdIsEmpty = 401,
+    BlockIdIsEmpty = 420,
     #[display(fmt = "Row id is empty")]
-    RowIdIsEmpty = 402,
-    #[display(fmt = "Grid id is empty")]
-    GridIdIsEmpty = 403,
+    RowIdIsEmpty = 430,
+    #[display(fmt = "Field id is empty")]
+    FieldIdIsEmpty = 440,
+    #[display(fmt = "Field's type option should not be empty")]
+    TypeOptionIsEmpty = 441,
+
     #[display(fmt = "Invalid data")]
-    InvalidData = 404,
+    InvalidData = 500,
 }
 
 impl ErrorCode {

+ 21 - 14
shared-lib/flowy-error-code/src/protobuf/model/code.rs

@@ -57,10 +57,12 @@ pub enum ErrorCode {
     UserIdInvalid = 311,
     UserNotExist = 312,
     TextTooLong = 400,
-    BlockIdIsEmpty = 401,
-    RowIdIsEmpty = 402,
-    GridIdIsEmpty = 403,
-    InvalidData = 404,
+    GridIdIsEmpty = 410,
+    BlockIdIsEmpty = 420,
+    RowIdIsEmpty = 430,
+    FieldIdIsEmpty = 440,
+    TypeOptionIsEmpty = 441,
+    InvalidData = 500,
 }
 
 impl ::protobuf::ProtobufEnum for ErrorCode {
@@ -102,10 +104,12 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             311 => ::std::option::Option::Some(ErrorCode::UserIdInvalid),
             312 => ::std::option::Option::Some(ErrorCode::UserNotExist),
             400 => ::std::option::Option::Some(ErrorCode::TextTooLong),
-            401 => ::std::option::Option::Some(ErrorCode::BlockIdIsEmpty),
-            402 => ::std::option::Option::Some(ErrorCode::RowIdIsEmpty),
-            403 => ::std::option::Option::Some(ErrorCode::GridIdIsEmpty),
-            404 => ::std::option::Option::Some(ErrorCode::InvalidData),
+            410 => ::std::option::Option::Some(ErrorCode::GridIdIsEmpty),
+            420 => ::std::option::Option::Some(ErrorCode::BlockIdIsEmpty),
+            430 => ::std::option::Option::Some(ErrorCode::RowIdIsEmpty),
+            440 => ::std::option::Option::Some(ErrorCode::FieldIdIsEmpty),
+            441 => ::std::option::Option::Some(ErrorCode::TypeOptionIsEmpty),
+            500 => ::std::option::Option::Some(ErrorCode::InvalidData),
             _ => ::std::option::Option::None
         }
     }
@@ -144,9 +148,11 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             ErrorCode::UserIdInvalid,
             ErrorCode::UserNotExist,
             ErrorCode::TextTooLong,
+            ErrorCode::GridIdIsEmpty,
             ErrorCode::BlockIdIsEmpty,
             ErrorCode::RowIdIsEmpty,
-            ErrorCode::GridIdIsEmpty,
+            ErrorCode::FieldIdIsEmpty,
+            ErrorCode::TypeOptionIsEmpty,
             ErrorCode::InvalidData,
         ];
         values
@@ -176,7 +182,7 @@ impl ::protobuf::reflect::ProtobufValue for ErrorCode {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\ncode.proto*\xb7\x06\n\tErrorCode\x12\x0c\n\x08Internal\x10\0\x12\x14\
+    \n\ncode.proto*\xe4\x06\n\tErrorCode\x12\x0c\n\x08Internal\x10\0\x12\x14\
     \n\x10UserUnauthorized\x10\x02\x12\x12\n\x0eRecordNotFound\x10\x03\x12\
     \x11\n\rUserIdIsEmpty\x10\x04\x12\x18\n\x14WorkspaceNameInvalid\x10d\x12\
     \x16\n\x12WorkspaceIdInvalid\x10e\x12\x18\n\x14AppColorStyleInvalid\x10f\
@@ -193,10 +199,11 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     swordNotMatch\x10\xb3\x02\x12\x14\n\x0fUserNameTooLong\x10\xb4\x02\x12'\
     \n\"UserNameContainForbiddenCharacters\x10\xb5\x02\x12\x14\n\x0fUserName\
     IsEmpty\x10\xb6\x02\x12\x12\n\rUserIdInvalid\x10\xb7\x02\x12\x11\n\x0cUs\
-    erNotExist\x10\xb8\x02\x12\x10\n\x0bTextTooLong\x10\x90\x03\x12\x13\n\
-    \x0eBlockIdIsEmpty\x10\x91\x03\x12\x11\n\x0cRowIdIsEmpty\x10\x92\x03\x12\
-    \x12\n\rGridIdIsEmpty\x10\x93\x03\x12\x10\n\x0bInvalidData\x10\x94\x03b\
-    \x06proto3\
+    erNotExist\x10\xb8\x02\x12\x10\n\x0bTextTooLong\x10\x90\x03\x12\x12\n\rG\
+    ridIdIsEmpty\x10\x9a\x03\x12\x13\n\x0eBlockIdIsEmpty\x10\xa4\x03\x12\x11\
+    \n\x0cRowIdIsEmpty\x10\xae\x03\x12\x13\n\x0eFieldIdIsEmpty\x10\xb8\x03\
+    \x12\x16\n\x11TypeOptionIsEmpty\x10\xb9\x03\x12\x10\n\x0bInvalidData\x10\
+    \xf4\x03b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 6 - 4
shared-lib/flowy-error-code/src/protobuf/proto/code.proto

@@ -33,8 +33,10 @@ enum ErrorCode {
     UserIdInvalid = 311;
     UserNotExist = 312;
     TextTooLong = 400;
-    BlockIdIsEmpty = 401;
-    RowIdIsEmpty = 402;
-    GridIdIsEmpty = 403;
-    InvalidData = 404;
+    GridIdIsEmpty = 410;
+    BlockIdIsEmpty = 420;
+    RowIdIsEmpty = 430;
+    FieldIdIsEmpty = 440;
+    TypeOptionIsEmpty = 441;
+    InvalidData = 500;
 }

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

@@ -178,6 +178,12 @@ pub struct GridBlockOrder {
     pub block_id: String,
 }
 
+impl std::convert::From<&str> for GridBlockOrder {
+    fn from(s: &str) -> Self {
+        GridBlockOrder { block_id: s.to_owned() }
+    }
+}
+
 #[derive(Debug, Default, ProtoBuf)]
 pub struct GridBlock {
     #[pb(index = 1)]
@@ -284,6 +290,21 @@ pub struct CreateRowPayload {
     pub start_row_id: Option<String>,
 }
 
+#[derive(ProtoBuf, Default)]
+pub struct CreateFieldPayload {
+    #[pb(index = 1)]
+    pub grid_id: String,
+
+    #[pb(index = 2)]
+    pub field: Field,
+
+    #[pb(index = 3)]
+    pub type_option_data: Vec<u8>,
+
+    #[pb(index = 4, one_of)]
+    pub start_field_id: Option<String>,
+}
+
 #[derive(ProtoBuf, Default)]
 pub struct QueryFieldPayload {
     #[pb(index = 1)]

+ 12 - 9
shared-lib/flowy-grid-data-model/src/entities/meta.rs

@@ -98,7 +98,7 @@ pub struct FieldMeta {
     pub width: i32,
 
     #[pb(index = 8)]
-    pub type_options: String,
+    pub type_option: String,
 }
 
 impl FieldMeta {
@@ -111,7 +111,7 @@ impl FieldMeta {
             frozen: false,
             visibility: true,
             width: DEFAULT_FIELD_WIDTH,
-            type_options: Default::default(),
+            type_option: Default::default(),
         }
     }
 }
@@ -121,25 +121,28 @@ pub struct FieldChangeset {
     #[pb(index = 1)]
     pub field_id: String,
 
-    #[pb(index = 2, one_of)]
-    pub name: Option<String>,
+    #[pb(index = 2)]
+    pub grid_id: String,
 
     #[pb(index = 3, one_of)]
-    pub desc: Option<String>,
+    pub name: Option<String>,
 
     #[pb(index = 4, one_of)]
-    pub field_type: Option<FieldType>,
+    pub desc: Option<String>,
 
     #[pb(index = 5, one_of)]
-    pub frozen: Option<bool>,
+    pub field_type: Option<FieldType>,
 
     #[pb(index = 6, one_of)]
-    pub visibility: Option<bool>,
+    pub frozen: Option<bool>,
 
     #[pb(index = 7, one_of)]
-    pub width: Option<i32>,
+    pub visibility: Option<bool>,
 
     #[pb(index = 8, one_of)]
+    pub width: Option<i32>,
+
+    #[pb(index = 9, one_of)]
     pub type_options: Option<String>,
 }
 

+ 0 - 82
shared-lib/flowy-grid-data-model/src/parser/grid.rs

@@ -1,82 +0,0 @@
-use crate::entities::{
-    CreateRowPayload, GridBlockOrder, QueryFieldPayload, QueryGridBlocksPayload, QueryRowPayload, RepeatedFieldOrder,
-};
-use crate::parser::NonEmptyId;
-use flowy_error_code::ErrorCode;
-
-#[derive(Default)]
-pub struct CreateRowParams {
-    pub grid_id: String,
-    pub start_row_id: Option<String>,
-}
-
-impl TryInto<CreateRowParams> for CreateRowPayload {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<CreateRowParams, Self::Error> {
-        let grid_id = NonEmptyId::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
-        Ok(CreateRowParams {
-            grid_id: grid_id.0,
-            start_row_id: self.start_row_id,
-        })
-    }
-}
-
-#[derive(Default)]
-pub struct QueryFieldParams {
-    pub grid_id: String,
-    pub field_orders: RepeatedFieldOrder,
-}
-
-impl TryInto<QueryFieldParams> for QueryFieldPayload {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<QueryFieldParams, Self::Error> {
-        let grid_id = NonEmptyId::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
-        Ok(QueryFieldParams {
-            grid_id: grid_id.0,
-            field_orders: self.field_orders,
-        })
-    }
-}
-
-#[derive(Default)]
-pub struct QueryGridBlocksParams {
-    pub grid_id: String,
-    pub block_orders: Vec<GridBlockOrder>,
-}
-
-impl TryInto<QueryGridBlocksParams> for QueryGridBlocksPayload {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<QueryGridBlocksParams, Self::Error> {
-        let grid_id = NonEmptyId::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
-        Ok(QueryGridBlocksParams {
-            grid_id: grid_id.0,
-            block_orders: self.block_orders,
-        })
-    }
-}
-
-#[derive(Default)]
-pub struct QueryRowParams {
-    pub grid_id: String,
-    pub block_id: String,
-    pub row_id: String,
-}
-
-impl TryInto<QueryRowParams> for QueryRowPayload {
-    type Error = ErrorCode;
-
-    fn try_into(self) -> Result<QueryRowParams, Self::Error> {
-        let grid_id = NonEmptyId::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
-        let block_id = NonEmptyId::parse(self.block_id).map_err(|_| ErrorCode::BlockIdIsEmpty)?;
-        let row_id = NonEmptyId::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
-
-        Ok(QueryRowParams {
-            grid_id: grid_id.0,
-            block_id: block_id.0,
-            row_id: row_id.0,
-        })
-    }
-}

+ 116 - 0
shared-lib/flowy-grid-data-model/src/parser/grid_params.rs

@@ -0,0 +1,116 @@
+use crate::entities::{
+    CreateFieldPayload, CreateRowPayload, Field, GridBlockOrder, QueryFieldPayload, QueryGridBlocksPayload,
+    QueryRowPayload, RepeatedFieldOrder,
+};
+use crate::parser::NotEmptyUuid;
+use flowy_error_code::ErrorCode;
+
+#[derive(Default)]
+pub struct CreateRowParams {
+    pub grid_id: String,
+    pub start_row_id: Option<String>,
+}
+
+impl TryInto<CreateRowParams> for CreateRowPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<CreateRowParams, Self::Error> {
+        let grid_id = NotEmptyUuid::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+        Ok(CreateRowParams {
+            grid_id: grid_id.0,
+            start_row_id: self.start_row_id,
+        })
+    }
+}
+
+#[derive(Default)]
+pub struct QueryFieldParams {
+    pub grid_id: String,
+    pub field_orders: RepeatedFieldOrder,
+}
+
+impl TryInto<QueryFieldParams> for QueryFieldPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<QueryFieldParams, Self::Error> {
+        let grid_id = NotEmptyUuid::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+        Ok(QueryFieldParams {
+            grid_id: grid_id.0,
+            field_orders: self.field_orders,
+        })
+    }
+}
+
+#[derive(Default)]
+pub struct QueryGridBlocksParams {
+    pub grid_id: String,
+    pub block_orders: Vec<GridBlockOrder>,
+}
+
+impl TryInto<QueryGridBlocksParams> for QueryGridBlocksPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<QueryGridBlocksParams, Self::Error> {
+        let grid_id = NotEmptyUuid::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+        Ok(QueryGridBlocksParams {
+            grid_id: grid_id.0,
+            block_orders: self.block_orders,
+        })
+    }
+}
+
+#[derive(Default)]
+pub struct QueryRowParams {
+    pub grid_id: String,
+    pub block_id: String,
+    pub row_id: String,
+}
+
+impl TryInto<QueryRowParams> for QueryRowPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<QueryRowParams, Self::Error> {
+        let grid_id = NotEmptyUuid::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+        let block_id = NotEmptyUuid::parse(self.block_id).map_err(|_| ErrorCode::BlockIdIsEmpty)?;
+        let row_id = NotEmptyUuid::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
+
+        Ok(QueryRowParams {
+            grid_id: grid_id.0,
+            block_id: block_id.0,
+            row_id: row_id.0,
+        })
+    }
+}
+
+#[derive(Default, Clone)]
+pub struct CreateFieldParams {
+    pub grid_id: String,
+    pub field: Field,
+    pub type_option_data: Vec<u8>,
+    pub start_field_id: Option<String>,
+}
+
+impl TryInto<CreateFieldParams> for CreateFieldPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<CreateFieldParams, Self::Error> {
+        let grid_id = NotEmptyUuid::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+        let _ = NotEmptyUuid::parse(self.field.id.clone()).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
+
+        let start_field_id = match self.start_field_id {
+            None => None,
+            Some(id) => Some(NotEmptyUuid::parse(id).map_err(|_| ErrorCode::FieldIdIsEmpty)?.0),
+        };
+
+        if self.type_option_data.is_empty() {
+            return Err(ErrorCode::TypeOptionIsEmpty);
+        }
+
+        Ok(CreateFieldParams {
+            grid_id: grid_id.0,
+            field: self.field,
+            type_option_data: self.type_option_data,
+            start_field_id,
+        })
+    }
+}

+ 0 - 18
shared-lib/flowy-grid-data-model/src/parser/id.rs

@@ -1,18 +0,0 @@
-#[derive(Debug)]
-pub struct NonEmptyId(pub String);
-
-impl NonEmptyId {
-    pub fn parse(s: String) -> Result<NonEmptyId, ()> {
-        if s.trim().is_empty() {
-            return Err(());
-        }
-
-        Ok(Self(s))
-    }
-}
-
-impl AsRef<str> for NonEmptyId {
-    fn as_ref(&self) -> &str {
-        &self.0
-    }
-}

+ 22 - 0
shared-lib/flowy-grid-data-model/src/parser/id_parser.rs

@@ -0,0 +1,22 @@
+use uuid::Uuid;
+
+#[derive(Debug)]
+pub struct NotEmptyUuid(pub String);
+
+impl NotEmptyUuid {
+    pub fn parse(s: String) -> Result<NotEmptyUuid, ()> {
+        debug_assert!(Uuid::parse_str(&s).is_ok());
+
+        if s.trim().is_empty() {
+            return Err(());
+        }
+
+        Ok(Self(s))
+    }
+}
+
+impl AsRef<str> for NotEmptyUuid {
+    fn as_ref(&self) -> &str {
+        &self.0
+    }
+}

+ 4 - 4
shared-lib/flowy-grid-data-model/src/parser/mod.rs

@@ -1,5 +1,5 @@
-mod grid;
-mod id;
+mod grid_params;
+mod id_parser;
 
-pub use grid::*;
-pub use id::*;
+pub use grid_params::*;
+pub use id_parser::*;

+ 353 - 8
shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs

@@ -3401,6 +3401,346 @@ impl ::protobuf::reflect::ProtobufValue for CreateRowPayload {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct CreateFieldPayload {
+    // message fields
+    pub grid_id: ::std::string::String,
+    pub field: ::protobuf::SingularPtrField<Field>,
+    pub type_option_data: ::std::vec::Vec<u8>,
+    // message oneof groups
+    pub one_of_start_field_id: ::std::option::Option<CreateFieldPayload_oneof_one_of_start_field_id>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a CreateFieldPayload {
+    fn default() -> &'a CreateFieldPayload {
+        <CreateFieldPayload as ::protobuf::Message>::default_instance()
+    }
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum CreateFieldPayload_oneof_one_of_start_field_id {
+    start_field_id(::std::string::String),
+}
+
+impl CreateFieldPayload {
+    pub fn new() -> CreateFieldPayload {
+        ::std::default::Default::default()
+    }
+
+    // string grid_id = 1;
+
+
+    pub fn get_grid_id(&self) -> &str {
+        &self.grid_id
+    }
+    pub fn clear_grid_id(&mut self) {
+        self.grid_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_grid_id(&mut self, v: ::std::string::String) {
+        self.grid_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_grid_id(&mut self) -> &mut ::std::string::String {
+        &mut self.grid_id
+    }
+
+    // Take field
+    pub fn take_grid_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.grid_id, ::std::string::String::new())
+    }
+
+    // .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] {
+        &self.type_option_data
+    }
+    pub fn clear_type_option_data(&mut self) {
+        self.type_option_data.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_type_option_data(&mut self, v: ::std::vec::Vec<u8>) {
+        self.type_option_data = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_type_option_data(&mut self) -> &mut ::std::vec::Vec<u8> {
+        &mut self.type_option_data
+    }
+
+    // Take field
+    pub fn take_type_option_data(&mut self) -> ::std::vec::Vec<u8> {
+        ::std::mem::replace(&mut self.type_option_data, ::std::vec::Vec::new())
+    }
+
+    // string start_field_id = 4;
+
+
+    pub fn get_start_field_id(&self) -> &str {
+        match self.one_of_start_field_id {
+            ::std::option::Option::Some(CreateFieldPayload_oneof_one_of_start_field_id::start_field_id(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_start_field_id(&mut self) {
+        self.one_of_start_field_id = ::std::option::Option::None;
+    }
+
+    pub fn has_start_field_id(&self) -> bool {
+        match self.one_of_start_field_id {
+            ::std::option::Option::Some(CreateFieldPayload_oneof_one_of_start_field_id::start_field_id(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_start_field_id(&mut self, v: ::std::string::String) {
+        self.one_of_start_field_id = ::std::option::Option::Some(CreateFieldPayload_oneof_one_of_start_field_id::start_field_id(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_start_field_id(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(CreateFieldPayload_oneof_one_of_start_field_id::start_field_id(_)) = self.one_of_start_field_id {
+        } else {
+            self.one_of_start_field_id = ::std::option::Option::Some(CreateFieldPayload_oneof_one_of_start_field_id::start_field_id(::std::string::String::new()));
+        }
+        match self.one_of_start_field_id {
+            ::std::option::Option::Some(CreateFieldPayload_oneof_one_of_start_field_id::start_field_id(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_start_field_id(&mut self) -> ::std::string::String {
+        if self.has_start_field_id() {
+            match self.one_of_start_field_id.take() {
+                ::std::option::Option::Some(CreateFieldPayload_oneof_one_of_start_field_id::start_field_id(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+}
+
+impl ::protobuf::Message for CreateFieldPayload {
+    fn is_initialized(&self) -> bool {
+        for v in &self.field {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_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)?;
+                },
+                4 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_start_field_id = ::std::option::Option::Some(CreateFieldPayload_oneof_one_of_start_field_id::start_field_id(is.read_string()?));
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.grid_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.grid_id);
+        }
+        if 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(3, &self.type_option_data);
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_start_field_id {
+            match v {
+                &CreateFieldPayload_oneof_one_of_start_field_id::start_field_id(ref v) => {
+                    my_size += ::protobuf::rt::string_size(4, &v);
+                },
+            };
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.grid_id.is_empty() {
+            os.write_string(1, &self.grid_id)?;
+        }
+        if 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(3, &self.type_option_data)?;
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_start_field_id {
+            match v {
+                &CreateFieldPayload_oneof_one_of_start_field_id::start_field_id(ref v) => {
+                    os.write_string(4, v)?;
+                },
+            };
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> CreateFieldPayload {
+        CreateFieldPayload::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "grid_id",
+                |m: &CreateFieldPayload| { &m.grid_id },
+                |m: &mut CreateFieldPayload| { &mut m.grid_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<Field>>(
+                "field",
+                |m: &CreateFieldPayload| { &m.field },
+                |m: &mut CreateFieldPayload| { &mut m.field },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
+                "type_option_data",
+                |m: &CreateFieldPayload| { &m.type_option_data },
+                |m: &mut CreateFieldPayload| { &mut m.type_option_data },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "start_field_id",
+                CreateFieldPayload::has_start_field_id,
+                CreateFieldPayload::get_start_field_id,
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CreateFieldPayload>(
+                "CreateFieldPayload",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static CreateFieldPayload {
+        static instance: ::protobuf::rt::LazyV2<CreateFieldPayload> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(CreateFieldPayload::new)
+    }
+}
+
+impl ::protobuf::Clear for CreateFieldPayload {
+    fn clear(&mut self) {
+        self.grid_id.clear();
+        self.field.clear();
+        self.type_option_data.clear();
+        self.one_of_start_field_id = ::std::option::Option::None;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for CreateFieldPayload {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for CreateFieldPayload {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(PartialEq,Clone,Default)]
 pub struct QueryFieldPayload {
     // message fields
@@ -4101,14 +4441,19 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \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\nstart\
-    RowIdB\x15\n\x13one_of_start_row_id\"d\n\x11QueryFieldPayload\x12\x17\n\
-    \x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_orders\x18\x02\
-    \x20\x01(\x0b2\x13.RepeatedFieldOrderR\x0bfieldOrders\"e\n\x16QueryGridB\
-    locksPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\
-    \x0cblock_orders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrder\
-    s\"\\\n\x0fQueryRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06g\
-    ridId\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07blockId\x12\x15\n\x06\
-    row_id\x18\x03\x20\x01(\tR\x05rowIdb\x06proto3\
+    RowIdB\x15\n\x13one_of_start_row_id\"\xb6\x01\n\x12CreateFieldPayload\
+    \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_field_id\x18\
+    \x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\x15one_of_start_field_id\"d\n\
+    \x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\
+    \x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFieldOrderR\
+    \x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\x12\x17\n\x07grid_id\x18\
+    \x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\x0b2\
+    \x0f.GridBlockOrderR\x0bblockOrders\"\\\n\x0fQueryRowPayload\x12\x17\n\
+    \x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08block_id\x18\x02\
+    \x20\x01(\tR\x07blockId\x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\x05rowId\
+    b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 126 - 83
shared-lib/flowy-grid-data-model/src/protobuf/model/meta.rs

@@ -727,7 +727,7 @@ pub struct FieldMeta {
     pub frozen: bool,
     pub visibility: bool,
     pub width: i32,
-    pub type_options: ::std::string::String,
+    pub type_option: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -882,30 +882,30 @@ impl FieldMeta {
         self.width = v;
     }
 
-    // string type_options = 8;
+    // string type_option = 8;
 
 
-    pub fn get_type_options(&self) -> &str {
-        &self.type_options
+    pub fn get_type_option(&self) -> &str {
+        &self.type_option
     }
-    pub fn clear_type_options(&mut self) {
-        self.type_options.clear();
+    pub fn clear_type_option(&mut self) {
+        self.type_option.clear();
     }
 
     // Param is passed by value, moved
-    pub fn set_type_options(&mut self, v: ::std::string::String) {
-        self.type_options = v;
+    pub fn set_type_option(&mut self, v: ::std::string::String) {
+        self.type_option = v;
     }
 
     // Mutable pointer to the field.
     // If field is not initialized, it is initialized with default value first.
-    pub fn mut_type_options(&mut self) -> &mut ::std::string::String {
-        &mut self.type_options
+    pub fn mut_type_option(&mut self) -> &mut ::std::string::String {
+        &mut self.type_option
     }
 
     // Take field
-    pub fn take_type_options(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.type_options, ::std::string::String::new())
+    pub fn take_type_option(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.type_option, ::std::string::String::new())
     }
 }
 
@@ -952,7 +952,7 @@ impl ::protobuf::Message for FieldMeta {
                     self.width = tmp;
                 },
                 8 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.type_options)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.type_option)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -987,8 +987,8 @@ impl ::protobuf::Message for FieldMeta {
         if self.width != 0 {
             my_size += ::protobuf::rt::value_size(7, self.width, ::protobuf::wire_format::WireTypeVarint);
         }
-        if !self.type_options.is_empty() {
-            my_size += ::protobuf::rt::string_size(8, &self.type_options);
+        if !self.type_option.is_empty() {
+            my_size += ::protobuf::rt::string_size(8, &self.type_option);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -1017,8 +1017,8 @@ impl ::protobuf::Message for FieldMeta {
         if self.width != 0 {
             os.write_int32(7, self.width)?;
         }
-        if !self.type_options.is_empty() {
-            os.write_string(8, &self.type_options)?;
+        if !self.type_option.is_empty() {
+            os.write_string(8, &self.type_option)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -1094,9 +1094,9 @@ impl ::protobuf::Message for FieldMeta {
                 |m: &mut FieldMeta| { &mut m.width },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "type_options",
-                |m: &FieldMeta| { &m.type_options },
-                |m: &mut FieldMeta| { &mut m.type_options },
+                "type_option",
+                |m: &FieldMeta| { &m.type_option },
+                |m: &mut FieldMeta| { &mut m.type_option },
             ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<FieldMeta>(
                 "FieldMeta",
@@ -1121,7 +1121,7 @@ impl ::protobuf::Clear for FieldMeta {
         self.frozen = false;
         self.visibility = false;
         self.width = 0;
-        self.type_options.clear();
+        self.type_option.clear();
         self.unknown_fields.clear();
     }
 }
@@ -1142,6 +1142,7 @@ impl ::protobuf::reflect::ProtobufValue for FieldMeta {
 pub struct FieldChangeset {
     // message fields
     pub field_id: ::std::string::String,
+    pub grid_id: ::std::string::String,
     // message oneof groups
     pub one_of_name: ::std::option::Option<FieldChangeset_oneof_one_of_name>,
     pub one_of_desc: ::std::option::Option<FieldChangeset_oneof_one_of_desc>,
@@ -1227,7 +1228,33 @@ impl FieldChangeset {
         ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
     }
 
-    // string name = 2;
+    // string grid_id = 2;
+
+
+    pub fn get_grid_id(&self) -> &str {
+        &self.grid_id
+    }
+    pub fn clear_grid_id(&mut self) {
+        self.grid_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_grid_id(&mut self, v: ::std::string::String) {
+        self.grid_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_grid_id(&mut self) -> &mut ::std::string::String {
+        &mut self.grid_id
+    }
+
+    // Take field
+    pub fn take_grid_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.grid_id, ::std::string::String::new())
+    }
+
+    // string name = 3;
 
 
     pub fn get_name(&self) -> &str {
@@ -1276,7 +1303,7 @@ impl FieldChangeset {
         }
     }
 
-    // string desc = 3;
+    // string desc = 4;
 
 
     pub fn get_desc(&self) -> &str {
@@ -1325,7 +1352,7 @@ impl FieldChangeset {
         }
     }
 
-    // .FieldType field_type = 4;
+    // .FieldType field_type = 5;
 
 
     pub fn get_field_type(&self) -> FieldType {
@@ -1350,7 +1377,7 @@ impl FieldChangeset {
         self.one_of_field_type = ::std::option::Option::Some(FieldChangeset_oneof_one_of_field_type::field_type(v))
     }
 
-    // bool frozen = 5;
+    // bool frozen = 6;
 
 
     pub fn get_frozen(&self) -> bool {
@@ -1375,7 +1402,7 @@ impl FieldChangeset {
         self.one_of_frozen = ::std::option::Option::Some(FieldChangeset_oneof_one_of_frozen::frozen(v))
     }
 
-    // bool visibility = 6;
+    // bool visibility = 7;
 
 
     pub fn get_visibility(&self) -> bool {
@@ -1400,7 +1427,7 @@ impl FieldChangeset {
         self.one_of_visibility = ::std::option::Option::Some(FieldChangeset_oneof_one_of_visibility::visibility(v))
     }
 
-    // int32 width = 7;
+    // int32 width = 8;
 
 
     pub fn get_width(&self) -> i32 {
@@ -1425,7 +1452,7 @@ impl FieldChangeset {
         self.one_of_width = ::std::option::Option::Some(FieldChangeset_oneof_one_of_width::width(v))
     }
 
-    // string type_options = 8;
+    // string type_options = 9;
 
 
     pub fn get_type_options(&self) -> &str {
@@ -1488,42 +1515,45 @@ impl ::protobuf::Message for FieldChangeset {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
                 },
                 2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?;
+                },
+                3 => {
                     if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
                     self.one_of_name = ::std::option::Option::Some(FieldChangeset_oneof_one_of_name::name(is.read_string()?));
                 },
-                3 => {
+                4 => {
                     if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
                     self.one_of_desc = ::std::option::Option::Some(FieldChangeset_oneof_one_of_desc::desc(is.read_string()?));
                 },
-                4 => {
+                5 => {
                     if wire_type != ::protobuf::wire_format::WireTypeVarint {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
                     self.one_of_field_type = ::std::option::Option::Some(FieldChangeset_oneof_one_of_field_type::field_type(is.read_enum()?));
                 },
-                5 => {
+                6 => {
                     if wire_type != ::protobuf::wire_format::WireTypeVarint {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
                     self.one_of_frozen = ::std::option::Option::Some(FieldChangeset_oneof_one_of_frozen::frozen(is.read_bool()?));
                 },
-                6 => {
+                7 => {
                     if wire_type != ::protobuf::wire_format::WireTypeVarint {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
                     self.one_of_visibility = ::std::option::Option::Some(FieldChangeset_oneof_one_of_visibility::visibility(is.read_bool()?));
                 },
-                7 => {
+                8 => {
                     if wire_type != ::protobuf::wire_format::WireTypeVarint {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
                     self.one_of_width = ::std::option::Option::Some(FieldChangeset_oneof_one_of_width::width(is.read_int32()?));
                 },
-                8 => {
+                9 => {
                     if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
@@ -1544,24 +1574,27 @@ impl ::protobuf::Message for FieldChangeset {
         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(2, &self.grid_id);
+        }
         if let ::std::option::Option::Some(ref v) = self.one_of_name {
             match v {
                 &FieldChangeset_oneof_one_of_name::name(ref v) => {
-                    my_size += ::protobuf::rt::string_size(2, &v);
+                    my_size += ::protobuf::rt::string_size(3, &v);
                 },
             };
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_desc {
             match v {
                 &FieldChangeset_oneof_one_of_desc::desc(ref v) => {
-                    my_size += ::protobuf::rt::string_size(3, &v);
+                    my_size += ::protobuf::rt::string_size(4, &v);
                 },
             };
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_field_type {
             match v {
                 &FieldChangeset_oneof_one_of_field_type::field_type(v) => {
-                    my_size += ::protobuf::rt::enum_size(4, v);
+                    my_size += ::protobuf::rt::enum_size(5, v);
                 },
             };
         }
@@ -1582,14 +1615,14 @@ impl ::protobuf::Message for FieldChangeset {
         if let ::std::option::Option::Some(ref v) = self.one_of_width {
             match v {
                 &FieldChangeset_oneof_one_of_width::width(v) => {
-                    my_size += ::protobuf::rt::value_size(7, v, ::protobuf::wire_format::WireTypeVarint);
+                    my_size += ::protobuf::rt::value_size(8, v, ::protobuf::wire_format::WireTypeVarint);
                 },
             };
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_type_options {
             match v {
                 &FieldChangeset_oneof_one_of_type_options::type_options(ref v) => {
-                    my_size += ::protobuf::rt::string_size(8, &v);
+                    my_size += ::protobuf::rt::string_size(9, &v);
                 },
             };
         }
@@ -1602,52 +1635,55 @@ impl ::protobuf::Message for FieldChangeset {
         if !self.field_id.is_empty() {
             os.write_string(1, &self.field_id)?;
         }
+        if !self.grid_id.is_empty() {
+            os.write_string(2, &self.grid_id)?;
+        }
         if let ::std::option::Option::Some(ref v) = self.one_of_name {
             match v {
                 &FieldChangeset_oneof_one_of_name::name(ref v) => {
-                    os.write_string(2, v)?;
+                    os.write_string(3, v)?;
                 },
             };
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_desc {
             match v {
                 &FieldChangeset_oneof_one_of_desc::desc(ref v) => {
-                    os.write_string(3, v)?;
+                    os.write_string(4, v)?;
                 },
             };
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_field_type {
             match v {
                 &FieldChangeset_oneof_one_of_field_type::field_type(v) => {
-                    os.write_enum(4, ::protobuf::ProtobufEnum::value(&v))?;
+                    os.write_enum(5, ::protobuf::ProtobufEnum::value(&v))?;
                 },
             };
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_frozen {
             match v {
                 &FieldChangeset_oneof_one_of_frozen::frozen(v) => {
-                    os.write_bool(5, v)?;
+                    os.write_bool(6, v)?;
                 },
             };
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_visibility {
             match v {
                 &FieldChangeset_oneof_one_of_visibility::visibility(v) => {
-                    os.write_bool(6, v)?;
+                    os.write_bool(7, v)?;
                 },
             };
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_width {
             match v {
                 &FieldChangeset_oneof_one_of_width::width(v) => {
-                    os.write_int32(7, v)?;
+                    os.write_int32(8, v)?;
                 },
             };
         }
         if let ::std::option::Option::Some(ref v) = self.one_of_type_options {
             match v {
                 &FieldChangeset_oneof_one_of_type_options::type_options(ref v) => {
-                    os.write_string(8, v)?;
+                    os.write_string(9, v)?;
                 },
             };
         }
@@ -1694,6 +1730,11 @@ impl ::protobuf::Message for FieldChangeset {
                 |m: &FieldChangeset| { &m.field_id },
                 |m: &mut FieldChangeset| { &mut m.field_id },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "grid_id",
+                |m: &FieldChangeset| { &m.grid_id },
+                |m: &mut FieldChangeset| { &mut m.grid_id },
+            ));
             fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
                 "name",
                 FieldChangeset::has_name,
@@ -1746,6 +1787,7 @@ impl ::protobuf::Message for FieldChangeset {
 impl ::protobuf::Clear for FieldChangeset {
     fn clear(&mut self) {
         self.field_id.clear();
+        self.grid_id.clear();
         self.one_of_name = ::std::option::Option::None;
         self.one_of_desc = ::std::option::Option::None;
         self.one_of_field_type = ::std::option::Option::None;
@@ -3465,49 +3507,50 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     ockId\x12&\n\x0fstart_row_index\x18\x02\x20\x01(\x05R\rstartRowIndex\x12\
     \x1b\n\trow_count\x18\x03\x20\x01(\x05R\x08rowCount\"V\n\x12GridBlockMet\
     aSerde\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x12%\n\trow_\
-    metas\x18\x02\x20\x03(\x0b2\x08.RowMetaR\x08rowMetas\"\xdf\x01\n\tFieldM\
+    metas\x18\x02\x20\x03(\x0b2\x08.RowMetaR\x08rowMetas\"\xdd\x01\n\tFieldM\
     eta\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x12\n\x04name\x18\x02\
     \x20\x01(\tR\x04name\x12\x12\n\x04desc\x18\x03\x20\x01(\tR\x04desc\x12)\
     \n\nfield_type\x18\x04\x20\x01(\x0e2\n.FieldTypeR\tfieldType\x12\x16\n\
     \x06frozen\x18\x05\x20\x01(\x08R\x06frozen\x12\x1e\n\nvisibility\x18\x06\
     \x20\x01(\x08R\nvisibility\x12\x14\n\x05width\x18\x07\x20\x01(\x05R\x05w\
-    idth\x12!\n\x0ctype_options\x18\x08\x20\x01(\tR\x0btypeOptions\"\xfd\x02\
+    idth\x12\x1f\n\x0btype_option\x18\x08\x20\x01(\tR\ntypeOption\"\x96\x03\
     \n\x0eFieldChangeset\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldI\
-    d\x12\x14\n\x04name\x18\x02\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\
-    \x03\x20\x01(\tH\x01R\x04desc\x12+\n\nfield_type\x18\x04\x20\x01(\x0e2\n\
-    .FieldTypeH\x02R\tfieldType\x12\x18\n\x06frozen\x18\x05\x20\x01(\x08H\
-    \x03R\x06frozen\x12\x20\n\nvisibility\x18\x06\x20\x01(\x08H\x04R\nvisibi\
-    lity\x12\x16\n\x05width\x18\x07\x20\x01(\x05H\x05R\x05width\x12#\n\x0cty\
-    pe_options\x18\x08\x20\x01(\tH\x06R\x0btypeOptionsB\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\x15\n\x13one_of_type\
-    _options\"8\n\x07AnyData\x12\x17\n\x07type_id\x18\x01\x20\x01(\tR\x06typ\
-    eId\x12\x14\n\x05value\x18\x02\x20\x01(\x0cR\x05value\"\xff\x01\n\x07Row\
-    Meta\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x19\n\x08block_id\x18\
-    \x02\x20\x01(\tR\x07blockId\x12D\n\x10cell_by_field_id\x18\x03\x20\x03(\
-    \x0b2\x1b.RowMeta.CellByFieldIdEntryR\rcellByFieldId\x12\x16\n\x06height\
-    \x18\x04\x20\x01(\x05R\x06height\x12\x1e\n\nvisibility\x18\x05\x20\x01(\
-    \x08R\nvisibility\x1aK\n\x12CellByFieldIdEntry\x12\x10\n\x03key\x18\x01\
-    \x20\x01(\tR\x03key\x12\x1f\n\x05value\x18\x02\x20\x01(\x0b2\t.CellMetaR\
-    \x05value:\x028\x01\"\xa7\x02\n\x10RowMetaChangeset\x12\x15\n\x06row_id\
-    \x18\x01\x20\x01(\tR\x05rowId\x12\x18\n\x06height\x18\x02\x20\x01(\x05H\
-    \0R\x06height\x12\x20\n\nvisibility\x18\x03\x20\x01(\x08H\x01R\nvisibili\
-    ty\x12M\n\x10cell_by_field_id\x18\x04\x20\x03(\x0b2$.RowMetaChangeset.Ce\
-    llByFieldIdEntryR\rcellByFieldId\x1aK\n\x12CellByFieldIdEntry\x12\x10\n\
-    \x03key\x18\x01\x20\x01(\tR\x03key\x12\x1f\n\x05value\x18\x02\x20\x01(\
-    \x0b2\t.CellMetaR\x05value:\x028\x01B\x0f\n\rone_of_heightB\x13\n\x11one\
-    _of_visibility\"9\n\x08CellMeta\x12\x19\n\x08field_id\x18\x01\x20\x01(\t\
-    R\x07fieldId\x12\x12\n\x04data\x18\x02\x20\x01(\tR\x04data\"\x83\x01\n\
-    \x11CellMetaChangeset\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\
-    \x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08field_id\
-    \x18\x03\x20\x01(\tR\x07fieldId\x12\x14\n\x04data\x18\x04\x20\x01(\tH\0R\
-    \x04dataB\r\n\x0bone_of_data\"\xad\x01\n\x10BuildGridContext\x12+\n\x0bf\
-    ield_metas\x18\x01\x20\x03(\x0b2\n.FieldMetaR\nfieldMetas\x12/\n\x0bbloc\
-    k_metas\x18\x02\x20\x01(\x0b2\x0e.GridBlockMetaR\nblockMetas\x12;\n\x0fb\
-    lock_meta_data\x18\x03\x20\x01(\x0b2\x13.GridBlockMetaSerdeR\rblockMetaD\
-    ata*d\n\tFieldType\x12\x0c\n\x08RichText\x10\0\x12\n\n\x06Number\x10\x01\
-    \x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\
-    \n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Checkbox\x10\x05b\x06proto3\
+    d\x12\x17\n\x07grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04name\
+    \x18\x03\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\
+    \x01R\x04desc\x12+\n\nfield_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\
+    \tfieldType\x12\x18\n\x06frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\
+    \x20\n\nvisibility\x18\x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05w\
+    idth\x18\x08\x20\x01(\x05H\x05R\x05width\x12#\n\x0ctype_options\x18\t\
+    \x20\x01(\tH\x06R\x0btypeOptionsB\r\n\x0bone_of_nameB\r\n\x0bone_of_desc\
+    B\x13\n\x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_of_visi\
+    bilityB\x0e\n\x0cone_of_widthB\x15\n\x13one_of_type_options\"8\n\x07AnyD\
+    ata\x12\x17\n\x07type_id\x18\x01\x20\x01(\tR\x06typeId\x12\x14\n\x05valu\
+    e\x18\x02\x20\x01(\x0cR\x05value\"\xff\x01\n\x07RowMeta\x12\x0e\n\x02id\
+    \x18\x01\x20\x01(\tR\x02id\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07\
+    blockId\x12D\n\x10cell_by_field_id\x18\x03\x20\x03(\x0b2\x1b.RowMeta.Cel\
+    lByFieldIdEntryR\rcellByFieldId\x12\x16\n\x06height\x18\x04\x20\x01(\x05\
+    R\x06height\x12\x1e\n\nvisibility\x18\x05\x20\x01(\x08R\nvisibility\x1aK\
+    \n\x12CellByFieldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\
+    \x1f\n\x05value\x18\x02\x20\x01(\x0b2\t.CellMetaR\x05value:\x028\x01\"\
+    \xa7\x02\n\x10RowMetaChangeset\x12\x15\n\x06row_id\x18\x01\x20\x01(\tR\
+    \x05rowId\x12\x18\n\x06height\x18\x02\x20\x01(\x05H\0R\x06height\x12\x20\
+    \n\nvisibility\x18\x03\x20\x01(\x08H\x01R\nvisibility\x12M\n\x10cell_by_\
+    field_id\x18\x04\x20\x03(\x0b2$.RowMetaChangeset.CellByFieldIdEntryR\rce\
+    llByFieldId\x1aK\n\x12CellByFieldIdEntry\x12\x10\n\x03key\x18\x01\x20\
+    \x01(\tR\x03key\x12\x1f\n\x05value\x18\x02\x20\x01(\x0b2\t.CellMetaR\x05\
+    value:\x028\x01B\x0f\n\rone_of_heightB\x13\n\x11one_of_visibility\"9\n\
+    \x08CellMeta\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\
+    \x12\n\x04data\x18\x02\x20\x01(\tR\x04data\"\x83\x01\n\x11CellMetaChange\
+    set\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x15\n\x06row_\
+    id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08field_id\x18\x03\x20\x01(\t\
+    R\x07fieldId\x12\x14\n\x04data\x18\x04\x20\x01(\tH\0R\x04dataB\r\n\x0bon\
+    e_of_data\"\xad\x01\n\x10BuildGridContext\x12+\n\x0bfield_metas\x18\x01\
+    \x20\x03(\x0b2\n.FieldMetaR\nfieldMetas\x12/\n\x0bblock_metas\x18\x02\
+    \x20\x01(\x0b2\x0e.GridBlockMetaR\nblockMetas\x12;\n\x0fblock_meta_data\
+    \x18\x03\x20\x01(\x0b2\x13.GridBlockMetaSerdeR\rblockMetaData*d\n\tField\
+    Type\x12\x0c\n\x08RichText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08\
+    DateTime\x10\x02\x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSel\
+    ect\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 - 0
shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto

@@ -67,6 +67,12 @@ message CreateRowPayload {
     string grid_id = 1;
     oneof one_of_start_row_id { string start_row_id = 2; };
 }
+message CreateFieldPayload {
+    string grid_id = 1;
+    Field field = 2;
+    bytes type_option_data = 3;
+    oneof one_of_start_field_id { string start_field_id = 4; };
+}
 message QueryFieldPayload {
     string grid_id = 1;
     RepeatedFieldOrder field_orders = 2;

+ 9 - 8
shared-lib/flowy-grid-data-model/src/protobuf/proto/meta.proto

@@ -22,17 +22,18 @@ message FieldMeta {
     bool frozen = 5;
     bool visibility = 6;
     int32 width = 7;
-    string type_options = 8;
+    string type_option = 8;
 }
 message FieldChangeset {
     string field_id = 1;
-    oneof one_of_name { string name = 2; };
-    oneof one_of_desc { string desc = 3; };
-    oneof one_of_field_type { FieldType field_type = 4; };
-    oneof one_of_frozen { bool frozen = 5; };
-    oneof one_of_visibility { bool visibility = 6; };
-    oneof one_of_width { int32 width = 7; };
-    oneof one_of_type_options { string type_options = 8; };
+    string grid_id = 2;
+    oneof one_of_name { string name = 3; };
+    oneof one_of_desc { string desc = 4; };
+    oneof one_of_field_type { FieldType field_type = 5; };
+    oneof one_of_frozen { bool frozen = 6; };
+    oneof one_of_visibility { bool visibility = 7; };
+    oneof one_of_width { int32 width = 8; };
+    oneof one_of_type_options { string type_options = 9; };
 }
 message AnyData {
     string type_id = 1;

+ 43 - 8
shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs

@@ -5,6 +5,7 @@ use bytes::Bytes;
 use flowy_grid_data_model::entities::{
     FieldChangeset, FieldMeta, FieldOrder, GridBlockMeta, GridBlockMetaChangeset, GridMeta, RepeatedFieldOrder,
 };
+use flowy_grid_data_model::parser::CreateFieldParams;
 use lib_infra::uuid;
 use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
 use std::collections::HashMap;
@@ -35,15 +36,49 @@ impl GridMetaPad {
         Self::from_delta(grid_delta)
     }
 
-    pub fn create_field(&mut self, field_meta: FieldMeta) -> CollaborateResult<Option<GridChangeset>> {
+    pub fn create_field(&mut self, params: CreateFieldParams) -> CollaborateResult<Option<GridChangeset>> {
         self.modify_grid(|grid| {
-            if grid.fields.contains(&field_meta) {
-                tracing::warn!("Duplicate grid field");
-                Ok(None)
-            } else {
-                grid.fields.push(field_meta);
-                Ok(Some(()))
+            let CreateFieldParams {
+                field,
+                type_option_data,
+                start_field_id,
+                ..
+            } = params;
+
+            if grid
+                .fields
+                .iter()
+                .find(|field_meta| field_meta.id == field.id)
+                .is_some()
+            {
+                tracing::warn!("Create grid field");
+                return Ok(None);
+            }
+
+            let type_option =
+                String::from_utf8(type_option_data).map_err(|e| CollaborateError::internal().context(e))?;
+
+            let field_meta = FieldMeta {
+                id: field.id,
+                name: field.name,
+                desc: field.desc,
+                field_type: field.field_type,
+                frozen: field.frozen,
+                visibility: field.visibility,
+                width: field.width,
+                type_option,
+            };
+
+            let insert_index = match start_field_id {
+                None => None,
+                Some(start_field_id) => grid.fields.iter().position(|field| field.id == start_field_id),
+            };
+
+            match insert_index {
+                None => grid.fields.push(field_meta),
+                Some(index) => grid.fields.insert(index, field_meta),
             }
+            Ok(Some(()))
         })
     }
 
@@ -130,7 +165,7 @@ impl GridMetaPad {
             }
 
             if let Some(type_options) = changeset.type_options {
-                field.type_options = type_options;
+                field.type_option = type_options;
                 is_changed = Some(())
             }